Merge pull request #141 from matrix-org/markjh/full_http_api_docs

Include the full schema for an http API in the docs
pull/977/head
Mark Haines 9 years ago
commit b7cc5b1018

@ -1,4 +1,5 @@
{ {
"title": "PushRule",
"type": "object", "type": "object",
"properties": { "properties": {
"default": { "default": {
@ -17,4 +18,4 @@
"type": "array" "type": "array"
} }
} }
} }

@ -1,60 +1,65 @@
{ {
"type": "object", "type": "object",
"properties": { "properties": {
"content": { "content": {
"items": { "items": {
"type": "object", "type": "object",
"title": "PushRule",
"allOf": [ "allOf": [
{ {
"$ref": "push_rule.json" "$ref": "push_rule.json"
} }
] ]
}, },
"type": "array" "type": "array"
}, },
"override": { "override": {
"items": { "items": {
"type": "object", "type": "object",
"title": "PushRule",
"allOf": [ "allOf": [
{ {
"$ref": "push_rule.json" "$ref": "push_rule.json"
} }
] ]
}, },
"type": "array" "type": "array"
}, },
"sender": { "sender": {
"items": { "items": {
"type": "object", "type": "object",
"title": "PushRule",
"allOf": [ "allOf": [
{ {
"$ref": "push_rule.json" "$ref": "push_rule.json"
} }
] ]
}, },
"type": "array" "type": "array"
}, },
"underride": { "underride": {
"items": { "items": {
"type": "object", "type": "object",
"title": "PushRule",
"allOf": [ "allOf": [
{ {
"$ref": "push_rule.json" "$ref": "push_rule.json"
} }
] ]
}, },
"type": "array" "type": "array"
}, },
"room": { "room": {
"items": { "items": {
"type": "object", "type": "object",
"title": "PushRule",
"allOf": [ "allOf": [
{ {
"$ref": "push_rule.json" "$ref": "push_rule.json"
} }
] ]
}, },
"type": "array" "type": "array"
} }
} }
} }

@ -5,6 +5,7 @@
"type": "array", "type": "array",
"description": "List of events", "description": "List of events",
"items": { "items": {
"title": "Event",
"type": "object" "type": "object"
} }
} }

@ -5,6 +5,7 @@
"properties": { "properties": {
"content": { "content": {
"type": "object", "type": "object",
"title": "EventContent",
"description": "The fields in this object will vary depending on the type of event. When interacting with the REST API, this is the HTTP body." "description": "The fields in this object will vary depending on the type of event. When interacting with the REST API, this is the HTTP body."
}, },
"type": { "type": {

@ -11,6 +11,7 @@
"description": "A unique key which defines the overwriting semantics for this piece of room state. This value is often a zero-length string. The presence of this key makes this event a State Event. The key MUST NOT start with '_'." "description": "A unique key which defines the overwriting semantics for this piece of room state. This value is often a zero-length string. The presence of this key makes this event a State Event. The key MUST NOT start with '_'."
}, },
"prev_content": { "prev_content": {
"title": "EventContent",
"type": "object", "type": "object",
"description": "Optional. The previous ``content`` for this event. If there is no previous content, this key will be missing." "description": "Optional. The previous ``content`` for this event. If there is no previous content, this key will be missing."
} }

@ -8,6 +8,7 @@
"properties": { "properties": {
"content": { "content": {
"type": "object", "type": "object",
"title": "EventContent",
"properties": { "properties": {
"membership": { "membership": {
"type": "string", "type": "string",

@ -28,7 +28,25 @@ ROOM_EVENT = "core-event-schema/room_event.json"
STATE_EVENT = "core-event-schema/state_event.json" STATE_EVENT = "core-event-schema/state_event.json"
def get_json_schema_object_fields(obj, enforce_title=False): def resolve_references(path, schema):
if isinstance(schema, dict):
result = {}
for key, value in schema.items():
if key == "$ref":
path = os.path.join(os.path.dirname(path), value)
with open(path) as f:
schema = json.load(f)
return resolve_references(path, schema)
else:
result[key] = resolve_references(path, value)
return result
elif isinstance(schema, list):
return [resolve_references(path, value) for value in schema]
else:
return schema
def get_json_schema_object_fields(obj, enforce_title=False, include_parents=False):
# Algorithm: # Algorithm:
# f.e. property => add field info (if field is object then recurse) # f.e. property => add field info (if field is object then recurse)
if obj.get("type") != "object": if obj.get("type") != "object":
@ -36,9 +54,9 @@ def get_json_schema_object_fields(obj, enforce_title=False):
"get_json_schema_object_fields: Object %s isn't an object." % obj "get_json_schema_object_fields: Object %s isn't an object." % obj
) )
if enforce_title and not obj.get("title"): if enforce_title and not obj.get("title"):
raise Exception( # Force a default titile of "NO_TITLE" to make it obvious in the
"get_json_schema_object_fields: Nested object %s doesn't have a title." % obj # specification output which parts of the schema are missing a title
) obj["title"] = 'NO_TITLE'
required_keys = obj.get("required") required_keys = obj.get("required")
if not required_keys: if not required_keys:
@ -73,9 +91,15 @@ def get_json_schema_object_fields(obj, enforce_title=False):
"Object %s has no properties or parents." % obj "Object %s has no properties or parents." % obj
) )
if not props: # parents only if not props: # parents only
if include_parents:
if obj["title"] == "NO_TITLE" and parents[0].get("title"):
obj["title"] = parents[0].get("title")
props = parents[0].get("properties")
if not props:
return [{ return [{
"title": obj["title"], "title": obj["title"],
"parent": parents[0]["$ref"], "parent": parents[0].get("$ref"),
"no-table": True "no-table": True
}] }]
@ -91,7 +115,8 @@ def get_json_schema_object_fields(obj, enforce_title=False):
if prop_val == "object": if prop_val == "object":
nested_object = get_json_schema_object_fields( nested_object = get_json_schema_object_fields(
props[key_name]["additionalProperties"], props[key_name]["additionalProperties"],
enforce_title=True enforce_title=True,
include_parents=include_parents,
) )
key = props[key_name]["additionalProperties"].get( key = props[key_name]["additionalProperties"].get(
"x-pattern", "string" "x-pattern", "string"
@ -103,8 +128,9 @@ def get_json_schema_object_fields(obj, enforce_title=False):
value_type = "{string: %s}" % prop_val value_type = "{string: %s}" % prop_val
else: else:
nested_object = get_json_schema_object_fields( nested_object = get_json_schema_object_fields(
props[key_name], props[key_name],
enforce_title=True enforce_title=True,
include_parents=include_parents,
) )
value_type = "{%s}" % nested_object[0]["title"] value_type = "{%s}" % nested_object[0]["title"]
@ -114,13 +140,17 @@ def get_json_schema_object_fields(obj, enforce_title=False):
# if the items of the array are objects then recurse # if the items of the array are objects then recurse
if props[key_name]["items"]["type"] == "object": if props[key_name]["items"]["type"] == "object":
nested_object = get_json_schema_object_fields( nested_object = get_json_schema_object_fields(
props[key_name]["items"], props[key_name]["items"],
enforce_title=True enforce_title=True,
include_parents=include_parents,
) )
value_type = "[%s]" % nested_object[0]["title"] value_type = "[%s]" % nested_object[0]["title"]
tables += nested_object tables += nested_object
else: else:
value_type = "[%s]" % props[key_name]["items"]["type"] value_type = props[key_name]["items"]["type"]
if isinstance(value_type, list):
value_type = " or ".join(value_type)
value_type = "[%s]" % value_type
array_enums = props[key_name]["items"].get("enum") array_enums = props[key_name]["items"].get("enum")
if array_enums: if array_enums:
if len(array_enums) > 1: if len(array_enums) > 1:
@ -137,12 +167,16 @@ def get_json_schema_object_fields(obj, enforce_title=False):
if props[key_name].get("enum"): if props[key_name].get("enum"):
if len(props[key_name].get("enum")) > 1: if len(props[key_name].get("enum")) > 1:
value_type = "enum" value_type = "enum"
if desc:
desc += " "
desc += ( desc += (
" One of: %s" % json.dumps(props[key_name]["enum"]) "One of: %s" % json.dumps(props[key_name]["enum"])
) )
else: else:
if desc:
desc += " "
desc += ( desc += (
" Must be '%s'." % props[key_name]["enum"][0] "Must be '%s'." % props[key_name]["enum"][0]
) )
if isinstance(value_type, list): if isinstance(value_type, list):
value_type = " or ".join(value_type) value_type = " or ".join(value_type)
@ -154,12 +188,22 @@ def get_json_schema_object_fields(obj, enforce_title=False):
"desc": desc, "desc": desc,
"req_str": "**Required.** " if required else "" "req_str": "**Required.** " if required else ""
}) })
return tables
titles = set()
filtered = []
for table in tables:
if table.get("title") in titles:
continue
titles.add(table.get("title"))
filtered.append(table)
return filtered
class MatrixUnits(Units): class MatrixUnits(Units):
def _load_swagger_meta(self, api, group_name): def _load_swagger_meta(self, filepath, api, group_name):
endpoints = [] endpoints = []
for path in api["paths"]: for path in api["paths"]:
for method in api["paths"][path]: for method in api["paths"][path]:
@ -262,7 +306,10 @@ class MatrixUnits(Units):
if is_array_of_objects: if is_array_of_objects:
req_obj = req_obj["items"] req_obj = req_obj["items"]
req_tables = get_json_schema_object_fields(req_obj) req_tables = get_json_schema_object_fields(
resolve_references(filepath, req_obj),
include_parents=True,
)
if req_tables > 1: if req_tables > 1:
for table in req_tables[1:]: for table in req_tables[1:]:
@ -379,7 +426,10 @@ class MatrixUnits(Units):
elif res_type and Units.prop(good_response, "schema/properties"): elif res_type and Units.prop(good_response, "schema/properties"):
# response is an object: # response is an object:
schema = good_response["schema"] schema = good_response["schema"]
res_tables = get_json_schema_object_fields(schema) res_tables = get_json_schema_object_fields(
resolve_references(filepath, schema),
include_parents=True,
)
for table in res_tables: for table in res_tables:
if "no-table" not in table: if "no-table" not in table:
endpoint["res_tables"].append(table) endpoint["res_tables"].append(table)
@ -445,13 +495,16 @@ class MatrixUnits(Units):
if not filename.endswith(".yaml"): if not filename.endswith(".yaml"):
continue continue
self.log("Reading swagger API: %s" % filename) self.log("Reading swagger API: %s" % filename)
with open(os.path.join(path, filename), "r") as f: filepath = os.path.join(path, filename)
with open(filepath, "r") as f:
# strip .yaml # strip .yaml
group_name = filename[:-5].replace("-", "_") group_name = filename[:-5].replace("-", "_")
if is_v2: if is_v2:
group_name = "v2_" + group_name group_name = "v2_" + group_name
api = yaml.load(f.read()) api = yaml.load(f.read())
api["__meta"] = self._load_swagger_meta(api, group_name) api["__meta"] = self._load_swagger_meta(
filepath, api, group_name
)
apis[group_name] = api apis[group_name] = api
return apis return apis

Loading…
Cancel
Save