From 918f5203fd67235f9286e19ccefca005f9e1595f Mon Sep 17 00:00:00 2001 From: Victor Torre Date: Tue, 3 Sep 2024 13:06:08 +0200 Subject: [PATCH] Feature/code generator (#18) * Example files * Model generator * Add cli for code generator --- README.md | 10 + pyproject.toml | 11 +- src/lima_api/code_generator/__init__.py | 0 src/lima_api/code_generator/cli_gen.py | 315 ++ src/lima_api/code_generator/main.py | 97 + src/lima_api/code_generator/schemas.py | 336 ++ src/lima_api/code_generator/templates.py | 25 + src/lima_api/code_generator/utils.py | 38 + tests/resources/calculadora_openapi.json | 3227 +++++++++++++++++ .../examples/v3.0/api-with-examples.json | 167 + .../examples/v3.0/callback-example.json | 84 + .../resources/examples/v3.0/link-example.json | 323 ++ .../examples/v3.0/petstore-expanded.json | 242 ++ tests/resources/examples/v3.0/petstore.json | 189 + tests/resources/examples/v3.0/uspto.json | 252 ++ .../examples/v3.1/non-oauth-scopes.json | 31 + .../examples/v3.1/webhook-example.json | 50 + tests/resources/satellite-passes_openapi.json | 234 ++ 18 files changed, 5628 insertions(+), 3 deletions(-) create mode 100644 src/lima_api/code_generator/__init__.py create mode 100644 src/lima_api/code_generator/cli_gen.py create mode 100644 src/lima_api/code_generator/main.py create mode 100644 src/lima_api/code_generator/schemas.py create mode 100644 src/lima_api/code_generator/templates.py create mode 100644 src/lima_api/code_generator/utils.py create mode 100644 tests/resources/calculadora_openapi.json create mode 100644 tests/resources/examples/v3.0/api-with-examples.json create mode 100644 tests/resources/examples/v3.0/callback-example.json create mode 100644 tests/resources/examples/v3.0/link-example.json create mode 100644 tests/resources/examples/v3.0/petstore-expanded.json create mode 100644 tests/resources/examples/v3.0/petstore.json create mode 100644 tests/resources/examples/v3.0/uspto.json create mode 100644 tests/resources/examples/v3.1/non-oauth-scopes.json create mode 100644 tests/resources/examples/v3.1/webhook-example.json create mode 100644 tests/resources/satellite-passes_openapi.json diff --git a/README.md b/README.md index 18f5d2c..ce4a362 100644 --- a/README.md +++ b/README.md @@ -210,3 +210,13 @@ class PetApi(lima_api.LimaApi): uv pip compile pyproject.toml --extra=test --extra=pydantic2 > requirements.txt uv pip install requirements.txt ``` + + +# Code generator +In order to help developers to improve they work you could auto-generate your clients. + +You could run: +```shell +lima-generator tests/resources/examples/v3.0/api-with-examples.json +``` +That create a folder `tests/resources/examples/v3.0/api-with-examples` with two files, `client.py` and `models.py` diff --git a/pyproject.toml b/pyproject.toml index 48a789f..9f53237 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -10,10 +10,12 @@ build-backend = "setuptools.build_meta" name = "lima-api" dynamic = ["version"] description = "Lima-API is sync and async library that allows implements Rest APIs libs with python typing." -readme = "README.md" +readme = { file = "README.md", content-type = "text/markdown" } authors = [ - { name = "Cesar Gonzalez", email = "cgonzalez@paradigmadigital.com" }, - { name = "Victor Torre", email = "vatorre@paradigmadigital.com" } + { name = "Cesar Gonzalez" }, + { name = "Victor Torre", email = "vatorre@paradigmadigital.com" }, +] +maintainers = [ ] license = { file = "LICENSE" } classifiers = [ @@ -29,6 +31,9 @@ dependencies = [ "opentelemetry-instrumentation-httpx", ] +[project.scripts] +lima-generator = "lima_api.code_generator.main:main" + [tool.setuptools_scm] [tool.setuptools.packages.find] diff --git a/src/lima_api/code_generator/__init__.py b/src/lima_api/code_generator/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/lima_api/code_generator/cli_gen.py b/src/lima_api/code_generator/cli_gen.py new file mode 100644 index 0000000..b2850b3 --- /dev/null +++ b/src/lima_api/code_generator/cli_gen.py @@ -0,0 +1,315 @@ +import re +from typing import Optional + +from lima_api.code_generator.schemas import ( + SchemaObject, + SchemaObjectType, + SchemaParser, +) +from lima_api.code_generator.templates import ( + BASE_CLASS, + BASE_PARAM, + LIMA_FUNCTION, +) +from lima_api.code_generator.utils import ( + OPENAPI_2_TYPE_MAPPING, + camel_to_snake, + snake_to_camel, +) + +STAR_WITH_NUMBER = re.compile("^[0-9]+") +PARAM_MAPPING = { + "path": "lima_api.PathParameter", + "query": "lima_api.QueryParameter", + "header": "lima_api.HeaderParameter", + # "cookie": "", # Not supported +} + + +class LimaExceptionGenerator: + def __init__(self, name: str, details: str, model: Optional[str] = None): + self.name: str = snake_to_camel(name) + self.details: str = details + self.model: Optional[str] = model + + def __str__(self): + class_attributes = f' detail: str = "{self.details}"' + class_methods = "" + if self.model: + class_methods = f" model = {self.model}" + return BASE_CLASS.substitute( + model_class_name=self.name, + model_class_parent="lima_api.LimaException", + class_attributes=class_attributes, + class_methods=class_methods, + ) + + def __hash__(self): + return hash(str(self)) + + def __eq__(self, other): + return str(self) == str(other) + + +class LimaFunction: + def __init__(self, client_generator: "ClientGenerator", method: str, path: str, spec: dict): + self.client_generator: ClientGenerator = client_generator + self.method: str = method.lower() + self.path: str = path + self.spec: dict = spec + self._default_status: Optional[int] = None + self._default_response: Optional[str] = None + self._str = "" + self._exceptions: list[LimaExceptionGenerator] = [] + self._embed_cls: list[SchemaObject] = [] + self.models = set() + + @property + def name(self) -> str: + funct_name = f"{self.method}_{camel_to_snake(self.path)}" + if "operationId" in self.spec: + funct_name = camel_to_snake(self.spec.get("operationId")) + # TODO generate name + return funct_name + + @property + def _parameters(self) -> list[dict]: + return self.spec.get("parameters", []) + + @property + def params(self) -> str: + params = "" + + request_body: dict = self.spec.get("requestBody", {}).get("content", {}) + content: dict = request_body.get("application/json") or {} + + if content: + obj = self.client_generator.process_schema("", content.get("schema")) + if obj.type == SchemaObjectType.UNION: + obj.name = str(obj) + self.models.update(obj.models) + elif obj.type == SchemaObjectType.ALIAS: + obj.name = obj.attributes + self.models.update(obj.models) + elif not obj.name: + obj.name = "dict" + else: + self.models.add(obj.name) + params += BASE_PARAM.substitute( + param_name="body", + param_type=obj.name, + param_field="lima_api.BodyParameter", + param_kwargs="", + ) + + for param in self._parameters: + param_kwargs = [] + param_field = PARAM_MAPPING.get(param.get("in")) + if param_field is None: + continue + + schema = param.get("schema", {}) + param_type = OPENAPI_2_TYPE_MAPPING.get(schema.get("type")) + if "anyOf" in schema: + param_type = self.client_generator.process_schema("", schema) + self.models.update(param_type.models) + + if not param_type: + raise NotImplementedError("Invalid type for parameter") + + alias = param.get("name") + param_name = camel_to_snake(alias) + if alias != param_name: + param_kwargs.append(f'alias="{alias}"') + + default = param.get("default") + if default: + param_kwargs.append(f"default={default}") + + params += BASE_PARAM.substitute( + param_name=param_name, + param_type=param_type, + param_field=param_field, + param_kwargs=", ".join(param_kwargs), + ) + ... + if params: + params = "*," + params + return params + + @property + def headers(self) -> str: + return "{}" + + @property + def _responses(self) -> dict: + return self.spec.get("responses", {}) + + @property + def returned_type(self) -> str: + return self._get_type(str(self.default_response_code)) + + def _get_type(self, status: str) -> str: + returned_type = "bytes" + if status in self._responses: + content = self._responses[status].get("content", {}) + if not content: + returned_type = "None" + elif "application/json" in content: + schema = content.get("application/json").get("schema") + if not schema: + returned_type = "dict" + elif "anyOf" in schema: + options: set[str] = set() + for any_of in schema["anyOf"]: + if "$ref" in any_of: + ref = self.client_generator.get_ref(any_of["$ref"]) + options.add(ref.name) + self.models.add(ref.name) + else: + # TODO generate model on fly + options.add("dict") + returned_type = f"typing.Union[{', '.join(options)}]" if len(options) > 1 else options.pop() + elif schema.get("type") in OPENAPI_2_TYPE_MAPPING: + returned_type = OPENAPI_2_TYPE_MAPPING[schema.get("type")] + else: + candidate = self.client_generator.process_schema("", schema) + if candidate.name: + returned_type = candidate.name + self.models.add(candidate.name) + elif candidate.type == SchemaObjectType.ALIAS: + returned_type = candidate.attributes + self.models.update(candidate.models) + elif candidate.type == SchemaObjectType.OBJECT: + obj_name = snake_to_camel(schema.get("description", "")) + candidate = self.client_generator.process_schema(obj_name, schema) + if candidate.name: + self._embed_cls.append(candidate) + returned_type = candidate.name or "dict" + else: + raise NotImplementedError("Unexpected") + elif "application/xml" in content or "text/plain" in content: + returned_type = "str" + return returned_type + + @property + def default_response_code(self) -> int: + if not self._default_status: + codes = [int(status) for status in self._responses if status.isnumeric()] + self._default_status = 200 + if len(codes) == 1: + self._default_status = codes[0] + elif codes: + if "default" in self._responses: + self._default_status = 200 + if 200 not in codes: + for status in sorted(codes): + if 200 >= status < 400: + self._default_status = status + break + else: + self._default_status = codes[0] + return self._default_status + + @property + def response_mapping(self) -> dict[int, LimaExceptionGenerator]: + mapping = {} + for status in self._responses: + if status == "default": + continue + int_status = int(status) + if int_status != self.default_response_code: + details = self.spec["responses"][status].get("description") + model_type = self._get_type(status) + if model_type in ["dict", "list"]: + model_type = "None" + exception_name = model_type if model_type != "None" else details + + if exception_name in self.models: + exception_name = details + if "[" in exception_name: + exception_name = details + + numbers = STAR_WITH_NUMBER.match(exception_name) + if numbers: + number = numbers.group() + exception_name = exception_name[len(number) :] + number + + low_ex = exception_name.lower() + if not any(word in low_ex for word in ["error", "invalid", "exception"]): + exception_name += "Error" + + lima_exception = LimaExceptionGenerator( + name=exception_name, + details=details, + model=model_type, + ) + self._exceptions.append(lima_exception) + mapping[int_status] = lima_exception + return mapping + + def __str__(self) -> str: + response_mapping: str = "{" + if self.response_mapping: + for code, ex in self.response_mapping.items(): + response_mapping += f"\n {code}: {ex.name}," + response_mapping += "\n }" + else: + response_mapping += "}" + + self._str += LIMA_FUNCTION.substitute( + method=self.method, + path=self.path, + default_response_code=self.default_response_code, + response_mapping=response_mapping, + headers=self.headers, + default_exception="lima_api.LimaException", + function_name=self.name, + function_params=self.params, + function_return=self.returned_type, + ) + return self._str + + +class ClientGenerator: + def __init__(self, schema_parser: SchemaParser, paths: dict): + self.schema_parser: SchemaParser = schema_parser + self.paths: dict = paths + self.models = set() + self._str = "" + + def __str__(self) -> str: + return self._str + + def get_ref(self, ref: str) -> SchemaObject: + return self.schema_parser.get_ref(ref) + + def process_schema(self, schema_name: str, schema_data: dict) -> SchemaObject: + return self.schema_parser.process_schema(schema_name, schema_data) + + def parse(self): + exceptions = set() + embed_cls = set() + class_attributes = " response_mapping = {}" + class_methods = "" + for path, methods in self.paths.items(): + for method, data in methods.items(): + funct = LimaFunction(self, method, path, data) + class_methods += str(funct) + self.models.update(funct.models) + exceptions.update(funct._exceptions) + embed_cls.update(funct._embed_cls) + + self._str = BASE_CLASS.substitute( + model_class_name="ApiClient", + model_class_parent="lima_api.SyncLimaApi", + class_attributes=class_attributes, + class_methods=class_methods, + ) + _str = "\n".join(str(ex) for ex in exceptions) + if exceptions: + _str += "\n" + _str += "\n".join(str(model) for model in embed_cls) + if embed_cls: + _str += "\n" + self._str = _str + self._str diff --git a/src/lima_api/code_generator/main.py b/src/lima_api/code_generator/main.py new file mode 100644 index 0000000..50c1d57 --- /dev/null +++ b/src/lima_api/code_generator/main.py @@ -0,0 +1,97 @@ +import argparse +import json +import os +from io import StringIO + +from lima_api.code_generator.cli_gen import ClientGenerator +from lima_api.code_generator.schemas import SchemaParser + + +def generate_models(openapi_content: dict) -> SchemaParser: + components = openapi_content.get("components", {}) + schema_parse = SchemaParser(components.get("schemas", {})) + schema_parse.parse() + return schema_parse + + +def gen_from_file(file_path): + with open(file_path) as f: + openapi_content = json.load(f) + + base_name: str = os.path.basename(file_path).replace(".json", "") + base_dir = os.path.join(os.path.dirname(file_path), base_name) + if not os.path.isdir(base_dir): + os.mkdir(base_dir) + + api_title = openapi_content.get("info", {}).get("title", "") + servers = openapi_content.get("servers", []) + server = "http://localhost/" + if servers and isinstance(servers, (list, tuple)): + server = servers[0].get("url") + if servers[0].get("variables", {}): + vars = {key: value.get("default") for key, value in servers[0]["variables"].items()} + for key, value in vars.items(): + server = server.replace("{" + key + "}", value) + elif servers: + server = servers + + schema_parse: SchemaParser = generate_models(openapi_content) + model_content: StringIO = StringIO() + schema_parse.print(file=model_content) + model_content.seek(0) + model_content: str = model_content.read() + has_model = False + if "pydantic" in model_content: + has_model = True + with open(os.path.join(base_dir, "models.py"), "w") as f: + add_enter = False + if "typing" in model_content: + f.write("import typing\n") + add_enter = True + if "Enum" in model_content: + f.write("from enum import Enum\n") + add_enter = True + if add_enter: + f.write("\n") + + f.write("import pydantic\n\n") + f.write(model_content) + + paths = openapi_content.get("paths", {}) + generator = ClientGenerator(schema_parse, paths) + generator.parse() + client_content: str = str(generator) + with open(os.path.join(base_dir, "client.py"), "w") as f: + f.write(f"#\n# Client auto generated for {api_title}\n#\n") + add_enter = False + if "typing" in client_content: + f.write("import typing\n") + add_enter = True + if "Enum" in client_content: + f.write("from enum import Enum\n") + add_enter = True + if add_enter: + f.write("\n") + + f.write("import lima_api\n") + if "pydantic" in client_content: + f.write("import pydantic\n") + f.write("\n") + if has_model: + f.write("from .models import (") + for model in sorted(generator.models): + f.write(f"\n {model},") + f.write("\n)\n\n") + f.write(client_content) + f.write(f'\nclient = ApiClient(base_url="{server}")\n') + + +def main(): + parser = argparse.ArgumentParser() + parser.add_argument("file") + options = parser.parse_args() + gen_from_file(options.file) + + +if __name__ == "__main__": + main() diff --git a/src/lima_api/code_generator/schemas.py b/src/lima_api/code_generator/schemas.py new file mode 100644 index 0000000..de7572e --- /dev/null +++ b/src/lima_api/code_generator/schemas.py @@ -0,0 +1,336 @@ +from enum import Enum +from typing import Any, Optional + +from lima_api.code_generator.templates import BASE_CLASS +from lima_api.code_generator.utils import ( + OPENAPI_2_TYPE_MAPPING, + OpenApiType, + camel_to_snake, + snake_to_camel, +) + + +class EnumObject: + def __init__(self, type_name, options: list[Any], enum_type: OpenApiType) -> None: + self.name: str = type_name + self.type: OpenApiType = enum_type + self.options = options + + def __str__(self): + attributes = "" + for choice in self.options: + key = f"OP_{choice}" + if self.type == OpenApiType.STRING: + key = camel_to_snake(choice) + attributes += f' {key.upper()} = "{choice}"\n' + return BASE_CLASS.substitute( + model_class_name=self.name, + model_class_parent=f"{OPENAPI_2_TYPE_MAPPING.get(self.type)}, Enum", + class_attributes=attributes, + class_methods="", + ).replace("\n\n", "\n") + + +class SchemaObjectType(str, Enum): + OBJECT = "object" + ALIAS = "alias" + ARRAY = "array" + ENUM = "enum" + UNION = "union" + CONST = "const" + + +class PropertyParser: + def __init__(self, parser: "SchemaParser", name: str, definition: dict, is_required=False): + self.name: str = name + self.type: str = "" + self.parser: SchemaParser = parser + self.definition: dict = definition + self.is_required: bool = is_required + self._str: str = "" + self.enum: Optional[EnumObject] = None + self.embed_cls: Optional[SchemaObject] = None + self.models = set() + + def _get_final_type(self, obj): + def_type = None + if "$ref" in obj: + ref = self.parser.get_ref(obj.get("$ref")) + def_type = ref.name + self.models.add(ref.name) + self.models.update(ref.models) + + return def_type + + def parse(self): + def_type = self.definition.get("type") + if def_type in OPENAPI_2_TYPE_MAPPING: + def_type: str = OPENAPI_2_TYPE_MAPPING.get(def_type) + if "items" in self.definition: + items = self.definition.get("items") + item_type = items.get("type") + if "$ref" in items: + ref = self.parser.get_ref(items.get("$ref")) + self.models.add(ref.name) + self.models.update(ref.models) + def_type = f"{def_type}[{ref.name}]" + else: + is_list = False + item_type = OPENAPI_2_TYPE_MAPPING.get(item_type) + if "anyOf" in items or "oneOf" in items: + key_of = "anyOf" if "anyOf" in items else "oneOf" + any_of = [] + for item in items[key_of]: + if "$ref" in item: + ref = self.parser.get_ref(item.get("$ref")) + any_of.append(ref.name) + self.models.add(ref.name) + self.models.update(ref.models) + elif item.get("type") in OPENAPI_2_TYPE_MAPPING: + item_type = OPENAPI_2_TYPE_MAPPING.get(item.get("type")) + any_of.append(item_type) + else: + raise NotImplementedError(f"Unsupported type {item.get('type')}") + item_type = ", ".join(any_of) + if def_type == "list": + is_list = True + def_type = "typing.Union" + elif item_type is None: + if "properties" in items: + item_type = snake_to_camel(self.name) + "Embed" + obj = SchemaObject(self.parser, item_type) + obj.set_as_object(items.get("properties"), items.get("required", [])) + self.models.add(obj.name) + self.models.update(obj.models) + self.embed_cls = obj + else: + raise NotImplementedError("Unsupported jet") + + def_type = f"{def_type}[{item_type}]" + if is_list: + def_type = f"list[{def_type}]" + + if "enum" in self.definition: + type_name = snake_to_camel(self.definition.get("title", self.name)) + self.enum = EnumObject( + type_name=type_name, + options=self.definition.get("enum"), + enum_type=self.definition.get("type"), + ) + def_type = type_name + if "$ref" in self.definition: + ref = self.parser.get_ref(self.definition.get("$ref")) + self.models.add(ref.name) + self.models.update(ref.models) + def_type = ref.name + field_kwargs = "" + field_name = camel_to_snake(self.name) + kwargs = [] + if field_name != self.name: + kwargs.append(f' alias="{self.name}",\n') + for key in ["title", "description"]: + value = self.definition.get(key, None) + if value is None: + continue + if isinstance(value, str): + kwargs.append(f' {key}="{value}",\n') + else: + kwargs.append(f" {key}={value},\n") + if kwargs: + field_kwargs += "\n" + field_kwargs += "".join(kwargs) + field_kwargs += " " + self.type = def_type + self._str = f"{field_name}: {def_type} = " f"pydantic.Field({field_kwargs})" + + def __str__(self): + return self._str + + +class SchemaObject: + def __init__(self, parser: "SchemaParser", name: str): + self.name: str = name + self.parser: SchemaParser = parser + self.type: Optional[SchemaObjectType] = None + self._str: str = "" + self.enums: list[EnumObject] = [] + self.embed_cls: list[SchemaObject] = [] + self.attributes: str = "" + self.models = set() + + def __str__(self): + return self._str + + def __hash__(self): + return hash(str(self)) + + def __eq__(self, other): + return str(self) == str(other) + + def set_as_object(self, properties: dict, required: Optional[list] = None) -> None: + self.type = SchemaObjectType.OBJECT + self.name = snake_to_camel(self.name) + self.models.add(self.name) + if required is None: + required = [] + + props: list[PropertyParser] = [] + for prop, definition in properties.items(): + parser = PropertyParser(self.parser, prop, definition, is_required=prop in required) + parser.parse() + self.models.update(parser.models) + if parser.enum: + self.enums.append(parser.enum) + props.append(parser) + if parser.embed_cls is not None: + self.embed_cls.append(parser.embed_cls) + self.attributes += f"\n {parser}" + self._str += "".join([str(enum) for enum in self.enums]) + self._str += "\n\n".join([str(cls) for cls in self.embed_cls]) + if self.embed_cls: + self._str += "\n" + self._str += BASE_CLASS.substitute( + model_class_name=self.name, + model_class_parent="pydantic.BaseModel", + class_attributes=self.attributes, + class_methods="", + ).replace("\n\n", "\n") + + def set_as_alias(self, alias_type: str) -> None: + self.type = SchemaObjectType.ALIAS + self.name = snake_to_camel(self.name) + self.attributes = alias_type + self._str = f"\n{self.name}: typing.TypeAlias = {alias_type}\n" + + def set_as_array(self, array_type: str, required=False) -> None: + self.type = SchemaObjectType.ARRAY + if required: + self._str = f"typing.Optional[list[{array_type}]] = None" + else: + self._str = f"list[{array_type}]" + + def set_as_enum(self, type_name, options: list[Any], enum_type: OpenApiType): + self.type = SchemaObjectType.ENUM + self.models.add(type_name) + self._str = str(EnumObject(type_name, options, enum_type)) + + def set_as_const(self, value): + self.type = SchemaObjectType.CONST + self._str = f"Literal[{value}]" + + def set_as_union(self, items): + self.type = SchemaObjectType.UNION + options: set[str] = set() + for any_of in items: + if "$ref" in any_of: + ref = self.parser.get_ref(any_of["$ref"]) + self.models.add(ref.name) + options.add(ref.name) + elif "items" in any_of: + ops = [] + if "$ref" in any_of["items"]: + ref = self.parser.get_ref(any_of["$ref"]) + self.models.add(ref.name) + ops.append(ref.name) + elif any_of["items"].get("type") in OPENAPI_2_TYPE_MAPPING: + ops.append(OPENAPI_2_TYPE_MAPPING[any_of["items"]["type"]]) + else: + raise NotImplementedError("Not supported type") + options.add(f"list[{', '.join(ops)}]") + elif any_of.get("type") in OPENAPI_2_TYPE_MAPPING: + some_type = OPENAPI_2_TYPE_MAPPING[any_of.get("type")] + options.add(some_type) + else: + # Add on embed_cls + raise NotImplementedError("not supported") + self._str = f"typing.Union[{', '.join(options)}]" if len(options) > 1 else options.pop() + + +class SchemaParser: + def __init__(self, schemas: dict[str, dict], base_name: str = "#/components/schemas/"): + self.raw_schemas: dict[str, dict] = schemas + self.base_name: str = base_name + self.schemas: dict[str, SchemaObject] = {} + self.order: list[str] = [] + self.models = set() + + def parse(self): + if self.schemas: + return + for name in self.raw_schemas: + self.get_ref(f"{self.base_name}{name}") + + def process_schema(self, schema_name: str, schema_data: dict) -> SchemaObject: + if {"not", "oneOf"}.intersection(schema_data.keys()): + raise NotImplementedError("Not implemented") + + if "anyOf" in schema_data: + new_schema = SchemaObject(self, schema_name) + new_schema.set_as_union(schema_data.get("anyOf", [])) + self.models.update(new_schema.models) + return new_schema + + if "allOf" in schema_data: + new_schema = SchemaObject(self, schema_name) + for item in schema_data.get("allOf", []): + schema = self.process_schema(schema_name, item) + self.models.update(new_schema.models) + new_schema.attributes += schema.attributes + new_schema.set_as_object({}, []) + self.models.update(new_schema.models) + return new_schema + + if "properties" in schema_data and "type" not in schema_data: + schema_data["type"] = "object" + + new_schema = SchemaObject(self, schema_name) + match schema_data.get("type"): + case "object": + new_schema.set_as_object( + properties=schema_data.get("properties") or {}, + required=schema_data.get("required"), + ) + self.models.update(new_schema.models) + case "array": + items = schema_data.get("items", {}) + array_type = items.get("type") + if array_type in OPENAPI_2_TYPE_MAPPING: + new_schema.set_as_alias(f"list[{OPENAPI_2_TYPE_MAPPING.get(array_type)}]") + elif "$ref" in items: + obj = self.get_ref(items.get("$ref")) + self.models.add(obj.name) + self.models.update(obj.models) + new_schema.set_as_alias(f"list[{obj.name}]") + else: + raise NotImplementedError(f"Type for list {array_type} not supported") + case "string": + if "enum" in schema_data: + new_schema.set_as_enum(schema_name, schema_data.get("enum"), schema_data.get("type")) + self.models.update(new_schema.models) + return new_schema + if "const" in schema_data: + new_schema.set_as_const(schema_data.get("const")) + return new_schema + raise NotImplementedError(f"Type {schema_data.get('type')} not supported") + case _: + if "$ref" in schema_data: + obj = self.get_ref(schema_data.get("$ref")) + self.models.add(obj.name) + self.models.update(obj.models) + return obj + raise NotImplementedError(f"Type {schema_data.get('type')} not supported") + return new_schema + + def get_ref(self, ref: str) -> SchemaObject: + if ref not in self.schemas: + schema_name = ref.replace(self.base_name, "") + if schema_name not in self.raw_schemas: + raise ValueError(f"Schema {ref} not found") + + self.schemas[ref] = self.process_schema(schema_name, self.raw_schemas[schema_name]) + self.order.append(ref) + return self.schemas[ref] + + def print(self, file=None): + for schema_name in self.order: + print(self.schemas.get(schema_name), file=file) diff --git a/src/lima_api/code_generator/templates.py b/src/lima_api/code_generator/templates.py new file mode 100644 index 0000000..2eec2bd --- /dev/null +++ b/src/lima_api/code_generator/templates.py @@ -0,0 +1,25 @@ +from string import Template + +BASE_CLASS = Template(""" +class ${model_class_name}(${model_class_parent}): +${class_attributes} +${class_methods} +""") + +LIMA_FUNCTION = Template(""" + @lima_api.${method}( + path="${path}", + default_response_code=${default_response_code}, + response_mapping=${response_mapping}, + headers=${headers}, + default_exception=${default_exception}, + ) + def ${function_name}( + self, + ${function_params} + ) -> ${function_return}: + ... +""") + +BASE_PARAM = Template(""" + ${param_name}: ${param_type} = ${param_field}(${param_kwargs}),""") diff --git a/src/lima_api/code_generator/utils.py b/src/lima_api/code_generator/utils.py new file mode 100644 index 0000000..e63314b --- /dev/null +++ b/src/lima_api/code_generator/utils.py @@ -0,0 +1,38 @@ +import re +from enum import Enum + + +class OpenApiType(str, Enum): + STRING = "string" + BOOLEAN = "boolean" + INTEGER = "integer" + NUMBER = "number" + ARRAY = "array" + NULL = "null" + + +OPENAPI_2_TYPE_MAPPING: dict[OpenApiType | str, str] = { + OpenApiType.STRING: "str", + OpenApiType.BOOLEAN: "bool", + OpenApiType.INTEGER: "int", + OpenApiType.NUMBER: "float", + OpenApiType.ARRAY: "list", + OpenApiType.NULL: "None", +} + +CAMEL2SNAKE_RE = re.compile("([A-Za-z0-9][a-z0-9]*)") +START_WITH_UPPER = re.compile("^[A-Z]") + + +def camel_to_snake(camel_str: str) -> str: + camel_str = camel_str.encode("ascii", "ignore").decode("ascii") + groups = CAMEL2SNAKE_RE.findall(camel_str.replace(" ", "_").replace(".", "")) + return "_".join([i.lower() for i in groups]) + + +def snake_to_camel(snake_str: str) -> str: + if not snake_str: + return "" + snake_str = snake_str.encode("ascii", "ignore").decode("ascii") + _str = snake_str.replace("-", "_").replace(" ", "_").replace(".", "") + return "".join(x if START_WITH_UPPER.match(x) else x[0].upper() + x[1:] if x else "" for x in _str.split("_")) diff --git a/tests/resources/calculadora_openapi.json b/tests/resources/calculadora_openapi.json new file mode 100644 index 0000000..779d40a --- /dev/null +++ b/tests/resources/calculadora_openapi.json @@ -0,0 +1,3227 @@ +{ + "openapi": "3.1.0", + "info": { + "title": "Api Calculadora Simple", + "summary": "Api de Calculadora simple desarrollada con FastApi 🚀", + "description": "\n## ⚓ Listado de Funcionalidades\n### 🤓 Cálculo Básico\n- Sumar\n- Restar\n- Multiplicar\n- Dividir\n- Módulo\n- Raíz n-ésima \n- Potenciación\n- Logaritmo\n\n### 🤖 Conversión\n- Ángulo\n- Área\n- Datos\n- Longitud\n- Masa\n- Velocidad\n- Sistema Numérico (BIN, DEC, HEX, OCT)\n- Temperatura\n- Tiempo\n- Volumen\n- Divisas (+160 divisas en tiempo real la tasa de cambio)\n\n### 💥 Trigonometría\n- Seno\n- Coseno\n- Tangente\n- Cotangente\n- Secante\n- Cosecante\n\n### 🎓 Calculadora\n- Evaluación y Cálculo de expresiones\n- Descuento\n- IMC\n\n### 📈 Estadística\n- Media\n- Mediana\n- Multi-Moda\n- Desviación Estándar de la población\n- Desviación Estándar de la muestra\n- Varianza de la población\n- Varianza de la muestra\n- Correlación\n- Covarianza\n\n### 🎈 Otros\n- Constantes\n", + "contact": { + "name": "Eduardo González", + "url": "https://github.com/EduardoProfe666", + "email": "eduardoglez64377@gmail.com" + }, + "license": { + "name": "MIT License", + "url": "https://opensource.org/license/mit/" + }, + "version": "1.0.2" + }, + "paths": { + "/calculo-basico/sumar/": { + "get": { + "tags": [ + "Cálculo Básico" + ], + "summary": "Permite sumar 2 números (num1 + num2)", + "operationId": "sumar_calculo_basico_sumar__get", + "parameters": [ + { + "name": "num1", + "in": "query", + "required": true, + "schema": { + "type": "number", + "title": "Num1" + } + }, + { + "name": "num2", + "in": "query", + "required": true, + "schema": { + "type": "number", + "title": "Num2" + } + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ResultadoFloat" + } + } + } + }, + "400": { + "description": "Bad Request" + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/calculo-basico/restar/": { + "get": { + "tags": [ + "Cálculo Básico" + ], + "summary": "Permite restar 2 números (num1 - num2)", + "operationId": "restar_calculo_basico_restar__get", + "parameters": [ + { + "name": "num1", + "in": "query", + "required": true, + "schema": { + "type": "number", + "title": "Num1" + } + }, + { + "name": "num2", + "in": "query", + "required": true, + "schema": { + "type": "number", + "title": "Num2" + } + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ResultadoFloat" + } + } + } + }, + "400": { + "description": "Bad Request" + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/calculo-basico/multiplicar/": { + "get": { + "tags": [ + "Cálculo Básico" + ], + "summary": "Permite multiplicar 2 números (num1 * num2)", + "operationId": "multiplicar_calculo_basico_multiplicar__get", + "parameters": [ + { + "name": "num1", + "in": "query", + "required": true, + "schema": { + "type": "number", + "title": "Num1" + } + }, + { + "name": "num2", + "in": "query", + "required": true, + "schema": { + "type": "number", + "title": "Num2" + } + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ResultadoFloat" + } + } + } + }, + "400": { + "description": "Bad Request" + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/calculo-basico/dividir/": { + "get": { + "tags": [ + "Cálculo Básico" + ], + "summary": "Permite dividir 2 números (num1 / num2)", + "operationId": "dividir_calculo_basico_dividir__get", + "parameters": [ + { + "name": "num1", + "in": "query", + "required": true, + "schema": { + "type": "number", + "title": "Num1" + } + }, + { + "name": "num2", + "in": "query", + "required": true, + "schema": { + "type": "number", + "title": "Num2" + } + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ResultadoFloat" + } + } + } + }, + "400": { + "description": "Bad Request" + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/calculo-basico/modulo/": { + "get": { + "tags": [ + "Cálculo Básico" + ], + "summary": "Permite calcular el módulo dados 2 números (num1 % num2)", + "operationId": "modulo_calculo_basico_modulo__get", + "parameters": [ + { + "name": "num1", + "in": "query", + "required": true, + "schema": { + "type": "number", + "title": "Num1" + } + }, + { + "name": "num2", + "in": "query", + "required": true, + "schema": { + "type": "number", + "title": "Num2" + } + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ResultadoFloat" + } + } + } + }, + "400": { + "description": "Bad Request" + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/calculo-basico/raiz/": { + "get": { + "tags": [ + "Cálculo Básico" + ], + "summary": "Permite calcular la raíz n-ésima de un número (radicando ^ 1/radical)", + "operationId": "raiz_calculo_basico_raiz__get", + "parameters": [ + { + "name": "radicando", + "in": "query", + "required": true, + "schema": { + "type": "number", + "title": "Radicando" + } + }, + { + "name": "radical", + "in": "query", + "required": true, + "schema": { + "type": "number", + "title": "Radical" + } + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ResultadoFloat" + } + } + } + }, + "400": { + "description": "Bad Request" + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/calculo-basico/potenciacion/": { + "get": { + "tags": [ + "Cálculo Básico" + ], + "summary": "Permite calcular la potenciación n-ésima de un número (base ^ exponente)", + "operationId": "potenciacion_calculo_basico_potenciacion__get", + "parameters": [ + { + "name": "base", + "in": "query", + "required": true, + "schema": { + "type": "number", + "title": "Base" + } + }, + { + "name": "exponente", + "in": "query", + "required": true, + "schema": { + "type": "number", + "title": "Exponente" + } + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ResultadoFloat" + } + } + } + }, + "400": { + "description": "Bad Request" + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/calculo-basico/logaritmo/": { + "get": { + "tags": [ + "Cálculo Básico" + ], + "summary": "Permite calcular el logaritmo n-ésimo de un número (logbase argumento)", + "operationId": "logaritmo_calculo_basico_logaritmo__get", + "parameters": [ + { + "name": "base", + "in": "query", + "required": true, + "schema": { + "type": "number", + "title": "Base" + } + }, + { + "name": "argumento", + "in": "query", + "required": true, + "schema": { + "type": "number", + "title": "Argumento" + } + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ResultadoFloat" + } + } + } + }, + "400": { + "description": "Bad Request" + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/calculo-basico/logaritmo-natural/": { + "get": { + "tags": [ + "Cálculo Básico" + ], + "summary": "Permite calcular el logaritmo natural de un número (lg argumento)", + "operationId": "logaritmo_natural_calculo_basico_logaritmo_natural__get", + "parameters": [ + { + "name": "argumento", + "in": "query", + "required": true, + "schema": { + "type": "number", + "title": "Argumento" + } + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ResultadoFloat" + } + } + } + }, + "400": { + "description": "Bad Request" + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/calculadora/calcular": { + "post": { + "tags": [ + "Calculadora" + ], + "summary": "Permite calcular la expresión matemática simple dada (sin funciones trigonométricas)", + "operationId": "calcular_calculadora_calcular_post", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Expresion" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ResultadoFloat" + } + } + } + }, + "400": { + "description": "Bad Request" + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/calculadora/descuento/": { + "post": { + "tags": [ + "Calculadora" + ], + "summary": "Permite calcular el descuento dado el precio original y el porcentaje de descuento.", + "operationId": "descuento_calculadora_descuento__post", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DatosDescuentoInicio" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DatosDescuentoFinal" + } + } + } + }, + "400": { + "description": "Bad Request" + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/calculadora/imc/": { + "post": { + "tags": [ + "Calculadora" + ], + "summary": "Permite calcular el índice de masa corporal de una persona dados su peso y altura.", + "operationId": "imc_calculadora_imc__post", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/IMCDatos" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/IMCRespuesta" + } + } + } + }, + "400": { + "description": "Bad Request" + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/trigonometria/seno/{num}": { + "get": { + "tags": [ + "Trigonometría" + ], + "summary": "Permite calcular el seno de un número (sen(num))", + "operationId": "seno_trigonometria_seno__num__get", + "parameters": [ + { + "name": "num", + "in": "path", + "required": true, + "schema": { + "type": "number", + "title": "Num" + } + }, + { + "name": "es_radian", + "in": "query", + "required": false, + "schema": { + "type": "boolean", + "default": true, + "title": "Es Radian" + } + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ResultadoFloat" + } + } + } + }, + "400": { + "description": "Bad Request" + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/trigonometria/coseno/{num}": { + "get": { + "tags": [ + "Trigonometría" + ], + "summary": "Permite calcular el coseno de un número (cos(num))", + "operationId": "coseno_trigonometria_coseno__num__get", + "parameters": [ + { + "name": "num", + "in": "path", + "required": true, + "schema": { + "type": "number", + "title": "Num" + } + }, + { + "name": "es_radian", + "in": "query", + "required": false, + "schema": { + "type": "boolean", + "default": true, + "title": "Es Radian" + } + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ResultadoFloat" + } + } + } + }, + "400": { + "description": "Bad Request" + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/trigonometria/tangente/{num}": { + "get": { + "tags": [ + "Trigonometría" + ], + "summary": "Permite calcular la tangente de un número (tan(num))", + "operationId": "tangente_trigonometria_tangente__num__get", + "parameters": [ + { + "name": "num", + "in": "path", + "required": true, + "schema": { + "type": "number", + "title": "Num" + } + }, + { + "name": "es_radian", + "in": "query", + "required": false, + "schema": { + "type": "boolean", + "default": true, + "title": "Es Radian" + } + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ResultadoFloat" + } + } + } + }, + "400": { + "description": "Bad Request" + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/trigonometria/cotangente/{num}": { + "get": { + "tags": [ + "Trigonometría" + ], + "summary": "Permite calcular la cotangente de un número (cot(num))", + "operationId": "cotangente_trigonometria_cotangente__num__get", + "parameters": [ + { + "name": "num", + "in": "path", + "required": true, + "schema": { + "type": "number", + "title": "Num" + } + }, + { + "name": "es_radian", + "in": "query", + "required": false, + "schema": { + "type": "boolean", + "default": true, + "title": "Es Radian" + } + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ResultadoFloat" + } + } + } + }, + "400": { + "description": "Bad Request" + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/trigonometria/secante/{num}": { + "get": { + "tags": [ + "Trigonometría" + ], + "summary": "Permite calcular la secante de un número (sec(num))", + "operationId": "secante_trigonometria_secante__num__get", + "parameters": [ + { + "name": "num", + "in": "path", + "required": true, + "schema": { + "type": "number", + "title": "Num" + } + }, + { + "name": "es_radian", + "in": "query", + "required": false, + "schema": { + "type": "boolean", + "default": true, + "title": "Es Radian" + } + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ResultadoFloat" + } + } + } + }, + "400": { + "description": "Bad Request" + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/trigonometria/cosecante/{num}": { + "get": { + "tags": [ + "Trigonometría" + ], + "summary": "Permite calcular la cosecante de un número (csc(num))", + "operationId": "cosecante_trigonometria_cosecante__num__get", + "parameters": [ + { + "name": "num", + "in": "path", + "required": true, + "schema": { + "type": "number", + "title": "Num" + } + }, + { + "name": "es_radian", + "in": "query", + "required": false, + "schema": { + "type": "boolean", + "default": true, + "title": "Es Radian" + } + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ResultadoFloat" + } + } + } + }, + "400": { + "description": "Bad Request" + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/conversion/area/": { + "get": { + "tags": [ + "Conversión" + ], + "summary": "Permite la conversión en unidades de área", + "operationId": "area_conversion_area__get", + "parameters": [ + { + "name": "num", + "in": "query", + "required": true, + "schema": { + "type": "number", + "title": "Num" + } + }, + { + "name": "unidad1", + "in": "query", + "required": true, + "schema": { + "enum": [ + "km2", + "ha", + "are", + "m2", + "dm2", + "cm2", + "mm2", + "micrometro2", + "acre", + "milla2", + "yd2", + "ft2", + "in2", + "rd2", + "qing", + "chi2", + "cun2", + "gongli2" + ], + "type": "string", + "title": "Unidad1" + } + }, + { + "name": "unidad2", + "in": "query", + "required": true, + "schema": { + "enum": [ + "km2", + "ha", + "are", + "m2", + "dm2", + "cm2", + "mm2", + "micrometro2", + "acre", + "milla2", + "yd2", + "ft2", + "in2", + "rd2", + "qing", + "chi2", + "cun2", + "gongli2" + ], + "type": "string", + "title": "Unidad2" + } + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ResultadoFloat" + } + } + } + }, + "400": { + "description": "Bad Request" + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/conversion/datos/": { + "get": { + "tags": [ + "Conversión" + ], + "summary": "Permite la conversión en unidades de datos. Nota: 1 KB = 1024 B", + "operationId": "datos_conversion_datos__get", + "parameters": [ + { + "name": "num", + "in": "query", + "required": true, + "schema": { + "type": "number", + "title": "Num" + } + }, + { + "name": "unidad1", + "in": "query", + "required": true, + "schema": { + "enum": [ + "B", + "KB", + "MB", + "GB", + "TB", + "PB" + ], + "type": "string", + "title": "Unidad1" + } + }, + { + "name": "unidad2", + "in": "query", + "required": true, + "schema": { + "enum": [ + "B", + "KB", + "MB", + "GB", + "TB", + "PB" + ], + "type": "string", + "title": "Unidad2" + } + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ResultadoFloat" + } + } + } + }, + "400": { + "description": "Bad Request" + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/conversion/longitud/": { + "get": { + "tags": [ + "Conversión" + ], + "summary": "Permite la conversión en unidades de longitud.", + "operationId": "longitud_conversion_longitud__get", + "parameters": [ + { + "name": "num", + "in": "query", + "required": true, + "schema": { + "type": "number", + "title": "Num" + } + }, + { + "name": "unidad1", + "in": "query", + "required": true, + "schema": { + "enum": [ + "km", + "m", + "dm", + "cm", + "mm", + "micrometro", + "nanometro", + "picometro", + "nmi", + "mi", + "fur", + "ftm", + "yd", + "ft", + "in", + "gongli", + "li", + "chi", + "cun", + "fen", + "lii", + "parsec", + "distancia-lunar", + "unidad-astronomica", + "anno-luz" + ], + "type": "string", + "title": "Unidad1" + } + }, + { + "name": "unidad2", + "in": "query", + "required": true, + "schema": { + "enum": [ + "km", + "m", + "dm", + "cm", + "mm", + "micrometro", + "nanometro", + "picometro", + "nmi", + "mi", + "fur", + "ftm", + "yd", + "ft", + "in", + "gongli", + "li", + "chi", + "cun", + "fen", + "lii", + "parsec", + "distancia-lunar", + "unidad-astronomica", + "anno-luz" + ], + "type": "string", + "title": "Unidad2" + } + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ResultadoFloat" + } + } + } + }, + "400": { + "description": "Bad Request" + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/conversion/masa/": { + "get": { + "tags": [ + "Conversión" + ], + "summary": "Permite la conversión en unidades de masa.", + "operationId": "masa_conversion_masa__get", + "parameters": [ + { + "name": "num", + "in": "query", + "required": true, + "schema": { + "type": "number", + "title": "Num" + } + }, + { + "name": "unidad1", + "in": "query", + "required": true, + "schema": { + "enum": [ + "t", + "kg", + "g", + "mg", + "microgramo", + "quintal", + "lb", + "oz", + "carat", + "grano" + ], + "type": "string", + "title": "Unidad1" + } + }, + { + "name": "unidad2", + "in": "query", + "required": true, + "schema": { + "enum": [ + "t", + "kg", + "g", + "mg", + "microgramo", + "quintal", + "lb", + "oz", + "carat", + "grano" + ], + "type": "string", + "title": "Unidad2" + } + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ResultadoFloat" + } + } + } + }, + "400": { + "description": "Bad Request" + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/conversion/velocidad/": { + "get": { + "tags": [ + "Conversión" + ], + "summary": "Permite la conversión en unidades de velocidad.", + "operationId": "velocidad_conversion_velocidad__get", + "parameters": [ + { + "name": "num", + "in": "query", + "required": true, + "schema": { + "type": "number", + "title": "Num" + } + }, + { + "name": "unidad1", + "in": "query", + "required": true, + "schema": { + "enum": [ + "c", + "Ma", + "m-s", + "km-h", + "km-s", + "kn", + "mph", + "fps", + "ips" + ], + "type": "string", + "title": "Unidad1" + } + }, + { + "name": "unidad2", + "in": "query", + "required": true, + "schema": { + "enum": [ + "c", + "Ma", + "m-s", + "km-h", + "km-s", + "kn", + "mph", + "fps", + "ips" + ], + "type": "string", + "title": "Unidad2" + } + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ResultadoFloat" + } + } + } + }, + "400": { + "description": "Bad Request" + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/conversion/temperatura/": { + "get": { + "tags": [ + "Conversión" + ], + "summary": "Permite la conversión en unidades de temperatura.", + "operationId": "temperatura_conversion_temperatura__get", + "parameters": [ + { + "name": "num", + "in": "query", + "required": true, + "schema": { + "type": "number", + "title": "Num" + } + }, + { + "name": "unidad1", + "in": "query", + "required": true, + "schema": { + "enum": [ + "celsius", + "fahrenheit", + "kelvin", + "rankine", + "reaumur" + ], + "type": "string", + "title": "Unidad1" + } + }, + { + "name": "unidad2", + "in": "query", + "required": true, + "schema": { + "enum": [ + "celsius", + "fahrenheit", + "kelvin", + "rankine", + "reaumur" + ], + "type": "string", + "title": "Unidad2" + } + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ResultadoFloat" + } + } + } + }, + "400": { + "description": "Bad Request" + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/conversion/tiempo/": { + "get": { + "tags": [ + "Conversión" + ], + "summary": "Permite la conversión en unidades de tiempo.", + "operationId": "tiempo_conversion_tiempo__get", + "parameters": [ + { + "name": "num", + "in": "query", + "required": true, + "schema": { + "type": "number", + "title": "Num" + } + }, + { + "name": "unidad1", + "in": "query", + "required": true, + "schema": { + "enum": [ + "anno", + "semana", + "dia", + "hora", + "minuto", + "segundo", + "milisegundo", + "microsegundo", + "picosegundo" + ], + "type": "string", + "title": "Unidad1" + } + }, + { + "name": "unidad2", + "in": "query", + "required": true, + "schema": { + "enum": [ + "anno", + "semana", + "dia", + "hora", + "minuto", + "segundo", + "milisegundo", + "microsegundo", + "picosegundo" + ], + "type": "string", + "title": "Unidad2" + } + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ResultadoFloat" + } + } + } + }, + "400": { + "description": "Bad Request" + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/conversion/volumen/": { + "get": { + "tags": [ + "Conversión" + ], + "summary": "Permite la conversión en unidades de volumen.", + "operationId": "volumen_conversion_volumen__get", + "parameters": [ + { + "name": "num", + "in": "query", + "required": true, + "schema": { + "type": "number", + "title": "Num" + } + }, + { + "name": "unidad1", + "in": "query", + "required": true, + "schema": { + "enum": [ + "m3", + "dm3", + "cm3", + "mm3", + "hl", + "l", + "dl", + "cl", + "ml", + "ft3", + "in3", + "yd3", + "af3" + ], + "type": "string", + "title": "Unidad1" + } + }, + { + "name": "unidad2", + "in": "query", + "required": true, + "schema": { + "enum": [ + "m3", + "dm3", + "cm3", + "mm3", + "hl", + "l", + "dl", + "cl", + "ml", + "ft3", + "in3", + "yd3", + "af3" + ], + "type": "string", + "title": "Unidad2" + } + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ResultadoFloat" + } + } + } + }, + "400": { + "description": "Bad Request" + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/conversion/angulo/": { + "get": { + "tags": [ + "Conversión" + ], + "summary": "Permite la conversión de unidades de ángulo (radianes-grados). Nota: Si la unidad de destino es radianes, se asume que la unidad inicial es grados, y viceversa.", + "operationId": "angulo_conversion_angulo__get", + "parameters": [ + { + "name": "num", + "in": "query", + "required": true, + "schema": { + "type": "number", + "title": "Num" + } + }, + { + "name": "unidad_destino", + "in": "query", + "required": true, + "schema": { + "enum": [ + "radianes", + "grados" + ], + "type": "string", + "title": "Unidad Destino" + } + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ResultadoFloat" + } + } + } + }, + "400": { + "description": "Bad Request" + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/conversion/sistema-numerico/": { + "get": { + "tags": [ + "Conversión" + ], + "summary": "Permite la conversión de sistema numérico. No admite números fraccionarios.", + "operationId": "sistema_numerico_conversion_sistema_numerico__get", + "parameters": [ + { + "name": "num", + "in": "query", + "required": true, + "schema": { + "type": "string", + "title": "Num" + } + }, + { + "name": "unidad1", + "in": "query", + "required": true, + "schema": { + "enum": [ + "BIN", + "OCT", + "DEC", + "HEX" + ], + "type": "string", + "title": "Unidad1" + } + }, + { + "name": "unidad2", + "in": "query", + "required": true, + "schema": { + "enum": [ + "BIN", + "OCT", + "DEC", + "HEX" + ], + "type": "string", + "title": "Unidad2" + } + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ResultadoStr" + } + } + } + }, + "400": { + "description": "Bad Request" + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/conversion/divisas/": { + "get": { + "tags": [ + "Conversión" + ], + "summary": "Permite la conversión de las divisas según la tasa de cambio en tiempo real.", + "operationId": "divisas_conversion_divisas__get", + "parameters": [ + { + "name": "num", + "in": "query", + "required": true, + "schema": { + "type": "number", + "title": "Num" + } + }, + { + "name": "unidad1", + "in": "query", + "required": true, + "schema": { + "enum": [ + "AED", + "AFN", + "ALL", + "AMD", + "ANG", + "AOA", + "ARS", + "AUD", + "AWG", + "AZN", + "BAM", + "BBD", + "BDT", + "BGN", + "BHD", + "BIF", + "BMD", + "BND", + "BOB", + "BRL", + "BSD", + "BTN", + "BWP", + "BYN", + "BZD", + "CAD", + "CDF", + "CHF", + "CLP", + "CNY", + "COP", + "CRC", + "CUC", + "CUP", + "CVE", + "CZK", + "DJF", + "DKK", + "DOP", + "DZD", + "EGP", + "ERN", + "ETB", + "EUR", + "FJD", + "FKP", + "GBP", + "GEL", + "GGP", + "GHS", + "GIP", + "GMD", + "GNF", + "GTQ", + "GYD", + "HKD", + "HNL", + "HRK", + "HTG", + "HUF", + "IDR", + "ILS", + "IMP", + "INR", + "IQD", + "IRR", + "ISK", + "JEP", + "JMD", + "JOD", + "JPY", + "KES", + "KGS", + "KHR", + "KMF", + "KRW", + "KWD", + "KYD", + "KZT", + "LAK", + "LBP", + "LKR", + "LRD", + "LSL", + "LYD", + "MAD", + "MDL", + "MGA", + "MKD", + "MMK", + "MNT", + "MOP", + "MRU", + "MUR", + "MVR", + "MWK", + "MXN", + "MYR", + "MZN", + "NAD", + "NGN", + "NIO", + "NOK", + "NPR", + "NZD", + "OMR", + "PAB", + "PEN", + "PGK", + "PHP", + "PKR", + "PLN", + "PYG", + "QAR", + "RON", + "RSD", + "RUB", + "RWF", + "SAR", + "SBD", + "SCR", + "SDG", + "SEK", + "SGD", + "SHP", + "SLL", + "SOS", + "SRD", + "SSP", + "STN", + "SVC", + "SYP", + "SZL", + "THB", + "TJS", + "TMT", + "TND", + "TOP", + "TRY", + "TTD", + "TWD", + "TZS", + "UAH", + "UGX", + "USD", + "USDC", + "USDT", + "UYU", + "UZS", + "VES", + "VND", + "VUV", + "WST", + "XAF", + "XAG", + "XAU", + "XCD", + "XDR", + "XOF", + "XPF", + "YER", + "ZAR", + "ZMW", + "ZWL" + ], + "type": "string", + "title": "Unidad1" + } + }, + { + "name": "unidad2", + "in": "query", + "required": true, + "schema": { + "enum": [ + "AED", + "AFN", + "ALL", + "AMD", + "ANG", + "AOA", + "ARS", + "AUD", + "AWG", + "AZN", + "BAM", + "BBD", + "BDT", + "BGN", + "BHD", + "BIF", + "BMD", + "BND", + "BOB", + "BRL", + "BSD", + "BTN", + "BWP", + "BYN", + "BZD", + "CAD", + "CDF", + "CHF", + "CLP", + "CNY", + "COP", + "CRC", + "CUC", + "CUP", + "CVE", + "CZK", + "DJF", + "DKK", + "DOP", + "DZD", + "EGP", + "ERN", + "ETB", + "EUR", + "FJD", + "FKP", + "GBP", + "GEL", + "GGP", + "GHS", + "GIP", + "GMD", + "GNF", + "GTQ", + "GYD", + "HKD", + "HNL", + "HRK", + "HTG", + "HUF", + "IDR", + "ILS", + "IMP", + "INR", + "IQD", + "IRR", + "ISK", + "JEP", + "JMD", + "JOD", + "JPY", + "KES", + "KGS", + "KHR", + "KMF", + "KRW", + "KWD", + "KYD", + "KZT", + "LAK", + "LBP", + "LKR", + "LRD", + "LSL", + "LYD", + "MAD", + "MDL", + "MGA", + "MKD", + "MMK", + "MNT", + "MOP", + "MRU", + "MUR", + "MVR", + "MWK", + "MXN", + "MYR", + "MZN", + "NAD", + "NGN", + "NIO", + "NOK", + "NPR", + "NZD", + "OMR", + "PAB", + "PEN", + "PGK", + "PHP", + "PKR", + "PLN", + "PYG", + "QAR", + "RON", + "RSD", + "RUB", + "RWF", + "SAR", + "SBD", + "SCR", + "SDG", + "SEK", + "SGD", + "SHP", + "SLL", + "SOS", + "SRD", + "SSP", + "STN", + "SVC", + "SYP", + "SZL", + "THB", + "TJS", + "TMT", + "TND", + "TOP", + "TRY", + "TTD", + "TWD", + "TZS", + "UAH", + "UGX", + "USD", + "USDC", + "USDT", + "UYU", + "UZS", + "VES", + "VND", + "VUV", + "WST", + "XAF", + "XAG", + "XAU", + "XCD", + "XDR", + "XOF", + "XPF", + "YER", + "ZAR", + "ZMW", + "ZWL" + ], + "type": "string", + "title": "Unidad2" + } + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ResultadoFloat" + } + } + } + }, + "400": { + "description": "Bad Request" + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/estadisticas/media/": { + "post": { + "tags": [ + "Estadísticas" + ], + "summary": "Permite determinar la media de un conjunto de elementos", + "operationId": "media_estadisticas_media__post", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/EstadisticaDato" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ResultadoFloat" + } + } + } + }, + "400": { + "description": "Bad Request" + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/estadisticas/mediana/": { + "post": { + "tags": [ + "Estadísticas" + ], + "summary": "Permite determinar la mediana de un conjunto de elementos", + "operationId": "mediana_estadisticas_mediana__post", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/EstadisticaDato" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ResultadoFloat" + } + } + } + }, + "400": { + "description": "Bad Request" + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/estadisticas/moda/": { + "post": { + "tags": [ + "Estadísticas" + ], + "summary": "Permite determinar la moda de un conjunto de elementos. Funciona también como multi-moda.", + "operationId": "moda_estadisticas_moda__post", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/EstadisticaDato" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ModaResultado" + } + } + } + }, + "400": { + "description": "Bad Request" + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/estadisticas/desviacion-poblacion/": { + "post": { + "tags": [ + "Estadísticas" + ], + "summary": "Permite determinar la desviación estándar de la población dada", + "operationId": "pdesv_estadisticas_desviacion_poblacion__post", + "parameters": [ + { + "name": "mu", + "in": "query", + "required": false, + "schema": { + "type": "number", + "title": "Mu" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/EstadisticaDato" + } + } + } + }, + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ResultadoFloat" + } + } + } + }, + "400": { + "description": "Bad Request" + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/estadisticas/varianza-poblacion/": { + "post": { + "tags": [ + "Estadísticas" + ], + "summary": "Permite determinar la varianza de la población dada", + "operationId": "pvar_estadisticas_varianza_poblacion__post", + "parameters": [ + { + "name": "mu", + "in": "query", + "required": false, + "schema": { + "type": "number", + "title": "Mu" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/EstadisticaDato" + } + } + } + }, + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ResultadoFloat" + } + } + } + }, + "400": { + "description": "Bad Request" + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/estadisticas/desviacion-muestra/": { + "post": { + "tags": [ + "Estadísticas" + ], + "summary": "Permite determinar la desviación estándar de la muestra dada", + "operationId": "mdesv_estadisticas_desviacion_muestra__post", + "parameters": [ + { + "name": "xbar", + "in": "query", + "required": false, + "schema": { + "type": "number", + "title": "Xbar" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/EstadisticaDato" + } + } + } + }, + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ResultadoFloat" + } + } + } + }, + "400": { + "description": "Bad Request" + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/estadisticas/varianza-muestra/": { + "post": { + "tags": [ + "Estadísticas" + ], + "summary": "Permite determinar la varianza de la muestra dada", + "operationId": "mvar_estadisticas_varianza_muestra__post", + "parameters": [ + { + "name": "xbar", + "in": "query", + "required": false, + "schema": { + "type": "number", + "title": "Xbar" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/EstadisticaDato" + } + } + } + }, + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ResultadoFloat" + } + } + } + }, + "400": { + "description": "Bad Request" + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/estadisticas/correlacion/": { + "post": { + "tags": [ + "Estadísticas" + ], + "summary": "Permite determinar el coeficiente de correlación de Pearson", + "operationId": "correlacion_estadisticas_correlacion__post", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Body_correlacion_estadisticas_correlacion__post" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ResultadoFloat" + } + } + } + }, + "400": { + "description": "Bad Request" + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/estadisticas/covarianza/": { + "post": { + "tags": [ + "Estadísticas" + ], + "summary": "Permite determinar la covarianza", + "operationId": "covarianza_estadisticas_covarianza__post", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Body_covarianza_estadisticas_covarianza__post" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ResultadoFloat" + } + } + } + }, + "400": { + "description": "Bad Request" + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/otros/constantes/{nombre}": { + "get": { + "tags": [ + "Otros" + ], + "summary": "Permite obtener el valor de una constante matemática", + "operationId": "constantes_otros_constantes__nombre__get", + "parameters": [ + { + "name": "nombre", + "in": "path", + "required": true, + "schema": { + "enum": [ + "e", + "pi", + "sqrt-2", + "sqrt-3", + "sqrt-5", + "aureo", + "alfa-feigenbaum", + "delta-feigenbaum", + "artin", + "primos-gemelos", + "brun" + ], + "type": "string", + "title": "Nombre" + } + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ResultadoFloat" + } + } + } + }, + "400": { + "description": "Bad Request" + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/otros/tipos": { + "get": { + "tags": [ + "Otros" + ], + "summary": "Permite obtener todos los tipos usados en la api.", + "operationId": "tipos_otros_tipos_get", + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": {} + } + } + }, + "400": { + "description": "Bad Request" + } + } + } + }, + "/": { + "get": { + "tags": [ + "Páginas Extras" + ], + "summary": "Root", + "operationId": "root__get", + "responses": { + "200": { + "description": "Successful Response", + "content": { + "text/html": { + "schema": { + "type": "string" + } + } + } + }, + "400": { + "description": "Bad Request" + } + } + } + }, + "/calculadora": { + "get": { + "tags": [ + "Páginas Extras" + ], + "summary": "Root", + "operationId": "root_calculadora_get", + "responses": { + "200": { + "description": "Successful Response", + "content": { + "text/html": { + "schema": { + "type": "string" + } + } + } + }, + "400": { + "description": "Bad Request" + } + } + } + }, + "/calculo_basico": { + "get": { + "tags": [ + "Páginas Extras" + ], + "summary": "Root", + "operationId": "root_calculo_basico_get", + "responses": { + "200": { + "description": "Successful Response", + "content": { + "text/html": { + "schema": { + "type": "string" + } + } + } + }, + "400": { + "description": "Bad Request" + } + } + } + }, + "/conversion": { + "get": { + "tags": [ + "Páginas Extras" + ], + "summary": "Root", + "operationId": "root_conversion_get", + "responses": { + "200": { + "description": "Successful Response", + "content": { + "text/html": { + "schema": { + "type": "string" + } + } + } + }, + "400": { + "description": "Bad Request" + } + } + } + }, + "/otros": { + "get": { + "tags": [ + "Páginas Extras" + ], + "summary": "Root", + "operationId": "root_otros_get", + "responses": { + "200": { + "description": "Successful Response", + "content": { + "text/html": { + "schema": { + "type": "string" + } + } + } + }, + "400": { + "description": "Bad Request" + } + } + } + }, + "/trigonometria": { + "get": { + "tags": [ + "Páginas Extras" + ], + "summary": "Root", + "operationId": "root_trigonometria_get", + "responses": { + "200": { + "description": "Successful Response", + "content": { + "text/html": { + "schema": { + "type": "string" + } + } + } + }, + "400": { + "description": "Bad Request" + } + } + } + }, + "/estadisticas": { + "get": { + "tags": [ + "Páginas Extras" + ], + "summary": "Root", + "operationId": "root_estadisticas_get", + "responses": { + "200": { + "description": "Successful Response", + "content": { + "text/html": { + "schema": { + "type": "string" + } + } + } + }, + "400": { + "description": "Bad Request" + } + } + } + }, + "/email": { + "post": { + "tags": [ + "Páginas Extras" + ], + "summary": "Root", + "operationId": "root_email_post", + "requestBody": { + "content": { + "application/x-www-form-urlencoded": { + "schema": { + "$ref": "#/components/schemas/Body_root_email_post" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "Successful Response", + "content": { + "text/html": { + "schema": { + "type": "string" + } + } + } + }, + "400": { + "description": "Bad Request" + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + } + }, + "components": { + "schemas": { + "Body_correlacion_estadisticas_correlacion__post": { + "properties": { + "x": { + "$ref": "#/components/schemas/EstadisticaDato" + }, + "y": { + "$ref": "#/components/schemas/EstadisticaDato" + } + }, + "type": "object", + "required": [ + "x", + "y" + ], + "title": "Body_correlacion_estadisticas_correlacion__post" + }, + "Body_covarianza_estadisticas_covarianza__post": { + "properties": { + "x": { + "$ref": "#/components/schemas/EstadisticaDato" + }, + "y": { + "$ref": "#/components/schemas/EstadisticaDato" + } + }, + "type": "object", + "required": [ + "x", + "y" + ], + "title": "Body_covarianza_estadisticas_covarianza__post" + }, + "Body_root_email_post": { + "properties": { + "name": { + "type": "string", + "title": "Name" + }, + "email": { + "type": "string", + "title": "Email" + }, + "message": { + "type": "string", + "title": "Message" + } + }, + "type": "object", + "required": [ + "name", + "email", + "message" + ], + "title": "Body_root_email_post" + }, + "DatosDescuentoFinal": { + "properties": { + "precio_final": { + "type": "number", + "title": "Precio Final" + }, + "ahorro": { + "type": "number", + "title": "Ahorro" + } + }, + "type": "object", + "required": [ + "precio_final", + "ahorro" + ], + "title": "DatosDescuentoFinal" + }, + "DatosDescuentoInicio": { + "properties": { + "precio_original": { + "type": "number", + "minimum": 0.0, + "title": "Precio Original" + }, + "descuento_porciento": { + "type": "number", + "minimum": 0.0, + "title": "Descuento Porciento" + } + }, + "type": "object", + "required": [ + "precio_original", + "descuento_porciento" + ], + "title": "DatosDescuentoInicio" + }, + "EstadisticaDato": { + "properties": { + "lista": { + "items": { + "type": "number" + }, + "type": "array", + "title": "Lista" + } + }, + "type": "object", + "required": [ + "lista" + ], + "title": "EstadisticaDato" + }, + "Expresion": { + "properties": { + "expr": { + "type": "string", + "title": "Expr" + } + }, + "type": "object", + "required": [ + "expr" + ], + "title": "Expresion" + }, + "HTTPValidationError": { + "properties": { + "detail": { + "items": { + "$ref": "#/components/schemas/ValidationError" + }, + "type": "array", + "title": "Detail" + } + }, + "type": "object", + "title": "HTTPValidationError" + }, + "IMCDatos": { + "properties": { + "peso": { + "type": "number", + "exclusiveMinimum": 0.0, + "title": "Peso" + }, + "altura": { + "type": "number", + "exclusiveMinimum": 0.0, + "title": "Altura" + }, + "peso_um": { + "type": "string", + "enum": [ + "kg", + "lb" + ], + "title": "Peso Um" + }, + "altura_um": { + "type": "string", + "enum": [ + "cm", + "m", + "ft", + "in" + ], + "title": "Altura Um" + } + }, + "type": "object", + "required": [ + "peso", + "altura", + "peso_um", + "altura_um" + ], + "title": "IMCDatos" + }, + "IMCRespuesta": { + "properties": { + "imc": { + "type": "number", + "title": "Imc" + }, + "info": { + "type": "string", + "enum": [ + "Bajo Peso Severo", + "Bajo Peso", + "Peso Normal", + "Sobrepeso", + "Obesidad Moderada", + "Obesidad Severa", + "Obesidad Mórbida", + "Obesidad Mortal" + ], + "title": "Info" + } + }, + "type": "object", + "required": [ + "imc", + "info" + ], + "title": "IMCRespuesta" + }, + "ModaResultado": { + "properties": { + "resultado": { + "items": { + "type": "number" + }, + "type": "array", + "title": "Resultado" + } + }, + "type": "object", + "required": [ + "resultado" + ], + "title": "ModaResultado" + }, + "ResultadoFloat": { + "properties": { + "resultado": { + "type": "number", + "title": "Resultado" + } + }, + "type": "object", + "required": [ + "resultado" + ], + "title": "ResultadoFloat" + }, + "ResultadoStr": { + "properties": { + "resultado": { + "type": "string", + "title": "Resultado" + } + }, + "type": "object", + "required": [ + "resultado" + ], + "title": "ResultadoStr" + }, + "ValidationError": { + "properties": { + "loc": { + "items": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "integer" + } + ] + }, + "type": "array", + "title": "Location" + }, + "msg": { + "type": "string", + "title": "Message" + }, + "type": { + "type": "string", + "title": "Error Type" + } + }, + "type": "object", + "required": [ + "loc", + "msg", + "type" + ], + "title": "ValidationError" + } + } + } +} diff --git a/tests/resources/examples/v3.0/api-with-examples.json b/tests/resources/examples/v3.0/api-with-examples.json new file mode 100644 index 0000000..0fd2077 --- /dev/null +++ b/tests/resources/examples/v3.0/api-with-examples.json @@ -0,0 +1,167 @@ +{ + "openapi": "3.0.0", + "info": { + "title": "Simple API overview", + "version": "2.0.0" + }, + "paths": { + "/": { + "get": { + "operationId": "listVersionsv2", + "summary": "List API versions", + "responses": { + "200": { + "description": "200 response", + "content": { + "application/json": { + "examples": { + "foo": { + "value": { + "versions": [ + { + "status": "CURRENT", + "updated": "2011-01-21T11:33:21Z", + "id": "v2.0", + "links": [ + { + "href": "http://127.0.0.1:8774/v2/", + "rel": "self" + } + ] + }, + { + "status": "EXPERIMENTAL", + "updated": "2013-07-23T11:33:21Z", + "id": "v3.0", + "links": [ + { + "href": "http://127.0.0.1:8774/v3/", + "rel": "self" + } + ] + } + ] + } + } + } + } + } + }, + "300": { + "description": "300 response", + "content": { + "application/json": { + "examples": { + "foo": { + "value": "{\n \"versions\": [\n {\n \"status\": \"CURRENT\",\n \"updated\": \"2011-01-21T11:33:21Z\",\n \"id\": \"v2.0\",\n \"links\": [\n {\n \"href\": \"http://127.0.0.1:8774/v2/\",\n \"rel\": \"self\"\n }\n ]\n },\n {\n \"status\": \"EXPERIMENTAL\",\n \"updated\": \"2013-07-23T11:33:21Z\",\n \"id\": \"v3.0\",\n \"links\": [\n {\n \"href\": \"http://127.0.0.1:8774/v3/\",\n \"rel\": \"self\"\n }\n ]\n }\n ]\n}\n" + } + } + } + } + } + } + } + }, + "/v2": { + "get": { + "operationId": "getVersionDetailsv2", + "summary": "Show API version details", + "responses": { + "200": { + "description": "200 response", + "content": { + "application/json": { + "examples": { + "foo": { + "value": { + "version": { + "status": "CURRENT", + "updated": "2011-01-21T11:33:21Z", + "media-types": [ + { + "base": "application/xml", + "type": "application/vnd.openstack.compute+xml;version=2" + }, + { + "base": "application/json", + "type": "application/vnd.openstack.compute+json;version=2" + } + ], + "id": "v2.0", + "links": [ + { + "href": "http://127.0.0.1:8774/v2/", + "rel": "self" + }, + { + "href": "http://docs.openstack.org/api/openstack-compute/2/os-compute-devguide-2.pdf", + "type": "application/pdf", + "rel": "describedby" + }, + { + "href": "http://docs.openstack.org/api/openstack-compute/2/wadl/os-compute-2.wadl", + "type": "application/vnd.sun.wadl+xml", + "rel": "describedby" + }, + { + "href": "http://docs.openstack.org/api/openstack-compute/2/wadl/os-compute-2.wadl", + "type": "application/vnd.sun.wadl+xml", + "rel": "describedby" + } + ] + } + } + } + } + } + } + }, + "203": { + "description": "203 response", + "content": { + "application/json": { + "examples": { + "foo": { + "value": { + "version": { + "status": "CURRENT", + "updated": "2011-01-21T11:33:21Z", + "media-types": [ + { + "base": "application/xml", + "type": "application/vnd.openstack.compute+xml;version=2" + }, + { + "base": "application/json", + "type": "application/vnd.openstack.compute+json;version=2" + } + ], + "id": "v2.0", + "links": [ + { + "href": "http://23.253.228.211:8774/v2/", + "rel": "self" + }, + { + "href": "http://docs.openstack.org/api/openstack-compute/2/os-compute-devguide-2.pdf", + "type": "application/pdf", + "rel": "describedby" + }, + { + "href": "http://docs.openstack.org/api/openstack-compute/2/wadl/os-compute-2.wadl", + "type": "application/vnd.sun.wadl+xml", + "rel": "describedby" + } + ] + } + } + } + } + } + } + } + } + } + } + } +} \ No newline at end of file diff --git a/tests/resources/examples/v3.0/callback-example.json b/tests/resources/examples/v3.0/callback-example.json new file mode 100644 index 0000000..9a4df39 --- /dev/null +++ b/tests/resources/examples/v3.0/callback-example.json @@ -0,0 +1,84 @@ +{ + "openapi": "3.0.0", + "info": { + "title": "Callback Example", + "version": "1.0.0" + }, + "paths": { + "/streams": { + "post": { + "description": "subscribes a client to receive out-of-band data", + "parameters": [ + { + "name": "callbackUrl", + "in": "query", + "required": true, + "description": "the location where data will be sent. Must be network accessible\nby the source server\n", + "schema": { + "type": "string", + "format": "uri", + "example": "https://tonys-server.com" + } + } + ], + "responses": { + "201": { + "description": "subscription successfully created", + "content": { + "application/json": { + "schema": { + "description": "subscription information", + "required": [ + "subscriptionId" + ], + "properties": { + "subscriptionId": { + "description": "this unique identifier allows management of the subscription", + "type": "string", + "example": "2531329f-fb09-4ef7-887e-84e648214436" + } + } + } + } + } + } + }, + "callbacks": { + "onData": { + "{$request.query.callbackUrl}/data": { + "post": { + "requestBody": { + "description": "subscription payload", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "timestamp": { + "type": "string", + "format": "date-time" + }, + "userData": { + "type": "string" + } + } + } + } + } + }, + "responses": { + "202": { + "description": "Your server implementation should return this HTTP status code\nif the data was received successfully\n" + }, + "204": { + "description": "Your server should return this HTTP status code if no longer interested\nin further updates\n" + } + } + } + } + } + } + } + } + } +} \ No newline at end of file diff --git a/tests/resources/examples/v3.0/link-example.json b/tests/resources/examples/v3.0/link-example.json new file mode 100644 index 0000000..bc98e63 --- /dev/null +++ b/tests/resources/examples/v3.0/link-example.json @@ -0,0 +1,323 @@ +{ + "openapi": "3.0.0", + "info": { + "title": "Link Example", + "version": "1.0.0" + }, + "paths": { + "/2.0/users/{username}": { + "get": { + "operationId": "getUserByName", + "parameters": [ + { + "name": "username", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "The User", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/user" + } + } + }, + "links": { + "userRepositories": { + "$ref": "#/components/links/UserRepositories" + } + } + } + } + } + }, + "/2.0/repositories/{username}": { + "get": { + "operationId": "getRepositoriesByOwner", + "parameters": [ + { + "name": "username", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "repositories owned by the supplied user", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/repository" + } + } + } + }, + "links": { + "userRepository": { + "$ref": "#/components/links/UserRepository" + } + } + } + } + } + }, + "/2.0/repositories/{username}/{slug}": { + "get": { + "operationId": "getRepository", + "parameters": [ + { + "name": "username", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "slug", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "The repository", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/repository" + } + } + }, + "links": { + "repositoryPullRequests": { + "$ref": "#/components/links/RepositoryPullRequests" + } + } + } + } + } + }, + "/2.0/repositories/{username}/{slug}/pullrequests": { + "get": { + "operationId": "getPullRequestsByRepository", + "parameters": [ + { + "name": "username", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "slug", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "state", + "in": "query", + "schema": { + "type": "string", + "enum": [ + "open", + "merged", + "declined" + ] + } + } + ], + "responses": { + "200": { + "description": "an array of pull request objects", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/pullrequest" + } + } + } + } + } + } + } + }, + "/2.0/repositories/{username}/{slug}/pullrequests/{pid}": { + "get": { + "operationId": "getPullRequestsById", + "parameters": [ + { + "name": "username", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "slug", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "pid", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "a pull request object", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/pullrequest" + } + } + }, + "links": { + "pullRequestMerge": { + "$ref": "#/components/links/PullRequestMerge" + } + } + } + } + } + }, + "/2.0/repositories/{username}/{slug}/pullrequests/{pid}/merge": { + "post": { + "operationId": "mergePullRequest", + "parameters": [ + { + "name": "username", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "slug", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "pid", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "204": { + "description": "the PR was successfully merged" + } + } + } + } + }, + "components": { + "links": { + "UserRepositories": { + "operationId": "getRepositoriesByOwner", + "parameters": { + "username": "$response.body#/username" + } + }, + "UserRepository": { + "operationId": "getRepository", + "parameters": { + "username": "$response.body#/owner/username", + "slug": "$response.body#/slug" + } + }, + "RepositoryPullRequests": { + "operationId": "getPullRequestsByRepository", + "parameters": { + "username": "$response.body#/owner/username", + "slug": "$response.body#/slug" + } + }, + "PullRequestMerge": { + "operationId": "mergePullRequest", + "parameters": { + "username": "$response.body#/author/username", + "slug": "$response.body#/repository/slug", + "pid": "$response.body#/id" + } + } + }, + "schemas": { + "user": { + "type": "object", + "properties": { + "username": { + "type": "string" + }, + "uuid": { + "type": "string" + } + } + }, + "repository": { + "type": "object", + "properties": { + "slug": { + "type": "string" + }, + "owner": { + "$ref": "#/components/schemas/user" + } + } + }, + "pullrequest": { + "type": "object", + "properties": { + "id": { + "type": "integer" + }, + "title": { + "type": "string" + }, + "repository": { + "$ref": "#/components/schemas/repository" + }, + "author": { + "$ref": "#/components/schemas/user" + } + } + } + } + } +} \ No newline at end of file diff --git a/tests/resources/examples/v3.0/petstore-expanded.json b/tests/resources/examples/v3.0/petstore-expanded.json new file mode 100644 index 0000000..420eede --- /dev/null +++ b/tests/resources/examples/v3.0/petstore-expanded.json @@ -0,0 +1,242 @@ +{ + "openapi": "3.0.0", + "info": { + "version": "1.0.0", + "title": "Swagger Petstore", + "description": "A sample API that uses a petstore as an example to demonstrate features in the OpenAPI 3.0 specification", + "termsOfService": "http://swagger.io/terms/", + "contact": { + "name": "Swagger API Team", + "email": "apiteam@swagger.io", + "url": "http://swagger.io" + }, + "license": { + "name": "Apache 2.0", + "url": "https://www.apache.org/licenses/LICENSE-2.0.html" + } + }, + "servers": [ + { + "url": "https://petstore.swagger.io/v2" + } + ], + "paths": { + "/pets": { + "get": { + "description": "Returns all pets from the system that the user has access to\nNam sed condimentum est. Maecenas tempor sagittis sapien, nec rhoncus sem sagittis sit amet. Aenean at gravida augue, ac iaculis sem. Curabitur odio lorem, ornare eget elementum nec, cursus id lectus. Duis mi turpis, pulvinar ac eros ac, tincidunt varius justo. In hac habitasse platea dictumst. Integer at adipiscing ante, a sagittis ligula. Aenean pharetra tempor ante molestie imperdiet. Vivamus id aliquam diam. Cras quis velit non tortor eleifend sagittis. Praesent at enim pharetra urna volutpat venenatis eget eget mauris. In eleifend fermentum facilisis. Praesent enim enim, gravida ac sodales sed, placerat id erat. Suspendisse lacus dolor, consectetur non augue vel, vehicula interdum libero. Morbi euismod sagittis libero sed lacinia.\n\nSed tempus felis lobortis leo pulvinar rutrum. Nam mattis velit nisl, eu condimentum ligula luctus nec. Phasellus semper velit eget aliquet faucibus. In a mattis elit. Phasellus vel urna viverra, condimentum lorem id, rhoncus nibh. Ut pellentesque posuere elementum. Sed a varius odio. Morbi rhoncus ligula libero, vel eleifend nunc tristique vitae. Fusce et sem dui. Aenean nec scelerisque tortor. Fusce malesuada accumsan magna vel tempus. Quisque mollis felis eu dolor tristique, sit amet auctor felis gravida. Sed libero lorem, molestie sed nisl in, accumsan tempor nisi. Fusce sollicitudin massa ut lacinia mattis. Sed vel eleifend lorem. Pellentesque vitae felis pretium, pulvinar elit eu, euismod sapien.\n", + "operationId": "findPets", + "parameters": [ + { + "name": "tags", + "in": "query", + "description": "tags to filter by", + "required": false, + "style": "form", + "schema": { + "type": "array", + "items": { + "type": "string" + } + } + }, + { + "name": "limit", + "in": "query", + "description": "maximum number of results to return", + "required": false, + "schema": { + "type": "integer", + "format": "int32" + } + } + ], + "responses": { + "200": { + "description": "pet response", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Pet" + } + } + } + } + }, + "default": { + "description": "unexpected error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + } + } + }, + "post": { + "description": "Creates a new pet in the store. Duplicates are allowed", + "operationId": "addPet", + "requestBody": { + "description": "Pet to add to the store", + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/NewPet" + } + } + } + }, + "responses": { + "200": { + "description": "pet response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Pet" + } + } + } + }, + "default": { + "description": "unexpected error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + } + } + } + }, + "/pets/{id}": { + "get": { + "description": "Returns a user based on a single ID, if the user does not have access to the pet", + "operationId": "find pet by id", + "parameters": [ + { + "name": "id", + "in": "path", + "description": "ID of pet to fetch", + "required": true, + "schema": { + "type": "integer", + "format": "int64" + } + } + ], + "responses": { + "200": { + "description": "pet response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Pet" + } + } + } + }, + "default": { + "description": "unexpected error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + } + } + }, + "delete": { + "description": "deletes a single pet based on the ID supplied", + "operationId": "deletePet", + "parameters": [ + { + "name": "id", + "in": "path", + "description": "ID of pet to delete", + "required": true, + "schema": { + "type": "integer", + "format": "int64" + } + } + ], + "responses": { + "204": { + "description": "pet deleted" + }, + "default": { + "description": "unexpected error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + } + } + } + } + }, + "components": { + "schemas": { + "Pet": { + "allOf": [ + { + "$ref": "#/components/schemas/NewPet" + }, + { + "type": "object", + "required": [ + "id" + ], + "properties": { + "id": { + "type": "integer", + "format": "int64" + } + } + } + ] + }, + "NewPet": { + "type": "object", + "required": [ + "name" + ], + "properties": { + "name": { + "type": "string" + }, + "tag": { + "type": "string" + } + } + }, + "Error": { + "type": "object", + "required": [ + "code", + "message" + ], + "properties": { + "code": { + "type": "integer", + "format": "int32" + }, + "message": { + "type": "string" + } + } + } + } + } +} \ No newline at end of file diff --git a/tests/resources/examples/v3.0/petstore.json b/tests/resources/examples/v3.0/petstore.json new file mode 100644 index 0000000..d14eb5e --- /dev/null +++ b/tests/resources/examples/v3.0/petstore.json @@ -0,0 +1,189 @@ +{ + "openapi": "3.0.0", + "info": { + "version": "1.0.0", + "title": "Swagger Petstore", + "license": { + "name": "MIT" + } + }, + "servers": [ + { + "url": "http://petstore.swagger.io/v1" + } + ], + "paths": { + "/pets": { + "get": { + "summary": "List all pets", + "operationId": "listPets", + "tags": [ + "pets" + ], + "parameters": [ + { + "name": "limit", + "in": "query", + "description": "How many items to return at one time (max 100)", + "required": false, + "schema": { + "type": "integer", + "maximum": 100, + "format": "int32" + } + } + ], + "responses": { + "200": { + "description": "A paged array of pets", + "headers": { + "x-next": { + "description": "A link to the next page of responses", + "schema": { + "type": "string" + } + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Pets" + } + } + } + }, + "default": { + "description": "unexpected error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + } + } + }, + "post": { + "summary": "Create a pet", + "operationId": "createPets", + "tags": [ + "pets" + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Pet" + } + } + }, + "required": true + }, + "responses": { + "201": { + "description": "Null response" + }, + "default": { + "description": "unexpected error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + } + } + } + }, + "/pets/{petId}": { + "get": { + "summary": "Info for a specific pet", + "operationId": "showPetById", + "tags": [ + "pets" + ], + "parameters": [ + { + "name": "petId", + "in": "path", + "required": true, + "description": "The id of the pet to retrieve", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Expected response to a valid request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Pet" + } + } + } + }, + "default": { + "description": "unexpected error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + } + } + } + } + }, + "components": { + "schemas": { + "Pet": { + "type": "object", + "required": [ + "id", + "name" + ], + "properties": { + "id": { + "type": "integer", + "format": "int64" + }, + "name": { + "type": "string" + }, + "tag": { + "type": "string" + } + } + }, + "Pets": { + "type": "array", + "maxItems": 100, + "items": { + "$ref": "#/components/schemas/Pet" + } + }, + "Error": { + "type": "object", + "required": [ + "code", + "message" + ], + "properties": { + "code": { + "type": "integer", + "format": "int32" + }, + "message": { + "type": "string" + } + } + } + } + } +} \ No newline at end of file diff --git a/tests/resources/examples/v3.0/uspto.json b/tests/resources/examples/v3.0/uspto.json new file mode 100644 index 0000000..cd32f64 --- /dev/null +++ b/tests/resources/examples/v3.0/uspto.json @@ -0,0 +1,252 @@ +{ + "openapi": "3.0.1", + "servers": [ + { + "url": "{scheme}://developer.uspto.gov/ds-api", + "variables": { + "scheme": { + "description": "The Data Set API is accessible via https and http", + "enum": [ + "https", + "http" + ], + "default": "https" + } + } + } + ], + "info": { + "description": "The Data Set API (DSAPI) allows the public users to discover and search USPTO exported data sets. This is a generic API that allows USPTO users to make any CSV based data files searchable through API. With the help of GET call, it returns the list of data fields that are searchable. With the help of POST call, data can be fetched based on the filters on the field names. Please note that POST call is used to search the actual data. The reason for the POST call is that it allows users to specify any complex search criteria without worry about the GET size limitations as well as encoding of the input parameters.", + "version": "1.0.0", + "title": "USPTO Data Set API", + "contact": { + "name": "Open Data Portal", + "url": "https://developer.uspto.gov", + "email": "developer@uspto.gov" + } + }, + "tags": [ + { + "name": "metadata", + "description": "Find out about the data sets" + }, + { + "name": "search", + "description": "Search a data set" + } + ], + "paths": { + "/": { + "get": { + "tags": [ + "metadata" + ], + "operationId": "list-data-sets", + "summary": "List available data sets", + "responses": { + "200": { + "description": "Returns a list of data sets", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/dataSetList" + }, + "example": { + "total": 2, + "apis": [ + { + "apiKey": "oa_citations", + "apiVersionNumber": "v1", + "apiUrl": "https://developer.uspto.gov/ds-api/oa_citations/v1/fields", + "apiDocumentationUrl": "https://developer.uspto.gov/ds-api-docs/index.html?url=https://developer.uspto.gov/ds-api/swagger/docs/oa_citations.json" + }, + { + "apiKey": "cancer_moonshot", + "apiVersionNumber": "v1", + "apiUrl": "https://developer.uspto.gov/ds-api/cancer_moonshot/v1/fields", + "apiDocumentationUrl": "https://developer.uspto.gov/ds-api-docs/index.html?url=https://developer.uspto.gov/ds-api/swagger/docs/cancer_moonshot.json" + } + ] + } + } + } + } + } + } + }, + "/{dataset}/{version}/fields": { + "get": { + "tags": [ + "metadata" + ], + "summary": "Provides the general information about the API and the list of fields that can be used to query the dataset.", + "description": "This GET API returns the list of all the searchable field names that are in the oa_citations. Please see the 'fields' attribute which returns an array of field names. Each field or a combination of fields can be searched using the syntax options shown below.", + "operationId": "list-searchable-fields", + "parameters": [ + { + "name": "dataset", + "in": "path", + "description": "Name of the dataset.", + "required": true, + "example": "oa_citations", + "schema": { + "type": "string" + } + }, + { + "name": "version", + "in": "path", + "description": "Version of the dataset.", + "required": true, + "example": "v1", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "The dataset API for the given version is found and it is accessible to consume.", + "content": { + "application/json": { + "schema": { + "type": "string" + } + } + } + }, + "404": { + "description": "The combination of dataset name and version is not found in the system or it is not published yet to be consumed by public.", + "content": { + "application/json": { + "schema": { + "type": "string" + } + } + } + } + } + } + }, + "/{dataset}/{version}/records": { + "post": { + "tags": [ + "search" + ], + "summary": "Provides search capability for the data set with the given search criteria.", + "description": "This API is based on Solr/Lucene Search. The data is indexed using SOLR. This GET API returns the list of all the searchable field names that are in the Solr Index. Please see the 'fields' attribute which returns an array of field names. Each field or a combination of fields can be searched using the Solr/Lucene Syntax. Please refer https://lucene.apache.org/core/3_6_2/queryparsersyntax.html#Overview for the query syntax. List of field names that are searchable can be determined using above GET api.", + "operationId": "perform-search", + "parameters": [ + { + "name": "version", + "in": "path", + "description": "Version of the dataset.", + "required": true, + "schema": { + "type": "string", + "default": "v1" + } + }, + { + "name": "dataset", + "in": "path", + "description": "Name of the dataset. In this case, the default value is oa_citations", + "required": true, + "schema": { + "type": "string", + "default": "oa_citations" + } + } + ], + "responses": { + "200": { + "description": "successful operation", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "type": "object", + "additionalProperties": { + "type": "object" + } + } + } + } + } + }, + "404": { + "description": "No matching record found for the given criteria." + } + }, + "requestBody": { + "content": { + "application/x-www-form-urlencoded": { + "schema": { + "type": "object", + "properties": { + "criteria": { + "description": "Uses Lucene Query Syntax in the format of propertyName:value, propertyName:[num1 TO num2] and date range format: propertyName:[yyyyMMdd TO yyyyMMdd]. In the response please see the 'docs' element which has the list of record objects. Each record structure would consist of all the fields and their corresponding values.", + "type": "string", + "default": "*:*" + }, + "start": { + "description": "Starting record number. Default value is 0.", + "type": "integer", + "default": 0 + }, + "rows": { + "description": "Specify number of rows to be returned. If you run the search with default values, in the response you will see 'numFound' attribute which will tell the number of records available in the dataset.", + "type": "integer", + "default": 100 + } + }, + "required": [ + "criteria" + ] + } + } + } + } + } + } + }, + "components": { + "schemas": { + "dataSetList": { + "type": "object", + "properties": { + "total": { + "type": "integer" + }, + "apis": { + "type": "array", + "items": { + "type": "object", + "properties": { + "apiKey": { + "type": "string", + "description": "To be used as a dataset parameter value" + }, + "apiVersionNumber": { + "type": "string", + "description": "To be used as a version parameter value" + }, + "apiUrl": { + "type": "string", + "format": "uriref", + "description": "The URL describing the dataset's fields" + }, + "apiDocumentationUrl": { + "type": "string", + "format": "uriref", + "description": "A URL to the API console for each API" + } + } + } + } + } + } + } + } +} \ No newline at end of file diff --git a/tests/resources/examples/v3.1/non-oauth-scopes.json b/tests/resources/examples/v3.1/non-oauth-scopes.json new file mode 100644 index 0000000..b3e5990 --- /dev/null +++ b/tests/resources/examples/v3.1/non-oauth-scopes.json @@ -0,0 +1,31 @@ +{ + "openapi": "3.1.0", + "info": { + "title": "Non-oAuth Scopes example", + "version": "1.0.0" + }, + "paths": { + "/users": { + "get": { + "security": [ + { + "bearerAuth": [ + "read:users", + "public" + ] + } + ] + } + } + }, + "components": { + "securitySchemes": { + "bearerAuth": { + "type": "http", + "scheme": "bearer", + "bearerFormat": "jwt", + "description": "note: non-oauth scopes are not defined at the securityScheme level" + } + } + } +} \ No newline at end of file diff --git a/tests/resources/examples/v3.1/webhook-example.json b/tests/resources/examples/v3.1/webhook-example.json new file mode 100644 index 0000000..712a42f --- /dev/null +++ b/tests/resources/examples/v3.1/webhook-example.json @@ -0,0 +1,50 @@ +{ + "openapi": "3.1.0", + "info": { + "title": "Webhook Example", + "version": "1.0.0" + }, + "webhooks": { + "newPet": { + "post": { + "requestBody": { + "description": "Information about a new pet in the system", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Pet" + } + } + } + }, + "responses": { + "200": { + "description": "Return a 200 status to indicate that the data was received successfully" + } + } + } + } + }, + "components": { + "schemas": { + "Pet": { + "required": [ + "id", + "name" + ], + "properties": { + "id": { + "type": "integer", + "format": "int64" + }, + "name": { + "type": "string" + }, + "tag": { + "type": "string" + } + } + } + } + } +} \ No newline at end of file diff --git a/tests/resources/satellite-passes_openapi.json b/tests/resources/satellite-passes_openapi.json new file mode 100644 index 0000000..dfeaf62 --- /dev/null +++ b/tests/resources/satellite-passes_openapi.json @@ -0,0 +1,234 @@ +{ + "openapi": "3.0.3", + "info": { + "title": "Satellite Passes API", + "description": "Query next passes for a given satellite above you.", + "version": "0.1.0" + }, + "servers": [ + {"url": "https://sat.terrestre.ar/"}, + {"url": "http://0.0.0.0:8000/"} + ], + "paths": { + "/passes/{norad_id}": { + "get": { + "tags": ["Satellite Passes"], + "parameters": [ + { + "name": "norad_id", + "in": "path", + "required": true, + "schema": { + "type": "integer", + "minimum": 1, + "maximum": 999999999 + } + }, + { + "name": "lat", + "in": "query", + "required": true, + "schema": { + "type": "number", + "minimum": -90.0, + "maximum": 90.0 + } + }, + { + "name": "lon", + "in": "query", + "required": true, + "schema": { + "type": "number", + "minimum": -180.0, + "maximum": 180.0 + } + }, + { + "name": "limit", + "in": "query", + "required": false, + "schema": { + "type": "integer", + "minimum": 1, + "maximum": 100, + "default": 100 + } + }, + { + "name": "days", + "in": "query", + "required": false, + "schema": { + "type": "integer", + "minimum": 1, + "maximum": 15, + "default": 7 + } + }, + { + "name": "visible_only", + "in": "query", + "required": false, + "schema": { + "type": "boolean", + "default": false + } + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/SatellitePass" + } + } + } + } + }, + "400": { + "description": "Invalid request", + "content": { + "application/json": { + "schema": { + "anyOf": [ + { + "$ref": "#/components/schemas/ValidationError" + }, + { + "type": "object", + "properties": { + "error": { + "type": "string" + } + }, + "required": [ + "error" + ] + } + ] + } + } + } + }, + "default": { + "description": "Unexpected error" + } + } + } + } + }, + "components": { + "schemas": { + "SatellitePass": { + "type": "object", + "properties": { + "rise": { + "$ref": "#/components/schemas/Event" + }, + "culmination": { + "$ref": "#/components/schemas/Event" + }, + "set": { + "$ref": "#/components/schemas/Event" + }, + "visible": { + "type": "boolean" + } + }, + "required": [ + "visible" + ] + }, + "Event": { + "type": "object", + "title": "Event", + "required": [ + "alt", + "az", + "az_octant", + "utc_datetime", + "utc_timestamp", + "is_sunlit", + "visible" + ], + "properties": { + "alt": { + "type": "string", + "title": "Altitude", + "description": "Altitude above the horizon. In degrees." + }, + "az": { + "type": "string", + "title": "Azimuth", + "description": "The angle between the satellite and the North. In degrees." + }, + "az_octant": { + "type": "string", + "title": "Azimuth octant", + "enum": [ + "N", + "NE", + "E", + "SE", + "S", + "SW", + "W", + "NW" + ] + }, + "utc_datetime": { + "type": "string", + "title": "UTC date-time", + "format": "date-time" + }, + "utc_timestamp": { + "type": "integer", + "title": "UTC timestamp", + "minimum": 0 + }, + "is_sunlit": { + "type": "boolean", + "title": "Is sunlit", + "description": "If satellite is being illuminated by the sun." + }, + "visible": { + "type": "boolean", + "title": "Visible", + "description": "If the satellite will be probably visible, considering the sun is near the horizon, and the observer is at night." + } + } + }, + "ValidationError": { + "type": "object", + "properties": { + "lat": { + "$ref": "#/components/schemas/FieldError" + }, + "lon": { + "$ref": "#/components/schemas/FieldError" + }, + "limit": { + "$ref": "#/components/schemas/FieldError" + }, + "days": { + "$ref": "#/components/schemas/FieldError" + }, + "visible_only": { + "$ref": "#/components/schemas/FieldError" + } + } + }, + "FieldError": { + "type": "array", + "items": { + "type": "string" + } + } + } + } +}