diff --git a/api/check_examples.py b/api/check_examples.py new file mode 100755 index 00000000..00e75263 --- /dev/null +++ b/api/check_examples.py @@ -0,0 +1,84 @@ +#! /usr/bin/env python + +import sys +import json +import os + + +def import_error(module, package, debian, error): + sys.stderr.write(( + "Error importing %(module)s: %(error)r\n" + "To install %(module)s run:\n" + " pip install %(package)s\n" + "or on Debian run:\n" + " sudo apt-get install python-%(debian)s\n" + ) % locals()) + if __name__ == '__main__': + sys.exit(1) + +try: + import jsonschema +except ImportError as e: + import_error("jsonschema", "jsonschema", "jsonschema", e) + raise + +try: + import yaml +except ImportError as e: + import_error("yaml", "PyYAML", "yaml", e) + raise + + +def check_response(filepath, request, code, response): + example = None + try: + example_json = response.get('examples', {}).get('application/json') + if example_json: + example = json.loads(example_json) + except Exception as e: + raise ValueError("Error parsing JSON example response for %r %r" % ( + request, code + ), e) + schema = response.get('schema') + fileurl = "file://" + os.path.abspath(filepath) + if example and schema: + try: + print ("Checking schema for: %r %r %r" % (filepath, request, code)) + # Setting the 'id' tells jsonschema where the file is so that it + # can correctly resolve relative $ref references in the schema + schema['id'] = fileurl + jsonschema.validate(example, schema) + except Exception as e: + raise ValueError("Error validating JSON schema for %r %r" % ( + request, code + ), e) + + +def check_swagger_file(filepath): + with open(filepath) as f: + swagger = yaml.load(f) + + for path, path_api in swagger.get('paths', {}).items(): + for method, request_api in path_api.items(): + request = "%s %s" % (method.upper(), path) + try: + responses = request_api['responses'] + except KeyError: + raise ValueError("No responses for %r" % (request,)) + for code, response in responses.items(): + check_response(filepath, request, code, response) + + +if __name__ == '__main__': + paths = sys.argv[1:] + if not paths: + paths = [] + for (root, dirs, files) in os.walk(os.curdir): + for filename in files: + if filename.endswith(".yaml"): + paths.append(os.path.join(root, filename)) + for path in paths: + try: + check_swagger_file(path) + except Exception as e: + raise ValueError("Error checking file %r" % (path,), e) diff --git a/api/client-server/v1/presence.yaml b/api/client-server/v1/presence.yaml index 717e9c47..5684398b 100644 --- a/api/client-server/v1/presence.yaml +++ b/api/client-server/v1/presence.yaml @@ -101,7 +101,7 @@ paths: The length of time in milliseconds since an action was performed by this user. status_msg: - type: string + type: [string, "null"] description: The state message for this user if one was set. 404: description: |- @@ -185,7 +185,7 @@ paths: "last_active_ago": 395, "presence": "offline", "user_id": "@alice:matrix.org" - } + }, "type": "m.presence" }, { @@ -195,7 +195,7 @@ paths: "last_active_ago": 16874, "presence": "online", "user_id": "@marisa:matrix.org" - } + }, "type": "m.presence" } ] diff --git a/api/client-server/v1/sync.yaml b/api/client-server/v1/sync.yaml index 27c7073c..833c425a 100644 --- a/api/client-server/v1/sync.yaml +++ b/api/client-server/v1/sync.yaml @@ -343,7 +343,7 @@ paths: "body": "Hello world!", "msgtype": "m.text" }, - "room_id:" "!wfgy43Sg4a:matrix.org", + "room_id:": "!wfgy43Sg4a:matrix.org", "user_id": "@bob:matrix.org", "event_id": "$asfDuShaf7Gafaw:matrix.org", "type": "m.room.message" diff --git a/event-schemas/check_examples.py b/event-schemas/check_examples.py new file mode 100755 index 00000000..e54d3a1c --- /dev/null +++ b/event-schemas/check_examples.py @@ -0,0 +1,76 @@ +#! /usr/bin/env python + +import sys +import json +import os +import traceback + + +def import_error(module, package, debian, error): + sys.stderr.write(( + "Error importing %(module)s: %(error)r\n" + "To install %(module)s run:\n" + " pip install %(package)s\n" + "or on Debian run:\n" + " sudo apt-get install python-%(debian)s\n" + ) % locals()) + if __name__ == '__main__': + sys.exit(1) + +try: + import jsonschema +except ImportError as e: + import_error("jsonschema", "jsonschema", "jsonschema", e) + raise + +try: + import yaml +except ImportError as e: + import_error("yaml", "PyYAML", "yaml", e) + raise + + +def check_example_file(examplepath, schemapath): + with open(examplepath) as f: + example = yaml.load(f) + + with open(schemapath) as f: + schema = yaml.load(f) + + fileurl = "file://" + os.path.abspath(schemapath) + + print ("Checking schema for: %r %r" % (examplepath, schemapath)) + # Setting the 'id' tells jsonschema where the file is so that it + # can correctly resolve relative $ref references in the schema + schema['id'] = fileurl + try: + jsonschema.validate(example, schema) + except Exception as e: + raise ValueError("Error validating JSON schema for %r %r" % ( + examplepath, schemapath + ), e) + + +def check_example_dir(exampledir, schemadir): + errors = [] + for root, dirs, files in os.walk(exampledir): + for filename in files: + if filename.startswith("."): + # Skip over any vim .swp files. + continue + examplepath = os.path.join(root, filename) + schemapath = examplepath.replace(exampledir, schemadir) + try: + check_example_file(examplepath, schemapath) + except Exception as e: + errors.append(sys.exc_info()) + for (exc_type, exc_value, exc_trace) in errors: + traceback.print_exception(exc_type, exc_value, exc_trace) + if errors: + raise ValueError("Error validating examples") + +if __name__ == '__main__': + try: + check_example_dir("examples", "schema") + except: + sys.exit(1) diff --git a/jenkins.sh b/jenkins.sh new file mode 100755 index 00000000..e1043644 --- /dev/null +++ b/jenkins.sh @@ -0,0 +1,9 @@ +#! /bin/bash + +set -ex + +(cd event-schemas/ && ./check_examples.py) +(cd api && ./check_examples.py) +(cd scripts && ./gendoc.py) +(cd api && npm install && node validator.js -s "client-server/v1") +(cd event-schemas/ && ./check.sh) diff --git a/scripts/codehighlight.css b/scripts/codehighlight.css new file mode 100644 index 00000000..5c9b0c36 --- /dev/null +++ b/scripts/codehighlight.css @@ -0,0 +1,6 @@ +pre.code .comment, code .comment { color: green } +pre.code .keyword, code .keyword { color: darkred; font-weight: bold } +pre.code .name.builtin, code .name.builtin { color: darkred; font-weight: bold } +pre.code .literal.number, code .literal.number { color: blue } +pre.code .name.tag, code .name.tag { color: darkgreen } +pre.code .literal.string, code .literal.string { color: darkblue } diff --git a/scripts/gendoc.py b/scripts/gendoc.py index 22aaad11..2e303ea2 100755 --- a/scripts/gendoc.py +++ b/scripts/gendoc.py @@ -13,7 +13,7 @@ import yaml os.chdir(os.path.dirname(os.path.abspath(__file__))) stylesheets = { - "stylesheet_path": ["basic.css", "nature.css"] + "stylesheet_path": ["basic.css", "nature.css", "codehighlight.css"] } diff --git a/templating/matrix_templates/templates/http-api.tmpl b/templating/matrix_templates/templates/http-api.tmpl index a0b25924..a03ffa7d 100644 --- a/templating/matrix_templates/templates/http-api.tmpl +++ b/templating/matrix_templates/templates/http-api.tmpl @@ -62,7 +62,9 @@ Response{{"s" if endpoint.example.responses|length > 1 else "" }}: {{res["description"]}} -Example:: +Example + +.. code:: json {{res["example"] | indent_block(2)}} diff --git a/templating/matrix_templates/units.py b/templating/matrix_templates/units.py index 9ee14598..1a9d981a 100644 --- a/templating/matrix_templates/units.py +++ b/templating/matrix_templates/units.py @@ -97,6 +97,8 @@ def get_json_schema_object_fields(obj, enforce_title=False): desc += ( " Must be '%s'." % props[key_name]["enum"][0] ) + if isinstance(value_type, list): + value_type = " or ".join(value_type) fields["rows"].append({ "key": key_name,