curl: Add code to auto generate cURL examples from OpenAPI docs.

This commit extends api_code_examples.py to support automatically
generating cURL examples from the OpenAPI documentation. This way
work won't have to be repeated and we can also drastically reduce
the chance of introducing faulty cURL examples (via. an automated
test which can now be easily created).
This commit is contained in:
Hemanth V. Alluri
2019-07-29 19:16:48 +05:30
committed by Tim Abbott
parent f280e9cf84
commit b20cf095e7
3 changed files with 296 additions and 3 deletions

View File

@@ -8,7 +8,8 @@ from typing import Any, Dict, Optional, List
import markdown
import zerver.openapi.python_examples
from zerver.lib.openapi import get_openapi_fixture
from zerver.lib.openapi import get_openapi_fixture, openapi_spec, \
get_openapi_parameters
MACRO_REGEXP = re.compile(r'\{generate_code_example(\(\s*(.+?)\s*\))*\|\s*(.+?)\s*\|\s*(.+?)\s*(\(\s*(.+)\s*\))?\}')
CODE_EXAMPLE_REGEX = re.compile(r'\# \{code_example\|\s*(.+?)\s*\}')
@@ -33,6 +34,15 @@ client = zulip.Client(config_file="~/zuliprc-admin")
"""
DEFAULT_API_URL = "localhost:9991/api"
DEFAULT_AUTH_EMAIL = "BOT_EMAIL_ADDRESS"
DEFAULT_AUTH_API_KEY = "BOT_API_KEY"
DEFAULT_EXAMPLE = {
"integer": 1,
"string": "demo",
"boolean": False,
}
def extract_python_code_example(source: List[str], snippet: List[str]) -> List[str]:
start = -1
end = -1
@@ -77,11 +87,88 @@ def render_python_code_example(function: str, admin_config: Optional[bool]=False
return code_example
def curl_method_arguments(endpoint: str, method: str,
api_url: str) -> List[str]:
method = method.upper()
url = "{}/v1{}".format(api_url, endpoint)
valid_methods = ["GET", "POST", "DELETE", "PUT", "PATCH", "OPTIONS"]
if method == valid_methods[0]:
# Then we need to make sure that each -d option translates to becoming
# a GET parameter (in the URL) and not a POST parameter (in the body).
# TODO: remove the -X part by updating the linting rule. It's redundant.
return ["-X", "GET", "-G", url]
elif method in valid_methods:
return ["-X", method, url]
else:
msg = "The request method {} is not one of {}".format(method,
valid_methods)
raise ValueError(msg)
def generate_curl_example(endpoint: str, method: str,
auth_email: str=DEFAULT_AUTH_EMAIL,
auth_api_key: str=DEFAULT_AUTH_API_KEY,
api_url: str=DEFAULT_API_URL) -> List[str]:
lines = ["```curl"]
openapi_entry = openapi_spec.spec()['paths'][endpoint][method.lower()]
curl_first_line_parts = ["curl"] + curl_method_arguments(endpoint, method,
api_url)
lines.append(" ".join(curl_first_line_parts))
authentication_required = openapi_entry.get("security", False)
if authentication_required:
lines.append(" -u %s:%s" % (auth_email, auth_api_key))
openapi_example_params = get_openapi_parameters(endpoint, method)
for packet in openapi_example_params:
param_name = packet["name"]
param_type = packet["schema"]["type"]
if param_type in ["object", "array"]:
example_value = packet.get("example", None)
if not example_value:
msg = """All array and object type request parameters must have
concrete examples. The openAPI documentation for {}/{} is missing an example
value for the {} parameter. Without this we cannot automatically generate a
cURL example.""".format(endpoint, method, param_name)
raise ValueError(msg)
ordered_ex_val_str = json.dumps(example_value, sort_keys=True)
line = " --data-urlencode {}='{}'".format(param_name, ordered_ex_val_str)
else:
example_value = packet.get("example", DEFAULT_EXAMPLE[param_type])
if type(example_value) == bool:
example_value = str(example_value).lower()
line = " -d '{}={}'".format(param_name, example_value)
lines.append(line)
for i in range(1, len(lines)-1):
lines[i] = lines[i] + " \\"
lines.append("```")
return lines
def render_curl_example(function: str) -> List[str]:
""" A simple wrapper around generate_curl_example. """
parts = function.split(":")
endpoint = parts[0]
method = parts[1]
kwargs = dict()
if len(parts) > 2:
kwargs["auth_email"] = parts[2]
if len(parts) > 3:
kwargs["auth_api_key"] = parts[3]
if len(parts) > 4:
kwargs["api_url"] = parts[4]
return generate_curl_example(endpoint, method, **kwargs)
SUPPORTED_LANGUAGES = {
'python': {
'client_config': PYTHON_CLIENT_CONFIG,
'admin_config': PYTHON_CLIENT_ADMIN_CONFIG,
'render': render_python_code_example,
},
'curl': {
'render': render_curl_example
}
} # type: Dict[str, Any]
@@ -91,7 +178,6 @@ class APICodeExamplesGenerator(Extension):
'generate_code_example', APICodeExamplesPreprocessor(md, self.getConfigs()), '_begin'
)
class APICodeExamplesPreprocessor(Preprocessor):
def __init__(self, md: markdown.Markdown, config: Dict[str, Any]) -> None:
super(APICodeExamplesPreprocessor, self).__init__(md)