diff --git a/swh/web/api/apidoc.py b/swh/web/api/apidoc.py --- a/swh/web/api/apidoc.py +++ b/swh/web/api/apidoc.py @@ -34,6 +34,10 @@ # httpdomain roles we want to parse (based on sphinxcontrib.httpdomain 1.6) parameter_roles = ('param', 'parameter', 'arg', 'argument') + request_json_object_roles = ('reqjsonobj', 'reqjson', 'jsonobj', '>json') response_json_array_roles = ('resjsonarr', '>jsonarr') @@ -53,6 +57,7 @@ self.data = data self.args_set = set() self.params_set = set() + self.inputs_set = set() self.returns_set = set() self.status_codes_set = set() self.reqheaders_set = set() @@ -115,9 +120,24 @@ 'type': field_data[1], 'doc': text}) self.params_set.add(field_data[2]) + # Request data type + if (field_data[0] in self.request_json_array_roles or + field_data[0] in self.request_json_object_roles): + # array + if field_data[0] in self.request_json_array_roles: + self.data['input_type'] = 'array' + # object + else: + self.data['input_type'] = 'object' + # input object field + if field_data[2] not in self.inputs_set: + self.data['inputs'].append({'name': field_data[2], + 'type': field_data[1], + 'doc': text}) + self.inputs_set.add(field_data[2]) # Response type - if field_data[0] in self.response_json_array_roles or \ - field_data[0] in self.response_json_object_roles: + if (field_data[0] in self.response_json_array_roles or + field_data[0] in self.response_json_object_roles): # array if field_data[0] in self.response_json_array_roles: self.data['return_type'] = 'array' @@ -347,6 +367,8 @@ 'urls': [], 'args': [], 'params': [], + 'input_type': '', + 'inputs': [], 'resheaders': [], 'reqheaders': [], 'return_type': '', @@ -371,11 +393,22 @@ # sphinx extension, not needed and raise errors with sphinx >= 1.7) elif 'SWH_WEB_DOC_BUILD' not in os.environ: _parse_httpdomain_doc(f.__doc__, data) - # process returned object info for nicer html display + # process input/returned object info for nicer html display + inputs_list = '' returns_list = '' + for inp in data['inputs']: + # special case for array of non object type, for instance + # :jsonarr string -: an array of string + if ret['name'] != '-': + returns_list += ('\t* **%s (%s)**: %s\n' % + (ret['name'], ret['type'], ret['doc'])) + data['inputs_list'] = inputs_list data['returns_list'] = returns_list return data diff --git a/swh/web/templates/api/apidoc.html b/swh/web/templates/api/apidoc.html --- a/swh/web/templates/api/apidoc.html +++ b/swh/web/templates/api/apidoc.html @@ -116,6 +116,31 @@
{% endif %} +{% if input_type %} +
+

Request data

+
+
{{ input_type }}
+
+

+ {% if input_type == 'array' and inputs_list == '' %} + {{ inputs.0.doc | safe }} + {% elif input_type == 'array' and inputs_list != '' %} + an array of objects containing the following keys: + {% elif input_type == 'octet stream' %} + raw data as an octet stream + {% elif input_type == 'object' %} + an object containing the following keys: + {% endif %} + {% if inputs_list != '' %} + {{ inputs_list | safe_docstring_display | safe }} + {% endif %} +

+
+
+
+
+{% endif %} {% if resheaders and resheaders|length > 0 %}

Response headers

@@ -135,14 +160,18 @@
{{ return_type }}

- {% if return_type == 'array' %} + {% if return_type == 'array' and returns_list == '' %} + {{ returns.0.doc | safe }} + {% elif return_type == 'array' and returns_list != '' %} an array of objects containing the following keys: {% elif return_type == 'octet stream' %} - the raw data as an octet stream - {% else %} + raw data as an octet stream + {% elif return_type == 'object' %} an object containing the following keys: {% endif %} - {{ returns_list | safe_docstring_display | safe }} + {% if returns_list != '' %} + {{ returns_list | safe_docstring_display | safe }} + {% endif %}

diff --git a/swh/web/tests/api/test_apidoc.py b/swh/web/tests/api/test_apidoc.py --- a/swh/web/tests/api/test_apidoc.py +++ b/swh/web/tests/api/test_apidoc.py @@ -13,10 +13,10 @@ from swh.web.api.apiurls import api_route from swh.web.common.exc import BadInputExc, ForbiddenExc, NotFoundExc from swh.web.common.utils import reverse -from swh.web.tests.django_asserts import assert_template_used +from swh.web.tests.django_asserts import assert_template_used, assert_contains -httpdomain_doc = """ +_httpdomain_doc = """ .. http:get:: /api/1/revision/(sha1_git)/ Get information about a revision in the archive. @@ -33,6 +33,10 @@ :resheader Content-Type: this depends on :http:header:`Accept` header of request + :json object author: information about the author of the revision :>json object committer: information about the committer of the revision :>json string committer_date: ISO representation of the commit date @@ -67,7 +71,7 @@ """ -exception_http_code = { +_exception_http_code = { BadInputExc: 400, ForbiddenExc: 403, NotFoundExc: 404, @@ -115,13 +119,13 @@ """ Sample doc """ - for e in exception_http_code.keys(): + for e in _exception_http_code.keys(): if e.__name__ == exc_name: raise e('Error') def test_apidoc_error(api_client): - for exc, code in exception_http_code.items(): + for exc, code in _exception_http_code.items(): url = reverse('api-1-test-error', url_args={'exc_name': exc.__name__}) rv = api_client.get(url) @@ -181,13 +185,15 @@ 'params': [], 'resheaders': [], 'reqheaders': [], + 'input_type': '', + 'inputs': [], 'return_type': '', 'returns': [], 'status_codes': [], 'examples': [] } - _parse_httpdomain_doc(httpdomain_doc, doc_data) + _parse_httpdomain_doc(_httpdomain_doc, doc_data) expected_urls = [{ 'rule': '/api/1/revision/ **\\(sha1_git\\)** /', @@ -256,10 +262,36 @@ assert 'status_codes' in doc_data assert doc_data['status_codes'] == expected_statuscodes + expected_input_type = 'object' + + assert 'input_type' in doc_data + assert doc_data['input_type'] == expected_input_type + + expected_inputs = [ + { + 'name': 'n', + 'type': 'int', + 'doc': 'sample input integer' + }, + { + 'name': 's', + 'type': 'string', + 'doc': 'sample input string' + }, + { + 'name': 'a', + 'type': 'array', + 'doc': 'sample input array' + }, + ] + + assert 'inputs' in doc_data + assert doc_data['inputs'] == expected_inputs + expected_return_type = 'object' assert 'return_type' in doc_data - assert doc_data['return_type'] in expected_return_type + assert doc_data['return_type'] == expected_return_type expected_returns = [ { @@ -333,3 +365,67 @@ assert 'examples' in doc_data assert doc_data['examples'] == expected_examples + + +@api_route(r'/post/endpoint/', 'api-1-post-endpoint', + methods=['POST']) +@api_doc('/post/endpoint/') +def apidoc_test_post_endpoint(request): + """ + .. http:post:: /api/1/post/endpoint/ + + Endpoint documentation + + :jsonarr string type: swh object type + :>jsonarr string sha1_git: swh object sha1_git + :>jsonarr boolean found: whether the object was found or not + + """ + pass + + +def test_apidoc_input_output_doc(client): + url = reverse('api-1-post-endpoint-doc') + rv = client.get(url, HTTP_ACCEPT='text/html') + assert rv.status_code == 200, rv.content + assert_template_used(rv, 'api/apidoc.html') + + input_html_doc = ( + '
\n' + '
array
\n' + '
\n' + '

\n' + ' \n' + ' Input array of pids\n' + ' \n' + ' \n' + '

\n' + '
\n' + '
\n' + ) + + output_html_doc = ( + '
\n' + '
array
\n' + '
\n' + '

\n' + ' \n' + ' an array of objects containing the following keys:\n' + ' \n' + ' \n' + '

    \n' + '
  • type (string): swh object type

  • \n' + '
  • sha1_git (string): swh object sha1_git

  • \n' # noqa + '
  • found (boolean): whether the object was found or not

  • \n' # noqa + '
\n' + '
\n' + ' \n' + '

\n' + '
\n' + '
' + ) + + assert_contains(rv, input_html_doc) + assert_contains(rv, output_html_doc)