HTTP Response Code Table
Change-Id: Ie65366c1f5cb76af50ce116c1bb8747ed610f103
This commit is contained in:
@@ -182,6 +182,114 @@ max_version
|
|||||||
a *Deprecated in $version* stanza in the html output.
|
a *Deprecated in $version* stanza in the html output.
|
||||||
|
|
||||||
|
|
||||||
|
rest_status_code
|
||||||
|
----------------
|
||||||
|
|
||||||
|
The ``rest_status_code`` stanza is how you can show what HTTP status codes your
|
||||||
|
API uses and what they indicate
|
||||||
|
|
||||||
|
.. code-block:: rst
|
||||||
|
|
||||||
|
.. rest_status_code:: <sucess|failure> <location of status.yaml file>
|
||||||
|
|
||||||
|
This stanza should be the first element after the narrative section of the
|
||||||
|
method description.
|
||||||
|
|
||||||
|
An example from the Designate documentation is:
|
||||||
|
|
||||||
|
.. code-block:: rst
|
||||||
|
:emphasize-lines: 11-25
|
||||||
|
|
||||||
|
Create Zone
|
||||||
|
===========
|
||||||
|
|
||||||
|
.. rest_method:: POST /v2/zones
|
||||||
|
|
||||||
|
Create a zone
|
||||||
|
|
||||||
|
Response codes
|
||||||
|
--------------
|
||||||
|
|
||||||
|
.. rest_status_code:: success status.yaml
|
||||||
|
|
||||||
|
- 200
|
||||||
|
- 100
|
||||||
|
- 201
|
||||||
|
|
||||||
|
|
||||||
|
.. rest_status_code:: error status.yaml
|
||||||
|
|
||||||
|
- 405
|
||||||
|
- 403
|
||||||
|
- 401
|
||||||
|
- 400
|
||||||
|
- 500
|
||||||
|
- 409: duplcate_zone
|
||||||
|
|
||||||
|
And corresponding entries in ``status.yaml``:
|
||||||
|
|
||||||
|
.. code-block:: yaml
|
||||||
|
|
||||||
|
100:
|
||||||
|
default: |
|
||||||
|
An unusual code for an API
|
||||||
|
200:
|
||||||
|
default: |
|
||||||
|
Request was successful.
|
||||||
|
201:
|
||||||
|
default: >
|
||||||
|
Resource was created and is ready to use. The ``Location`` header
|
||||||
|
will have the URL to the new item
|
||||||
|
|
||||||
|
400:
|
||||||
|
default: |
|
||||||
|
Some content in the request was invalid
|
||||||
|
zone_data_error: |
|
||||||
|
Some of the data for the
|
||||||
|
401:
|
||||||
|
default: |
|
||||||
|
User must authenticate before making a request
|
||||||
|
403:
|
||||||
|
default: |
|
||||||
|
Policy does not allow current user to do this operation.
|
||||||
|
405:
|
||||||
|
default: |
|
||||||
|
Method is not valid for this endpoint. Not all endpoints allow all HTTP methods.
|
||||||
|
409:
|
||||||
|
default: |
|
||||||
|
This operation conflicted with another operation on this resource
|
||||||
|
duplcate_zone: |
|
||||||
|
There is already a zone with this name.
|
||||||
|
500:
|
||||||
|
default: |
|
||||||
|
Something went wrong inside the service.
|
||||||
|
|
||||||
|
|
||||||
|
This will create a 2 tables of response codes, one for success and one for
|
||||||
|
errors.
|
||||||
|
|
||||||
|
status file format
|
||||||
|
------------------
|
||||||
|
|
||||||
|
This is a simple yaml file, with a single object of status codes and the
|
||||||
|
reasons that each would be used.
|
||||||
|
|
||||||
|
Each status code **must** have a default entry. This is used when a code is
|
||||||
|
used in a ``rest_status_code`` stanza with no value.
|
||||||
|
|
||||||
|
There may be situations where the reason for a code may be different across
|
||||||
|
endpoints, or a different message may be appropriate.
|
||||||
|
|
||||||
|
In this case, adding a entry at the same level as the ``default`` and
|
||||||
|
referencing that in the stanaza like so:
|
||||||
|
|
||||||
|
.. code-block:: yaml
|
||||||
|
|
||||||
|
- 409: duplcate_zone
|
||||||
|
|
||||||
|
This will overide the default message with the newly defined one.
|
||||||
|
|
||||||
|
|
||||||
rest_expand_all
|
rest_expand_all
|
||||||
---------------
|
---------------
|
||||||
|
|
||||||
|
@@ -23,6 +23,9 @@ from sphinx.util.compat import Directive
|
|||||||
from sphinx.util.osutil import copyfile
|
from sphinx.util.osutil import copyfile
|
||||||
import yaml
|
import yaml
|
||||||
|
|
||||||
|
from os_api_ref.http_codes import http_code
|
||||||
|
from os_api_ref.http_codes import http_code_html
|
||||||
|
from os_api_ref.http_codes import HTTPResponseCodeDirective
|
||||||
|
|
||||||
__version__ = pbr.version.VersionInfo(
|
__version__ = pbr.version.VersionInfo(
|
||||||
'os_api_ref').version_string()
|
'os_api_ref').version_string()
|
||||||
@@ -532,11 +535,14 @@ def setup(app):
|
|||||||
html=(rest_method_html, None))
|
html=(rest_method_html, None))
|
||||||
app.add_node(rest_expand_all,
|
app.add_node(rest_expand_all,
|
||||||
html=(rest_expand_all_html, None))
|
html=(rest_expand_all_html, None))
|
||||||
|
app.add_node(http_code,
|
||||||
|
html=(http_code_html, None))
|
||||||
|
|
||||||
# This specifies all our directives that we're adding
|
# This specifies all our directives that we're adding
|
||||||
app.add_directive('rest_parameters', RestParametersDirective)
|
app.add_directive('rest_parameters', RestParametersDirective)
|
||||||
app.add_directive('rest_method', RestMethodDirective)
|
app.add_directive('rest_method', RestMethodDirective)
|
||||||
app.add_directive('rest_expand_all', RestExpandAllDirective)
|
app.add_directive('rest_expand_all', RestExpandAllDirective)
|
||||||
|
app.add_directive('rest_status_code', HTTPResponseCodeDirective)
|
||||||
|
|
||||||
# The doctree-read hook is used do the slightly crazy doc
|
# The doctree-read hook is used do the slightly crazy doc
|
||||||
# transformation that we do to get the rest_method document
|
# transformation that we do to get the rest_method document
|
||||||
|
232
os_api_ref/http_codes.py
Normal file
232
os_api_ref/http_codes.py
Normal file
@@ -0,0 +1,232 @@
|
|||||||
|
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||||
|
# not use this file except in compliance with the License. You may obtain
|
||||||
|
# a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
# License for the specific language governing permissions and limitations
|
||||||
|
# under the License.
|
||||||
|
|
||||||
|
from docutils import nodes
|
||||||
|
from docutils.parsers.rst.directives.tables import Table
|
||||||
|
from docutils.statemachine import ViewList
|
||||||
|
from httplib import responses
|
||||||
|
import yaml
|
||||||
|
|
||||||
|
# cache for file -> yaml so we only do the load and check of a yaml
|
||||||
|
# file once during a sphinx processing run.
|
||||||
|
HTTP_YAML_CACHE = {}
|
||||||
|
|
||||||
|
|
||||||
|
class HTTPResponseCodeDirective(Table):
|
||||||
|
|
||||||
|
headers = ["Code", "Reason"]
|
||||||
|
|
||||||
|
status_types = ("success", "error")
|
||||||
|
|
||||||
|
# This is for HTTP response codes that OpenStack may use that are not part
|
||||||
|
# the httplib response dict.
|
||||||
|
CODES = {
|
||||||
|
429: "Too Many Requests",
|
||||||
|
}
|
||||||
|
|
||||||
|
required_arguments = 2
|
||||||
|
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
self.CODES.update(responses)
|
||||||
|
super(HTTPResponseCodeDirective, self).__init__(*args, **kwargs)
|
||||||
|
|
||||||
|
def _load_status_file(self, fpath):
|
||||||
|
global HTTP_YAML_CACHE
|
||||||
|
if fpath in HTTP_YAML_CACHE:
|
||||||
|
return HTTP_YAML_CACHE[fpath]
|
||||||
|
|
||||||
|
# self.app.info("Fpath: %s" % fpath)
|
||||||
|
try:
|
||||||
|
with open(fpath, 'r') as stream:
|
||||||
|
lookup = yaml.load(stream)
|
||||||
|
except IOError:
|
||||||
|
self.env.warn(
|
||||||
|
self.env.docname,
|
||||||
|
"Parameters file %s not found" % fpath)
|
||||||
|
return
|
||||||
|
except yaml.YAMLError as exc:
|
||||||
|
self.app.warn(exc)
|
||||||
|
raise
|
||||||
|
|
||||||
|
HTTP_YAML_CACHE[fpath] = lookup
|
||||||
|
return lookup
|
||||||
|
|
||||||
|
def run(self):
|
||||||
|
self.env = self.state.document.settings.env
|
||||||
|
self.app = self.env.app
|
||||||
|
|
||||||
|
# Make sure we have some content, which should be yaml that
|
||||||
|
# defines some parameters.
|
||||||
|
if not self.content:
|
||||||
|
error = self.state_machine.reporter.error(
|
||||||
|
'No parameters defined',
|
||||||
|
nodes.literal_block(self.block_text, self.block_text),
|
||||||
|
line=self.lineno)
|
||||||
|
return [error]
|
||||||
|
|
||||||
|
if not len(self.arguments) >= 2:
|
||||||
|
error = self.state_machine.reporter.error(
|
||||||
|
'%s' % self.arguments,
|
||||||
|
nodes.literal_block(self.block_text, self.block_text),
|
||||||
|
line=self.lineno)
|
||||||
|
return [error]
|
||||||
|
|
||||||
|
_, status_defs_file = self.env.relfn2path(self.arguments.pop())
|
||||||
|
status_type = self.arguments.pop()
|
||||||
|
|
||||||
|
self.status_defs = self._load_status_file(status_defs_file)
|
||||||
|
|
||||||
|
# self.app.info("%s" % str(self.status_defs))
|
||||||
|
|
||||||
|
if status_type not in self.status_types:
|
||||||
|
error = self.state_machine.reporter.error(
|
||||||
|
'Type %s is not one of %s' % (status_type, self.status_types),
|
||||||
|
nodes.literal_block(self.block_text, self.block_text),
|
||||||
|
line=self.lineno)
|
||||||
|
return [error]
|
||||||
|
|
||||||
|
self.yaml = self._load_codes()
|
||||||
|
|
||||||
|
self.max_cols = len(self.headers)
|
||||||
|
# TODO(sdague): it would be good to dynamically set column
|
||||||
|
# widths (or basically make the colwidth thing go away
|
||||||
|
# entirely)
|
||||||
|
self.options['widths'] = (30, 70)
|
||||||
|
self.col_widths = self.get_column_widths(self.max_cols)
|
||||||
|
# Actually convert the yaml
|
||||||
|
title, messages = self.make_title()
|
||||||
|
# self.app.info("Title %s, messages %s" % (title, messages))
|
||||||
|
table_node = self.build_table()
|
||||||
|
self.add_name(table_node)
|
||||||
|
|
||||||
|
title_block = nodes.title(
|
||||||
|
text=status_type.capitalize())
|
||||||
|
|
||||||
|
section = nodes.section(ids=title_block)
|
||||||
|
section += title_block
|
||||||
|
section += table_node
|
||||||
|
|
||||||
|
return [section] + messages
|
||||||
|
|
||||||
|
def _load_codes(self):
|
||||||
|
content = "\n".join(self.content)
|
||||||
|
parsed = yaml.load(content)
|
||||||
|
|
||||||
|
new_content = list()
|
||||||
|
|
||||||
|
for item in parsed:
|
||||||
|
if isinstance(item, int):
|
||||||
|
new_content.append((item, self.status_defs[item]['default']))
|
||||||
|
else:
|
||||||
|
try:
|
||||||
|
for code, reason in item.items():
|
||||||
|
new_content.append(
|
||||||
|
(code, self.status_defs[code][reason])
|
||||||
|
)
|
||||||
|
except KeyError:
|
||||||
|
self.app.warn(
|
||||||
|
"Could not find %s for code %s" % (reason, code))
|
||||||
|
new_content.append(
|
||||||
|
(code, self.status_defs[code]['default']))
|
||||||
|
|
||||||
|
return new_content
|
||||||
|
|
||||||
|
def build_table(self):
|
||||||
|
table = nodes.table()
|
||||||
|
tgroup = nodes.tgroup(cols=len(self.headers))
|
||||||
|
table += tgroup
|
||||||
|
|
||||||
|
# TODO(sdague): it would be really nice to figure out how not
|
||||||
|
# to have this stanza, it kind of messes up all of the table
|
||||||
|
# formatting because it doesn't let tables just be the right
|
||||||
|
# size.
|
||||||
|
tgroup.extend(
|
||||||
|
nodes.colspec(colwidth=col_width, colname='c' + str(idx))
|
||||||
|
for idx, col_width in enumerate(self.col_widths)
|
||||||
|
)
|
||||||
|
|
||||||
|
thead = nodes.thead()
|
||||||
|
tgroup += thead
|
||||||
|
|
||||||
|
row_node = nodes.row()
|
||||||
|
thead += row_node
|
||||||
|
row_node.extend(nodes.entry(h, nodes.paragraph(text=h))
|
||||||
|
for h in self.headers)
|
||||||
|
|
||||||
|
tbody = nodes.tbody()
|
||||||
|
tgroup += tbody
|
||||||
|
|
||||||
|
rows, groups = self.collect_rows()
|
||||||
|
tbody.extend(rows)
|
||||||
|
table.extend(groups)
|
||||||
|
|
||||||
|
return table
|
||||||
|
|
||||||
|
def add_col(self, node):
|
||||||
|
entry = nodes.entry()
|
||||||
|
entry.append(node)
|
||||||
|
return entry
|
||||||
|
|
||||||
|
def add_desc_col(self, value):
|
||||||
|
entry = nodes.entry()
|
||||||
|
result = ViewList(value.split('\n'))
|
||||||
|
self.state.nested_parse(result, 0, entry)
|
||||||
|
return entry
|
||||||
|
|
||||||
|
def collect_rows(self):
|
||||||
|
rows = []
|
||||||
|
groups = []
|
||||||
|
try:
|
||||||
|
# self.app.info("Parsed content is: %s" % self.yaml)
|
||||||
|
for code, desc in self.yaml:
|
||||||
|
|
||||||
|
h_code = http_code()
|
||||||
|
h_code['code'] = code
|
||||||
|
h_code['title'] = self.CODES.get(code, 'Unknown')
|
||||||
|
|
||||||
|
trow = nodes.row()
|
||||||
|
trow += self.add_col(h_code)
|
||||||
|
trow += self.add_desc_col(desc)
|
||||||
|
rows.append(trow)
|
||||||
|
except AttributeError as exc:
|
||||||
|
# if 'key' in locals():
|
||||||
|
self.app.warn("Failure on key: %s, values: %s. %s" %
|
||||||
|
(code, desc, exc))
|
||||||
|
# else:
|
||||||
|
# rows.append(self.show_no_yaml_error())
|
||||||
|
return rows, groups
|
||||||
|
|
||||||
|
|
||||||
|
def http_code_html(self, node):
|
||||||
|
tmpl = "<code>%(code)s - %(title)s</code>"
|
||||||
|
self.body.append(tmpl % node)
|
||||||
|
raise nodes.SkipNode
|
||||||
|
|
||||||
|
|
||||||
|
class http_code(nodes.Part, nodes.Element):
|
||||||
|
"""Node for http_code stanza
|
||||||
|
|
||||||
|
Because we need to insert very specific HTML at the final stage of
|
||||||
|
processing, the http_code stanza needs a custom node type. This
|
||||||
|
lets us accumulate the relevant data into this node, during
|
||||||
|
parsing, but not turn it into known sphinx types (lists, tables,
|
||||||
|
sections).
|
||||||
|
|
||||||
|
Then, during the final build phase we transform directly to the
|
||||||
|
html that we want.
|
||||||
|
|
||||||
|
NOTE: this means we error trying to build latex or man pages for
|
||||||
|
these stanza types right now. This is all fixable if we add an
|
||||||
|
output formatter for this node type, but it's not yet a
|
||||||
|
priority. Contributions welcomed.
|
||||||
|
"""
|
||||||
|
pass
|
@@ -11,3 +11,22 @@ I am text, hear me roar!
|
|||||||
.. rest_parameters:: parameters.yaml
|
.. rest_parameters:: parameters.yaml
|
||||||
|
|
||||||
- name: name
|
- name: name
|
||||||
|
|
||||||
|
Response codes
|
||||||
|
--------------
|
||||||
|
|
||||||
|
.. rest_status_code:: success status.yaml
|
||||||
|
|
||||||
|
- 200
|
||||||
|
- 100
|
||||||
|
- 201
|
||||||
|
|
||||||
|
|
||||||
|
.. rest_status_code:: error status.yaml
|
||||||
|
|
||||||
|
- 405
|
||||||
|
- 403
|
||||||
|
- 401
|
||||||
|
- 400
|
||||||
|
- 500
|
||||||
|
- 409: duplcate_zone
|
||||||
|
39
os_api_ref/tests/examples/basic/status.yaml
Normal file
39
os_api_ref/tests/examples/basic/status.yaml
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
#################
|
||||||
|
# Success Codes #
|
||||||
|
#################
|
||||||
|
100:
|
||||||
|
default: |
|
||||||
|
An unusual code for an API
|
||||||
|
200:
|
||||||
|
default: |
|
||||||
|
Request was successful.
|
||||||
|
201:
|
||||||
|
default: |
|
||||||
|
Resource was created and is ready to use.
|
||||||
|
|
||||||
|
#################
|
||||||
|
# Error Codes #
|
||||||
|
#################
|
||||||
|
|
||||||
|
400:
|
||||||
|
default: |
|
||||||
|
Some content in the request was invalid
|
||||||
|
zone_data_error: |
|
||||||
|
Some of the data for the
|
||||||
|
401:
|
||||||
|
default: |
|
||||||
|
User must authenticate before making a request
|
||||||
|
403:
|
||||||
|
default: |
|
||||||
|
Policy does not allow current user to do this operation.
|
||||||
|
405:
|
||||||
|
default: |
|
||||||
|
Method is not valid for this endpoint.
|
||||||
|
409:
|
||||||
|
default: |
|
||||||
|
This operation conflicted with another operation on this resource
|
||||||
|
duplcate_zone: |
|
||||||
|
There is already a zone with this name.
|
||||||
|
500:
|
||||||
|
default: |
|
||||||
|
Something went wrong inside the service.
|
@@ -102,3 +102,64 @@ class TestBasicExample(base.TestCase):
|
|||||||
</table>"""
|
</table>"""
|
||||||
|
|
||||||
self.assertIn(table, self.content)
|
self.assertIn(table, self.content)
|
||||||
|
|
||||||
|
def test_rest_response(self):
|
||||||
|
|
||||||
|
success_table = """table border="1" class="docutils">
|
||||||
|
<colgroup>
|
||||||
|
<col width="30%"></col>
|
||||||
|
<col width="70%"></col>
|
||||||
|
</colgroup>
|
||||||
|
<thead valign="bottom">
|
||||||
|
<tr class="row-odd"><th class="head">Code</th>
|
||||||
|
<th class="head">Reason</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody valign="top">
|
||||||
|
<tr class="row-even"><td><code>200 - OK</code></td>
|
||||||
|
<td>Request was successful.</td>
|
||||||
|
</tr>
|
||||||
|
<tr class="row-odd"><td><code>100 - Continue</code></td>
|
||||||
|
<td>An unusual code for an API</td>
|
||||||
|
</tr>
|
||||||
|
<tr class="row-even"><td><code>201 - Created</code></td>
|
||||||
|
<td>Resource was created and is ready to use.</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
"""
|
||||||
|
|
||||||
|
error_table = """<table border="1" class="docutils">
|
||||||
|
<colgroup>
|
||||||
|
<col width="30%"></col>
|
||||||
|
<col width="70%"></col>
|
||||||
|
</colgroup>
|
||||||
|
<thead valign="bottom">
|
||||||
|
<tr class="row-odd"><th class="head">Code</th>
|
||||||
|
<th class="head">Reason</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody valign="top">
|
||||||
|
<tr class="row-even"><td><code>405 - Method Not Allowed</code></td>
|
||||||
|
<td>Method is not valid for this endpoint.</td>
|
||||||
|
</tr>
|
||||||
|
<tr class="row-odd"><td><code>403 - Forbidden</code></td>
|
||||||
|
<td>Policy does not allow current user to do this operation.</td>
|
||||||
|
</tr>
|
||||||
|
<tr class="row-even"><td><code>401 - Unauthorized</code></td>
|
||||||
|
<td>User must authenticate before making a request</td>
|
||||||
|
</tr>
|
||||||
|
<tr class="row-odd"><td><code>400 - Bad Request</code></td>
|
||||||
|
<td>Some content in the request was invalid</td>
|
||||||
|
</tr>
|
||||||
|
<tr class="row-even"><td><code>500 - Internal Server Error</code></td>
|
||||||
|
<td>Something went wrong inside the service.</td>
|
||||||
|
</tr>
|
||||||
|
<tr class="row-odd"><td><code>409 - Conflict</code></td>
|
||||||
|
<td>There is already a zone with this name.</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
"""
|
||||||
|
self.assertIn(success_table, self.content)
|
||||||
|
self.assertIn(error_table, self.content)
|
||||||
|
Reference in New Issue
Block a user