From 6b23598a26f37a498f48a81aa7fdc20d4601ee76 Mon Sep 17 00:00:00 2001 From: Richard van der Hoff Date: Fri, 6 May 2016 15:42:31 +0100 Subject: [PATCH 1/3] Improve API examples in the spec * Show response codes even if we don't have examples for them * Walk the objects to build param examples if none are given at the top level --- .../matrix_templates/templates/http-api.tmpl | 11 +- templating/matrix_templates/units.py | 107 ++++++++++-------- 2 files changed, 66 insertions(+), 52 deletions(-) diff --git a/templating/matrix_templates/templates/http-api.tmpl b/templating/matrix_templates/templates/http-api.tmpl index adecd5d2..ebf3b4ff 100644 --- a/templating/matrix_templates/templates/http-api.tmpl +++ b/templating/matrix_templates/templates/http-api.tmpl @@ -44,22 +44,25 @@ Example request: {{endpoint.example.req | indent_block(2)}} -{% if endpoint.example.responses|length > 0 -%} -Response{{"s" if endpoint.example.responses|length > 1 else "" }}: +{% if endpoint.responses|length > 0 -%} +Response{{"s" if endpoint.responses|length > 1 else "" }}: {% endif -%} -{% for res in endpoint.example.responses -%} +{% for res in endpoint.responses -%} **Status code {{res["code"]}}:** {{res["description"]}} +{% if res["example"] -%} + Example .. code:: json {{res["example"] | indent_block(2)}} -{% endfor %} +{% endif -%} +{% endfor %} diff --git a/templating/matrix_templates/units.py b/templating/matrix_templates/units.py index df347d40..8a820459 100644 --- a/templating/matrix_templates/units.py +++ b/templating/matrix_templates/units.py @@ -273,9 +273,30 @@ def get_tables_for_schema(schema, mark_required=True): return filtered +def get_example_for_schema(schema): + """Returns a python object representing a suitable example for this object""" + if 'example' in schema: + return schema['example'] + if 'properties' in schema: + res = {} + for prop_name, prop in schema['properties'].iteritems(): + logger.debug("Parsing property %r" % prop_name) + prop_example = get_example_for_schema(prop) + res[prop_name] = prop_example + return res + if 'items' in schema: + return [get_example_for_schema(schema['items'])] + return schema.get('type', '??') + +def get_example_for_param(param): + if 'x-example' in param: + return param['x-example'] + if 'schema' not in param: + return None + return json.dumps(get_example_for_schema(param['schema']), + indent=2) class MatrixUnits(Units): - def _load_swagger_meta(self, api, group_name): endpoints = [] for path in api["paths"]: @@ -293,10 +314,9 @@ class MatrixUnits(Units): "req_param_by_loc": {}, "req_body_tables": [], "res_tables": [], + "responses": [], "example": { "req": "", - "responses": [], - "good_response": "" } } self.log(" ------- Endpoint: %s %s ------- " % (method, path)) @@ -330,60 +350,51 @@ class MatrixUnits(Units): # endfor[param] good_response = None - for code, res in single_api.get("responses", {}).items(): + for code in sorted(single_api.get("responses", {}).keys()): + res = single_api["responses"][code] if not good_response and code == 200: good_response = res description = res.get("description", "") example = res.get("examples", {}).get("application/json", "") - if description and example: - endpoint["example"]["responses"].append({ - "code": code, - "description": description, - "example": example, - }) + endpoint["responses"].append({ + "code": code, + "description": description, + "example": example, + }) - # form example request if it has one. It "has one" if all params - # have either "x-example" or a "schema" with an "example". - params_missing_examples = [ - p for p in single_api.get("parameters", []) if ( - "x-example" not in p and - not Units.prop(p, "schema/example") - ) - ] - if len(params_missing_examples) == 0: - path_template = api.get("basePath", "").rstrip("/") + path - qps = [] - body = "" - for param in single_api.get("parameters", []): - if param["in"] == "path": - path_template = path_template.replace( - "{%s}" % param["name"], urllib.quote( - param["x-example"] - ) - ) - elif param["in"] == "body": - body = param["schema"]["example"] - elif param["in"] == "query": - example = param["x-example"] - if type(example) == list: - for value in example: - qps.append((param["name"], value)) + path_template = api.get("basePath", "").rstrip("/") + path + qps = [] + body = "" + for param in single_api.get("parameters", []): + example = get_example_for_param(param) + + if not example: + self.log( + "The parameter %s is missing an example." % + param["name"]) + continue + + if param["in"] == "path": + path_template = path_template.replace( + "{%s}" % param["name"], urllib.quote(example) + ) + elif param["in"] == "body": + body = example + elif param["in"] == "query": + if type(example) == list: + for value in example: + qps.append((param["name"], value)) else: qps.append((param["name"], example)) - query_string = "" if len(qps) == 0 else "?"+urllib.urlencode(qps) - if body: - endpoint["example"]["req"] = "%s %s%s HTTP/1.1\nContent-Type: application/json\n\n%s" % ( - method.upper(), path_template, query_string, body - ) - else: - endpoint["example"]["req"] = "%s %s%s HTTP/1.1\n\n" % ( - method.upper(), path_template, query_string - ) + query_string = "" if len(qps) == 0 else "?"+urllib.urlencode(qps) + if body: + endpoint["example"]["req"] = "%s %s%s HTTP/1.1\nContent-Type: application/json\n\n%s" % ( + method.upper(), path_template, query_string, body + ) else: - self.log( - "The following parameters are missing examples :( \n %s" % - [ p["name"] for p in params_missing_examples ] + endpoint["example"]["req"] = "%s %s%s HTTP/1.1\n\n" % ( + method.upper(), path_template, query_string ) # add response params if this API has any. From 45199d0edc7abb7494b2c088de01b3e758d3cec1 Mon Sep 17 00:00:00 2001 From: Richard van der Hoff Date: Fri, 6 May 2016 16:05:05 +0100 Subject: [PATCH 2/3] Attempt to parse examples as json ... because some of them are, and we don't want to double-escape them. --- templating/matrix_templates/units.py | 52 +++++++++++++++++----------- 1 file changed, 31 insertions(+), 21 deletions(-) diff --git a/templating/matrix_templates/units.py b/templating/matrix_templates/units.py index 8a820459..d0f24157 100644 --- a/templating/matrix_templates/units.py +++ b/templating/matrix_templates/units.py @@ -276,7 +276,14 @@ def get_tables_for_schema(schema, mark_required=True): def get_example_for_schema(schema): """Returns a python object representing a suitable example for this object""" if 'example' in schema: - return schema['example'] + example = schema['example'] + if isinstance(example, basestring): + try: + example = json.loads(example) + except ValueError: + # not json, just use the string + pass + return example if 'properties' in schema: res = {} for prop_name, prop in schema['properties'].iteritems(): @@ -293,8 +300,7 @@ def get_example_for_param(param): return param['x-example'] if 'schema' not in param: return None - return json.dumps(get_example_for_schema(param['schema']), - indent=2) + return get_example_for_schema(param['schema']) class MatrixUnits(Units): def _load_swagger_meta(self, api, group_name): @@ -366,26 +372,30 @@ class MatrixUnits(Units): qps = [] body = "" for param in single_api.get("parameters", []): - example = get_example_for_param(param) + try: + example = get_example_for_param(param) - if not example: - self.log( - "The parameter %s is missing an example." % - param["name"]) - continue + if not example: + self.log( + "The parameter %s is missing an example." % + param["name"]) + continue - if param["in"] == "path": - path_template = path_template.replace( - "{%s}" % param["name"], urllib.quote(example) - ) - elif param["in"] == "body": - body = example - elif param["in"] == "query": - if type(example) == list: - for value in example: - qps.append((param["name"], value)) - else: - qps.append((param["name"], example)) + if param["in"] == "path": + path_template = path_template.replace( + "{%s}" % param["name"], urllib.quote(example) + ) + elif param["in"] == "body": + body = json.dumps(example, indent=2) + elif param["in"] == "query": + if type(example) == list: + for value in example: + qps.append((param["name"], value)) + else: + qps.append((param["name"], example)) + except Exception, e: + raise Exception("Error handling parameter %s" % param["name"], + e) query_string = "" if len(qps) == 0 else "?"+urllib.urlencode(qps) if body: From 167b84cc7fa7e5802a9f3de24811dc8daa17e37a Mon Sep 17 00:00:00 2001 From: Richard van der Hoff Date: Fri, 6 May 2016 16:30:07 +0100 Subject: [PATCH 3/3] Another go at formatting examples Let's try to avoid parsing the JSON, as it will reorder the examples. --- templating/matrix_templates/units.py | 28 +++++++++++++--------------- 1 file changed, 13 insertions(+), 15 deletions(-) diff --git a/templating/matrix_templates/units.py b/templating/matrix_templates/units.py index d0f24157..94ba5775 100644 --- a/templating/matrix_templates/units.py +++ b/templating/matrix_templates/units.py @@ -99,10 +99,11 @@ def get_json_schema_object_fields(obj, enforce_title=False, obj["title"] = 'NO_TITLE' additionalProps = obj.get("additionalProperties") - if additionalProps: + props = obj.get("properties") + if additionalProps and not props: + # not "really" an object, just a KV store additionalProps = inherit_parents(additionalProps) - # not "really" an object, just a KV store logger.debug("%s is a pseudo-object", obj.get("title")) key_type = additionalProps.get("x-pattern", "string") @@ -127,7 +128,6 @@ def get_json_schema_object_fields(obj, enforce_title=False, logger.debug("%s done: returning %s", obj.get("title"), tables) return tables - props = obj.get("properties") if not props: props = obj.get("patternProperties") if props: @@ -277,12 +277,6 @@ def get_example_for_schema(schema): """Returns a python object representing a suitable example for this object""" if 'example' in schema: example = schema['example'] - if isinstance(example, basestring): - try: - example = json.loads(example) - except ValueError: - # not json, just use the string - pass return example if 'properties' in schema: res = {} @@ -298,9 +292,13 @@ def get_example_for_schema(schema): def get_example_for_param(param): if 'x-example' in param: return param['x-example'] - if 'schema' not in param: + schema = param.get('schema') + if not schema: return None - return get_example_for_schema(param['schema']) + if 'example' in schema: + return schema['example'] + return json.dumps(get_example_for_schema(param['schema']), + indent=2) class MatrixUnits(Units): def _load_swagger_meta(self, api, group_name): @@ -386,7 +384,7 @@ class MatrixUnits(Units): "{%s}" % param["name"], urllib.quote(example) ) elif param["in"] == "body": - body = json.dumps(example, indent=2) + body = example elif param["in"] == "query": if type(example) == list: for value in example: @@ -486,9 +484,9 @@ class MatrixUnits(Units): try: req_body_tables = get_tables_for_schema(param["schema"]) except Exception, e: - logger.warning("Error decoding body of API endpoint %s %s: %s", - endpoint_data["method"], endpoint_data["path"], - e.args[0]) + logger.warning("Error decoding body of API endpoint %s %s" % + (endpoint_data["method"], endpoint_data["path"]), + exc_info=1) return # put the top-level parameters into 'req_param_by_loc', and the others