diff options
Diffstat (limited to 'synapse/api/events')
-rw-r--r-- | synapse/api/events/__init__.py | 152 | ||||
-rw-r--r-- | synapse/api/events/factory.py | 50 | ||||
-rw-r--r-- | synapse/api/events/room.py | 99 |
3 files changed, 301 insertions, 0 deletions
diff --git a/synapse/api/events/__init__.py b/synapse/api/events/__init__.py new file mode 100644 index 0000000000..bc2daf3361 --- /dev/null +++ b/synapse/api/events/__init__.py @@ -0,0 +1,152 @@ +# -*- coding: utf-8 -*- +# Copyright 2014 matrix.org +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +from synapse.api.errors import SynapseError, Codes +from synapse.util.jsonobject import JsonEncodedObject + + +class SynapseEvent(JsonEncodedObject): + + """Base class for Synapse events. These are JSON objects which must abide + by a certain well-defined structure. + """ + + # Attributes that are currently assumed by the federation side: + # Mandatory: + # - event_id + # - room_id + # - type + # - is_state + # + # Optional: + # - state_key (mandatory when is_state is True) + # - prev_events (these can be filled out by the federation layer itself.) + # - prev_state + + valid_keys = [ + "event_id", + "type", + "room_id", + "user_id", # sender/initiator + "content", # HTTP body, JSON + ] + + internal_keys = [ + "is_state", + "state_key", + "prev_events", + "prev_state", + "depth", + "destinations", + "origin", + ] + + required_keys = [ + "event_id", + "room_id", + "content", + ] + + def __init__(self, raises=True, **kwargs): + super(SynapseEvent, self).__init__(**kwargs) + if "content" in kwargs: + self.check_json(self.content, raises=raises) + + def get_content_template(self): + """ Retrieve the JSON template for this event as a dict. + + The template must be a dict representing the JSON to match. Only + required keys should be present. The values of the keys in the template + are checked via type() to the values of the same keys in the actual + event JSON. + + NB: If loading content via json.loads, you MUST define strings as + unicode. + + For example: + Content: + { + "name": u"bob", + "age": 18, + "friends": [u"mike", u"jill"] + } + Template: + { + "name": u"string", + "age": 0, + "friends": [u"string"] + } + The values "string" and 0 could be anything, so long as the types + are the same as the content. + """ + raise NotImplementedError("get_content_template not implemented.") + + def check_json(self, content, raises=True): + """Checks the given JSON content abides by the rules of the template. + + Args: + content : A JSON object to check. + raises: True to raise a SynapseError if the check fails. + Returns: + True if the content passes the template. Returns False if the check + fails and raises=False. + Raises: + SynapseError if the check fails and raises=True. + """ + # recursively call to inspect each layer + err_msg = self._check_json(content, self.get_content_template()) + if err_msg: + if raises: + raise SynapseError(400, err_msg, Codes.BAD_JSON) + else: + return False + else: + return True + + def _check_json(self, content, template): + """Check content and template matches. + + If the template is a dict, each key in the dict will be validated with + the content, else it will just compare the types of content and + template. This basic type check is required because this function will + be recursively called and could be called with just strs or ints. + + Args: + content: The content to validate. + template: The validation template. + Returns: + str: An error message if the validation fails, else None. + """ + if type(content) != type(template): + return "Mismatched types: %s" % template + + if type(template) == dict: + for key in template: + if key not in content: + return "Missing %s key" % key + + if type(content[key]) != type(template[key]): + return "Key %s is of the wrong type." % key + + if type(content[key]) == dict: + # we must go deeper + msg = self._check_json(content[key], template[key]) + if msg: + return msg + elif type(content[key]) == list: + # make sure each item type in content matches the template + for entry in content[key]: + msg = self._check_json(entry, template[key][0]) + if msg: + return msg diff --git a/synapse/api/events/factory.py b/synapse/api/events/factory.py new file mode 100644 index 0000000000..ea7afa234e --- /dev/null +++ b/synapse/api/events/factory.py @@ -0,0 +1,50 @@ +# -*- coding: utf-8 -*- +# Copyright 2014 matrix.org +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +from synapse.api.events.room import ( + RoomTopicEvent, MessageEvent, RoomMemberEvent, FeedbackEvent, + InviteJoinEvent, RoomConfigEvent +) + +from synapse.util.stringutils import random_string + + +class EventFactory(object): + + _event_classes = [ + RoomTopicEvent, + MessageEvent, + RoomMemberEvent, + FeedbackEvent, + InviteJoinEvent, + RoomConfigEvent + ] + + def __init__(self): + self._event_list = {} # dict of TYPE to event class + for event_class in EventFactory._event_classes: + self._event_list[event_class.TYPE] = event_class + + def create_event(self, etype=None, **kwargs): + kwargs["type"] = etype + if "event_id" not in kwargs: + kwargs["event_id"] = random_string(10) + + try: + handler = self._event_list[etype] + except KeyError: # unknown event type + # TODO allow custom event types. + raise NotImplementedError("Unknown etype=%s" % etype) + + return handler(**kwargs) diff --git a/synapse/api/events/room.py b/synapse/api/events/room.py new file mode 100644 index 0000000000..b31cd19f4b --- /dev/null +++ b/synapse/api/events/room.py @@ -0,0 +1,99 @@ +# -*- coding: utf-8 -*- +# Copyright 2014 matrix.org +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +from . import SynapseEvent + + +class RoomTopicEvent(SynapseEvent): + TYPE = "m.room.topic" + + def __init__(self, **kwargs): + kwargs["state_key"] = "" + super(RoomTopicEvent, self).__init__(**kwargs) + + def get_content_template(self): + return {"topic": u"string"} + + +class RoomMemberEvent(SynapseEvent): + TYPE = "m.room.member" + + valid_keys = SynapseEvent.valid_keys + [ + "target_user_id", # target + "membership", # action + ] + + def __init__(self, **kwargs): + if "target_user_id" in kwargs: + kwargs["state_key"] = kwargs["target_user_id"] + super(RoomMemberEvent, self).__init__(**kwargs) + + def get_content_template(self): + return {"membership": u"string"} + + +class MessageEvent(SynapseEvent): + TYPE = "m.room.message" + + valid_keys = SynapseEvent.valid_keys + [ + "msg_id", # unique per room + user combo + ] + + def __init__(self, **kwargs): + super(MessageEvent, self).__init__(**kwargs) + + def get_content_template(self): + return {"msgtype": u"string"} + + +class FeedbackEvent(SynapseEvent): + TYPE = "m.room.message.feedback" + + valid_keys = SynapseEvent.valid_keys + [ + "msg_id", # the message ID being acknowledged + "msg_sender_id", # person who is sending the feedback is 'user_id' + "feedback_type", # the type of feedback (delivery, read, etc) + ] + + def __init__(self, **kwargs): + super(FeedbackEvent, self).__init__(**kwargs) + + def get_content_template(self): + return {} + + +class InviteJoinEvent(SynapseEvent): + TYPE = "m.room.invite_join" + + valid_keys = SynapseEvent.valid_keys + [ + "target_user_id", + "target_host", + ] + + def __init__(self, **kwargs): + super(InviteJoinEvent, self).__init__(**kwargs) + + def get_content_template(self): + return {} + + +class RoomConfigEvent(SynapseEvent): + TYPE = "m.room.config" + + def __init__(self, **kwargs): + kwargs["state_key"] = "" + super(RoomConfigEvent, self).__init__(**kwargs) + + def get_content_template(self): + return {} |