diff --git a/libpathod/language/actions.py b/libpathod/language/actions.py index e86394a02..f5b828fe3 100644 --- a/libpathod/language/actions.py +++ b/libpathod/language/actions.py @@ -45,6 +45,8 @@ class _Action(base.Token): class PauseAt(_Action): + unique_name = None + def __init__(self, offset, seconds): _Action.__init__(self, offset) self.seconds = seconds @@ -93,6 +95,8 @@ class DisconnectAt(_Action): class InjectAt(_Action): + unique_name = None + def __init__(self, offset, value): _Action.__init__(self, offset) self.value = value diff --git a/libpathod/language/base.py b/libpathod/language/base.py index 3773fde1a..2a9e4ed31 100644 --- a/libpathod/language/base.py +++ b/libpathod/language/base.py @@ -77,6 +77,15 @@ class Token(object): """ return None + @property + def unique_name(self): + """ + Controls uniqueness constraints for tokens. No two tokens with the + same name will be allowed. If no uniquness should be applied, this + should be None. + """ + return self.__class__.__name__ + def resolve(self, settings, msg): """ Resolves this token to ready it for transmission. This means that diff --git a/libpathod/language/http.py b/libpathod/language/http.py index 070cc5f44..daee7e54a 100644 --- a/libpathod/language/http.py +++ b/libpathod/language/http.py @@ -46,6 +46,8 @@ class Method(base.OptionsOrValue): class _HeaderMixin(object): + unique_name = None + def format_header(self, key, value): return [key, ": ", value, "\r\n"] @@ -166,6 +168,7 @@ class _HTTPMessage(message.Message): class Response(_HTTPMessage): + unique_name = None comps = ( Body, Header, diff --git a/libpathod/language/message.py b/libpathod/language/message.py index b5ef70455..dbc0cfddf 100644 --- a/libpathod/language/message.py +++ b/libpathod/language/message.py @@ -1,5 +1,5 @@ import abc -from . import actions +from . import actions, exceptions LOG_TRUNCATE = 1024 @@ -9,6 +9,17 @@ class Message(object): logattrs = [] def __init__(self, tokens): + track = set([]) + for i in tokens: + if i.unique_name: + if i.unique_name in track: + raise exceptions.ParseException( + "Message has multiple %s clauses, " + "but should only have one." % i.unique_name, + 0, 0 + ) + else: + track.add(i.unique_name) self.tokens = tokens def toks(self, klass): diff --git a/test/test_language_actions.py b/test/test_language_actions.py index 7676fb726..b7361dfff 100644 --- a/test/test_language_actions.py +++ b/test/test_language_actions.py @@ -8,6 +8,11 @@ def parse_request(s): return language.parse_requests(s)[0] +def test_unique_name(): + assert not actions.PauseAt(0, "f").unique_name + assert actions.DisconnectAt(0).unique_name + + class TestDisconnects: def test_parse_response(self): a = language.parse_response("400:d0").actions[0] diff --git a/test/test_language_base.py b/test/test_language_base.py index bd67c0109..404b302d4 100644 --- a/test/test_language_base.py +++ b/test/test_language_base.py @@ -310,6 +310,11 @@ class TBoolean(base.Boolean): name = "test" +def test_unique_name(): + b = TBoolean(True) + assert b.unique_name + + class test_boolean(): e = TBoolean.expr() assert e.parseString("test")[0].value diff --git a/test/test_language_http.py b/test/test_language_http.py index a7313bfb9..17bce802e 100644 --- a/test/test_language_http.py +++ b/test/test_language_http.py @@ -342,3 +342,11 @@ def test_pathodspec_freeze(): ) assert e.freeze({}) assert e.values({}) + + +def test_unique_components(): + tutils.raises( + "multiple body clauses", + language.parse_response, + "400:b@1:b@1" + )