|
|
@ -152,18 +152,13 @@ def get_json_schema_object_fields(obj, enforce_title=False):
|
|
|
|
del props[key_name]
|
|
|
|
del props[key_name]
|
|
|
|
|
|
|
|
|
|
|
|
# Sometimes you just want to specify that a thing is an object without
|
|
|
|
# Sometimes you just want to specify that a thing is an object without
|
|
|
|
# doing all the keys. Allow people to do that if they set a 'title'.
|
|
|
|
# doing all the keys.
|
|
|
|
if not props and obj.get("title"):
|
|
|
|
if not props:
|
|
|
|
return [{
|
|
|
|
return [{
|
|
|
|
"title": obj["title"],
|
|
|
|
"title": obj.get("title"),
|
|
|
|
"no-table": True
|
|
|
|
"no-table": True
|
|
|
|
}]
|
|
|
|
}]
|
|
|
|
|
|
|
|
|
|
|
|
if not props:
|
|
|
|
|
|
|
|
raise Exception(
|
|
|
|
|
|
|
|
"Object %s has no properties and no title" % obj
|
|
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
required_keys = set(obj.get("required", []))
|
|
|
|
required_keys = set(obj.get("required", []))
|
|
|
|
|
|
|
|
|
|
|
|
obj_title = obj.get("title")
|
|
|
|
obj_title = obj.get("title")
|
|
|
@ -279,11 +274,7 @@ def process_prop(key_name, prop, required):
|
|
|
|
"tables": tables,
|
|
|
|
"tables": tables,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def deduplicate_tables(tables):
|
|
|
|
def get_tables_for_schema(schema):
|
|
|
|
|
|
|
|
schema = inherit_parents(schema)
|
|
|
|
|
|
|
|
tables = get_json_schema_object_fields(schema)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# the result may contain duplicates, if objects are referred to more than
|
|
|
|
# the result may contain duplicates, if objects are referred to more than
|
|
|
|
# once. Filter them out.
|
|
|
|
# once. Filter them out.
|
|
|
|
#
|
|
|
|
#
|
|
|
@ -305,6 +296,64 @@ def get_tables_for_schema(schema):
|
|
|
|
|
|
|
|
|
|
|
|
return filtered
|
|
|
|
return filtered
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def get_tables_for_schema(schema):
|
|
|
|
|
|
|
|
schema = inherit_parents(schema)
|
|
|
|
|
|
|
|
tables = get_json_schema_object_fields(schema)
|
|
|
|
|
|
|
|
return deduplicate_tables(tables)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def get_tables_for_response(api, schema):
|
|
|
|
|
|
|
|
schema = inherit_parents(schema)
|
|
|
|
|
|
|
|
resp_type = schema.get("type")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if resp_type is None:
|
|
|
|
|
|
|
|
raise KeyError("Response definition for api '%s' missing 'type' field"
|
|
|
|
|
|
|
|
% (api))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
resp_title = schema.get("title", "")
|
|
|
|
|
|
|
|
resp_description = schema.get("description", "")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
logger.debug("Found a 200 response for this API; type %s" % resp_type)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if resp_type == "object":
|
|
|
|
|
|
|
|
tables = get_json_schema_object_fields(
|
|
|
|
|
|
|
|
schema,
|
|
|
|
|
|
|
|
enforce_title=False,
|
|
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
else:
|
|
|
|
|
|
|
|
nested_items = []
|
|
|
|
|
|
|
|
if resp_type == "array":
|
|
|
|
|
|
|
|
items = inherit_parents(schema["items"])
|
|
|
|
|
|
|
|
if items["type"] == "object":
|
|
|
|
|
|
|
|
nested_items = get_json_schema_object_fields(
|
|
|
|
|
|
|
|
items,
|
|
|
|
|
|
|
|
enforce_title=True,
|
|
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
value_id = nested_items[0]["title"]
|
|
|
|
|
|
|
|
resp_type = "[%s]" % value_id
|
|
|
|
|
|
|
|
else:
|
|
|
|
|
|
|
|
raise Exception("Unsupported array response type [%s] for %s" %
|
|
|
|
|
|
|
|
(items["type"], api))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
tables = [{
|
|
|
|
|
|
|
|
"title": resp_title,
|
|
|
|
|
|
|
|
"rows": [{
|
|
|
|
|
|
|
|
"key": "<body>",
|
|
|
|
|
|
|
|
"type": resp_type,
|
|
|
|
|
|
|
|
"desc": resp_description,
|
|
|
|
|
|
|
|
}]
|
|
|
|
|
|
|
|
}] + nested_items
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
res = deduplicate_tables(tables)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if len(res) == 0:
|
|
|
|
|
|
|
|
logger.warn(
|
|
|
|
|
|
|
|
"This API appears to have no response table. Are you " +
|
|
|
|
|
|
|
|
"sure this API returns no parameters?"
|
|
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
return res
|
|
|
|
|
|
|
|
|
|
|
|
def get_example_for_schema(schema):
|
|
|
|
def get_example_for_schema(schema):
|
|
|
|
"""Returns a python object representing a suitable example for this object"""
|
|
|
|
"""Returns a python object representing a suitable example for this object"""
|
|
|
|
if 'example' in schema:
|
|
|
|
if 'example' in schema:
|
|
|
@ -349,13 +398,14 @@ class MatrixUnits(Units):
|
|
|
|
"rate_limited": 429 in single_api.get("responses", {}),
|
|
|
|
"rate_limited": 429 in single_api.get("responses", {}),
|
|
|
|
"req_param_by_loc": {},
|
|
|
|
"req_param_by_loc": {},
|
|
|
|
"req_body_tables": [],
|
|
|
|
"req_body_tables": [],
|
|
|
|
|
|
|
|
"res_headers": [],
|
|
|
|
"res_tables": [],
|
|
|
|
"res_tables": [],
|
|
|
|
"responses": [],
|
|
|
|
"responses": [],
|
|
|
|
"example": {
|
|
|
|
"example": {
|
|
|
|
"req": "",
|
|
|
|
"req": "",
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
self.log(" ------- Endpoint: %s %s ------- " % (method, path))
|
|
|
|
logger.info(" ------- Endpoint: %s %s ------- " % (method, path))
|
|
|
|
for param in single_api.get("parameters", []):
|
|
|
|
for param in single_api.get("parameters", []):
|
|
|
|
param_loc = param["in"]
|
|
|
|
param_loc = param["in"]
|
|
|
|
if param_loc == "body":
|
|
|
|
if param_loc == "body":
|
|
|
@ -398,6 +448,24 @@ class MatrixUnits(Units):
|
|
|
|
"example": example,
|
|
|
|
"example": example,
|
|
|
|
})
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# add response params if this API has any.
|
|
|
|
|
|
|
|
if good_response:
|
|
|
|
|
|
|
|
if "schema" in good_response:
|
|
|
|
|
|
|
|
endpoint["res_tables"] = get_tables_for_response(
|
|
|
|
|
|
|
|
"%s %s" % (method, path),
|
|
|
|
|
|
|
|
good_response["schema"]
|
|
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
if "headers" in good_response:
|
|
|
|
|
|
|
|
headers = []
|
|
|
|
|
|
|
|
for (header_name, header) in good_response["headers"].iteritems():
|
|
|
|
|
|
|
|
headers.append({
|
|
|
|
|
|
|
|
"key": header_name,
|
|
|
|
|
|
|
|
"type": header["type"],
|
|
|
|
|
|
|
|
"desc": header["description"],
|
|
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
endpoint["res_headers"] = headers
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# calculate the example request
|
|
|
|
path_template = api.get("basePath", "").rstrip("/") + path
|
|
|
|
path_template = api.get("basePath", "").rstrip("/") + path
|
|
|
|
qps = []
|
|
|
|
qps = []
|
|
|
|
body = ""
|
|
|
|
body = ""
|
|
|
@ -406,7 +474,7 @@ class MatrixUnits(Units):
|
|
|
|
example = get_example_for_param(param)
|
|
|
|
example = get_example_for_param(param)
|
|
|
|
|
|
|
|
|
|
|
|
if not example:
|
|
|
|
if not example:
|
|
|
|
self.log(
|
|
|
|
logger.warn(
|
|
|
|
"The parameter %s is missing an example." %
|
|
|
|
"The parameter %s is missing an example." %
|
|
|
|
param["name"])
|
|
|
|
param["name"])
|
|
|
|
continue
|
|
|
|
continue
|
|
|
@ -437,65 +505,6 @@ class MatrixUnits(Units):
|
|
|
|
method.upper(), path_template, query_string
|
|
|
|
method.upper(), path_template, query_string
|
|
|
|
)
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
# add response params if this API has any.
|
|
|
|
|
|
|
|
if good_response:
|
|
|
|
|
|
|
|
self.log("Found a 200 response for this API")
|
|
|
|
|
|
|
|
res_type = Units.prop(good_response, "schema/type")
|
|
|
|
|
|
|
|
res_name = Units.prop(good_response, "schema/name")
|
|
|
|
|
|
|
|
if res_type and res_type not in ["object", "array"]:
|
|
|
|
|
|
|
|
# response is a raw string or something like that
|
|
|
|
|
|
|
|
good_table = {
|
|
|
|
|
|
|
|
"title": None,
|
|
|
|
|
|
|
|
"rows": [{
|
|
|
|
|
|
|
|
"key": "<" + res_type + ">" if not res_name else res_name,
|
|
|
|
|
|
|
|
"type": res_type,
|
|
|
|
|
|
|
|
"desc": res.get("description", ""),
|
|
|
|
|
|
|
|
"req_str": ""
|
|
|
|
|
|
|
|
}]
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
if good_response.get("headers"):
|
|
|
|
|
|
|
|
for (header_name, header) in good_response.get("headers").iteritems():
|
|
|
|
|
|
|
|
good_table["rows"].append({
|
|
|
|
|
|
|
|
"key": header_name,
|
|
|
|
|
|
|
|
"type": "Header<" + header["type"] + ">",
|
|
|
|
|
|
|
|
"desc": header["description"],
|
|
|
|
|
|
|
|
"req_str": ""
|
|
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
endpoint["res_tables"].append(good_table)
|
|
|
|
|
|
|
|
elif res_type and Units.prop(good_response, "schema/properties"):
|
|
|
|
|
|
|
|
# response is an object:
|
|
|
|
|
|
|
|
schema = good_response["schema"]
|
|
|
|
|
|
|
|
res_tables = get_tables_for_schema(schema)
|
|
|
|
|
|
|
|
endpoint["res_tables"].extend(res_tables)
|
|
|
|
|
|
|
|
elif res_type and Units.prop(good_response, "schema/items"):
|
|
|
|
|
|
|
|
# response is an array:
|
|
|
|
|
|
|
|
# FIXME: Doesn't recurse at all.
|
|
|
|
|
|
|
|
schema = good_response["schema"]
|
|
|
|
|
|
|
|
array_type = Units.prop(schema, "items/type")
|
|
|
|
|
|
|
|
if Units.prop(schema, "items/allOf"):
|
|
|
|
|
|
|
|
array_type = (
|
|
|
|
|
|
|
|
Units.prop(schema, "items/title")
|
|
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
endpoint["res_tables"].append({
|
|
|
|
|
|
|
|
"title": schema.get("title", ""),
|
|
|
|
|
|
|
|
"rows": [{
|
|
|
|
|
|
|
|
"key": "N/A",
|
|
|
|
|
|
|
|
"type": ("[%s]" % array_type),
|
|
|
|
|
|
|
|
"desc": schema.get("description", ""),
|
|
|
|
|
|
|
|
"req_str": ""
|
|
|
|
|
|
|
|
}]
|
|
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
for response_table in endpoint["res_tables"]:
|
|
|
|
|
|
|
|
self.log("Response: %s" % response_table["title"])
|
|
|
|
|
|
|
|
for r in response_table["rows"]:
|
|
|
|
|
|
|
|
self.log("Row: %s" % r)
|
|
|
|
|
|
|
|
if len(endpoint["res_tables"]) == 0:
|
|
|
|
|
|
|
|
self.log(
|
|
|
|
|
|
|
|
"This API appears to have no response table. Are you " +
|
|
|
|
|
|
|
|
"sure this API returns no parameters?"
|
|
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
endpoints.append(endpoint)
|
|
|
|
endpoints.append(endpoint)
|
|
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
return {
|
|
|
@ -512,22 +521,34 @@ class MatrixUnits(Units):
|
|
|
|
:param dict endpoint_data dictionary of endpoint data to be updated
|
|
|
|
:param dict endpoint_data dictionary of endpoint data to be updated
|
|
|
|
"""
|
|
|
|
"""
|
|
|
|
try:
|
|
|
|
try:
|
|
|
|
req_body_tables = get_tables_for_schema(param["schema"])
|
|
|
|
schema = inherit_parents(param["schema"])
|
|
|
|
except Exception, e:
|
|
|
|
if schema["type"] != "object":
|
|
|
|
logger.warning("Error decoding body of API endpoint %s %s" %
|
|
|
|
logger.warn(
|
|
|
|
(endpoint_data["method"], endpoint_data["path"]),
|
|
|
|
"Unsupported body type %s for %s %s", schema["type"],
|
|
|
|
exc_info=1)
|
|
|
|
endpoint_data["method"], endpoint_data["path"]
|
|
|
|
return
|
|
|
|
)
|
|
|
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
req_body_tables = get_tables_for_schema(schema)
|
|
|
|
|
|
|
|
|
|
|
|
# put the top-level parameters into 'req_param_by_loc', and the others
|
|
|
|
if req_body_tables == []:
|
|
|
|
# into 'req_body_tables'
|
|
|
|
# no fields defined for the body.
|
|
|
|
body_params = endpoint_data['req_param_by_loc'].setdefault("JSON body",[])
|
|
|
|
return
|
|
|
|
body_params.extend(req_body_tables[0]["rows"])
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
body_tables = req_body_tables[1:]
|
|
|
|
# put the top-level parameters into 'req_param_by_loc', and the others
|
|
|
|
# TODO: remove this when PR #255 has landed
|
|
|
|
# into 'req_body_tables'
|
|
|
|
body_tables = (t for t in body_tables if not t.get('no-table'))
|
|
|
|
body_params = endpoint_data['req_param_by_loc'].setdefault("JSON body",[])
|
|
|
|
endpoint_data['req_body_tables'].extend(body_tables)
|
|
|
|
body_params.extend(req_body_tables[0]["rows"])
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
body_tables = req_body_tables[1:]
|
|
|
|
|
|
|
|
endpoint_data['req_body_tables'].extend(body_tables)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
except Exception, e:
|
|
|
|
|
|
|
|
e2 = Exception(
|
|
|
|
|
|
|
|
"Error decoding body of API endpoint %s %s: %s" %
|
|
|
|
|
|
|
|
(endpoint_data["method"], endpoint_data["path"], e)
|
|
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
raise e2, None, sys.exc_info()[2]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def load_swagger_apis(self):
|
|
|
|
def load_swagger_apis(self):
|
|
|
@ -536,7 +557,7 @@ class MatrixUnits(Units):
|
|
|
|
for filename in os.listdir(path):
|
|
|
|
for filename in os.listdir(path):
|
|
|
|
if not filename.endswith(".yaml"):
|
|
|
|
if not filename.endswith(".yaml"):
|
|
|
|
continue
|
|
|
|
continue
|
|
|
|
self.log("Reading swagger API: %s" % filename)
|
|
|
|
logger.info("Reading swagger API: %s" % filename)
|
|
|
|
filepath = os.path.join(path, filename)
|
|
|
|
filepath = os.path.join(path, filename)
|
|
|
|
with open(filepath, "r") as f:
|
|
|
|
with open(filepath, "r") as f:
|
|
|
|
# strip .yaml
|
|
|
|
# strip .yaml
|
|
|
@ -653,13 +674,13 @@ class MatrixUnits(Units):
|
|
|
|
return schemata
|
|
|
|
return schemata
|
|
|
|
|
|
|
|
|
|
|
|
def read_event_schema(self, filepath):
|
|
|
|
def read_event_schema(self, filepath):
|
|
|
|
self.log("Reading %s" % filepath)
|
|
|
|
logger.info("Reading %s" % filepath)
|
|
|
|
|
|
|
|
|
|
|
|
with open(filepath, "r") as f:
|
|
|
|
with open(filepath, "r") as f:
|
|
|
|
json_schema = yaml.load(f)
|
|
|
|
json_schema = yaml.load(f)
|
|
|
|
|
|
|
|
|
|
|
|
schema = {
|
|
|
|
schema = {
|
|
|
|
"typeof": None,
|
|
|
|
"typeof": "",
|
|
|
|
"typeof_info": "",
|
|
|
|
"typeof_info": "",
|
|
|
|
"type": None,
|
|
|
|
"type": None,
|
|
|
|
"title": None,
|
|
|
|
"title": None,
|
|
|
@ -682,11 +703,9 @@ class MatrixUnits(Units):
|
|
|
|
STATE_EVENT: "State Event"
|
|
|
|
STATE_EVENT: "State Event"
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if type(json_schema.get("allOf")) == list:
|
|
|
|
if type(json_schema.get("allOf")) == list:
|
|
|
|
schema["typeof"] = base_defs.get(
|
|
|
|
firstRef = json_schema["allOf"][0]["$ref"]
|
|
|
|
json_schema["allOf"][0].get("$ref")
|
|
|
|
if firstRef in base_defs:
|
|
|
|
)
|
|
|
|
schema["typeof"] = base_defs[firstRef]
|
|
|
|
elif json_schema.get("title"):
|
|
|
|
|
|
|
|
schema["typeof"] = json_schema["title"]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
json_schema = resolve_references(filepath, json_schema)
|
|
|
|
json_schema = resolve_references(filepath, json_schema)
|
|
|
|
|
|
|
|
|
|
|
|