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
|