openapi: Use Parameter class for generating curl examples.

Signed-off-by: Anders Kaseorg <anders@zulip.com>
(cherry picked from commit a67d1b57b9)
This commit is contained in:
Anders Kaseorg 2024-01-31 21:26:58 -08:00 committed by Tim Abbott
parent 33e77b6d15
commit 899819fb2f
2 changed files with 46 additions and 48 deletions

View File

@ -23,6 +23,7 @@ from zerver.lib.users import get_api_key
from zerver.models import Client, Message, UserGroup, UserPresence from zerver.models import Client, Message, UserGroup, UserPresence
from zerver.models.realms import get_realm from zerver.models.realms import get_realm
from zerver.models.users import get_user from zerver.models.users import get_user
from zerver.openapi.openapi import Parameter
GENERATOR_FUNCTIONS: Dict[str, Callable[[], Dict[str, object]]] = {} GENERATOR_FUNCTIONS: Dict[str, Callable[[], Dict[str, object]]] = {}
REGISTERED_GENERATOR_FUNCTIONS: Set[str] = set() REGISTERED_GENERATOR_FUNCTIONS: Set[str] = set()
@ -71,28 +72,24 @@ def assert_all_helper_functions_called() -> None:
def patch_openapi_example_values( def patch_openapi_example_values(
entry: str, entry: str,
params: List[Dict[str, Any]], parameters: List[Parameter],
request_body: Optional[Dict[str, Any]] = None, request_body: Optional[Dict[str, Any]] = None,
) -> Tuple[List[Dict[str, object]], Optional[Dict[str, object]]]: ) -> Tuple[List[Parameter], Optional[Dict[str, object]]]:
if entry not in GENERATOR_FUNCTIONS: if entry not in GENERATOR_FUNCTIONS:
return params, request_body return parameters, request_body
func = GENERATOR_FUNCTIONS[entry] func = GENERATOR_FUNCTIONS[entry]
realm_example_values: Dict[str, object] = func() realm_example_values: Dict[str, object] = func()
for param in params: for parameter in parameters:
param_name = param["name"] if parameter.name in realm_example_values:
if param_name in realm_example_values: parameter.example = realm_example_values[parameter.name]
if "content" in param:
param["content"]["application/json"]["example"] = realm_example_values[param_name]
else:
param["example"] = realm_example_values[param_name]
if request_body is not None: if request_body is not None:
properties = request_body["content"]["multipart/form-data"]["schema"]["properties"] properties = request_body["content"]["multipart/form-data"]["schema"]["properties"]
for key, property in properties.items(): for key, property in properties.items():
if key in realm_example_values: if key in realm_example_values:
property["example"] = realm_example_values[key] property["example"] = realm_example_values[key]
return params, request_body return parameters, request_body
@openapi_param_value_generator(["/fetch_api_key:post"]) @openapi_param_value_generator(["/fetch_api_key:post"])

View File

@ -21,11 +21,14 @@ from typing_extensions import override
import zerver.openapi.python_examples import zerver.openapi.python_examples
from zerver.lib.markdown.priorities import PREPROCESSOR_PRIORITES from zerver.lib.markdown.priorities import PREPROCESSOR_PRIORITES
from zerver.openapi.openapi import ( from zerver.openapi.openapi import (
NO_EXAMPLE,
Parameter,
check_additional_imports, check_additional_imports,
check_requires_administrator, check_requires_administrator,
generate_openapi_fixture, generate_openapi_fixture,
get_curl_include_exclude, get_curl_include_exclude,
get_openapi_description, get_openapi_description,
get_openapi_parameters,
get_openapi_summary, get_openapi_summary,
get_parameters_description, get_parameters_description,
get_responses_description, get_responses_description,
@ -217,46 +220,45 @@ def curl_method_arguments(endpoint: str, method: str, api_url: str) -> List[str]
def get_openapi_param_example_value_as_string( def get_openapi_param_example_value_as_string(
endpoint: str, method: str, param: Dict[str, Any], curl_argument: bool = False endpoint: str, method: str, parameter: Parameter, curl_argument: bool = False
) -> str: ) -> str:
jsonify = False if "type" in parameter.value_schema:
param_name = param["name"] param_type = parameter.value_schema["type"]
if "content" in param:
param = param["content"]["application/json"]
jsonify = True
if "type" in param["schema"]:
param_type = param["schema"]["type"]
else: else:
# Hack: Ideally, we'd extract a common function for handling # Hack: Ideally, we'd extract a common function for handling
# oneOf values in types and do something with the resulting # oneOf values in types and do something with the resulting
# union type. But for this logic's purpose, it's good enough # union type. But for this logic's purpose, it's good enough
# to just check the first parameter. # to just check the first parameter.
param_type = param["schema"]["oneOf"][0]["type"] param_type = parameter.value_schema["oneOf"][0]["type"]
if param_type in ["object", "array"]: if param_type in ["object", "array"]:
example_value = param.get("example", None) if parameter.example is NO_EXAMPLE:
if not example_value:
msg = f"""All array and object type request parameters must have msg = f"""All array and object type request parameters must have
concrete examples. The openAPI documentation for {endpoint}/{method} is missing an example concrete examples. The openAPI documentation for {endpoint}/{method} is missing an example
value for the {param_name} parameter. Without this we cannot automatically generate a value for the {parameter.name} parameter. Without this we cannot automatically generate a
cURL example.""" cURL example."""
raise ValueError(msg) raise ValueError(msg)
ordered_ex_val_str = json.dumps(example_value, sort_keys=True) ordered_ex_val_str = json.dumps(parameter.example, sort_keys=True)
# We currently don't have any non-JSON encoded arrays. # We currently don't have any non-JSON encoded arrays.
assert jsonify assert parameter.json_encoded
if curl_argument: if curl_argument:
return " --data-urlencode " + shlex.quote(f"{param_name}={ordered_ex_val_str}") return " --data-urlencode " + shlex.quote(f"{parameter.name}={ordered_ex_val_str}")
return ordered_ex_val_str # nocoverage return ordered_ex_val_str # nocoverage
else: else:
example_value = param.get("example", DEFAULT_EXAMPLE[param_type]) if parameter.example is NO_EXAMPLE:
if isinstance(example_value, bool): example_value = DEFAULT_EXAMPLE[param_type]
# Booleans are effectively JSON-encoded, in that we pass else:
# true/false, not the Python str(True) = "True" example_value = parameter.example
jsonify = True
if jsonify: # Booleans are effectively JSON-encoded, in that we pass
# true/false, not the Python str(True) = "True"
if parameter.json_encoded or isinstance(example_value, (bool, float, int)):
example_value = json.dumps(example_value) example_value = json.dumps(example_value)
else:
assert isinstance(example_value, str)
if curl_argument: if curl_argument:
return " --data-urlencode " + shlex.quote(f"{param_name}={example_value}") return " --data-urlencode " + shlex.quote(f"{parameter.name}={example_value}")
return example_value return example_value
@ -274,23 +276,23 @@ def generate_curl_example(
operation_entry = openapi_spec.openapi()["paths"][endpoint][method.lower()] operation_entry = openapi_spec.openapi()["paths"][endpoint][method.lower()]
global_security = openapi_spec.openapi()["security"] global_security = openapi_spec.openapi()["security"]
operation_params = operation_entry.get("parameters", []) parameters = get_openapi_parameters(endpoint, method)
operation_request_body = operation_entry.get("requestBody", None) operation_request_body = operation_entry.get("requestBody", None)
operation_security = operation_entry.get("security", None) operation_security = operation_entry.get("security", None)
if settings.RUNNING_OPENAPI_CURL_TEST: # nocoverage if settings.RUNNING_OPENAPI_CURL_TEST: # nocoverage
from zerver.openapi.curl_param_value_generators import patch_openapi_example_values from zerver.openapi.curl_param_value_generators import patch_openapi_example_values
operation_params, operation_request_body = patch_openapi_example_values( parameters, operation_request_body = patch_openapi_example_values(
operation, operation_params, operation_request_body operation, parameters, operation_request_body
) )
format_dict = {} format_dict = {}
for param in operation_params: for parameter in parameters:
if param["in"] != "path": if parameter.kind != "path":
continue continue
example_value = get_openapi_param_example_value_as_string(endpoint, method, param) example_value = get_openapi_param_example_value_as_string(endpoint, method, parameter)
format_dict[param["name"]] = example_value format_dict[parameter.name] = example_value
example_endpoint = endpoint.format_map(format_dict) example_endpoint = endpoint.format_map(format_dict)
curl_first_line_parts = ["curl", *curl_method_arguments(example_endpoint, method, api_url)] curl_first_line_parts = ["curl", *curl_method_arguments(example_endpoint, method, api_url)]
@ -319,19 +321,18 @@ def generate_curl_example(
if authentication_required: if authentication_required:
lines.append(" -u " + shlex.quote(f"{auth_email}:{auth_api_key}")) lines.append(" -u " + shlex.quote(f"{auth_email}:{auth_api_key}"))
for param in operation_params: for parameter in parameters:
if param["in"] == "path": if parameter.kind == "path":
continue
param_name = param["name"]
if include is not None and param_name not in include:
continue continue
if exclude is not None and param_name in exclude: if include is not None and parameter.name not in include:
continue
if exclude is not None and parameter.name in exclude:
continue continue
example_value = get_openapi_param_example_value_as_string( example_value = get_openapi_param_example_value_as_string(
endpoint, method, param, curl_argument=True endpoint, method, parameter, curl_argument=True
) )
lines.append(example_value) lines.append(example_value)