From 0b2d6e1079a8d93fbe2ebaec82901dc86905445c Mon Sep 17 00:00:00 2001 From: Scott Huang Date: Thu, 24 Oct 2024 17:52:21 +0800 Subject: [PATCH 01/19] add OpenAPI crawler --- metaphor/common/event_util.py | 8 +- metaphor/openapi/__init__.py | 6 ++ metaphor/openapi/config.py | 27 +++++++ metaphor/openapi/extractor.py | 138 ++++++++++++++++++++++++++++++++++ poetry.lock | 24 +++--- pyproject.toml | 2 +- 6 files changed, 190 insertions(+), 15 deletions(-) create mode 100644 metaphor/openapi/__init__.py create mode 100644 metaphor/openapi/config.py create mode 100644 metaphor/openapi/extractor.py diff --git a/metaphor/common/event_util.py b/metaphor/common/event_util.py index db847b3e..6a9a7169 100644 --- a/metaphor/common/event_util.py +++ b/metaphor/common/event_util.py @@ -8,6 +8,7 @@ from metaphor import models # type: ignore from metaphor.models.metadata_change_event import ( + API, Dashboard, Dataset, ExternalSearchDocument, @@ -26,6 +27,7 @@ logger.setLevel(logging.INFO) ENTITY_TYPES = Union[ + API, Dashboard, Dataset, ExternalSearchDocument, @@ -57,9 +59,11 @@ def _build_event(**kwargs) -> MetadataChangeEvent: return MetadataChangeEvent(**kwargs) @staticmethod - def build_event(entity: ENTITY_TYPES): + def build_event(entity: ENTITY_TYPES): # noqa: C901 """Build MCE given an entity""" - if type(entity) is Dashboard: + if type(entity) is API: + return EventUtil._build_event(api=entity) + elif type(entity) is Dashboard: return EventUtil._build_event(dashboard=entity) elif type(entity) is Dataset: return EventUtil._build_event(dataset=entity) diff --git a/metaphor/openapi/__init__.py b/metaphor/openapi/__init__.py new file mode 100644 index 00000000..7493fec7 --- /dev/null +++ b/metaphor/openapi/__init__.py @@ -0,0 +1,6 @@ +from metaphor.common.cli import cli_main +from metaphor.openapi.extractor import OpenAPIExtractor + + +def main(config_file: str): + cli_main(OpenAPIExtractor, config_file) diff --git a/metaphor/openapi/config.py b/metaphor/openapi/config.py new file mode 100644 index 00000000..5afeb111 --- /dev/null +++ b/metaphor/openapi/config.py @@ -0,0 +1,27 @@ +from typing import Optional + +from pydantic.dataclasses import dataclass + +from metaphor.common.base_config import BaseConfig +from metaphor.common.dataclass import ConnectorConfig + + +@dataclass(config=ConnectorConfig) +class BasicAuth: + user: str + password: str + + +@dataclass(config=ConnectorConfig) +class OpenAPIAuthConfig: + basic_auth: Optional[BasicAuth] = None + + +@dataclass(config=ConnectorConfig) +class OpenAPIRunConfig(BaseConfig): + base_url: str + openapi_json_path: str + + api_name: Optional[str] = None + + auth: Optional[OpenAPIAuthConfig] = None diff --git a/metaphor/openapi/extractor.py b/metaphor/openapi/extractor.py new file mode 100644 index 00000000..63d38b42 --- /dev/null +++ b/metaphor/openapi/extractor.py @@ -0,0 +1,138 @@ +import json +from typing import Collection, List, Optional +from urllib.parse import urljoin + +import requests + +from metaphor.common.base_extractor import BaseExtractor +from metaphor.common.event_util import ENTITY_TYPES +from metaphor.common.logger import get_logger +from metaphor.models.crawler_run_metadata import Platform +from metaphor.models.metadata_change_event import ( + API, + APILogicalID, + APIPlatform, + AssetPlatform, + AssetStructure, + Hierarchy, + HierarchyInfo, + HierarchyLogicalID, + HierarchyType, + OpenAPI, + OpenAPIMethod, + OpenAPISpecification, + OperationType, +) +from metaphor.openapi.config import OpenAPIRunConfig + +logger = get_logger() + + +class OpenAPIExtractor(BaseExtractor): + """OpenAPI metadata extractor""" + + _description = "OpenAPI metadata crawler" + _platform = Platform.OPEN_API + + @staticmethod + def from_config_file(config_file: str) -> "OpenAPIExtractor": + return OpenAPIExtractor(OpenAPIRunConfig.from_yaml_file(config_file)) + + def __init__(self, config: OpenAPIRunConfig): + super().__init__(config) + + self._base_url = config.base_url + self._api_name = config.api_name or config.base_url + self._openapi_json_path = config.openapi_json_path + self._auth = config.auth + self._requests_session = requests.sessions.Session() + + async def extract(self) -> Collection[ENTITY_TYPES]: + logger.info( + f"Fetching metadata from {self._base_url + self._openapi_json_path}" + ) + + self._initial_session() + openapi_json = self._get_openapi_json() + endpoints = self._extract_paths(openapi_json) + hierarchies = self._build_hierarchies(openapi_json) + + return hierarchies + endpoints + + def _initial_session(self): + if not self._auth: + return + + if self._auth.basic_auth: + basic_auth = self._auth.basic_auth + self._requests_session.auth = (basic_auth.user, basic_auth.password) + + def _get_openapi_json(self) -> dict: + url = self._base_url + self._openapi_json_path + return self._requests_session.get(url).json() + + def _extract_paths(self, openapi: dict) -> List[API]: + endpoints: List[API] = [] + servers = openapi.get("servers") + + for path, path_item in openapi["paths"].items(): + path_servers = path_item.get("servers") + base_path = ( + path_servers[0]["url"] + if path_servers + else servers[0]["url"] if servers else "" + ) + + if not base_path.startswith("http"): + endpoint_url = urljoin(self._base_url, base_path + path) + else: + endpoint_url = urljoin(base_path, path) + + endpoint = API( + logical_id=APILogicalID( + name=endpoint_url, platform=APIPlatform.OPEN_API + ), + open_api=OpenAPI(path=path, methods=self._extract_methods(path_item)), + structure=AssetStructure(directories=[self._api_name]), + ) + endpoints.append(endpoint) + return endpoints + + def _extract_methods(self, path_item: dict) -> List[OpenAPIMethod]: + def to_operation_type(method: str) -> Optional[OperationType]: + try: + operation_type = OperationType(method.upper()) + return operation_type + except ValueError: + return None + + methods: List[OpenAPIMethod] = [] + for method, item in path_item.items(): + operation_type = to_operation_type(method) + + if not operation_type: + continue + + methods.append( + OpenAPIMethod( + summary=item.get("summary"), + description=item.get("description"), + type=operation_type, + ) + ) + return methods + + def _build_hierarchies(self, openapi: dict) -> List[Hierarchy]: + title = openapi["info"]["title"] + hierarchy = Hierarchy( + logical_id=HierarchyLogicalID( + path=[AssetPlatform.OPEN_API.value] + [self._api_name], + ), + hierarchy_info=HierarchyInfo( + name=title, + open_api=OpenAPISpecification(definition=json.dumps(openapi)), + type=HierarchyType.OPEN_API, + ), + ) + + return [hierarchy] diff --git a/poetry.lock b/poetry.lock index 647a1547..2fccbdce 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,4 +1,4 @@ -# This file is automatically @generated by Poetry 1.8.3 and should not be changed by hand. +# This file is automatically @generated by Poetry 1.8.2 and should not be changed by hand. [[package]] name = "aiohappyeyeballs" @@ -646,8 +646,8 @@ files = [ jmespath = ">=0.7.1,<2.0.0" python-dateutil = ">=2.1,<3.0.0" urllib3 = [ - {version = ">=1.25.4,<1.27", markers = "python_version < \"3.10\""}, {version = ">=1.25.4,<2.2.0 || >2.2.0,<3", markers = "python_version >= \"3.10\""}, + {version = ">=1.25.4,<1.27", markers = "python_version < \"3.10\""}, ] [package.extras] @@ -1170,8 +1170,8 @@ files = [ [package.dependencies] lz4 = ">=4.0.2,<5.0.0" numpy = [ - {version = ">=1.16.6,<2.0.0", markers = "python_version >= \"3.8\" and python_version < \"3.11\""}, {version = ">=1.23.4,<2.0.0", markers = "python_version >= \"3.11\""}, + {version = ">=1.16.6,<2.0.0", markers = "python_version >= \"3.8\" and python_version < \"3.11\""}, ] oauthlib = ">=3.1.0,<4.0.0" openpyxl = ">=3.0.10,<4.0.0" @@ -1221,8 +1221,8 @@ isort = ">=4.3.21,<6.0" jinja2 = ">=2.10.1,<4.0" packaging = "*" pydantic = [ - {version = ">=1.5.1,<2.4.0 || >2.4.0,<3.0", extras = ["email"], markers = "python_version < \"3.10\""}, {version = ">=1.10.0,<2.4.0 || >2.4.0,<3.0", extras = ["email"], markers = "python_version >= \"3.11\" and python_version < \"4.0\""}, + {version = ">=1.5.1,<2.4.0 || >2.4.0,<3.0", extras = ["email"], markers = "python_version < \"3.10\""}, {version = ">=1.9.0,<2.4.0 || >2.4.0,<3.0", extras = ["email"], markers = "python_version >= \"3.10\" and python_version < \"3.11\""}, ] pyyaml = ">=6.0.1" @@ -1691,12 +1691,12 @@ files = [ google-auth = ">=2.14.1,<3.0.dev0" googleapis-common-protos = ">=1.56.2,<2.0.dev0" grpcio = [ - {version = ">=1.33.2,<2.0dev", optional = true, markers = "python_version < \"3.11\" and extra == \"grpc\""}, {version = ">=1.49.1,<2.0dev", optional = true, markers = "python_version >= \"3.11\" and extra == \"grpc\""}, + {version = ">=1.33.2,<2.0dev", optional = true, markers = "python_version < \"3.11\" and extra == \"grpc\""}, ] grpcio-status = [ - {version = ">=1.33.2,<2.0.dev0", optional = true, markers = "python_version < \"3.11\" and extra == \"grpc\""}, {version = ">=1.49.1,<2.0.dev0", optional = true, markers = "python_version >= \"3.11\" and extra == \"grpc\""}, + {version = ">=1.33.2,<2.0.dev0", optional = true, markers = "python_version < \"3.11\" and extra == \"grpc\""}, ] proto-plus = ">=1.22.3,<2.0.0dev" protobuf = ">=3.19.5,<3.20.0 || >3.20.0,<3.20.1 || >3.20.1,<4.21.0 || >4.21.0,<4.21.1 || >4.21.1,<4.21.2 || >4.21.2,<4.21.3 || >4.21.3,<4.21.4 || >4.21.4,<4.21.5 || >4.21.5,<6.0.0.dev0" @@ -1832,8 +1832,8 @@ google-cloud-core = ">=2.0.0,<3.0.0dev" grpc-google-iam-v1 = ">=0.12.4,<1.0.0dev" opentelemetry-api = ">=1.9.0" proto-plus = [ - {version = ">=1.22.0,<2.0.0dev", markers = "python_version < \"3.11\""}, {version = ">=1.22.2,<2.0.0dev", markers = "python_version >= \"3.11\""}, + {version = ">=1.22.0,<2.0.0dev", markers = "python_version < \"3.11\""}, ] protobuf = ">=3.20.2,<4.21.0 || >4.21.0,<4.21.1 || >4.21.1,<4.21.2 || >4.21.2,<4.21.3 || >4.21.3,<4.21.4 || >4.21.4,<4.21.5 || >4.21.5,<6.0.0dev" @@ -3220,13 +3220,13 @@ files = [ [[package]] name = "metaphor-models" -version = "0.40.5" +version = "0.41.0" description = "" optional = false python-versions = "<4.0,>=3.8" files = [ - {file = "metaphor_models-0.40.5-py3-none-any.whl", hash = "sha256:44db27ac48f496c289acd74ee6289d4b2d21cc68172d385e3c286acabb02facd"}, - {file = "metaphor_models-0.40.5.tar.gz", hash = "sha256:9585249d67997c3ce40aa84ba3f138c4a1ef3b0620a74cd4d06d44b2af96931c"}, + {file = "metaphor_models-0.41.0-py3-none-any.whl", hash = "sha256:f58035b3fd903696c0e30ebbebe3f155a05339e3f309071fef8a71f4f0f74d18"}, + {file = "metaphor_models-0.41.0.tar.gz", hash = "sha256:7e864ac309a86413277010bc647e1749f7d16cd569db4ab55a6e78ee469ffed8"}, ] [[package]] @@ -3956,8 +3956,8 @@ files = [ [package.dependencies] numpy = [ - {version = ">=1.22.4", markers = "python_version < \"3.11\""}, {version = ">=1.23.2", markers = "python_version == \"3.11\""}, + {version = ">=1.22.4", markers = "python_version < \"3.11\""}, ] python-dateutil = ">=2.8.2" pytz = ">=2020.1" @@ -6859,4 +6859,4 @@ unity-catalog = ["databricks-sdk", "databricks-sql-connector", "sqlglot"] [metadata] lock-version = "2.0" python-versions = ">=3.9,<3.12" -content-hash = "e5217a628be159d01cea6cf9a7f8c9a8c5b521cfb57c7033e5a08b4b90f6a5a9" +content-hash = "12c06ebe537206b51268ea9dc1f9591561c4ebfa707978b6f547b19b2cd260f6" diff --git a/pyproject.toml b/pyproject.toml index 1638d7f0..aa5fb6ef 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -42,7 +42,7 @@ llama-index-readers-confluence = { version = "^0.1.4", optional = true } llama-index-readers-notion = { version = "^0.1.6", optional = true } looker-sdk = { version = "^24.2.0", optional = true } lxml = { version = "~=5.0.0", optional = true } -metaphor-models = "0.40.5" +metaphor-models = "0.41.0" more-itertools = { version = "^10.1.0", optional = true } msal = { version = "^1.28.0", optional = true } msgraph-beta-sdk = { version = "~1.4.0", optional = true } From 405c900f39d93ae64e1c6df6e976d03b94ef6053 Mon Sep 17 00:00:00 2001 From: Scott Huang Date: Fri, 25 Oct 2024 15:17:54 +0800 Subject: [PATCH 02/19] Change config --- metaphor/openapi/config.py | 6 ++--- metaphor/openapi/extractor.py | 43 ++++++++++++++++++++++++++--------- 2 files changed, 34 insertions(+), 15 deletions(-) diff --git a/metaphor/openapi/config.py b/metaphor/openapi/config.py index 5afeb111..cfdf0689 100644 --- a/metaphor/openapi/config.py +++ b/metaphor/openapi/config.py @@ -19,9 +19,7 @@ class OpenAPIAuthConfig: @dataclass(config=ConnectorConfig) class OpenAPIRunConfig(BaseConfig): - base_url: str - openapi_json_path: str - - api_name: Optional[str] = None + openapi_json_path: str # URL or file path + base_url: str # base_url of endpoints auth: Optional[OpenAPIAuthConfig] = None diff --git a/metaphor/openapi/extractor.py b/metaphor/openapi/extractor.py index 63d38b42..12c04906 100644 --- a/metaphor/openapi/extractor.py +++ b/metaphor/openapi/extractor.py @@ -1,4 +1,5 @@ import json +from collections import OrderedDict from typing import Collection, List, Optional from urllib.parse import urljoin @@ -7,6 +8,7 @@ from metaphor.common.base_extractor import BaseExtractor from metaphor.common.event_util import ENTITY_TYPES from metaphor.common.logger import get_logger +from metaphor.common.utils import md5_digest from metaphor.models.crawler_run_metadata import Platform from metaphor.models.metadata_change_event import ( API, @@ -42,18 +44,21 @@ def __init__(self, config: OpenAPIRunConfig): super().__init__(config) self._base_url = config.base_url - self._api_name = config.api_name or config.base_url + self._api_id = md5_digest(config.base_url.encode("utf-8")) self._openapi_json_path = config.openapi_json_path self._auth = config.auth self._requests_session = requests.sessions.Session() async def extract(self) -> Collection[ENTITY_TYPES]: - logger.info( - f"Fetching metadata from {self._base_url + self._openapi_json_path}" - ) + logger.info(f"Fetching metadata from {self._openapi_json_path}") self._initial_session() openapi_json = self._get_openapi_json() + + if not openapi_json: + logger.error("Unable to get OAS json") + return [] + endpoints = self._extract_paths(openapi_json) hierarchies = self._build_hierarchies(openapi_json) @@ -67,9 +72,25 @@ def _initial_session(self): basic_auth = self._auth.basic_auth self._requests_session.auth = (basic_auth.user, basic_auth.password) - def _get_openapi_json(self) -> dict: - url = self._base_url + self._openapi_json_path - return self._requests_session.get(url).json() + def _get_openapi_json(self) -> Optional[dict]: + if not self._openapi_json_path.startswith("http"): + with open(self._openapi_json_path, "r") as f: + return json.load(f) + + headers = OrderedDict( + { + "User-Agent": None, + "Accept": None, + "Connection": None, + "Accept-Encoding": None, + } + ) + resp = self._requests_session.get(self._openapi_json_path, headers=headers) + + if resp.status_code != 200: + return None + + return resp.json() def _extract_paths(self, openapi: dict) -> List[API]: endpoints: List[API] = [] @@ -93,7 +114,7 @@ def _extract_paths(self, openapi: dict) -> List[API]: name=endpoint_url, platform=APIPlatform.OPEN_API ), open_api=OpenAPI(path=path, methods=self._extract_methods(path_item)), - structure=AssetStructure(directories=[self._api_name]), + structure=AssetStructure(directories=[self._api_id], name=path), ) endpoints.append(endpoint) return endpoints @@ -115,8 +136,8 @@ def to_operation_type(method: str) -> Optional[OperationType]: methods.append( OpenAPIMethod( - summary=item.get("summary"), - description=item.get("description"), + summary=item.get("summary") or None, + description=item.get("description") or None, type=operation_type, ) ) @@ -126,7 +147,7 @@ def _build_hierarchies(self, openapi: dict) -> List[Hierarchy]: title = openapi["info"]["title"] hierarchy = Hierarchy( logical_id=HierarchyLogicalID( - path=[AssetPlatform.OPEN_API.value] + [self._api_name], + path=[AssetPlatform.OPEN_API.value] + [self._api_id], ), hierarchy_info=HierarchyInfo( name=title, From dc02bce88080d72e156584bf29456399676f1a99 Mon Sep 17 00:00:00 2001 From: Scott Huang Date: Fri, 25 Oct 2024 16:22:31 +0800 Subject: [PATCH 03/19] Finalize --- README.md | 1 + metaphor/openapi/README.md | 41 + metaphor/openapi/extractor.py | 2 +- pyproject.toml | 2 +- tests/openapi/data/pet_store_20/expected.json | 344 +++++ tests/openapi/data/pet_store_20/openapi.json | 1054 ++++++++++++++ tests/openapi/data/pet_store_30/expected.json | 327 +++++ tests/openapi/data/pet_store_30/openapi.json | 1225 +++++++++++++++++ tests/openapi/data/pet_store_31/expected.json | 66 + tests/openapi/data/pet_store_31/openapi.json | 428 ++++++ .../expected.json | 66 + .../pet_store_31_absolute_server/openapi.json | 428 ++++++ tests/openapi/test_extractor.py | 35 + 13 files changed, 4017 insertions(+), 2 deletions(-) create mode 100644 metaphor/openapi/README.md create mode 100644 tests/openapi/data/pet_store_20/expected.json create mode 100644 tests/openapi/data/pet_store_20/openapi.json create mode 100644 tests/openapi/data/pet_store_30/expected.json create mode 100644 tests/openapi/data/pet_store_30/openapi.json create mode 100644 tests/openapi/data/pet_store_31/expected.json create mode 100644 tests/openapi/data/pet_store_31/openapi.json create mode 100644 tests/openapi/data/pet_store_31_absolute_server/expected.json create mode 100644 tests/openapi/data/pet_store_31_absolute_server/openapi.json create mode 100644 tests/openapi/test_extractor.py diff --git a/README.md b/README.md index db841ad6..73260520 100644 --- a/README.md +++ b/README.md @@ -72,6 +72,7 @@ Each connector is placed under its own directory under [metaphor](./metaphor) an | [monte_carlo](metaphor/monte_carlo/) | Data monitor | | [mssql](metaphor/mssql/) | Schema | | [mysql](metaphor/mysql/) | Schema, description | +| [openapi](metaphor/openapi/) | API, description | | [oracle](metaphor/oracle/) | Schema, description, queries | | [notion](metaphor/notion/) | Document embeddings | | [postgresql](metaphor/postgresql/) | Schema, description, statistics | diff --git a/metaphor/openapi/README.md b/metaphor/openapi/README.md new file mode 100644 index 00000000..b56560e7 --- /dev/null +++ b/metaphor/openapi/README.md @@ -0,0 +1,41 @@ +# OpenAPI Connector + +This connector extracts APIs from an OpenAPI Specification JSON. + +## Config File + +Create a YAML config file based on the following template. + +### Required Configurations + +```yaml +base_url: # BaseUrl for endpoints in OAS +openapi_json_path: # URL or path of OAS +``` + +### Optional Configurations + +If accessing the OAS JSON requires authentication, please include an optional auth configuration. + +```yaml +auth: + basic_auth: + user: + password: +``` + +#### Output Destination + +See [Output Config](../common/docs/output.md) for more information on the optional `output` config. + +## Testing + +Follow the [installation](../../README.md) instructions to install `metaphor-connectors` in your environment (or virtualenv). Make sure to include the `openapi` or `all` extra. + +Run the following command to test the connector locally: + +```shell +metaphor openapi +``` + +Manually verify the output after the run finishes. diff --git a/metaphor/openapi/extractor.py b/metaphor/openapi/extractor.py index 12c04906..a60d14f1 100644 --- a/metaphor/openapi/extractor.py +++ b/metaphor/openapi/extractor.py @@ -107,7 +107,7 @@ def _extract_paths(self, openapi: dict) -> List[API]: if not base_path.startswith("http"): endpoint_url = urljoin(self._base_url, base_path + path) else: - endpoint_url = urljoin(base_path, path) + endpoint_url = urljoin(base_path + "/", f"./{path}") endpoint = API( logical_id=APILogicalID( diff --git a/pyproject.toml b/pyproject.toml index aa5fb6ef..433a6f0b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "metaphor-connectors" -version = "0.14.135" +version = "0.14.136" license = "Apache-2.0" description = "A collection of Python-based 'connectors' that extract metadata from various sources to ingest into the Metaphor app." authors = ["Metaphor "] diff --git a/tests/openapi/data/pet_store_20/expected.json b/tests/openapi/data/pet_store_20/expected.json new file mode 100644 index 00000000..216e555d --- /dev/null +++ b/tests/openapi/data/pet_store_20/expected.json @@ -0,0 +1,344 @@ +[ + { + "hierarchyInfo": { + "name": "Swagger Petstore", + "openAPI": { + "definition": "{\"swagger\": \"2.0\", \"info\": {\"description\": \"This is a sample server Petstore server. You can find out more about Swagger at [http://swagger.io](http://swagger.io) or on [irc.freenode.net, #swagger](http://swagger.io/irc/). For this sample, you can use the api key `special-key` to test the authorization filters.\", \"version\": \"1.0.7\", \"title\": \"Swagger Petstore\", \"termsOfService\": \"http://swagger.io/terms/\", \"contact\": {\"email\": \"apiteam@swagger.io\"}, \"license\": {\"name\": \"Apache 2.0\", \"url\": \"http://www.apache.org/licenses/LICENSE-2.0.html\"}}, \"host\": \"petstore.swagger.io\", \"basePath\": \"/v2\", \"tags\": [{\"name\": \"pet\", \"description\": \"Everything about your Pets\", \"externalDocs\": {\"description\": \"Find out more\", \"url\": \"http://swagger.io\"}}, {\"name\": \"store\", \"description\": \"Access to Petstore orders\"}, {\"name\": \"user\", \"description\": \"Operations about user\", \"externalDocs\": {\"description\": \"Find out more about our store\", \"url\": \"http://swagger.io\"}}], \"schemes\": [\"https\", \"http\"], \"paths\": {\"/pet/{petId}/uploadImage\": {\"post\": {\"tags\": [\"pet\"], \"summary\": \"uploads an image\", \"description\": \"\", \"operationId\": \"uploadFile\", \"consumes\": [\"multipart/form-data\"], \"produces\": [\"application/json\"], \"parameters\": [{\"name\": \"petId\", \"in\": \"path\", \"description\": \"ID of pet to update\", \"required\": true, \"type\": \"integer\", \"format\": \"int64\"}, {\"name\": \"additionalMetadata\", \"in\": \"formData\", \"description\": \"Additional data to pass to server\", \"required\": false, \"type\": \"string\"}, {\"name\": \"file\", \"in\": \"formData\", \"description\": \"file to upload\", \"required\": false, \"type\": \"file\"}], \"responses\": {\"200\": {\"description\": \"successful operation\", \"schema\": {\"$ref\": \"#/definitions/ApiResponse\"}}}, \"security\": [{\"petstore_auth\": [\"write:pets\", \"read:pets\"]}]}}, \"/pet\": {\"post\": {\"tags\": [\"pet\"], \"summary\": \"Add a new pet to the store\", \"description\": \"\", \"operationId\": \"addPet\", \"consumes\": [\"application/json\", \"application/xml\"], \"produces\": [\"application/json\", \"application/xml\"], \"parameters\": [{\"in\": \"body\", \"name\": \"body\", \"description\": \"Pet object that needs to be added to the store\", \"required\": true, \"schema\": {\"$ref\": \"#/definitions/Pet\"}}], \"responses\": {\"405\": {\"description\": \"Invalid input\"}}, \"security\": [{\"petstore_auth\": [\"write:pets\", \"read:pets\"]}]}, \"put\": {\"tags\": [\"pet\"], \"summary\": \"Update an existing pet\", \"description\": \"\", \"operationId\": \"updatePet\", \"consumes\": [\"application/json\", \"application/xml\"], \"produces\": [\"application/json\", \"application/xml\"], \"parameters\": [{\"in\": \"body\", \"name\": \"body\", \"description\": \"Pet object that needs to be added to the store\", \"required\": true, \"schema\": {\"$ref\": \"#/definitions/Pet\"}}], \"responses\": {\"400\": {\"description\": \"Invalid ID supplied\"}, \"404\": {\"description\": \"Pet not found\"}, \"405\": {\"description\": \"Validation exception\"}}, \"security\": [{\"petstore_auth\": [\"write:pets\", \"read:pets\"]}]}}, \"/pet/findByStatus\": {\"get\": {\"tags\": [\"pet\"], \"summary\": \"Finds Pets by status\", \"description\": \"Multiple status values can be provided with comma separated strings\", \"operationId\": \"findPetsByStatus\", \"produces\": [\"application/json\", \"application/xml\"], \"parameters\": [{\"name\": \"status\", \"in\": \"query\", \"description\": \"Status values that need to be considered for filter\", \"required\": true, \"type\": \"array\", \"items\": {\"type\": \"string\", \"enum\": [\"available\", \"pending\", \"sold\"], \"default\": \"available\"}, \"collectionFormat\": \"multi\"}], \"responses\": {\"200\": {\"description\": \"successful operation\", \"schema\": {\"type\": \"array\", \"items\": {\"$ref\": \"#/definitions/Pet\"}}}, \"400\": {\"description\": \"Invalid status value\"}}, \"security\": [{\"petstore_auth\": [\"write:pets\", \"read:pets\"]}]}}, \"/pet/findByTags\": {\"get\": {\"tags\": [\"pet\"], \"summary\": \"Finds Pets by tags\", \"description\": \"Multiple tags can be provided with comma separated strings. Use tag1, tag2, tag3 for testing.\", \"operationId\": \"findPetsByTags\", \"produces\": [\"application/json\", \"application/xml\"], \"parameters\": [{\"name\": \"tags\", \"in\": \"query\", \"description\": \"Tags to filter by\", \"required\": true, \"type\": \"array\", \"items\": {\"type\": \"string\"}, \"collectionFormat\": \"multi\"}], \"responses\": {\"200\": {\"description\": \"successful operation\", \"schema\": {\"type\": \"array\", \"items\": {\"$ref\": \"#/definitions/Pet\"}}}, \"400\": {\"description\": \"Invalid tag value\"}}, \"security\": [{\"petstore_auth\": [\"write:pets\", \"read:pets\"]}], \"deprecated\": true}}, \"/pet/{petId}\": {\"get\": {\"tags\": [\"pet\"], \"summary\": \"Find pet by ID\", \"description\": \"Returns a single pet\", \"operationId\": \"getPetById\", \"produces\": [\"application/json\", \"application/xml\"], \"parameters\": [{\"name\": \"petId\", \"in\": \"path\", \"description\": \"ID of pet to return\", \"required\": true, \"type\": \"integer\", \"format\": \"int64\"}], \"responses\": {\"200\": {\"description\": \"successful operation\", \"schema\": {\"$ref\": \"#/definitions/Pet\"}}, \"400\": {\"description\": \"Invalid ID supplied\"}, \"404\": {\"description\": \"Pet not found\"}}, \"security\": [{\"api_key\": []}]}, \"post\": {\"tags\": [\"pet\"], \"summary\": \"Updates a pet in the store with form data\", \"description\": \"\", \"operationId\": \"updatePetWithForm\", \"consumes\": [\"application/x-www-form-urlencoded\"], \"produces\": [\"application/json\", \"application/xml\"], \"parameters\": [{\"name\": \"petId\", \"in\": \"path\", \"description\": \"ID of pet that needs to be updated\", \"required\": true, \"type\": \"integer\", \"format\": \"int64\"}, {\"name\": \"name\", \"in\": \"formData\", \"description\": \"Updated name of the pet\", \"required\": false, \"type\": \"string\"}, {\"name\": \"status\", \"in\": \"formData\", \"description\": \"Updated status of the pet\", \"required\": false, \"type\": \"string\"}], \"responses\": {\"405\": {\"description\": \"Invalid input\"}}, \"security\": [{\"petstore_auth\": [\"write:pets\", \"read:pets\"]}]}, \"delete\": {\"tags\": [\"pet\"], \"summary\": \"Deletes a pet\", \"description\": \"\", \"operationId\": \"deletePet\", \"produces\": [\"application/json\", \"application/xml\"], \"parameters\": [{\"name\": \"api_key\", \"in\": \"header\", \"required\": false, \"type\": \"string\"}, {\"name\": \"petId\", \"in\": \"path\", \"description\": \"Pet id to delete\", \"required\": true, \"type\": \"integer\", \"format\": \"int64\"}], \"responses\": {\"400\": {\"description\": \"Invalid ID supplied\"}, \"404\": {\"description\": \"Pet not found\"}}, \"security\": [{\"petstore_auth\": [\"write:pets\", \"read:pets\"]}]}}, \"/store/inventory\": {\"get\": {\"tags\": [\"store\"], \"summary\": \"Returns pet inventories by status\", \"description\": \"Returns a map of status codes to quantities\", \"operationId\": \"getInventory\", \"produces\": [\"application/json\"], \"parameters\": [], \"responses\": {\"200\": {\"description\": \"successful operation\", \"schema\": {\"type\": \"object\", \"additionalProperties\": {\"type\": \"integer\", \"format\": \"int32\"}}}}, \"security\": [{\"api_key\": []}]}}, \"/store/order\": {\"post\": {\"tags\": [\"store\"], \"summary\": \"Place an order for a pet\", \"description\": \"\", \"operationId\": \"placeOrder\", \"consumes\": [\"application/json\"], \"produces\": [\"application/json\", \"application/xml\"], \"parameters\": [{\"in\": \"body\", \"name\": \"body\", \"description\": \"order placed for purchasing the pet\", \"required\": true, \"schema\": {\"$ref\": \"#/definitions/Order\"}}], \"responses\": {\"200\": {\"description\": \"successful operation\", \"schema\": {\"$ref\": \"#/definitions/Order\"}}, \"400\": {\"description\": \"Invalid Order\"}}}}, \"/store/order/{orderId}\": {\"get\": {\"tags\": [\"store\"], \"summary\": \"Find purchase order by ID\", \"description\": \"For valid response try integer IDs with value >= 1 and <= 10. Other values will generated exceptions\", \"operationId\": \"getOrderById\", \"produces\": [\"application/json\", \"application/xml\"], \"parameters\": [{\"name\": \"orderId\", \"in\": \"path\", \"description\": \"ID of pet that needs to be fetched\", \"required\": true, \"type\": \"integer\", \"maximum\": 10, \"minimum\": 1, \"format\": \"int64\"}], \"responses\": {\"200\": {\"description\": \"successful operation\", \"schema\": {\"$ref\": \"#/definitions/Order\"}}, \"400\": {\"description\": \"Invalid ID supplied\"}, \"404\": {\"description\": \"Order not found\"}}}, \"delete\": {\"tags\": [\"store\"], \"summary\": \"Delete purchase order by ID\", \"description\": \"For valid response try integer IDs with positive integer value. Negative or non-integer values will generate API errors\", \"operationId\": \"deleteOrder\", \"produces\": [\"application/json\", \"application/xml\"], \"parameters\": [{\"name\": \"orderId\", \"in\": \"path\", \"description\": \"ID of the order that needs to be deleted\", \"required\": true, \"type\": \"integer\", \"minimum\": 1, \"format\": \"int64\"}], \"responses\": {\"400\": {\"description\": \"Invalid ID supplied\"}, \"404\": {\"description\": \"Order not found\"}}}}, \"/user/createWithList\": {\"post\": {\"tags\": [\"user\"], \"summary\": \"Creates list of users with given input array\", \"description\": \"\", \"operationId\": \"createUsersWithListInput\", \"consumes\": [\"application/json\"], \"produces\": [\"application/json\", \"application/xml\"], \"parameters\": [{\"in\": \"body\", \"name\": \"body\", \"description\": \"List of user object\", \"required\": true, \"schema\": {\"type\": \"array\", \"items\": {\"$ref\": \"#/definitions/User\"}}}], \"responses\": {\"default\": {\"description\": \"successful operation\"}}}}, \"/user/{username}\": {\"get\": {\"tags\": [\"user\"], \"summary\": \"Get user by user name\", \"description\": \"\", \"operationId\": \"getUserByName\", \"produces\": [\"application/json\", \"application/xml\"], \"parameters\": [{\"name\": \"username\", \"in\": \"path\", \"description\": \"The name that needs to be fetched. Use user1 for testing. \", \"required\": true, \"type\": \"string\"}], \"responses\": {\"200\": {\"description\": \"successful operation\", \"schema\": {\"$ref\": \"#/definitions/User\"}}, \"400\": {\"description\": \"Invalid username supplied\"}, \"404\": {\"description\": \"User not found\"}}}, \"put\": {\"tags\": [\"user\"], \"summary\": \"Updated user\", \"description\": \"This can only be done by the logged in user.\", \"operationId\": \"updateUser\", \"consumes\": [\"application/json\"], \"produces\": [\"application/json\", \"application/xml\"], \"parameters\": [{\"name\": \"username\", \"in\": \"path\", \"description\": \"name that need to be updated\", \"required\": true, \"type\": \"string\"}, {\"in\": \"body\", \"name\": \"body\", \"description\": \"Updated user object\", \"required\": true, \"schema\": {\"$ref\": \"#/definitions/User\"}}], \"responses\": {\"400\": {\"description\": \"Invalid user supplied\"}, \"404\": {\"description\": \"User not found\"}}}, \"delete\": {\"tags\": [\"user\"], \"summary\": \"Delete user\", \"description\": \"This can only be done by the logged in user.\", \"operationId\": \"deleteUser\", \"produces\": [\"application/json\", \"application/xml\"], \"parameters\": [{\"name\": \"username\", \"in\": \"path\", \"description\": \"The name that needs to be deleted\", \"required\": true, \"type\": \"string\"}], \"responses\": {\"400\": {\"description\": \"Invalid username supplied\"}, \"404\": {\"description\": \"User not found\"}}}}, \"/user/login\": {\"get\": {\"tags\": [\"user\"], \"summary\": \"Logs user into the system\", \"description\": \"\", \"operationId\": \"loginUser\", \"produces\": [\"application/json\", \"application/xml\"], \"parameters\": [{\"name\": \"username\", \"in\": \"query\", \"description\": \"The user name for login\", \"required\": true, \"type\": \"string\"}, {\"name\": \"password\", \"in\": \"query\", \"description\": \"The password for login in clear text\", \"required\": true, \"type\": \"string\"}], \"responses\": {\"200\": {\"description\": \"successful operation\", \"headers\": {\"X-Expires-After\": {\"type\": \"string\", \"format\": \"date-time\", \"description\": \"date in UTC when token expires\"}, \"X-Rate-Limit\": {\"type\": \"integer\", \"format\": \"int32\", \"description\": \"calls per hour allowed by the user\"}}, \"schema\": {\"type\": \"string\"}}, \"400\": {\"description\": \"Invalid username/password supplied\"}}}}, \"/user/logout\": {\"get\": {\"tags\": [\"user\"], \"summary\": \"Logs out current logged in user session\", \"description\": \"\", \"operationId\": \"logoutUser\", \"produces\": [\"application/json\", \"application/xml\"], \"parameters\": [], \"responses\": {\"default\": {\"description\": \"successful operation\"}}}}, \"/user/createWithArray\": {\"post\": {\"tags\": [\"user\"], \"summary\": \"Creates list of users with given input array\", \"description\": \"\", \"operationId\": \"createUsersWithArrayInput\", \"consumes\": [\"application/json\"], \"produces\": [\"application/json\", \"application/xml\"], \"parameters\": [{\"in\": \"body\", \"name\": \"body\", \"description\": \"List of user object\", \"required\": true, \"schema\": {\"type\": \"array\", \"items\": {\"$ref\": \"#/definitions/User\"}}}], \"responses\": {\"default\": {\"description\": \"successful operation\"}}}}, \"/user\": {\"post\": {\"tags\": [\"user\"], \"summary\": \"Create user\", \"description\": \"This can only be done by the logged in user.\", \"operationId\": \"createUser\", \"consumes\": [\"application/json\"], \"produces\": [\"application/json\", \"application/xml\"], \"parameters\": [{\"in\": \"body\", \"name\": \"body\", \"description\": \"Created user object\", \"required\": true, \"schema\": {\"$ref\": \"#/definitions/User\"}}], \"responses\": {\"default\": {\"description\": \"successful operation\"}}}}}, \"securityDefinitions\": {\"api_key\": {\"type\": \"apiKey\", \"name\": \"api_key\", \"in\": \"header\"}, \"petstore_auth\": {\"type\": \"oauth2\", \"authorizationUrl\": \"https://petstore.swagger.io/oauth/authorize\", \"flow\": \"implicit\", \"scopes\": {\"read:pets\": \"read your pets\", \"write:pets\": \"modify pets in your account\"}}}, \"definitions\": {\"ApiResponse\": {\"type\": \"object\", \"properties\": {\"code\": {\"type\": \"integer\", \"format\": \"int32\"}, \"type\": {\"type\": \"string\"}, \"message\": {\"type\": \"string\"}}}, \"Category\": {\"type\": \"object\", \"properties\": {\"id\": {\"type\": \"integer\", \"format\": \"int64\"}, \"name\": {\"type\": \"string\"}}, \"xml\": {\"name\": \"Category\"}}, \"Pet\": {\"type\": \"object\", \"required\": [\"name\", \"photoUrls\"], \"properties\": {\"id\": {\"type\": \"integer\", \"format\": \"int64\"}, \"category\": {\"$ref\": \"#/definitions/Category\"}, \"name\": {\"type\": \"string\", \"example\": \"doggie\"}, \"photoUrls\": {\"type\": \"array\", \"xml\": {\"wrapped\": true}, \"items\": {\"type\": \"string\", \"xml\": {\"name\": \"photoUrl\"}}}, \"tags\": {\"type\": \"array\", \"xml\": {\"wrapped\": true}, \"items\": {\"xml\": {\"name\": \"tag\"}, \"$ref\": \"#/definitions/Tag\"}}, \"status\": {\"type\": \"string\", \"description\": \"pet status in the store\", \"enum\": [\"available\", \"pending\", \"sold\"]}}, \"xml\": {\"name\": \"Pet\"}}, \"Tag\": {\"type\": \"object\", \"properties\": {\"id\": {\"type\": \"integer\", \"format\": \"int64\"}, \"name\": {\"type\": \"string\"}}, \"xml\": {\"name\": \"Tag\"}}, \"Order\": {\"type\": \"object\", \"properties\": {\"id\": {\"type\": \"integer\", \"format\": \"int64\"}, \"petId\": {\"type\": \"integer\", \"format\": \"int64\"}, \"quantity\": {\"type\": \"integer\", \"format\": \"int32\"}, \"shipDate\": {\"type\": \"string\", \"format\": \"date-time\"}, \"status\": {\"type\": \"string\", \"description\": \"Order Status\", \"enum\": [\"placed\", \"approved\", \"delivered\"]}, \"complete\": {\"type\": \"boolean\"}}, \"xml\": {\"name\": \"Order\"}}, \"User\": {\"type\": \"object\", \"properties\": {\"id\": {\"type\": \"integer\", \"format\": \"int64\"}, \"username\": {\"type\": \"string\"}, \"firstName\": {\"type\": \"string\"}, \"lastName\": {\"type\": \"string\"}, \"email\": {\"type\": \"string\"}, \"password\": {\"type\": \"string\"}, \"phone\": {\"type\": \"string\"}, \"userStatus\": {\"type\": \"integer\", \"format\": \"int32\", \"description\": \"User Status\"}}, \"xml\": {\"name\": \"User\"}}}, \"externalDocs\": {\"description\": \"Find out more about Swagger\", \"url\": \"http://swagger.io\"}}" + }, + "type": "OPEN_API" + }, + "logicalId": { + "path": [ + "OPEN_API", + "3a45981ff1a3539f49317040f895e979" + ] + } + }, + { + "logicalId": { + "name": "https://foo.bar/pet/{petId}/uploadImage", + "platform": "OPEN_API" + }, + "openAPI": { + "methods": [ + { + "summary": "uploads an image", + "type": "POST" + } + ], + "path": "/pet/{petId}/uploadImage" + }, + "structure": { + "directories": [ + "3a45981ff1a3539f49317040f895e979" + ], + "name": "/pet/{petId}/uploadImage" + } + }, + { + "logicalId": { + "name": "https://foo.bar/pet", + "platform": "OPEN_API" + }, + "openAPI": { + "methods": [ + { + "summary": "Add a new pet to the store", + "type": "POST" + }, + { + "summary": "Update an existing pet", + "type": "PUT" + } + ], + "path": "/pet" + }, + "structure": { + "directories": [ + "3a45981ff1a3539f49317040f895e979" + ], + "name": "/pet" + } + }, + { + "logicalId": { + "name": "https://foo.bar/pet/findByStatus", + "platform": "OPEN_API" + }, + "openAPI": { + "methods": [ + { + "description": "Multiple status values can be provided with comma separated strings", + "summary": "Finds Pets by status", + "type": "GET" + } + ], + "path": "/pet/findByStatus" + }, + "structure": { + "directories": [ + "3a45981ff1a3539f49317040f895e979" + ], + "name": "/pet/findByStatus" + } + }, + { + "logicalId": { + "name": "https://foo.bar/pet/findByTags", + "platform": "OPEN_API" + }, + "openAPI": { + "methods": [ + { + "description": "Multiple tags can be provided with comma separated strings. Use tag1, tag2, tag3 for testing.", + "summary": "Finds Pets by tags", + "type": "GET" + } + ], + "path": "/pet/findByTags" + }, + "structure": { + "directories": [ + "3a45981ff1a3539f49317040f895e979" + ], + "name": "/pet/findByTags" + } + }, + { + "logicalId": { + "name": "https://foo.bar/pet/{petId}", + "platform": "OPEN_API" + }, + "openAPI": { + "methods": [ + { + "description": "Returns a single pet", + "summary": "Find pet by ID", + "type": "GET" + }, + { + "summary": "Updates a pet in the store with form data", + "type": "POST" + }, + { + "summary": "Deletes a pet", + "type": "DELETE" + } + ], + "path": "/pet/{petId}" + }, + "structure": { + "directories": [ + "3a45981ff1a3539f49317040f895e979" + ], + "name": "/pet/{petId}" + } + }, + { + "logicalId": { + "name": "https://foo.bar/store/inventory", + "platform": "OPEN_API" + }, + "openAPI": { + "methods": [ + { + "description": "Returns a map of status codes to quantities", + "summary": "Returns pet inventories by status", + "type": "GET" + } + ], + "path": "/store/inventory" + }, + "structure": { + "directories": [ + "3a45981ff1a3539f49317040f895e979" + ], + "name": "/store/inventory" + } + }, + { + "logicalId": { + "name": "https://foo.bar/store/order", + "platform": "OPEN_API" + }, + "openAPI": { + "methods": [ + { + "summary": "Place an order for a pet", + "type": "POST" + } + ], + "path": "/store/order" + }, + "structure": { + "directories": [ + "3a45981ff1a3539f49317040f895e979" + ], + "name": "/store/order" + } + }, + { + "logicalId": { + "name": "https://foo.bar/store/order/{orderId}", + "platform": "OPEN_API" + }, + "openAPI": { + "methods": [ + { + "description": "For valid response try integer IDs with value >= 1 and <= 10. Other values will generated exceptions", + "summary": "Find purchase order by ID", + "type": "GET" + }, + { + "description": "For valid response try integer IDs with positive integer value. Negative or non-integer values will generate API errors", + "summary": "Delete purchase order by ID", + "type": "DELETE" + } + ], + "path": "/store/order/{orderId}" + }, + "structure": { + "directories": [ + "3a45981ff1a3539f49317040f895e979" + ], + "name": "/store/order/{orderId}" + } + }, + { + "logicalId": { + "name": "https://foo.bar/user/createWithList", + "platform": "OPEN_API" + }, + "openAPI": { + "methods": [ + { + "summary": "Creates list of users with given input array", + "type": "POST" + } + ], + "path": "/user/createWithList" + }, + "structure": { + "directories": [ + "3a45981ff1a3539f49317040f895e979" + ], + "name": "/user/createWithList" + } + }, + { + "logicalId": { + "name": "https://foo.bar/user/{username}", + "platform": "OPEN_API" + }, + "openAPI": { + "methods": [ + { + "summary": "Get user by user name", + "type": "GET" + }, + { + "description": "This can only be done by the logged in user.", + "summary": "Updated user", + "type": "PUT" + }, + { + "description": "This can only be done by the logged in user.", + "summary": "Delete user", + "type": "DELETE" + } + ], + "path": "/user/{username}" + }, + "structure": { + "directories": [ + "3a45981ff1a3539f49317040f895e979" + ], + "name": "/user/{username}" + } + }, + { + "logicalId": { + "name": "https://foo.bar/user/login", + "platform": "OPEN_API" + }, + "openAPI": { + "methods": [ + { + "summary": "Logs user into the system", + "type": "GET" + } + ], + "path": "/user/login" + }, + "structure": { + "directories": [ + "3a45981ff1a3539f49317040f895e979" + ], + "name": "/user/login" + } + }, + { + "logicalId": { + "name": "https://foo.bar/user/logout", + "platform": "OPEN_API" + }, + "openAPI": { + "methods": [ + { + "summary": "Logs out current logged in user session", + "type": "GET" + } + ], + "path": "/user/logout" + }, + "structure": { + "directories": [ + "3a45981ff1a3539f49317040f895e979" + ], + "name": "/user/logout" + } + }, + { + "logicalId": { + "name": "https://foo.bar/user/createWithArray", + "platform": "OPEN_API" + }, + "openAPI": { + "methods": [ + { + "summary": "Creates list of users with given input array", + "type": "POST" + } + ], + "path": "/user/createWithArray" + }, + "structure": { + "directories": [ + "3a45981ff1a3539f49317040f895e979" + ], + "name": "/user/createWithArray" + } + }, + { + "logicalId": { + "name": "https://foo.bar/user", + "platform": "OPEN_API" + }, + "openAPI": { + "methods": [ + { + "description": "This can only be done by the logged in user.", + "summary": "Create user", + "type": "POST" + } + ], + "path": "/user" + }, + "structure": { + "directories": [ + "3a45981ff1a3539f49317040f895e979" + ], + "name": "/user" + } + } +] diff --git a/tests/openapi/data/pet_store_20/openapi.json b/tests/openapi/data/pet_store_20/openapi.json new file mode 100644 index 00000000..8808efd2 --- /dev/null +++ b/tests/openapi/data/pet_store_20/openapi.json @@ -0,0 +1,1054 @@ +{ + "swagger": "2.0", + "info": { + "description": "This is a sample server Petstore server. You can find out more about Swagger at [http://swagger.io](http://swagger.io) or on [irc.freenode.net, #swagger](http://swagger.io/irc/). For this sample, you can use the api key `special-key` to test the authorization filters.", + "version": "1.0.7", + "title": "Swagger Petstore", + "termsOfService": "http://swagger.io/terms/", + "contact": { + "email": "apiteam@swagger.io" + }, + "license": { + "name": "Apache 2.0", + "url": "http://www.apache.org/licenses/LICENSE-2.0.html" + } + }, + "host": "petstore.swagger.io", + "basePath": "/v2", + "tags": [ + { + "name": "pet", + "description": "Everything about your Pets", + "externalDocs": { + "description": "Find out more", + "url": "http://swagger.io" + } + }, + { + "name": "store", + "description": "Access to Petstore orders" + }, + { + "name": "user", + "description": "Operations about user", + "externalDocs": { + "description": "Find out more about our store", + "url": "http://swagger.io" + } + } + ], + "schemes": [ + "https", + "http" + ], + "paths": { + "/pet/{petId}/uploadImage": { + "post": { + "tags": [ + "pet" + ], + "summary": "uploads an image", + "description": "", + "operationId": "uploadFile", + "consumes": [ + "multipart/form-data" + ], + "produces": [ + "application/json" + ], + "parameters": [ + { + "name": "petId", + "in": "path", + "description": "ID of pet to update", + "required": true, + "type": "integer", + "format": "int64" + }, + { + "name": "additionalMetadata", + "in": "formData", + "description": "Additional data to pass to server", + "required": false, + "type": "string" + }, + { + "name": "file", + "in": "formData", + "description": "file to upload", + "required": false, + "type": "file" + } + ], + "responses": { + "200": { + "description": "successful operation", + "schema": { + "$ref": "#/definitions/ApiResponse" + } + } + }, + "security": [ + { + "petstore_auth": [ + "write:pets", + "read:pets" + ] + } + ] + } + }, + "/pet": { + "post": { + "tags": [ + "pet" + ], + "summary": "Add a new pet to the store", + "description": "", + "operationId": "addPet", + "consumes": [ + "application/json", + "application/xml" + ], + "produces": [ + "application/json", + "application/xml" + ], + "parameters": [ + { + "in": "body", + "name": "body", + "description": "Pet object that needs to be added to the store", + "required": true, + "schema": { + "$ref": "#/definitions/Pet" + } + } + ], + "responses": { + "405": { + "description": "Invalid input" + } + }, + "security": [ + { + "petstore_auth": [ + "write:pets", + "read:pets" + ] + } + ] + }, + "put": { + "tags": [ + "pet" + ], + "summary": "Update an existing pet", + "description": "", + "operationId": "updatePet", + "consumes": [ + "application/json", + "application/xml" + ], + "produces": [ + "application/json", + "application/xml" + ], + "parameters": [ + { + "in": "body", + "name": "body", + "description": "Pet object that needs to be added to the store", + "required": true, + "schema": { + "$ref": "#/definitions/Pet" + } + } + ], + "responses": { + "400": { + "description": "Invalid ID supplied" + }, + "404": { + "description": "Pet not found" + }, + "405": { + "description": "Validation exception" + } + }, + "security": [ + { + "petstore_auth": [ + "write:pets", + "read:pets" + ] + } + ] + } + }, + "/pet/findByStatus": { + "get": { + "tags": [ + "pet" + ], + "summary": "Finds Pets by status", + "description": "Multiple status values can be provided with comma separated strings", + "operationId": "findPetsByStatus", + "produces": [ + "application/json", + "application/xml" + ], + "parameters": [ + { + "name": "status", + "in": "query", + "description": "Status values that need to be considered for filter", + "required": true, + "type": "array", + "items": { + "type": "string", + "enum": [ + "available", + "pending", + "sold" + ], + "default": "available" + }, + "collectionFormat": "multi" + } + ], + "responses": { + "200": { + "description": "successful operation", + "schema": { + "type": "array", + "items": { + "$ref": "#/definitions/Pet" + } + } + }, + "400": { + "description": "Invalid status value" + } + }, + "security": [ + { + "petstore_auth": [ + "write:pets", + "read:pets" + ] + } + ] + } + }, + "/pet/findByTags": { + "get": { + "tags": [ + "pet" + ], + "summary": "Finds Pets by tags", + "description": "Multiple tags can be provided with comma separated strings. Use tag1, tag2, tag3 for testing.", + "operationId": "findPetsByTags", + "produces": [ + "application/json", + "application/xml" + ], + "parameters": [ + { + "name": "tags", + "in": "query", + "description": "Tags to filter by", + "required": true, + "type": "array", + "items": { + "type": "string" + }, + "collectionFormat": "multi" + } + ], + "responses": { + "200": { + "description": "successful operation", + "schema": { + "type": "array", + "items": { + "$ref": "#/definitions/Pet" + } + } + }, + "400": { + "description": "Invalid tag value" + } + }, + "security": [ + { + "petstore_auth": [ + "write:pets", + "read:pets" + ] + } + ], + "deprecated": true + } + }, + "/pet/{petId}": { + "get": { + "tags": [ + "pet" + ], + "summary": "Find pet by ID", + "description": "Returns a single pet", + "operationId": "getPetById", + "produces": [ + "application/json", + "application/xml" + ], + "parameters": [ + { + "name": "petId", + "in": "path", + "description": "ID of pet to return", + "required": true, + "type": "integer", + "format": "int64" + } + ], + "responses": { + "200": { + "description": "successful operation", + "schema": { + "$ref": "#/definitions/Pet" + } + }, + "400": { + "description": "Invalid ID supplied" + }, + "404": { + "description": "Pet not found" + } + }, + "security": [ + { + "api_key": [] + } + ] + }, + "post": { + "tags": [ + "pet" + ], + "summary": "Updates a pet in the store with form data", + "description": "", + "operationId": "updatePetWithForm", + "consumes": [ + "application/x-www-form-urlencoded" + ], + "produces": [ + "application/json", + "application/xml" + ], + "parameters": [ + { + "name": "petId", + "in": "path", + "description": "ID of pet that needs to be updated", + "required": true, + "type": "integer", + "format": "int64" + }, + { + "name": "name", + "in": "formData", + "description": "Updated name of the pet", + "required": false, + "type": "string" + }, + { + "name": "status", + "in": "formData", + "description": "Updated status of the pet", + "required": false, + "type": "string" + } + ], + "responses": { + "405": { + "description": "Invalid input" + } + }, + "security": [ + { + "petstore_auth": [ + "write:pets", + "read:pets" + ] + } + ] + }, + "delete": { + "tags": [ + "pet" + ], + "summary": "Deletes a pet", + "description": "", + "operationId": "deletePet", + "produces": [ + "application/json", + "application/xml" + ], + "parameters": [ + { + "name": "api_key", + "in": "header", + "required": false, + "type": "string" + }, + { + "name": "petId", + "in": "path", + "description": "Pet id to delete", + "required": true, + "type": "integer", + "format": "int64" + } + ], + "responses": { + "400": { + "description": "Invalid ID supplied" + }, + "404": { + "description": "Pet not found" + } + }, + "security": [ + { + "petstore_auth": [ + "write:pets", + "read:pets" + ] + } + ] + } + }, + "/store/inventory": { + "get": { + "tags": [ + "store" + ], + "summary": "Returns pet inventories by status", + "description": "Returns a map of status codes to quantities", + "operationId": "getInventory", + "produces": [ + "application/json" + ], + "parameters": [], + "responses": { + "200": { + "description": "successful operation", + "schema": { + "type": "object", + "additionalProperties": { + "type": "integer", + "format": "int32" + } + } + } + }, + "security": [ + { + "api_key": [] + } + ] + } + }, + "/store/order": { + "post": { + "tags": [ + "store" + ], + "summary": "Place an order for a pet", + "description": "", + "operationId": "placeOrder", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json", + "application/xml" + ], + "parameters": [ + { + "in": "body", + "name": "body", + "description": "order placed for purchasing the pet", + "required": true, + "schema": { + "$ref": "#/definitions/Order" + } + } + ], + "responses": { + "200": { + "description": "successful operation", + "schema": { + "$ref": "#/definitions/Order" + } + }, + "400": { + "description": "Invalid Order" + } + } + } + }, + "/store/order/{orderId}": { + "get": { + "tags": [ + "store" + ], + "summary": "Find purchase order by ID", + "description": "For valid response try integer IDs with value >= 1 and <= 10. Other values will generated exceptions", + "operationId": "getOrderById", + "produces": [ + "application/json", + "application/xml" + ], + "parameters": [ + { + "name": "orderId", + "in": "path", + "description": "ID of pet that needs to be fetched", + "required": true, + "type": "integer", + "maximum": 10, + "minimum": 1, + "format": "int64" + } + ], + "responses": { + "200": { + "description": "successful operation", + "schema": { + "$ref": "#/definitions/Order" + } + }, + "400": { + "description": "Invalid ID supplied" + }, + "404": { + "description": "Order not found" + } + } + }, + "delete": { + "tags": [ + "store" + ], + "summary": "Delete purchase order by ID", + "description": "For valid response try integer IDs with positive integer value. Negative or non-integer values will generate API errors", + "operationId": "deleteOrder", + "produces": [ + "application/json", + "application/xml" + ], + "parameters": [ + { + "name": "orderId", + "in": "path", + "description": "ID of the order that needs to be deleted", + "required": true, + "type": "integer", + "minimum": 1, + "format": "int64" + } + ], + "responses": { + "400": { + "description": "Invalid ID supplied" + }, + "404": { + "description": "Order not found" + } + } + } + }, + "/user/createWithList": { + "post": { + "tags": [ + "user" + ], + "summary": "Creates list of users with given input array", + "description": "", + "operationId": "createUsersWithListInput", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json", + "application/xml" + ], + "parameters": [ + { + "in": "body", + "name": "body", + "description": "List of user object", + "required": true, + "schema": { + "type": "array", + "items": { + "$ref": "#/definitions/User" + } + } + } + ], + "responses": { + "default": { + "description": "successful operation" + } + } + } + }, + "/user/{username}": { + "get": { + "tags": [ + "user" + ], + "summary": "Get user by user name", + "description": "", + "operationId": "getUserByName", + "produces": [ + "application/json", + "application/xml" + ], + "parameters": [ + { + "name": "username", + "in": "path", + "description": "The name that needs to be fetched. Use user1 for testing. ", + "required": true, + "type": "string" + } + ], + "responses": { + "200": { + "description": "successful operation", + "schema": { + "$ref": "#/definitions/User" + } + }, + "400": { + "description": "Invalid username supplied" + }, + "404": { + "description": "User not found" + } + } + }, + "put": { + "tags": [ + "user" + ], + "summary": "Updated user", + "description": "This can only be done by the logged in user.", + "operationId": "updateUser", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json", + "application/xml" + ], + "parameters": [ + { + "name": "username", + "in": "path", + "description": "name that need to be updated", + "required": true, + "type": "string" + }, + { + "in": "body", + "name": "body", + "description": "Updated user object", + "required": true, + "schema": { + "$ref": "#/definitions/User" + } + } + ], + "responses": { + "400": { + "description": "Invalid user supplied" + }, + "404": { + "description": "User not found" + } + } + }, + "delete": { + "tags": [ + "user" + ], + "summary": "Delete user", + "description": "This can only be done by the logged in user.", + "operationId": "deleteUser", + "produces": [ + "application/json", + "application/xml" + ], + "parameters": [ + { + "name": "username", + "in": "path", + "description": "The name that needs to be deleted", + "required": true, + "type": "string" + } + ], + "responses": { + "400": { + "description": "Invalid username supplied" + }, + "404": { + "description": "User not found" + } + } + } + }, + "/user/login": { + "get": { + "tags": [ + "user" + ], + "summary": "Logs user into the system", + "description": "", + "operationId": "loginUser", + "produces": [ + "application/json", + "application/xml" + ], + "parameters": [ + { + "name": "username", + "in": "query", + "description": "The user name for login", + "required": true, + "type": "string" + }, + { + "name": "password", + "in": "query", + "description": "The password for login in clear text", + "required": true, + "type": "string" + } + ], + "responses": { + "200": { + "description": "successful operation", + "headers": { + "X-Expires-After": { + "type": "string", + "format": "date-time", + "description": "date in UTC when token expires" + }, + "X-Rate-Limit": { + "type": "integer", + "format": "int32", + "description": "calls per hour allowed by the user" + } + }, + "schema": { + "type": "string" + } + }, + "400": { + "description": "Invalid username/password supplied" + } + } + } + }, + "/user/logout": { + "get": { + "tags": [ + "user" + ], + "summary": "Logs out current logged in user session", + "description": "", + "operationId": "logoutUser", + "produces": [ + "application/json", + "application/xml" + ], + "parameters": [], + "responses": { + "default": { + "description": "successful operation" + } + } + } + }, + "/user/createWithArray": { + "post": { + "tags": [ + "user" + ], + "summary": "Creates list of users with given input array", + "description": "", + "operationId": "createUsersWithArrayInput", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json", + "application/xml" + ], + "parameters": [ + { + "in": "body", + "name": "body", + "description": "List of user object", + "required": true, + "schema": { + "type": "array", + "items": { + "$ref": "#/definitions/User" + } + } + } + ], + "responses": { + "default": { + "description": "successful operation" + } + } + } + }, + "/user": { + "post": { + "tags": [ + "user" + ], + "summary": "Create user", + "description": "This can only be done by the logged in user.", + "operationId": "createUser", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json", + "application/xml" + ], + "parameters": [ + { + "in": "body", + "name": "body", + "description": "Created user object", + "required": true, + "schema": { + "$ref": "#/definitions/User" + } + } + ], + "responses": { + "default": { + "description": "successful operation" + } + } + } + } + }, + "securityDefinitions": { + "api_key": { + "type": "apiKey", + "name": "api_key", + "in": "header" + }, + "petstore_auth": { + "type": "oauth2", + "authorizationUrl": "https://petstore.swagger.io/oauth/authorize", + "flow": "implicit", + "scopes": { + "read:pets": "read your pets", + "write:pets": "modify pets in your account" + } + } + }, + "definitions": { + "ApiResponse": { + "type": "object", + "properties": { + "code": { + "type": "integer", + "format": "int32" + }, + "type": { + "type": "string" + }, + "message": { + "type": "string" + } + } + }, + "Category": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "format": "int64" + }, + "name": { + "type": "string" + } + }, + "xml": { + "name": "Category" + } + }, + "Pet": { + "type": "object", + "required": [ + "name", + "photoUrls" + ], + "properties": { + "id": { + "type": "integer", + "format": "int64" + }, + "category": { + "$ref": "#/definitions/Category" + }, + "name": { + "type": "string", + "example": "doggie" + }, + "photoUrls": { + "type": "array", + "xml": { + "wrapped": true + }, + "items": { + "type": "string", + "xml": { + "name": "photoUrl" + } + } + }, + "tags": { + "type": "array", + "xml": { + "wrapped": true + }, + "items": { + "xml": { + "name": "tag" + }, + "$ref": "#/definitions/Tag" + } + }, + "status": { + "type": "string", + "description": "pet status in the store", + "enum": [ + "available", + "pending", + "sold" + ] + } + }, + "xml": { + "name": "Pet" + } + }, + "Tag": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "format": "int64" + }, + "name": { + "type": "string" + } + }, + "xml": { + "name": "Tag" + } + }, + "Order": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "format": "int64" + }, + "petId": { + "type": "integer", + "format": "int64" + }, + "quantity": { + "type": "integer", + "format": "int32" + }, + "shipDate": { + "type": "string", + "format": "date-time" + }, + "status": { + "type": "string", + "description": "Order Status", + "enum": [ + "placed", + "approved", + "delivered" + ] + }, + "complete": { + "type": "boolean" + } + }, + "xml": { + "name": "Order" + } + }, + "User": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "format": "int64" + }, + "username": { + "type": "string" + }, + "firstName": { + "type": "string" + }, + "lastName": { + "type": "string" + }, + "email": { + "type": "string" + }, + "password": { + "type": "string" + }, + "phone": { + "type": "string" + }, + "userStatus": { + "type": "integer", + "format": "int32", + "description": "User Status" + } + }, + "xml": { + "name": "User" + } + } + }, + "externalDocs": { + "description": "Find out more about Swagger", + "url": "http://swagger.io" + } +} diff --git a/tests/openapi/data/pet_store_30/expected.json b/tests/openapi/data/pet_store_30/expected.json new file mode 100644 index 00000000..a5514bc0 --- /dev/null +++ b/tests/openapi/data/pet_store_30/expected.json @@ -0,0 +1,327 @@ +[ + { + "hierarchyInfo": { + "name": "Swagger Petstore - OpenAPI 3.0", + "openAPI": { + "definition": "{\"openapi\": \"3.0.2\", \"info\": {\"title\": \"Swagger Petstore - OpenAPI 3.0\", \"description\": \"This is a sample Pet Store Server based on the OpenAPI 3.0 specification. You can find out more about\\nSwagger at [http://swagger.io](http://swagger.io). In the third iteration of the pet store, we've switched to the design first approach!\\nYou can now help us improve the API whether it's by making changes to the definition itself or to the code.\\nThat way, with time, we can improve the API in general, and expose some of the new features in OAS3.\\n\\nSome useful links:\\n- [The Pet Store repository](https://github.com/swagger-api/swagger-petstore)\\n- [The source API definition for the Pet Store](https://github.com/swagger-api/swagger-petstore/blob/master/src/main/resources/openapi.yaml)\", \"termsOfService\": \"http://swagger.io/terms/\", \"contact\": {\"email\": \"apiteam@swagger.io\"}, \"license\": {\"name\": \"Apache 2.0\", \"url\": \"http://www.apache.org/licenses/LICENSE-2.0.html\"}, \"version\": \"1.0.19\"}, \"externalDocs\": {\"description\": \"Find out more about Swagger\", \"url\": \"http://swagger.io\"}, \"servers\": [{\"url\": \"/api/v3\"}], \"tags\": [{\"name\": \"pet\", \"description\": \"Everything about your Pets\", \"externalDocs\": {\"description\": \"Find out more\", \"url\": \"http://swagger.io\"}}, {\"name\": \"store\", \"description\": \"Access to Petstore orders\", \"externalDocs\": {\"description\": \"Find out more about our store\", \"url\": \"http://swagger.io\"}}, {\"name\": \"user\", \"description\": \"Operations about user\"}], \"paths\": {\"/pet\": {\"put\": {\"tags\": [\"pet\"], \"summary\": \"Update an existing pet\", \"description\": \"Update an existing pet by Id\", \"operationId\": \"updatePet\", \"requestBody\": {\"description\": \"Update an existent pet in the store\", \"content\": {\"application/json\": {\"schema\": {\"$ref\": \"#/components/schemas/Pet\"}}, \"application/xml\": {\"schema\": {\"$ref\": \"#/components/schemas/Pet\"}}, \"application/x-www-form-urlencoded\": {\"schema\": {\"$ref\": \"#/components/schemas/Pet\"}}}, \"required\": true}, \"responses\": {\"200\": {\"description\": \"Successful operation\", \"content\": {\"application/xml\": {\"schema\": {\"$ref\": \"#/components/schemas/Pet\"}}, \"application/json\": {\"schema\": {\"$ref\": \"#/components/schemas/Pet\"}}}}, \"400\": {\"description\": \"Invalid ID supplied\"}, \"404\": {\"description\": \"Pet not found\"}, \"405\": {\"description\": \"Validation exception\"}}, \"security\": [{\"petstore_auth\": [\"write:pets\", \"read:pets\"]}]}, \"post\": {\"tags\": [\"pet\"], \"summary\": \"Add a new pet to the store\", \"description\": \"Add a new pet to the store\", \"operationId\": \"addPet\", \"requestBody\": {\"description\": \"Create a new pet in the store\", \"content\": {\"application/json\": {\"schema\": {\"$ref\": \"#/components/schemas/Pet\"}}, \"application/xml\": {\"schema\": {\"$ref\": \"#/components/schemas/Pet\"}}, \"application/x-www-form-urlencoded\": {\"schema\": {\"$ref\": \"#/components/schemas/Pet\"}}}, \"required\": true}, \"responses\": {\"200\": {\"description\": \"Successful operation\", \"content\": {\"application/xml\": {\"schema\": {\"$ref\": \"#/components/schemas/Pet\"}}, \"application/json\": {\"schema\": {\"$ref\": \"#/components/schemas/Pet\"}}}}, \"405\": {\"description\": \"Invalid input\"}}, \"security\": [{\"petstore_auth\": [\"write:pets\", \"read:pets\"]}]}}, \"/pet/findByStatus\": {\"get\": {\"tags\": [\"pet\"], \"summary\": \"Finds Pets by status\", \"description\": \"Multiple status values can be provided with comma separated strings\", \"operationId\": \"findPetsByStatus\", \"parameters\": [{\"name\": \"status\", \"in\": \"query\", \"description\": \"Status values that need to be considered for filter\", \"required\": false, \"explode\": true, \"schema\": {\"type\": \"string\", \"default\": \"available\", \"enum\": [\"available\", \"pending\", \"sold\"]}}], \"responses\": {\"200\": {\"description\": \"successful operation\", \"content\": {\"application/xml\": {\"schema\": {\"type\": \"array\", \"items\": {\"$ref\": \"#/components/schemas/Pet\"}}}, \"application/json\": {\"schema\": {\"type\": \"array\", \"items\": {\"$ref\": \"#/components/schemas/Pet\"}}}}}, \"400\": {\"description\": \"Invalid status value\"}}, \"security\": [{\"petstore_auth\": [\"write:pets\", \"read:pets\"]}]}}, \"/pet/findByTags\": {\"get\": {\"tags\": [\"pet\"], \"summary\": \"Finds Pets by tags\", \"description\": \"Multiple tags can be provided with comma separated strings. Use tag1, tag2, tag3 for testing.\", \"operationId\": \"findPetsByTags\", \"parameters\": [{\"name\": \"tags\", \"in\": \"query\", \"description\": \"Tags to filter by\", \"required\": false, \"explode\": true, \"schema\": {\"type\": \"array\", \"items\": {\"type\": \"string\"}}}], \"responses\": {\"200\": {\"description\": \"successful operation\", \"content\": {\"application/xml\": {\"schema\": {\"type\": \"array\", \"items\": {\"$ref\": \"#/components/schemas/Pet\"}}}, \"application/json\": {\"schema\": {\"type\": \"array\", \"items\": {\"$ref\": \"#/components/schemas/Pet\"}}}}}, \"400\": {\"description\": \"Invalid tag value\"}}, \"security\": [{\"petstore_auth\": [\"write:pets\", \"read:pets\"]}]}}, \"/pet/{petId}\": {\"get\": {\"tags\": [\"pet\"], \"summary\": \"Find pet by ID\", \"description\": \"Returns a single pet\", \"operationId\": \"getPetById\", \"parameters\": [{\"name\": \"petId\", \"in\": \"path\", \"description\": \"ID of pet to return\", \"required\": true, \"schema\": {\"type\": \"integer\", \"format\": \"int64\"}}], \"responses\": {\"200\": {\"description\": \"successful operation\", \"content\": {\"application/xml\": {\"schema\": {\"$ref\": \"#/components/schemas/Pet\"}}, \"application/json\": {\"schema\": {\"$ref\": \"#/components/schemas/Pet\"}}}}, \"400\": {\"description\": \"Invalid ID supplied\"}, \"404\": {\"description\": \"Pet not found\"}}, \"security\": [{\"api_key\": []}, {\"petstore_auth\": [\"write:pets\", \"read:pets\"]}]}, \"post\": {\"tags\": [\"pet\"], \"summary\": \"Updates a pet in the store with form data\", \"description\": \"\", \"operationId\": \"updatePetWithForm\", \"parameters\": [{\"name\": \"petId\", \"in\": \"path\", \"description\": \"ID of pet that needs to be updated\", \"required\": true, \"schema\": {\"type\": \"integer\", \"format\": \"int64\"}}, {\"name\": \"name\", \"in\": \"query\", \"description\": \"Name of pet that needs to be updated\", \"schema\": {\"type\": \"string\"}}, {\"name\": \"status\", \"in\": \"query\", \"description\": \"Status of pet that needs to be updated\", \"schema\": {\"type\": \"string\"}}], \"responses\": {\"405\": {\"description\": \"Invalid input\"}}, \"security\": [{\"petstore_auth\": [\"write:pets\", \"read:pets\"]}]}, \"delete\": {\"tags\": [\"pet\"], \"summary\": \"Deletes a pet\", \"description\": \"\", \"operationId\": \"deletePet\", \"parameters\": [{\"name\": \"api_key\", \"in\": \"header\", \"description\": \"\", \"required\": false, \"schema\": {\"type\": \"string\"}}, {\"name\": \"petId\", \"in\": \"path\", \"description\": \"Pet id to delete\", \"required\": true, \"schema\": {\"type\": \"integer\", \"format\": \"int64\"}}], \"responses\": {\"400\": {\"description\": \"Invalid pet value\"}}, \"security\": [{\"petstore_auth\": [\"write:pets\", \"read:pets\"]}]}}, \"/pet/{petId}/uploadImage\": {\"post\": {\"tags\": [\"pet\"], \"summary\": \"uploads an image\", \"description\": \"\", \"operationId\": \"uploadFile\", \"parameters\": [{\"name\": \"petId\", \"in\": \"path\", \"description\": \"ID of pet to update\", \"required\": true, \"schema\": {\"type\": \"integer\", \"format\": \"int64\"}}, {\"name\": \"additionalMetadata\", \"in\": \"query\", \"description\": \"Additional Metadata\", \"required\": false, \"schema\": {\"type\": \"string\"}}], \"requestBody\": {\"content\": {\"application/octet-stream\": {\"schema\": {\"type\": \"string\", \"format\": \"binary\"}}}}, \"responses\": {\"200\": {\"description\": \"successful operation\", \"content\": {\"application/json\": {\"schema\": {\"$ref\": \"#/components/schemas/ApiResponse\"}}}}}, \"security\": [{\"petstore_auth\": [\"write:pets\", \"read:pets\"]}]}}, \"/store/inventory\": {\"get\": {\"tags\": [\"store\"], \"summary\": \"Returns pet inventories by status\", \"description\": \"Returns a map of status codes to quantities\", \"operationId\": \"getInventory\", \"responses\": {\"200\": {\"description\": \"successful operation\", \"content\": {\"application/json\": {\"schema\": {\"type\": \"object\", \"additionalProperties\": {\"type\": \"integer\", \"format\": \"int32\"}}}}}}, \"security\": [{\"api_key\": []}]}}, \"/store/order\": {\"post\": {\"tags\": [\"store\"], \"summary\": \"Place an order for a pet\", \"description\": \"Place a new order in the store\", \"operationId\": \"placeOrder\", \"requestBody\": {\"content\": {\"application/json\": {\"schema\": {\"$ref\": \"#/components/schemas/Order\"}}, \"application/xml\": {\"schema\": {\"$ref\": \"#/components/schemas/Order\"}}, \"application/x-www-form-urlencoded\": {\"schema\": {\"$ref\": \"#/components/schemas/Order\"}}}}, \"responses\": {\"200\": {\"description\": \"successful operation\", \"content\": {\"application/json\": {\"schema\": {\"$ref\": \"#/components/schemas/Order\"}}}}, \"405\": {\"description\": \"Invalid input\"}}}}, \"/store/order/{orderId}\": {\"get\": {\"tags\": [\"store\"], \"summary\": \"Find purchase order by ID\", \"description\": \"For valid response try integer IDs with value <= 5 or > 10. Other values will generate exceptions.\", \"operationId\": \"getOrderById\", \"parameters\": [{\"name\": \"orderId\", \"in\": \"path\", \"description\": \"ID of order that needs to be fetched\", \"required\": true, \"schema\": {\"type\": \"integer\", \"format\": \"int64\"}}], \"responses\": {\"200\": {\"description\": \"successful operation\", \"content\": {\"application/xml\": {\"schema\": {\"$ref\": \"#/components/schemas/Order\"}}, \"application/json\": {\"schema\": {\"$ref\": \"#/components/schemas/Order\"}}}}, \"400\": {\"description\": \"Invalid ID supplied\"}, \"404\": {\"description\": \"Order not found\"}}}, \"delete\": {\"tags\": [\"store\"], \"summary\": \"Delete purchase order by ID\", \"description\": \"For valid response try integer IDs with value < 1000. Anything above 1000 or nonintegers will generate API errors\", \"operationId\": \"deleteOrder\", \"parameters\": [{\"name\": \"orderId\", \"in\": \"path\", \"description\": \"ID of the order that needs to be deleted\", \"required\": true, \"schema\": {\"type\": \"integer\", \"format\": \"int64\"}}], \"responses\": {\"400\": {\"description\": \"Invalid ID supplied\"}, \"404\": {\"description\": \"Order not found\"}}}}, \"/user\": {\"post\": {\"tags\": [\"user\"], \"summary\": \"Create user\", \"description\": \"This can only be done by the logged in user.\", \"operationId\": \"createUser\", \"requestBody\": {\"description\": \"Created user object\", \"content\": {\"application/json\": {\"schema\": {\"$ref\": \"#/components/schemas/User\"}}, \"application/xml\": {\"schema\": {\"$ref\": \"#/components/schemas/User\"}}, \"application/x-www-form-urlencoded\": {\"schema\": {\"$ref\": \"#/components/schemas/User\"}}}}, \"responses\": {\"default\": {\"description\": \"successful operation\", \"content\": {\"application/json\": {\"schema\": {\"$ref\": \"#/components/schemas/User\"}}, \"application/xml\": {\"schema\": {\"$ref\": \"#/components/schemas/User\"}}}}}}}, \"/user/createWithList\": {\"post\": {\"tags\": [\"user\"], \"summary\": \"Creates list of users with given input array\", \"description\": \"Creates list of users with given input array\", \"operationId\": \"createUsersWithListInput\", \"requestBody\": {\"content\": {\"application/json\": {\"schema\": {\"type\": \"array\", \"items\": {\"$ref\": \"#/components/schemas/User\"}}}}}, \"responses\": {\"200\": {\"description\": \"Successful operation\", \"content\": {\"application/xml\": {\"schema\": {\"$ref\": \"#/components/schemas/User\"}}, \"application/json\": {\"schema\": {\"$ref\": \"#/components/schemas/User\"}}}}, \"default\": {\"description\": \"successful operation\"}}}}, \"/user/login\": {\"get\": {\"tags\": [\"user\"], \"summary\": \"Logs user into the system\", \"description\": \"\", \"operationId\": \"loginUser\", \"parameters\": [{\"name\": \"username\", \"in\": \"query\", \"description\": \"The user name for login\", \"required\": false, \"schema\": {\"type\": \"string\"}}, {\"name\": \"password\", \"in\": \"query\", \"description\": \"The password for login in clear text\", \"required\": false, \"schema\": {\"type\": \"string\"}}], \"responses\": {\"200\": {\"description\": \"successful operation\", \"headers\": {\"X-Rate-Limit\": {\"description\": \"calls per hour allowed by the user\", \"schema\": {\"type\": \"integer\", \"format\": \"int32\"}}, \"X-Expires-After\": {\"description\": \"date in UTC when token expires\", \"schema\": {\"type\": \"string\", \"format\": \"date-time\"}}}, \"content\": {\"application/xml\": {\"schema\": {\"type\": \"string\"}}, \"application/json\": {\"schema\": {\"type\": \"string\"}}}}, \"400\": {\"description\": \"Invalid username/password supplied\"}}}}, \"/user/logout\": {\"get\": {\"tags\": [\"user\"], \"summary\": \"Logs out current logged in user session\", \"description\": \"\", \"operationId\": \"logoutUser\", \"parameters\": [], \"responses\": {\"default\": {\"description\": \"successful operation\"}}}}, \"/user/{username}\": {\"get\": {\"tags\": [\"user\"], \"summary\": \"Get user by user name\", \"description\": \"\", \"operationId\": \"getUserByName\", \"parameters\": [{\"name\": \"username\", \"in\": \"path\", \"description\": \"The name that needs to be fetched. Use user1 for testing. \", \"required\": true, \"schema\": {\"type\": \"string\"}}], \"responses\": {\"200\": {\"description\": \"successful operation\", \"content\": {\"application/xml\": {\"schema\": {\"$ref\": \"#/components/schemas/User\"}}, \"application/json\": {\"schema\": {\"$ref\": \"#/components/schemas/User\"}}}}, \"400\": {\"description\": \"Invalid username supplied\"}, \"404\": {\"description\": \"User not found\"}}}, \"put\": {\"tags\": [\"user\"], \"summary\": \"Update user\", \"description\": \"This can only be done by the logged in user.\", \"operationId\": \"updateUser\", \"parameters\": [{\"name\": \"username\", \"in\": \"path\", \"description\": \"name that needs to be updated\", \"required\": true, \"schema\": {\"type\": \"string\"}}], \"requestBody\": {\"description\": \"Update an existent user in the store\", \"content\": {\"application/json\": {\"schema\": {\"$ref\": \"#/components/schemas/User\"}}, \"application/xml\": {\"schema\": {\"$ref\": \"#/components/schemas/User\"}}, \"application/x-www-form-urlencoded\": {\"schema\": {\"$ref\": \"#/components/schemas/User\"}}}}, \"responses\": {\"default\": {\"description\": \"successful operation\"}}}, \"delete\": {\"tags\": [\"user\"], \"summary\": \"Delete user\", \"description\": \"This can only be done by the logged in user.\", \"operationId\": \"deleteUser\", \"parameters\": [{\"name\": \"username\", \"in\": \"path\", \"description\": \"The name that needs to be deleted\", \"required\": true, \"schema\": {\"type\": \"string\"}}], \"responses\": {\"400\": {\"description\": \"Invalid username supplied\"}, \"404\": {\"description\": \"User not found\"}}}}}, \"components\": {\"schemas\": {\"Order\": {\"type\": \"object\", \"properties\": {\"id\": {\"type\": \"integer\", \"format\": \"int64\", \"example\": 10}, \"petId\": {\"type\": \"integer\", \"format\": \"int64\", \"example\": 198772}, \"quantity\": {\"type\": \"integer\", \"format\": \"int32\", \"example\": 7}, \"shipDate\": {\"type\": \"string\", \"format\": \"date-time\"}, \"status\": {\"type\": \"string\", \"description\": \"Order Status\", \"example\": \"approved\", \"enum\": [\"placed\", \"approved\", \"delivered\"]}, \"complete\": {\"type\": \"boolean\"}}, \"xml\": {\"name\": \"order\"}}, \"Customer\": {\"type\": \"object\", \"properties\": {\"id\": {\"type\": \"integer\", \"format\": \"int64\", \"example\": 100000}, \"username\": {\"type\": \"string\", \"example\": \"fehguy\"}, \"address\": {\"type\": \"array\", \"xml\": {\"name\": \"addresses\", \"wrapped\": true}, \"items\": {\"$ref\": \"#/components/schemas/Address\"}}}, \"xml\": {\"name\": \"customer\"}}, \"Address\": {\"type\": \"object\", \"properties\": {\"street\": {\"type\": \"string\", \"example\": \"437 Lytton\"}, \"city\": {\"type\": \"string\", \"example\": \"Palo Alto\"}, \"state\": {\"type\": \"string\", \"example\": \"CA\"}, \"zip\": {\"type\": \"string\", \"example\": \"94301\"}}, \"xml\": {\"name\": \"address\"}}, \"Category\": {\"type\": \"object\", \"properties\": {\"id\": {\"type\": \"integer\", \"format\": \"int64\", \"example\": 1}, \"name\": {\"type\": \"string\", \"example\": \"Dogs\"}}, \"xml\": {\"name\": \"category\"}}, \"User\": {\"type\": \"object\", \"properties\": {\"id\": {\"type\": \"integer\", \"format\": \"int64\", \"example\": 10}, \"username\": {\"type\": \"string\", \"example\": \"theUser\"}, \"firstName\": {\"type\": \"string\", \"example\": \"John\"}, \"lastName\": {\"type\": \"string\", \"example\": \"James\"}, \"email\": {\"type\": \"string\", \"example\": \"john@email.com\"}, \"password\": {\"type\": \"string\", \"example\": \"12345\"}, \"phone\": {\"type\": \"string\", \"example\": \"12345\"}, \"userStatus\": {\"type\": \"integer\", \"description\": \"User Status\", \"format\": \"int32\", \"example\": 1}}, \"xml\": {\"name\": \"user\"}}, \"Tag\": {\"type\": \"object\", \"properties\": {\"id\": {\"type\": \"integer\", \"format\": \"int64\"}, \"name\": {\"type\": \"string\"}}, \"xml\": {\"name\": \"tag\"}}, \"Pet\": {\"required\": [\"name\", \"photoUrls\"], \"type\": \"object\", \"properties\": {\"id\": {\"type\": \"integer\", \"format\": \"int64\", \"example\": 10}, \"name\": {\"type\": \"string\", \"example\": \"doggie\"}, \"category\": {\"$ref\": \"#/components/schemas/Category\"}, \"photoUrls\": {\"type\": \"array\", \"xml\": {\"wrapped\": true}, \"items\": {\"type\": \"string\", \"xml\": {\"name\": \"photoUrl\"}}}, \"tags\": {\"type\": \"array\", \"xml\": {\"wrapped\": true}, \"items\": {\"$ref\": \"#/components/schemas/Tag\"}}, \"status\": {\"type\": \"string\", \"description\": \"pet status in the store\", \"enum\": [\"available\", \"pending\", \"sold\"]}}, \"xml\": {\"name\": \"pet\"}}, \"ApiResponse\": {\"type\": \"object\", \"properties\": {\"code\": {\"type\": \"integer\", \"format\": \"int32\"}, \"type\": {\"type\": \"string\"}, \"message\": {\"type\": \"string\"}}, \"xml\": {\"name\": \"##default\"}}}, \"requestBodies\": {\"Pet\": {\"description\": \"Pet object that needs to be added to the store\", \"content\": {\"application/json\": {\"schema\": {\"$ref\": \"#/components/schemas/Pet\"}}, \"application/xml\": {\"schema\": {\"$ref\": \"#/components/schemas/Pet\"}}}}, \"UserArray\": {\"description\": \"List of user object\", \"content\": {\"application/json\": {\"schema\": {\"type\": \"array\", \"items\": {\"$ref\": \"#/components/schemas/User\"}}}}}}, \"securitySchemes\": {\"petstore_auth\": {\"type\": \"oauth2\", \"flows\": {\"implicit\": {\"authorizationUrl\": \"https://petstore3.swagger.io/oauth/authorize\", \"scopes\": {\"write:pets\": \"modify pets in your account\", \"read:pets\": \"read your pets\"}}}}, \"api_key\": {\"type\": \"apiKey\", \"name\": \"api_key\", \"in\": \"header\"}}}}" + }, + "type": "OPEN_API" + }, + "logicalId": { + "path": [ + "OPEN_API", + "3a45981ff1a3539f49317040f895e979" + ] + } + }, + { + "logicalId": { + "name": "https://foo.bar/api/v3/pet", + "platform": "OPEN_API" + }, + "openAPI": { + "methods": [ + { + "description": "Update an existing pet by Id", + "summary": "Update an existing pet", + "type": "PUT" + }, + { + "description": "Add a new pet to the store", + "summary": "Add a new pet to the store", + "type": "POST" + } + ], + "path": "/pet" + }, + "structure": { + "directories": [ + "3a45981ff1a3539f49317040f895e979" + ], + "name": "/pet" + } + }, + { + "logicalId": { + "name": "https://foo.bar/api/v3/pet/findByStatus", + "platform": "OPEN_API" + }, + "openAPI": { + "methods": [ + { + "description": "Multiple status values can be provided with comma separated strings", + "summary": "Finds Pets by status", + "type": "GET" + } + ], + "path": "/pet/findByStatus" + }, + "structure": { + "directories": [ + "3a45981ff1a3539f49317040f895e979" + ], + "name": "/pet/findByStatus" + } + }, + { + "logicalId": { + "name": "https://foo.bar/api/v3/pet/findByTags", + "platform": "OPEN_API" + }, + "openAPI": { + "methods": [ + { + "description": "Multiple tags can be provided with comma separated strings. Use tag1, tag2, tag3 for testing.", + "summary": "Finds Pets by tags", + "type": "GET" + } + ], + "path": "/pet/findByTags" + }, + "structure": { + "directories": [ + "3a45981ff1a3539f49317040f895e979" + ], + "name": "/pet/findByTags" + } + }, + { + "logicalId": { + "name": "https://foo.bar/api/v3/pet/{petId}", + "platform": "OPEN_API" + }, + "openAPI": { + "methods": [ + { + "description": "Returns a single pet", + "summary": "Find pet by ID", + "type": "GET" + }, + { + "summary": "Updates a pet in the store with form data", + "type": "POST" + }, + { + "summary": "Deletes a pet", + "type": "DELETE" + } + ], + "path": "/pet/{petId}" + }, + "structure": { + "directories": [ + "3a45981ff1a3539f49317040f895e979" + ], + "name": "/pet/{petId}" + } + }, + { + "logicalId": { + "name": "https://foo.bar/api/v3/pet/{petId}/uploadImage", + "platform": "OPEN_API" + }, + "openAPI": { + "methods": [ + { + "summary": "uploads an image", + "type": "POST" + } + ], + "path": "/pet/{petId}/uploadImage" + }, + "structure": { + "directories": [ + "3a45981ff1a3539f49317040f895e979" + ], + "name": "/pet/{petId}/uploadImage" + } + }, + { + "logicalId": { + "name": "https://foo.bar/api/v3/store/inventory", + "platform": "OPEN_API" + }, + "openAPI": { + "methods": [ + { + "description": "Returns a map of status codes to quantities", + "summary": "Returns pet inventories by status", + "type": "GET" + } + ], + "path": "/store/inventory" + }, + "structure": { + "directories": [ + "3a45981ff1a3539f49317040f895e979" + ], + "name": "/store/inventory" + } + }, + { + "logicalId": { + "name": "https://foo.bar/api/v3/store/order", + "platform": "OPEN_API" + }, + "openAPI": { + "methods": [ + { + "description": "Place a new order in the store", + "summary": "Place an order for a pet", + "type": "POST" + } + ], + "path": "/store/order" + }, + "structure": { + "directories": [ + "3a45981ff1a3539f49317040f895e979" + ], + "name": "/store/order" + } + }, + { + "logicalId": { + "name": "https://foo.bar/api/v3/store/order/{orderId}", + "platform": "OPEN_API" + }, + "openAPI": { + "methods": [ + { + "description": "For valid response try integer IDs with value <= 5 or > 10. Other values will generate exceptions.", + "summary": "Find purchase order by ID", + "type": "GET" + }, + { + "description": "For valid response try integer IDs with value < 1000. Anything above 1000 or nonintegers will generate API errors", + "summary": "Delete purchase order by ID", + "type": "DELETE" + } + ], + "path": "/store/order/{orderId}" + }, + "structure": { + "directories": [ + "3a45981ff1a3539f49317040f895e979" + ], + "name": "/store/order/{orderId}" + } + }, + { + "logicalId": { + "name": "https://foo.bar/api/v3/user", + "platform": "OPEN_API" + }, + "openAPI": { + "methods": [ + { + "description": "This can only be done by the logged in user.", + "summary": "Create user", + "type": "POST" + } + ], + "path": "/user" + }, + "structure": { + "directories": [ + "3a45981ff1a3539f49317040f895e979" + ], + "name": "/user" + } + }, + { + "logicalId": { + "name": "https://foo.bar/api/v3/user/createWithList", + "platform": "OPEN_API" + }, + "openAPI": { + "methods": [ + { + "description": "Creates list of users with given input array", + "summary": "Creates list of users with given input array", + "type": "POST" + } + ], + "path": "/user/createWithList" + }, + "structure": { + "directories": [ + "3a45981ff1a3539f49317040f895e979" + ], + "name": "/user/createWithList" + } + }, + { + "logicalId": { + "name": "https://foo.bar/api/v3/user/login", + "platform": "OPEN_API" + }, + "openAPI": { + "methods": [ + { + "summary": "Logs user into the system", + "type": "GET" + } + ], + "path": "/user/login" + }, + "structure": { + "directories": [ + "3a45981ff1a3539f49317040f895e979" + ], + "name": "/user/login" + } + }, + { + "logicalId": { + "name": "https://foo.bar/api/v3/user/logout", + "platform": "OPEN_API" + }, + "openAPI": { + "methods": [ + { + "summary": "Logs out current logged in user session", + "type": "GET" + } + ], + "path": "/user/logout" + }, + "structure": { + "directories": [ + "3a45981ff1a3539f49317040f895e979" + ], + "name": "/user/logout" + } + }, + { + "logicalId": { + "name": "https://foo.bar/api/v3/user/{username}", + "platform": "OPEN_API" + }, + "openAPI": { + "methods": [ + { + "summary": "Get user by user name", + "type": "GET" + }, + { + "description": "This can only be done by the logged in user.", + "summary": "Update user", + "type": "PUT" + }, + { + "description": "This can only be done by the logged in user.", + "summary": "Delete user", + "type": "DELETE" + } + ], + "path": "/user/{username}" + }, + "structure": { + "directories": [ + "3a45981ff1a3539f49317040f895e979" + ], + "name": "/user/{username}" + } + } +] diff --git a/tests/openapi/data/pet_store_30/openapi.json b/tests/openapi/data/pet_store_30/openapi.json new file mode 100644 index 00000000..d84de512 --- /dev/null +++ b/tests/openapi/data/pet_store_30/openapi.json @@ -0,0 +1,1225 @@ +{ + "openapi": "3.0.2", + "info": { + "title": "Swagger Petstore - OpenAPI 3.0", + "description": "This is a sample Pet Store Server based on the OpenAPI 3.0 specification. You can find out more about\nSwagger at [http://swagger.io](http://swagger.io). In the third iteration of the pet store, we've switched to the design first approach!\nYou can now help us improve the API whether it's by making changes to the definition itself or to the code.\nThat way, with time, we can improve the API in general, and expose some of the new features in OAS3.\n\nSome useful links:\n- [The Pet Store repository](https://github.com/swagger-api/swagger-petstore)\n- [The source API definition for the Pet Store](https://github.com/swagger-api/swagger-petstore/blob/master/src/main/resources/openapi.yaml)", + "termsOfService": "http://swagger.io/terms/", + "contact": { + "email": "apiteam@swagger.io" + }, + "license": { + "name": "Apache 2.0", + "url": "http://www.apache.org/licenses/LICENSE-2.0.html" + }, + "version": "1.0.19" + }, + "externalDocs": { + "description": "Find out more about Swagger", + "url": "http://swagger.io" + }, + "servers": [ + { + "url": "/api/v3" + } + ], + "tags": [ + { + "name": "pet", + "description": "Everything about your Pets", + "externalDocs": { + "description": "Find out more", + "url": "http://swagger.io" + } + }, + { + "name": "store", + "description": "Access to Petstore orders", + "externalDocs": { + "description": "Find out more about our store", + "url": "http://swagger.io" + } + }, + { + "name": "user", + "description": "Operations about user" + } + ], + "paths": { + "/pet": { + "put": { + "tags": [ + "pet" + ], + "summary": "Update an existing pet", + "description": "Update an existing pet by Id", + "operationId": "updatePet", + "requestBody": { + "description": "Update an existent pet in the store", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Pet" + } + }, + "application/xml": { + "schema": { + "$ref": "#/components/schemas/Pet" + } + }, + "application/x-www-form-urlencoded": { + "schema": { + "$ref": "#/components/schemas/Pet" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "Successful operation", + "content": { + "application/xml": { + "schema": { + "$ref": "#/components/schemas/Pet" + } + }, + "application/json": { + "schema": { + "$ref": "#/components/schemas/Pet" + } + } + } + }, + "400": { + "description": "Invalid ID supplied" + }, + "404": { + "description": "Pet not found" + }, + "405": { + "description": "Validation exception" + } + }, + "security": [ + { + "petstore_auth": [ + "write:pets", + "read:pets" + ] + } + ] + }, + "post": { + "tags": [ + "pet" + ], + "summary": "Add a new pet to the store", + "description": "Add a new pet to the store", + "operationId": "addPet", + "requestBody": { + "description": "Create a new pet in the store", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Pet" + } + }, + "application/xml": { + "schema": { + "$ref": "#/components/schemas/Pet" + } + }, + "application/x-www-form-urlencoded": { + "schema": { + "$ref": "#/components/schemas/Pet" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "Successful operation", + "content": { + "application/xml": { + "schema": { + "$ref": "#/components/schemas/Pet" + } + }, + "application/json": { + "schema": { + "$ref": "#/components/schemas/Pet" + } + } + } + }, + "405": { + "description": "Invalid input" + } + }, + "security": [ + { + "petstore_auth": [ + "write:pets", + "read:pets" + ] + } + ] + } + }, + "/pet/findByStatus": { + "get": { + "tags": [ + "pet" + ], + "summary": "Finds Pets by status", + "description": "Multiple status values can be provided with comma separated strings", + "operationId": "findPetsByStatus", + "parameters": [ + { + "name": "status", + "in": "query", + "description": "Status values that need to be considered for filter", + "required": false, + "explode": true, + "schema": { + "type": "string", + "default": "available", + "enum": [ + "available", + "pending", + "sold" + ] + } + } + ], + "responses": { + "200": { + "description": "successful operation", + "content": { + "application/xml": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Pet" + } + } + }, + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Pet" + } + } + } + } + }, + "400": { + "description": "Invalid status value" + } + }, + "security": [ + { + "petstore_auth": [ + "write:pets", + "read:pets" + ] + } + ] + } + }, + "/pet/findByTags": { + "get": { + "tags": [ + "pet" + ], + "summary": "Finds Pets by tags", + "description": "Multiple tags can be provided with comma separated strings. Use tag1, tag2, tag3 for testing.", + "operationId": "findPetsByTags", + "parameters": [ + { + "name": "tags", + "in": "query", + "description": "Tags to filter by", + "required": false, + "explode": true, + "schema": { + "type": "array", + "items": { + "type": "string" + } + } + } + ], + "responses": { + "200": { + "description": "successful operation", + "content": { + "application/xml": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Pet" + } + } + }, + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Pet" + } + } + } + } + }, + "400": { + "description": "Invalid tag value" + } + }, + "security": [ + { + "petstore_auth": [ + "write:pets", + "read:pets" + ] + } + ] + } + }, + "/pet/{petId}": { + "get": { + "tags": [ + "pet" + ], + "summary": "Find pet by ID", + "description": "Returns a single pet", + "operationId": "getPetById", + "parameters": [ + { + "name": "petId", + "in": "path", + "description": "ID of pet to return", + "required": true, + "schema": { + "type": "integer", + "format": "int64" + } + } + ], + "responses": { + "200": { + "description": "successful operation", + "content": { + "application/xml": { + "schema": { + "$ref": "#/components/schemas/Pet" + } + }, + "application/json": { + "schema": { + "$ref": "#/components/schemas/Pet" + } + } + } + }, + "400": { + "description": "Invalid ID supplied" + }, + "404": { + "description": "Pet not found" + } + }, + "security": [ + { + "api_key": [] + }, + { + "petstore_auth": [ + "write:pets", + "read:pets" + ] + } + ] + }, + "post": { + "tags": [ + "pet" + ], + "summary": "Updates a pet in the store with form data", + "description": "", + "operationId": "updatePetWithForm", + "parameters": [ + { + "name": "petId", + "in": "path", + "description": "ID of pet that needs to be updated", + "required": true, + "schema": { + "type": "integer", + "format": "int64" + } + }, + { + "name": "name", + "in": "query", + "description": "Name of pet that needs to be updated", + "schema": { + "type": "string" + } + }, + { + "name": "status", + "in": "query", + "description": "Status of pet that needs to be updated", + "schema": { + "type": "string" + } + } + ], + "responses": { + "405": { + "description": "Invalid input" + } + }, + "security": [ + { + "petstore_auth": [ + "write:pets", + "read:pets" + ] + } + ] + }, + "delete": { + "tags": [ + "pet" + ], + "summary": "Deletes a pet", + "description": "", + "operationId": "deletePet", + "parameters": [ + { + "name": "api_key", + "in": "header", + "description": "", + "required": false, + "schema": { + "type": "string" + } + }, + { + "name": "petId", + "in": "path", + "description": "Pet id to delete", + "required": true, + "schema": { + "type": "integer", + "format": "int64" + } + } + ], + "responses": { + "400": { + "description": "Invalid pet value" + } + }, + "security": [ + { + "petstore_auth": [ + "write:pets", + "read:pets" + ] + } + ] + } + }, + "/pet/{petId}/uploadImage": { + "post": { + "tags": [ + "pet" + ], + "summary": "uploads an image", + "description": "", + "operationId": "uploadFile", + "parameters": [ + { + "name": "petId", + "in": "path", + "description": "ID of pet to update", + "required": true, + "schema": { + "type": "integer", + "format": "int64" + } + }, + { + "name": "additionalMetadata", + "in": "query", + "description": "Additional Metadata", + "required": false, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "content": { + "application/octet-stream": { + "schema": { + "type": "string", + "format": "binary" + } + } + } + }, + "responses": { + "200": { + "description": "successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiResponse" + } + } + } + } + }, + "security": [ + { + "petstore_auth": [ + "write:pets", + "read:pets" + ] + } + ] + } + }, + "/store/inventory": { + "get": { + "tags": [ + "store" + ], + "summary": "Returns pet inventories by status", + "description": "Returns a map of status codes to quantities", + "operationId": "getInventory", + "responses": { + "200": { + "description": "successful operation", + "content": { + "application/json": { + "schema": { + "type": "object", + "additionalProperties": { + "type": "integer", + "format": "int32" + } + } + } + } + } + }, + "security": [ + { + "api_key": [] + } + ] + } + }, + "/store/order": { + "post": { + "tags": [ + "store" + ], + "summary": "Place an order for a pet", + "description": "Place a new order in the store", + "operationId": "placeOrder", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Order" + } + }, + "application/xml": { + "schema": { + "$ref": "#/components/schemas/Order" + } + }, + "application/x-www-form-urlencoded": { + "schema": { + "$ref": "#/components/schemas/Order" + } + } + } + }, + "responses": { + "200": { + "description": "successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Order" + } + } + } + }, + "405": { + "description": "Invalid input" + } + } + } + }, + "/store/order/{orderId}": { + "get": { + "tags": [ + "store" + ], + "summary": "Find purchase order by ID", + "description": "For valid response try integer IDs with value <= 5 or > 10. Other values will generate exceptions.", + "operationId": "getOrderById", + "parameters": [ + { + "name": "orderId", + "in": "path", + "description": "ID of order that needs to be fetched", + "required": true, + "schema": { + "type": "integer", + "format": "int64" + } + } + ], + "responses": { + "200": { + "description": "successful operation", + "content": { + "application/xml": { + "schema": { + "$ref": "#/components/schemas/Order" + } + }, + "application/json": { + "schema": { + "$ref": "#/components/schemas/Order" + } + } + } + }, + "400": { + "description": "Invalid ID supplied" + }, + "404": { + "description": "Order not found" + } + } + }, + "delete": { + "tags": [ + "store" + ], + "summary": "Delete purchase order by ID", + "description": "For valid response try integer IDs with value < 1000. Anything above 1000 or nonintegers will generate API errors", + "operationId": "deleteOrder", + "parameters": [ + { + "name": "orderId", + "in": "path", + "description": "ID of the order that needs to be deleted", + "required": true, + "schema": { + "type": "integer", + "format": "int64" + } + } + ], + "responses": { + "400": { + "description": "Invalid ID supplied" + }, + "404": { + "description": "Order not found" + } + } + } + }, + "/user": { + "post": { + "tags": [ + "user" + ], + "summary": "Create user", + "description": "This can only be done by the logged in user.", + "operationId": "createUser", + "requestBody": { + "description": "Created user object", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/User" + } + }, + "application/xml": { + "schema": { + "$ref": "#/components/schemas/User" + } + }, + "application/x-www-form-urlencoded": { + "schema": { + "$ref": "#/components/schemas/User" + } + } + } + }, + "responses": { + "default": { + "description": "successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/User" + } + }, + "application/xml": { + "schema": { + "$ref": "#/components/schemas/User" + } + } + } + } + } + } + }, + "/user/createWithList": { + "post": { + "tags": [ + "user" + ], + "summary": "Creates list of users with given input array", + "description": "Creates list of users with given input array", + "operationId": "createUsersWithListInput", + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/User" + } + } + } + } + }, + "responses": { + "200": { + "description": "Successful operation", + "content": { + "application/xml": { + "schema": { + "$ref": "#/components/schemas/User" + } + }, + "application/json": { + "schema": { + "$ref": "#/components/schemas/User" + } + } + } + }, + "default": { + "description": "successful operation" + } + } + } + }, + "/user/login": { + "get": { + "tags": [ + "user" + ], + "summary": "Logs user into the system", + "description": "", + "operationId": "loginUser", + "parameters": [ + { + "name": "username", + "in": "query", + "description": "The user name for login", + "required": false, + "schema": { + "type": "string" + } + }, + { + "name": "password", + "in": "query", + "description": "The password for login in clear text", + "required": false, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "successful operation", + "headers": { + "X-Rate-Limit": { + "description": "calls per hour allowed by the user", + "schema": { + "type": "integer", + "format": "int32" + } + }, + "X-Expires-After": { + "description": "date in UTC when token expires", + "schema": { + "type": "string", + "format": "date-time" + } + } + }, + "content": { + "application/xml": { + "schema": { + "type": "string" + } + }, + "application/json": { + "schema": { + "type": "string" + } + } + } + }, + "400": { + "description": "Invalid username/password supplied" + } + } + } + }, + "/user/logout": { + "get": { + "tags": [ + "user" + ], + "summary": "Logs out current logged in user session", + "description": "", + "operationId": "logoutUser", + "parameters": [], + "responses": { + "default": { + "description": "successful operation" + } + } + } + }, + "/user/{username}": { + "get": { + "tags": [ + "user" + ], + "summary": "Get user by user name", + "description": "", + "operationId": "getUserByName", + "parameters": [ + { + "name": "username", + "in": "path", + "description": "The name that needs to be fetched. Use user1 for testing. ", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "successful operation", + "content": { + "application/xml": { + "schema": { + "$ref": "#/components/schemas/User" + } + }, + "application/json": { + "schema": { + "$ref": "#/components/schemas/User" + } + } + } + }, + "400": { + "description": "Invalid username supplied" + }, + "404": { + "description": "User not found" + } + } + }, + "put": { + "tags": [ + "user" + ], + "summary": "Update user", + "description": "This can only be done by the logged in user.", + "operationId": "updateUser", + "parameters": [ + { + "name": "username", + "in": "path", + "description": "name that needs to be updated", + "required": true, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "description": "Update an existent user in the store", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/User" + } + }, + "application/xml": { + "schema": { + "$ref": "#/components/schemas/User" + } + }, + "application/x-www-form-urlencoded": { + "schema": { + "$ref": "#/components/schemas/User" + } + } + } + }, + "responses": { + "default": { + "description": "successful operation" + } + } + }, + "delete": { + "tags": [ + "user" + ], + "summary": "Delete user", + "description": "This can only be done by the logged in user.", + "operationId": "deleteUser", + "parameters": [ + { + "name": "username", + "in": "path", + "description": "The name that needs to be deleted", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "400": { + "description": "Invalid username supplied" + }, + "404": { + "description": "User not found" + } + } + } + } + }, + "components": { + "schemas": { + "Order": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "format": "int64", + "example": 10 + }, + "petId": { + "type": "integer", + "format": "int64", + "example": 198772 + }, + "quantity": { + "type": "integer", + "format": "int32", + "example": 7 + }, + "shipDate": { + "type": "string", + "format": "date-time" + }, + "status": { + "type": "string", + "description": "Order Status", + "example": "approved", + "enum": [ + "placed", + "approved", + "delivered" + ] + }, + "complete": { + "type": "boolean" + } + }, + "xml": { + "name": "order" + } + }, + "Customer": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "format": "int64", + "example": 100000 + }, + "username": { + "type": "string", + "example": "fehguy" + }, + "address": { + "type": "array", + "xml": { + "name": "addresses", + "wrapped": true + }, + "items": { + "$ref": "#/components/schemas/Address" + } + } + }, + "xml": { + "name": "customer" + } + }, + "Address": { + "type": "object", + "properties": { + "street": { + "type": "string", + "example": "437 Lytton" + }, + "city": { + "type": "string", + "example": "Palo Alto" + }, + "state": { + "type": "string", + "example": "CA" + }, + "zip": { + "type": "string", + "example": "94301" + } + }, + "xml": { + "name": "address" + } + }, + "Category": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "format": "int64", + "example": 1 + }, + "name": { + "type": "string", + "example": "Dogs" + } + }, + "xml": { + "name": "category" + } + }, + "User": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "format": "int64", + "example": 10 + }, + "username": { + "type": "string", + "example": "theUser" + }, + "firstName": { + "type": "string", + "example": "John" + }, + "lastName": { + "type": "string", + "example": "James" + }, + "email": { + "type": "string", + "example": "john@email.com" + }, + "password": { + "type": "string", + "example": "12345" + }, + "phone": { + "type": "string", + "example": "12345" + }, + "userStatus": { + "type": "integer", + "description": "User Status", + "format": "int32", + "example": 1 + } + }, + "xml": { + "name": "user" + } + }, + "Tag": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "format": "int64" + }, + "name": { + "type": "string" + } + }, + "xml": { + "name": "tag" + } + }, + "Pet": { + "required": [ + "name", + "photoUrls" + ], + "type": "object", + "properties": { + "id": { + "type": "integer", + "format": "int64", + "example": 10 + }, + "name": { + "type": "string", + "example": "doggie" + }, + "category": { + "$ref": "#/components/schemas/Category" + }, + "photoUrls": { + "type": "array", + "xml": { + "wrapped": true + }, + "items": { + "type": "string", + "xml": { + "name": "photoUrl" + } + } + }, + "tags": { + "type": "array", + "xml": { + "wrapped": true + }, + "items": { + "$ref": "#/components/schemas/Tag" + } + }, + "status": { + "type": "string", + "description": "pet status in the store", + "enum": [ + "available", + "pending", + "sold" + ] + } + }, + "xml": { + "name": "pet" + } + }, + "ApiResponse": { + "type": "object", + "properties": { + "code": { + "type": "integer", + "format": "int32" + }, + "type": { + "type": "string" + }, + "message": { + "type": "string" + } + }, + "xml": { + "name": "##default" + } + } + }, + "requestBodies": { + "Pet": { + "description": "Pet object that needs to be added to the store", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Pet" + } + }, + "application/xml": { + "schema": { + "$ref": "#/components/schemas/Pet" + } + } + } + }, + "UserArray": { + "description": "List of user object", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/User" + } + } + } + } + } + }, + "securitySchemes": { + "petstore_auth": { + "type": "oauth2", + "flows": { + "implicit": { + "authorizationUrl": "https://petstore3.swagger.io/oauth/authorize", + "scopes": { + "write:pets": "modify pets in your account", + "read:pets": "read your pets" + } + } + } + }, + "api_key": { + "type": "apiKey", + "name": "api_key", + "in": "header" + } + } + } +} diff --git a/tests/openapi/data/pet_store_31/expected.json b/tests/openapi/data/pet_store_31/expected.json new file mode 100644 index 00000000..0873f0bf --- /dev/null +++ b/tests/openapi/data/pet_store_31/expected.json @@ -0,0 +1,66 @@ +[ + { + "hierarchyInfo": { + "name": "Swagger Petstore - OpenAPI 3.1", + "openAPI": { + "definition": "{\"openapi\": \"3.1.0\", \"info\": {\"title\": \"Swagger Petstore - OpenAPI 3.1\", \"description\": \"This is a sample Pet Store Server based on the OpenAPI 3.1 specification.\\nYou can find out more about\\nSwagger at [http://swagger.io](http://swagger.io).\", \"termsOfService\": \"http://swagger.io/terms/\", \"contact\": {\"email\": \"apiteam@swagger.io\"}, \"license\": {\"name\": \"Apache 2.0 AND (MIT OR GPL-2.0-only)\", \"identifier\": \"Apache-2.0 AND (MIT OR GPL-2.0-only)\"}, \"version\": \"1.0.6\", \"summary\": \"Pet Store 3.1\", \"x-namespace\": \"swagger\"}, \"externalDocs\": {\"description\": \"Find out more about Swagger\", \"url\": \"http://swagger.io\"}, \"servers\": [{\"url\": \"/api/v31\"}], \"tags\": [{\"name\": \"pet\", \"description\": \"Everything about your Pets\", \"externalDocs\": {\"description\": \"Find out more\", \"url\": \"http://swagger.io\"}}, {\"name\": \"store\", \"description\": \"Access to Petstore orders\", \"externalDocs\": {\"description\": \"Find out more about our store\", \"url\": \"http://swagger.io\"}}, {\"name\": \"user\", \"description\": \"Operations about user\"}], \"paths\": {\"/pet\": {\"put\": {\"tags\": [\"pet\"], \"summary\": \"Update an existing pet\", \"description\": \"Update an existing pet by Id\", \"operationId\": \"updatePet\", \"requestBody\": {\"description\": \"Pet object that needs to be updated in the store\", \"content\": {\"application/json\": {\"schema\": {\"$ref\": \"#/components/schemas/Pet\", \"description\": \"A Pet in JSON Format\", \"required\": [\"id\"], \"writeOnly\": true}}, \"application/xml\": {\"schema\": {\"$ref\": \"#/components/schemas/Pet\", \"description\": \"A Pet in XML Format\", \"required\": [\"id\"], \"writeOnly\": true}}}, \"required\": true}, \"responses\": {\"200\": {\"description\": \"Successful operation\", \"content\": {\"application/xml\": {\"schema\": {\"$ref\": \"#/components/schemas/Pet\", \"description\": \"A Pet in XML Format\", \"readOnly\": true}}, \"application/json\": {\"schema\": {\"$ref\": \"#/components/schemas/Pet\", \"description\": \"A Pet in JSON Format\", \"readOnly\": true}}}}, \"400\": {\"description\": \"Invalid ID supplied\"}, \"404\": {\"description\": \"Pet not found\"}, \"405\": {\"description\": \"Validation exception\"}}, \"security\": [{\"petstore_auth\": [\"write:pets\", \"read:pets\"]}]}, \"post\": {\"tags\": [\"pet\"], \"summary\": \"Add a new pet to the store\", \"description\": \"Add a new pet to the store\", \"operationId\": \"addPet\", \"requestBody\": {\"description\": \"Create a new pet in the store\", \"content\": {\"application/json\": {\"schema\": {\"$ref\": \"#/components/schemas/Pet\", \"description\": \"A Pet in JSON Format\", \"required\": [\"id\"], \"writeOnly\": true}}, \"application/xml\": {\"schema\": {\"$ref\": \"#/components/schemas/Pet\", \"description\": \"A Pet in XML Format\", \"required\": [\"id\"], \"writeOnly\": true}}}, \"required\": true}, \"responses\": {\"200\": {\"description\": \"Successful operation\", \"content\": {\"application/xml\": {\"schema\": {\"$ref\": \"#/components/schemas/Pet\", \"description\": \"A Pet in XML Format\", \"readOnly\": true}}, \"application/json\": {\"schema\": {\"$ref\": \"#/components/schemas/Pet\", \"description\": \"A Pet in JSON Format\", \"readOnly\": true}}}}, \"405\": {\"description\": \"Invalid input\"}}, \"security\": [{\"petstore_auth\": [\"write:pets\", \"read:pets\"]}]}}, \"/pet/{petId}\": {\"get\": {\"tags\": [\"pets\"], \"summary\": \"Find pet by ID\", \"description\": \"Returns a pet when 0 < ID <= 10. ID > 10 or nonintegers will simulate API error conditions\", \"operationId\": \"getPetById\", \"parameters\": [{\"name\": \"petId\", \"in\": \"path\", \"description\": \"ID of pet that needs to be fetched\", \"required\": true, \"schema\": {\"type\": \"integer\", \"format\": \"int64\", \"description\": \"param ID of pet that needs to be fetched\", \"exclusiveMaximum\": 10, \"exclusiveMinimum\": 1}}], \"responses\": {\"default\": {\"description\": \"The pet\", \"content\": {\"application/json\": {\"schema\": {\"$ref\": \"#/components/schemas/Pet\", \"description\": \"A Pet in JSON format\"}}, \"application/xml\": {\"schema\": {\"$ref\": \"#/components/schemas/Pet\", \"description\": \"A Pet in XML format\"}}}}, \"400\": {\"description\": \"Invalid ID supplied\"}, \"404\": {\"description\": \"Pet not found\"}}, \"security\": [{\"petstore_auth\": [\"write:pets\", \"read:pets\"]}, {\"api_key\": []}]}}}, \"components\": {\"schemas\": {\"Category\": {\"$id\": \"/components/schemas/category\", \"description\": \"Category\", \"properties\": {\"id\": {\"type\": \"integer\", \"format\": \"int64\", \"example\": 1}, \"name\": {\"type\": \"string\", \"example\": \"Dogs\"}}, \"xml\": {\"name\": \"Category\"}}, \"Pet\": {\"$schema\": \"https://json-schema.org/draft/2020-12/schema\", \"description\": \"Pet\", \"properties\": {\"id\": {\"type\": \"integer\", \"format\": \"int64\", \"example\": 10}, \"category\": {\"$ref\": \"#/components/schemas/Category\", \"description\": \"Pet Category\"}, \"name\": {\"type\": \"string\", \"example\": \"doggie\"}, \"photoUrls\": {\"type\": \"array\", \"items\": {\"type\": \"string\", \"xml\": {\"name\": \"photoUrl\"}}, \"xml\": {\"wrapped\": true}}, \"tags\": {\"type\": \"array\", \"items\": {\"$ref\": \"#/components/schemas/Tag\"}, \"xml\": {\"wrapped\": true}}, \"status\": {\"type\": \"string\", \"description\": \"pet status in the store\", \"enum\": [\"available\", \"pending\", \"sold\"]}, \"availableInstances\": {\"type\": \"integer\", \"format\": \"int32\", \"example\": 7, \"exclusiveMaximum\": 10, \"exclusiveMinimum\": 1, \"swagger-extension\": true}, \"petDetailsId\": {\"type\": \"integer\", \"format\": \"int64\", \"$ref\": \"/components/schemas/petdetails#pet_details_id\"}, \"petDetails\": {\"$ref\": \"/components/schemas/petdetails\"}}, \"required\": [\"name\", \"photoUrls\"], \"xml\": {\"name\": \"Pet\"}}, \"PetDetails\": {\"$id\": \"/components/schemas/petdetails\", \"$schema\": \"https://json-schema.org/draft/2020-12/schema\", \"$vocabulary\": \"https://spec.openapis.org/oas/3.1/schema-base\", \"properties\": {\"id\": {\"type\": \"integer\", \"format\": \"int64\", \"$anchor\": \"pet_details_id\", \"example\": 10}, \"category\": {\"$ref\": \"/components/schemas/category\", \"description\": \"PetDetails Category\"}, \"tag\": {\"$ref\": \"/components/schemas/tag\"}}, \"xml\": {\"name\": \"PetDetails\"}}, \"Tag\": {\"$id\": \"/components/schemas/tag\", \"properties\": {\"id\": {\"type\": \"integer\", \"format\": \"int64\"}, \"name\": {\"type\": \"string\"}}, \"xml\": {\"name\": \"Tag\"}}}, \"securitySchemes\": {\"petstore_auth\": {\"type\": \"oauth2\", \"flows\": {\"implicit\": {\"authorizationUrl\": \"https://petstore3.swagger.io/oauth/authorize\", \"scopes\": {\"write:pets\": \"modify pets in your account\", \"read:pets\": \"read your pets\"}}}}, \"mutual_tls\": {\"type\": \"mutualTLS\"}, \"api_key\": {\"type\": \"apiKey\", \"name\": \"api_key\", \"in\": \"header\"}}}, \"webhooks\": {\"newPet\": {\"post\": {\"requestBody\": {\"description\": \"Information about a new pet in the system\", \"content\": {\"application/json\": {\"schema\": {\"$ref\": \"#/components/schemas/Pet\", \"description\": \"Webhook Pet\"}}}}, \"responses\": {\"200\": {\"description\": \"Return a 200 status to indicate that the data was received successfully\"}}}}}}" + }, + "type": "OPEN_API" + }, + "logicalId": { + "path": [ + "OPEN_API", + "3a45981ff1a3539f49317040f895e979" + ] + } + }, + { + "logicalId": { + "name": "https://foo.bar/api/v31/pet", + "platform": "OPEN_API" + }, + "openAPI": { + "methods": [ + { + "description": "Update an existing pet by Id", + "summary": "Update an existing pet", + "type": "PUT" + }, + { + "description": "Add a new pet to the store", + "summary": "Add a new pet to the store", + "type": "POST" + } + ], + "path": "/pet" + }, + "structure": { + "directories": [ + "3a45981ff1a3539f49317040f895e979" + ], + "name": "/pet" + } + }, + { + "logicalId": { + "name": "https://foo.bar/api/v31/pet/{petId}", + "platform": "OPEN_API" + }, + "openAPI": { + "methods": [ + { + "description": "Returns a pet when 0 < ID <= 10. ID > 10 or nonintegers will simulate API error conditions", + "summary": "Find pet by ID", + "type": "GET" + } + ], + "path": "/pet/{petId}" + }, + "structure": { + "directories": [ + "3a45981ff1a3539f49317040f895e979" + ], + "name": "/pet/{petId}" + } + } +] diff --git a/tests/openapi/data/pet_store_31/openapi.json b/tests/openapi/data/pet_store_31/openapi.json new file mode 100644 index 00000000..9068da95 --- /dev/null +++ b/tests/openapi/data/pet_store_31/openapi.json @@ -0,0 +1,428 @@ +{ + "openapi": "3.1.0", + "info": { + "title": "Swagger Petstore - OpenAPI 3.1", + "description": "This is a sample Pet Store Server based on the OpenAPI 3.1 specification.\nYou can find out more about\nSwagger at [http://swagger.io](http://swagger.io).", + "termsOfService": "http://swagger.io/terms/", + "contact": { + "email": "apiteam@swagger.io" + }, + "license": { + "name": "Apache 2.0 AND (MIT OR GPL-2.0-only)", + "identifier": "Apache-2.0 AND (MIT OR GPL-2.0-only)" + }, + "version": "1.0.6", + "summary": "Pet Store 3.1", + "x-namespace": "swagger" + }, + "externalDocs": { + "description": "Find out more about Swagger", + "url": "http://swagger.io" + }, + "servers": [ + { + "url": "/api/v31" + } + ], + "tags": [ + { + "name": "pet", + "description": "Everything about your Pets", + "externalDocs": { + "description": "Find out more", + "url": "http://swagger.io" + } + }, + { + "name": "store", + "description": "Access to Petstore orders", + "externalDocs": { + "description": "Find out more about our store", + "url": "http://swagger.io" + } + }, + { + "name": "user", + "description": "Operations about user" + } + ], + "paths": { + "/pet": { + "put": { + "tags": [ + "pet" + ], + "summary": "Update an existing pet", + "description": "Update an existing pet by Id", + "operationId": "updatePet", + "requestBody": { + "description": "Pet object that needs to be updated in the store", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Pet", + "description": "A Pet in JSON Format", + "required": [ + "id" + ], + "writeOnly": true + } + }, + "application/xml": { + "schema": { + "$ref": "#/components/schemas/Pet", + "description": "A Pet in XML Format", + "required": [ + "id" + ], + "writeOnly": true + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "Successful operation", + "content": { + "application/xml": { + "schema": { + "$ref": "#/components/schemas/Pet", + "description": "A Pet in XML Format", + "readOnly": true + } + }, + "application/json": { + "schema": { + "$ref": "#/components/schemas/Pet", + "description": "A Pet in JSON Format", + "readOnly": true + } + } + } + }, + "400": { + "description": "Invalid ID supplied" + }, + "404": { + "description": "Pet not found" + }, + "405": { + "description": "Validation exception" + } + }, + "security": [ + { + "petstore_auth": [ + "write:pets", + "read:pets" + ] + } + ] + }, + "post": { + "tags": [ + "pet" + ], + "summary": "Add a new pet to the store", + "description": "Add a new pet to the store", + "operationId": "addPet", + "requestBody": { + "description": "Create a new pet in the store", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Pet", + "description": "A Pet in JSON Format", + "required": [ + "id" + ], + "writeOnly": true + } + }, + "application/xml": { + "schema": { + "$ref": "#/components/schemas/Pet", + "description": "A Pet in XML Format", + "required": [ + "id" + ], + "writeOnly": true + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "Successful operation", + "content": { + "application/xml": { + "schema": { + "$ref": "#/components/schemas/Pet", + "description": "A Pet in XML Format", + "readOnly": true + } + }, + "application/json": { + "schema": { + "$ref": "#/components/schemas/Pet", + "description": "A Pet in JSON Format", + "readOnly": true + } + } + } + }, + "405": { + "description": "Invalid input" + } + }, + "security": [ + { + "petstore_auth": [ + "write:pets", + "read:pets" + ] + } + ] + } + }, + "/pet/{petId}": { + "get": { + "tags": [ + "pets" + ], + "summary": "Find pet by ID", + "description": "Returns a pet when 0 < ID <= 10. ID > 10 or nonintegers will simulate API error conditions", + "operationId": "getPetById", + "parameters": [ + { + "name": "petId", + "in": "path", + "description": "ID of pet that needs to be fetched", + "required": true, + "schema": { + "type": "integer", + "format": "int64", + "description": "param ID of pet that needs to be fetched", + "exclusiveMaximum": 10, + "exclusiveMinimum": 1 + } + } + ], + "responses": { + "default": { + "description": "The pet", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Pet", + "description": "A Pet in JSON format" + } + }, + "application/xml": { + "schema": { + "$ref": "#/components/schemas/Pet", + "description": "A Pet in XML format" + } + } + } + }, + "400": { + "description": "Invalid ID supplied" + }, + "404": { + "description": "Pet not found" + } + }, + "security": [ + { + "petstore_auth": [ + "write:pets", + "read:pets" + ] + }, + { + "api_key": [] + } + ] + } + } + }, + "components": { + "schemas": { + "Category": { + "$id": "/components/schemas/category", + "description": "Category", + "properties": { + "id": { + "type": "integer", + "format": "int64", + "example": 1 + }, + "name": { + "type": "string", + "example": "Dogs" + } + }, + "xml": { + "name": "Category" + } + }, + "Pet": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "description": "Pet", + "properties": { + "id": { + "type": "integer", + "format": "int64", + "example": 10 + }, + "category": { + "$ref": "#/components/schemas/Category", + "description": "Pet Category" + }, + "name": { + "type": "string", + "example": "doggie" + }, + "photoUrls": { + "type": "array", + "items": { + "type": "string", + "xml": { + "name": "photoUrl" + } + }, + "xml": { + "wrapped": true + } + }, + "tags": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Tag" + }, + "xml": { + "wrapped": true + } + }, + "status": { + "type": "string", + "description": "pet status in the store", + "enum": [ + "available", + "pending", + "sold" + ] + }, + "availableInstances": { + "type": "integer", + "format": "int32", + "example": 7, + "exclusiveMaximum": 10, + "exclusiveMinimum": 1, + "swagger-extension": true + }, + "petDetailsId": { + "type": "integer", + "format": "int64", + "$ref": "/components/schemas/petdetails#pet_details_id" + }, + "petDetails": { + "$ref": "/components/schemas/petdetails" + } + }, + "required": [ + "name", + "photoUrls" + ], + "xml": { + "name": "Pet" + } + }, + "PetDetails": { + "$id": "/components/schemas/petdetails", + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$vocabulary": "https://spec.openapis.org/oas/3.1/schema-base", + "properties": { + "id": { + "type": "integer", + "format": "int64", + "$anchor": "pet_details_id", + "example": 10 + }, + "category": { + "$ref": "/components/schemas/category", + "description": "PetDetails Category" + }, + "tag": { + "$ref": "/components/schemas/tag" + } + }, + "xml": { + "name": "PetDetails" + } + }, + "Tag": { + "$id": "/components/schemas/tag", + "properties": { + "id": { + "type": "integer", + "format": "int64" + }, + "name": { + "type": "string" + } + }, + "xml": { + "name": "Tag" + } + } + }, + "securitySchemes": { + "petstore_auth": { + "type": "oauth2", + "flows": { + "implicit": { + "authorizationUrl": "https://petstore3.swagger.io/oauth/authorize", + "scopes": { + "write:pets": "modify pets in your account", + "read:pets": "read your pets" + } + } + } + }, + "mutual_tls": { + "type": "mutualTLS" + }, + "api_key": { + "type": "apiKey", + "name": "api_key", + "in": "header" + } + } + }, + "webhooks": { + "newPet": { + "post": { + "requestBody": { + "description": "Information about a new pet in the system", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Pet", + "description": "Webhook Pet" + } + } + } + }, + "responses": { + "200": { + "description": "Return a 200 status to indicate that the data was received successfully" + } + } + } + } + } +} diff --git a/tests/openapi/data/pet_store_31_absolute_server/expected.json b/tests/openapi/data/pet_store_31_absolute_server/expected.json new file mode 100644 index 00000000..ef7c7621 --- /dev/null +++ b/tests/openapi/data/pet_store_31_absolute_server/expected.json @@ -0,0 +1,66 @@ +[ + { + "hierarchyInfo": { + "name": "Swagger Petstore - OpenAPI 3.1", + "openAPI": { + "definition": "{\"openapi\": \"3.1.0\", \"info\": {\"title\": \"Swagger Petstore - OpenAPI 3.1\", \"description\": \"This is a sample Pet Store Server based on the OpenAPI 3.1 specification.\\nYou can find out more about\\nSwagger at [http://swagger.io](http://swagger.io).\", \"termsOfService\": \"http://swagger.io/terms/\", \"contact\": {\"email\": \"apiteam@swagger.io\"}, \"license\": {\"name\": \"Apache 2.0 AND (MIT OR GPL-2.0-only)\", \"identifier\": \"Apache-2.0 AND (MIT OR GPL-2.0-only)\"}, \"version\": \"1.0.6\", \"summary\": \"Pet Store 3.1\", \"x-namespace\": \"swagger\"}, \"externalDocs\": {\"description\": \"Find out more about Swagger\", \"url\": \"http://swagger.io\"}, \"servers\": [{\"url\": \"https://xyz.bar/api/v31\"}], \"tags\": [{\"name\": \"pet\", \"description\": \"Everything about your Pets\", \"externalDocs\": {\"description\": \"Find out more\", \"url\": \"http://swagger.io\"}}, {\"name\": \"store\", \"description\": \"Access to Petstore orders\", \"externalDocs\": {\"description\": \"Find out more about our store\", \"url\": \"http://swagger.io\"}}, {\"name\": \"user\", \"description\": \"Operations about user\"}], \"paths\": {\"/pet\": {\"put\": {\"tags\": [\"pet\"], \"summary\": \"Update an existing pet\", \"description\": \"Update an existing pet by Id\", \"operationId\": \"updatePet\", \"requestBody\": {\"description\": \"Pet object that needs to be updated in the store\", \"content\": {\"application/json\": {\"schema\": {\"$ref\": \"#/components/schemas/Pet\", \"description\": \"A Pet in JSON Format\", \"required\": [\"id\"], \"writeOnly\": true}}, \"application/xml\": {\"schema\": {\"$ref\": \"#/components/schemas/Pet\", \"description\": \"A Pet in XML Format\", \"required\": [\"id\"], \"writeOnly\": true}}}, \"required\": true}, \"responses\": {\"200\": {\"description\": \"Successful operation\", \"content\": {\"application/xml\": {\"schema\": {\"$ref\": \"#/components/schemas/Pet\", \"description\": \"A Pet in XML Format\", \"readOnly\": true}}, \"application/json\": {\"schema\": {\"$ref\": \"#/components/schemas/Pet\", \"description\": \"A Pet in JSON Format\", \"readOnly\": true}}}}, \"400\": {\"description\": \"Invalid ID supplied\"}, \"404\": {\"description\": \"Pet not found\"}, \"405\": {\"description\": \"Validation exception\"}}, \"security\": [{\"petstore_auth\": [\"write:pets\", \"read:pets\"]}]}, \"post\": {\"tags\": [\"pet\"], \"summary\": \"Add a new pet to the store\", \"description\": \"Add a new pet to the store\", \"operationId\": \"addPet\", \"requestBody\": {\"description\": \"Create a new pet in the store\", \"content\": {\"application/json\": {\"schema\": {\"$ref\": \"#/components/schemas/Pet\", \"description\": \"A Pet in JSON Format\", \"required\": [\"id\"], \"writeOnly\": true}}, \"application/xml\": {\"schema\": {\"$ref\": \"#/components/schemas/Pet\", \"description\": \"A Pet in XML Format\", \"required\": [\"id\"], \"writeOnly\": true}}}, \"required\": true}, \"responses\": {\"200\": {\"description\": \"Successful operation\", \"content\": {\"application/xml\": {\"schema\": {\"$ref\": \"#/components/schemas/Pet\", \"description\": \"A Pet in XML Format\", \"readOnly\": true}}, \"application/json\": {\"schema\": {\"$ref\": \"#/components/schemas/Pet\", \"description\": \"A Pet in JSON Format\", \"readOnly\": true}}}}, \"405\": {\"description\": \"Invalid input\"}}, \"security\": [{\"petstore_auth\": [\"write:pets\", \"read:pets\"]}]}}, \"/pet/{petId}\": {\"get\": {\"tags\": [\"pets\"], \"summary\": \"Find pet by ID\", \"description\": \"Returns a pet when 0 < ID <= 10. ID > 10 or nonintegers will simulate API error conditions\", \"operationId\": \"getPetById\", \"parameters\": [{\"name\": \"petId\", \"in\": \"path\", \"description\": \"ID of pet that needs to be fetched\", \"required\": true, \"schema\": {\"type\": \"integer\", \"format\": \"int64\", \"description\": \"param ID of pet that needs to be fetched\", \"exclusiveMaximum\": 10, \"exclusiveMinimum\": 1}}], \"responses\": {\"default\": {\"description\": \"The pet\", \"content\": {\"application/json\": {\"schema\": {\"$ref\": \"#/components/schemas/Pet\", \"description\": \"A Pet in JSON format\"}}, \"application/xml\": {\"schema\": {\"$ref\": \"#/components/schemas/Pet\", \"description\": \"A Pet in XML format\"}}}}, \"400\": {\"description\": \"Invalid ID supplied\"}, \"404\": {\"description\": \"Pet not found\"}}, \"security\": [{\"petstore_auth\": [\"write:pets\", \"read:pets\"]}, {\"api_key\": []}]}}}, \"components\": {\"schemas\": {\"Category\": {\"$id\": \"/components/schemas/category\", \"description\": \"Category\", \"properties\": {\"id\": {\"type\": \"integer\", \"format\": \"int64\", \"example\": 1}, \"name\": {\"type\": \"string\", \"example\": \"Dogs\"}}, \"xml\": {\"name\": \"Category\"}}, \"Pet\": {\"$schema\": \"https://json-schema.org/draft/2020-12/schema\", \"description\": \"Pet\", \"properties\": {\"id\": {\"type\": \"integer\", \"format\": \"int64\", \"example\": 10}, \"category\": {\"$ref\": \"#/components/schemas/Category\", \"description\": \"Pet Category\"}, \"name\": {\"type\": \"string\", \"example\": \"doggie\"}, \"photoUrls\": {\"type\": \"array\", \"items\": {\"type\": \"string\", \"xml\": {\"name\": \"photoUrl\"}}, \"xml\": {\"wrapped\": true}}, \"tags\": {\"type\": \"array\", \"items\": {\"$ref\": \"#/components/schemas/Tag\"}, \"xml\": {\"wrapped\": true}}, \"status\": {\"type\": \"string\", \"description\": \"pet status in the store\", \"enum\": [\"available\", \"pending\", \"sold\"]}, \"availableInstances\": {\"type\": \"integer\", \"format\": \"int32\", \"example\": 7, \"exclusiveMaximum\": 10, \"exclusiveMinimum\": 1, \"swagger-extension\": true}, \"petDetailsId\": {\"type\": \"integer\", \"format\": \"int64\", \"$ref\": \"/components/schemas/petdetails#pet_details_id\"}, \"petDetails\": {\"$ref\": \"/components/schemas/petdetails\"}}, \"required\": [\"name\", \"photoUrls\"], \"xml\": {\"name\": \"Pet\"}}, \"PetDetails\": {\"$id\": \"/components/schemas/petdetails\", \"$schema\": \"https://json-schema.org/draft/2020-12/schema\", \"$vocabulary\": \"https://spec.openapis.org/oas/3.1/schema-base\", \"properties\": {\"id\": {\"type\": \"integer\", \"format\": \"int64\", \"$anchor\": \"pet_details_id\", \"example\": 10}, \"category\": {\"$ref\": \"/components/schemas/category\", \"description\": \"PetDetails Category\"}, \"tag\": {\"$ref\": \"/components/schemas/tag\"}}, \"xml\": {\"name\": \"PetDetails\"}}, \"Tag\": {\"$id\": \"/components/schemas/tag\", \"properties\": {\"id\": {\"type\": \"integer\", \"format\": \"int64\"}, \"name\": {\"type\": \"string\"}}, \"xml\": {\"name\": \"Tag\"}}}, \"securitySchemes\": {\"petstore_auth\": {\"type\": \"oauth2\", \"flows\": {\"implicit\": {\"authorizationUrl\": \"https://petstore3.swagger.io/oauth/authorize\", \"scopes\": {\"write:pets\": \"modify pets in your account\", \"read:pets\": \"read your pets\"}}}}, \"mutual_tls\": {\"type\": \"mutualTLS\"}, \"api_key\": {\"type\": \"apiKey\", \"name\": \"api_key\", \"in\": \"header\"}}}, \"webhooks\": {\"newPet\": {\"post\": {\"requestBody\": {\"description\": \"Information about a new pet in the system\", \"content\": {\"application/json\": {\"schema\": {\"$ref\": \"#/components/schemas/Pet\", \"description\": \"Webhook Pet\"}}}}, \"responses\": {\"200\": {\"description\": \"Return a 200 status to indicate that the data was received successfully\"}}}}}}" + }, + "type": "OPEN_API" + }, + "logicalId": { + "path": [ + "OPEN_API", + "3a45981ff1a3539f49317040f895e979" + ] + } + }, + { + "logicalId": { + "name": "https://xyz.bar/api/v31/pet", + "platform": "OPEN_API" + }, + "openAPI": { + "methods": [ + { + "description": "Update an existing pet by Id", + "summary": "Update an existing pet", + "type": "PUT" + }, + { + "description": "Add a new pet to the store", + "summary": "Add a new pet to the store", + "type": "POST" + } + ], + "path": "/pet" + }, + "structure": { + "directories": [ + "3a45981ff1a3539f49317040f895e979" + ], + "name": "/pet" + } + }, + { + "logicalId": { + "name": "https://xyz.bar/api/v31/pet/{petId}", + "platform": "OPEN_API" + }, + "openAPI": { + "methods": [ + { + "description": "Returns a pet when 0 < ID <= 10. ID > 10 or nonintegers will simulate API error conditions", + "summary": "Find pet by ID", + "type": "GET" + } + ], + "path": "/pet/{petId}" + }, + "structure": { + "directories": [ + "3a45981ff1a3539f49317040f895e979" + ], + "name": "/pet/{petId}" + } + } +] diff --git a/tests/openapi/data/pet_store_31_absolute_server/openapi.json b/tests/openapi/data/pet_store_31_absolute_server/openapi.json new file mode 100644 index 00000000..d26eb74a --- /dev/null +++ b/tests/openapi/data/pet_store_31_absolute_server/openapi.json @@ -0,0 +1,428 @@ +{ + "openapi": "3.1.0", + "info": { + "title": "Swagger Petstore - OpenAPI 3.1", + "description": "This is a sample Pet Store Server based on the OpenAPI 3.1 specification.\nYou can find out more about\nSwagger at [http://swagger.io](http://swagger.io).", + "termsOfService": "http://swagger.io/terms/", + "contact": { + "email": "apiteam@swagger.io" + }, + "license": { + "name": "Apache 2.0 AND (MIT OR GPL-2.0-only)", + "identifier": "Apache-2.0 AND (MIT OR GPL-2.0-only)" + }, + "version": "1.0.6", + "summary": "Pet Store 3.1", + "x-namespace": "swagger" + }, + "externalDocs": { + "description": "Find out more about Swagger", + "url": "http://swagger.io" + }, + "servers": [ + { + "url": "https://xyz.bar/api/v31" + } + ], + "tags": [ + { + "name": "pet", + "description": "Everything about your Pets", + "externalDocs": { + "description": "Find out more", + "url": "http://swagger.io" + } + }, + { + "name": "store", + "description": "Access to Petstore orders", + "externalDocs": { + "description": "Find out more about our store", + "url": "http://swagger.io" + } + }, + { + "name": "user", + "description": "Operations about user" + } + ], + "paths": { + "/pet": { + "put": { + "tags": [ + "pet" + ], + "summary": "Update an existing pet", + "description": "Update an existing pet by Id", + "operationId": "updatePet", + "requestBody": { + "description": "Pet object that needs to be updated in the store", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Pet", + "description": "A Pet in JSON Format", + "required": [ + "id" + ], + "writeOnly": true + } + }, + "application/xml": { + "schema": { + "$ref": "#/components/schemas/Pet", + "description": "A Pet in XML Format", + "required": [ + "id" + ], + "writeOnly": true + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "Successful operation", + "content": { + "application/xml": { + "schema": { + "$ref": "#/components/schemas/Pet", + "description": "A Pet in XML Format", + "readOnly": true + } + }, + "application/json": { + "schema": { + "$ref": "#/components/schemas/Pet", + "description": "A Pet in JSON Format", + "readOnly": true + } + } + } + }, + "400": { + "description": "Invalid ID supplied" + }, + "404": { + "description": "Pet not found" + }, + "405": { + "description": "Validation exception" + } + }, + "security": [ + { + "petstore_auth": [ + "write:pets", + "read:pets" + ] + } + ] + }, + "post": { + "tags": [ + "pet" + ], + "summary": "Add a new pet to the store", + "description": "Add a new pet to the store", + "operationId": "addPet", + "requestBody": { + "description": "Create a new pet in the store", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Pet", + "description": "A Pet in JSON Format", + "required": [ + "id" + ], + "writeOnly": true + } + }, + "application/xml": { + "schema": { + "$ref": "#/components/schemas/Pet", + "description": "A Pet in XML Format", + "required": [ + "id" + ], + "writeOnly": true + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "Successful operation", + "content": { + "application/xml": { + "schema": { + "$ref": "#/components/schemas/Pet", + "description": "A Pet in XML Format", + "readOnly": true + } + }, + "application/json": { + "schema": { + "$ref": "#/components/schemas/Pet", + "description": "A Pet in JSON Format", + "readOnly": true + } + } + } + }, + "405": { + "description": "Invalid input" + } + }, + "security": [ + { + "petstore_auth": [ + "write:pets", + "read:pets" + ] + } + ] + } + }, + "/pet/{petId}": { + "get": { + "tags": [ + "pets" + ], + "summary": "Find pet by ID", + "description": "Returns a pet when 0 < ID <= 10. ID > 10 or nonintegers will simulate API error conditions", + "operationId": "getPetById", + "parameters": [ + { + "name": "petId", + "in": "path", + "description": "ID of pet that needs to be fetched", + "required": true, + "schema": { + "type": "integer", + "format": "int64", + "description": "param ID of pet that needs to be fetched", + "exclusiveMaximum": 10, + "exclusiveMinimum": 1 + } + } + ], + "responses": { + "default": { + "description": "The pet", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Pet", + "description": "A Pet in JSON format" + } + }, + "application/xml": { + "schema": { + "$ref": "#/components/schemas/Pet", + "description": "A Pet in XML format" + } + } + } + }, + "400": { + "description": "Invalid ID supplied" + }, + "404": { + "description": "Pet not found" + } + }, + "security": [ + { + "petstore_auth": [ + "write:pets", + "read:pets" + ] + }, + { + "api_key": [] + } + ] + } + } + }, + "components": { + "schemas": { + "Category": { + "$id": "/components/schemas/category", + "description": "Category", + "properties": { + "id": { + "type": "integer", + "format": "int64", + "example": 1 + }, + "name": { + "type": "string", + "example": "Dogs" + } + }, + "xml": { + "name": "Category" + } + }, + "Pet": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "description": "Pet", + "properties": { + "id": { + "type": "integer", + "format": "int64", + "example": 10 + }, + "category": { + "$ref": "#/components/schemas/Category", + "description": "Pet Category" + }, + "name": { + "type": "string", + "example": "doggie" + }, + "photoUrls": { + "type": "array", + "items": { + "type": "string", + "xml": { + "name": "photoUrl" + } + }, + "xml": { + "wrapped": true + } + }, + "tags": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Tag" + }, + "xml": { + "wrapped": true + } + }, + "status": { + "type": "string", + "description": "pet status in the store", + "enum": [ + "available", + "pending", + "sold" + ] + }, + "availableInstances": { + "type": "integer", + "format": "int32", + "example": 7, + "exclusiveMaximum": 10, + "exclusiveMinimum": 1, + "swagger-extension": true + }, + "petDetailsId": { + "type": "integer", + "format": "int64", + "$ref": "/components/schemas/petdetails#pet_details_id" + }, + "petDetails": { + "$ref": "/components/schemas/petdetails" + } + }, + "required": [ + "name", + "photoUrls" + ], + "xml": { + "name": "Pet" + } + }, + "PetDetails": { + "$id": "/components/schemas/petdetails", + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$vocabulary": "https://spec.openapis.org/oas/3.1/schema-base", + "properties": { + "id": { + "type": "integer", + "format": "int64", + "$anchor": "pet_details_id", + "example": 10 + }, + "category": { + "$ref": "/components/schemas/category", + "description": "PetDetails Category" + }, + "tag": { + "$ref": "/components/schemas/tag" + } + }, + "xml": { + "name": "PetDetails" + } + }, + "Tag": { + "$id": "/components/schemas/tag", + "properties": { + "id": { + "type": "integer", + "format": "int64" + }, + "name": { + "type": "string" + } + }, + "xml": { + "name": "Tag" + } + } + }, + "securitySchemes": { + "petstore_auth": { + "type": "oauth2", + "flows": { + "implicit": { + "authorizationUrl": "https://petstore3.swagger.io/oauth/authorize", + "scopes": { + "write:pets": "modify pets in your account", + "read:pets": "read your pets" + } + } + } + }, + "mutual_tls": { + "type": "mutualTLS" + }, + "api_key": { + "type": "apiKey", + "name": "api_key", + "in": "header" + } + } + }, + "webhooks": { + "newPet": { + "post": { + "requestBody": { + "description": "Information about a new pet in the system", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Pet", + "description": "Webhook Pet" + } + } + } + }, + "responses": { + "200": { + "description": "Return a 200 status to indicate that the data was received successfully" + } + } + } + } + } +} diff --git a/tests/openapi/test_extractor.py b/tests/openapi/test_extractor.py new file mode 100644 index 00000000..3a296d43 --- /dev/null +++ b/tests/openapi/test_extractor.py @@ -0,0 +1,35 @@ +import pytest + +from metaphor.common.base_config import OutputConfig +from metaphor.common.event_util import EventUtil +from metaphor.openapi.config import OpenAPIRunConfig +from metaphor.openapi.extractor import OpenAPIExtractor +from tests.test_utils import load_json + + +def get_dummy_config(openapi_json_path: str, base_url: str): + return OpenAPIRunConfig( + openapi_json_path=openapi_json_path, + base_url=base_url, + output=OutputConfig(), + ) + + +@pytest.mark.parametrize( + ["data_folder", "base_url"], + [ + ("pet_store_31", "https://foo.bar"), + ("pet_store_30", "https://foo.bar"), + ("pet_store_20", "https://foo.bar"), + ("pet_store_31_absolute_server", "https://foo.bar"), + ], +) +@pytest.mark.asyncio +async def test_extractor(data_folder, base_url, test_root_dir): + data_folder = f"{test_root_dir}/openapi/data/{data_folder}" + + extractor = OpenAPIExtractor( + config=get_dummy_config(f"{data_folder}/openapi.json", base_url) + ) + events = [EventUtil.trim_event(e) for e in await extractor.extract()] + assert events == load_json(f"{data_folder}/expected.json") From 24dbba59bb903882bd9b4cba35998e1b1b66a446 Mon Sep 17 00:00:00 2001 From: Scott Huang Date: Fri, 25 Oct 2024 16:35:01 +0800 Subject: [PATCH 04/19] Fix tests --- tests/openapi/__init__.py | 0 tests/quick_sight/__init__.py | 0 2 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 tests/openapi/__init__.py create mode 100644 tests/quick_sight/__init__.py diff --git a/tests/openapi/__init__.py b/tests/openapi/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/tests/quick_sight/__init__.py b/tests/quick_sight/__init__.py new file mode 100644 index 00000000..e69de29b From de9baf5c871e82d854d29f4be7594ad5c566883d Mon Sep 17 00:00:00 2001 From: Scott Huang Date: Fri, 25 Oct 2024 16:55:39 +0800 Subject: [PATCH 05/19] Update tests --- tests/openapi/data/pet_store_30/expected.json | 2 +- tests/openapi/data/pet_store_30/openapi.json | 24 +++++++++---------- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/tests/openapi/data/pet_store_30/expected.json b/tests/openapi/data/pet_store_30/expected.json index a5514bc0..289dad8b 100644 --- a/tests/openapi/data/pet_store_30/expected.json +++ b/tests/openapi/data/pet_store_30/expected.json @@ -3,7 +3,7 @@ "hierarchyInfo": { "name": "Swagger Petstore - OpenAPI 3.0", "openAPI": { - "definition": "{\"openapi\": \"3.0.2\", \"info\": {\"title\": \"Swagger Petstore - OpenAPI 3.0\", \"description\": \"This is a sample Pet Store Server based on the OpenAPI 3.0 specification. You can find out more about\\nSwagger at [http://swagger.io](http://swagger.io). In the third iteration of the pet store, we've switched to the design first approach!\\nYou can now help us improve the API whether it's by making changes to the definition itself or to the code.\\nThat way, with time, we can improve the API in general, and expose some of the new features in OAS3.\\n\\nSome useful links:\\n- [The Pet Store repository](https://github.com/swagger-api/swagger-petstore)\\n- [The source API definition for the Pet Store](https://github.com/swagger-api/swagger-petstore/blob/master/src/main/resources/openapi.yaml)\", \"termsOfService\": \"http://swagger.io/terms/\", \"contact\": {\"email\": \"apiteam@swagger.io\"}, \"license\": {\"name\": \"Apache 2.0\", \"url\": \"http://www.apache.org/licenses/LICENSE-2.0.html\"}, \"version\": \"1.0.19\"}, \"externalDocs\": {\"description\": \"Find out more about Swagger\", \"url\": \"http://swagger.io\"}, \"servers\": [{\"url\": \"/api/v3\"}], \"tags\": [{\"name\": \"pet\", \"description\": \"Everything about your Pets\", \"externalDocs\": {\"description\": \"Find out more\", \"url\": \"http://swagger.io\"}}, {\"name\": \"store\", \"description\": \"Access to Petstore orders\", \"externalDocs\": {\"description\": \"Find out more about our store\", \"url\": \"http://swagger.io\"}}, {\"name\": \"user\", \"description\": \"Operations about user\"}], \"paths\": {\"/pet\": {\"put\": {\"tags\": [\"pet\"], \"summary\": \"Update an existing pet\", \"description\": \"Update an existing pet by Id\", \"operationId\": \"updatePet\", \"requestBody\": {\"description\": \"Update an existent pet in the store\", \"content\": {\"application/json\": {\"schema\": {\"$ref\": \"#/components/schemas/Pet\"}}, \"application/xml\": {\"schema\": {\"$ref\": \"#/components/schemas/Pet\"}}, \"application/x-www-form-urlencoded\": {\"schema\": {\"$ref\": \"#/components/schemas/Pet\"}}}, \"required\": true}, \"responses\": {\"200\": {\"description\": \"Successful operation\", \"content\": {\"application/xml\": {\"schema\": {\"$ref\": \"#/components/schemas/Pet\"}}, \"application/json\": {\"schema\": {\"$ref\": \"#/components/schemas/Pet\"}}}}, \"400\": {\"description\": \"Invalid ID supplied\"}, \"404\": {\"description\": \"Pet not found\"}, \"405\": {\"description\": \"Validation exception\"}}, \"security\": [{\"petstore_auth\": [\"write:pets\", \"read:pets\"]}]}, \"post\": {\"tags\": [\"pet\"], \"summary\": \"Add a new pet to the store\", \"description\": \"Add a new pet to the store\", \"operationId\": \"addPet\", \"requestBody\": {\"description\": \"Create a new pet in the store\", \"content\": {\"application/json\": {\"schema\": {\"$ref\": \"#/components/schemas/Pet\"}}, \"application/xml\": {\"schema\": {\"$ref\": \"#/components/schemas/Pet\"}}, \"application/x-www-form-urlencoded\": {\"schema\": {\"$ref\": \"#/components/schemas/Pet\"}}}, \"required\": true}, \"responses\": {\"200\": {\"description\": \"Successful operation\", \"content\": {\"application/xml\": {\"schema\": {\"$ref\": \"#/components/schemas/Pet\"}}, \"application/json\": {\"schema\": {\"$ref\": \"#/components/schemas/Pet\"}}}}, \"405\": {\"description\": \"Invalid input\"}}, \"security\": [{\"petstore_auth\": [\"write:pets\", \"read:pets\"]}]}}, \"/pet/findByStatus\": {\"get\": {\"tags\": [\"pet\"], \"summary\": \"Finds Pets by status\", \"description\": \"Multiple status values can be provided with comma separated strings\", \"operationId\": \"findPetsByStatus\", \"parameters\": [{\"name\": \"status\", \"in\": \"query\", \"description\": \"Status values that need to be considered for filter\", \"required\": false, \"explode\": true, \"schema\": {\"type\": \"string\", \"default\": \"available\", \"enum\": [\"available\", \"pending\", \"sold\"]}}], \"responses\": {\"200\": {\"description\": \"successful operation\", \"content\": {\"application/xml\": {\"schema\": {\"type\": \"array\", \"items\": {\"$ref\": \"#/components/schemas/Pet\"}}}, \"application/json\": {\"schema\": {\"type\": \"array\", \"items\": {\"$ref\": \"#/components/schemas/Pet\"}}}}}, \"400\": {\"description\": \"Invalid status value\"}}, \"security\": [{\"petstore_auth\": [\"write:pets\", \"read:pets\"]}]}}, \"/pet/findByTags\": {\"get\": {\"tags\": [\"pet\"], \"summary\": \"Finds Pets by tags\", \"description\": \"Multiple tags can be provided with comma separated strings. Use tag1, tag2, tag3 for testing.\", \"operationId\": \"findPetsByTags\", \"parameters\": [{\"name\": \"tags\", \"in\": \"query\", \"description\": \"Tags to filter by\", \"required\": false, \"explode\": true, \"schema\": {\"type\": \"array\", \"items\": {\"type\": \"string\"}}}], \"responses\": {\"200\": {\"description\": \"successful operation\", \"content\": {\"application/xml\": {\"schema\": {\"type\": \"array\", \"items\": {\"$ref\": \"#/components/schemas/Pet\"}}}, \"application/json\": {\"schema\": {\"type\": \"array\", \"items\": {\"$ref\": \"#/components/schemas/Pet\"}}}}}, \"400\": {\"description\": \"Invalid tag value\"}}, \"security\": [{\"petstore_auth\": [\"write:pets\", \"read:pets\"]}]}}, \"/pet/{petId}\": {\"get\": {\"tags\": [\"pet\"], \"summary\": \"Find pet by ID\", \"description\": \"Returns a single pet\", \"operationId\": \"getPetById\", \"parameters\": [{\"name\": \"petId\", \"in\": \"path\", \"description\": \"ID of pet to return\", \"required\": true, \"schema\": {\"type\": \"integer\", \"format\": \"int64\"}}], \"responses\": {\"200\": {\"description\": \"successful operation\", \"content\": {\"application/xml\": {\"schema\": {\"$ref\": \"#/components/schemas/Pet\"}}, \"application/json\": {\"schema\": {\"$ref\": \"#/components/schemas/Pet\"}}}}, \"400\": {\"description\": \"Invalid ID supplied\"}, \"404\": {\"description\": \"Pet not found\"}}, \"security\": [{\"api_key\": []}, {\"petstore_auth\": [\"write:pets\", \"read:pets\"]}]}, \"post\": {\"tags\": [\"pet\"], \"summary\": \"Updates a pet in the store with form data\", \"description\": \"\", \"operationId\": \"updatePetWithForm\", \"parameters\": [{\"name\": \"petId\", \"in\": \"path\", \"description\": \"ID of pet that needs to be updated\", \"required\": true, \"schema\": {\"type\": \"integer\", \"format\": \"int64\"}}, {\"name\": \"name\", \"in\": \"query\", \"description\": \"Name of pet that needs to be updated\", \"schema\": {\"type\": \"string\"}}, {\"name\": \"status\", \"in\": \"query\", \"description\": \"Status of pet that needs to be updated\", \"schema\": {\"type\": \"string\"}}], \"responses\": {\"405\": {\"description\": \"Invalid input\"}}, \"security\": [{\"petstore_auth\": [\"write:pets\", \"read:pets\"]}]}, \"delete\": {\"tags\": [\"pet\"], \"summary\": \"Deletes a pet\", \"description\": \"\", \"operationId\": \"deletePet\", \"parameters\": [{\"name\": \"api_key\", \"in\": \"header\", \"description\": \"\", \"required\": false, \"schema\": {\"type\": \"string\"}}, {\"name\": \"petId\", \"in\": \"path\", \"description\": \"Pet id to delete\", \"required\": true, \"schema\": {\"type\": \"integer\", \"format\": \"int64\"}}], \"responses\": {\"400\": {\"description\": \"Invalid pet value\"}}, \"security\": [{\"petstore_auth\": [\"write:pets\", \"read:pets\"]}]}}, \"/pet/{petId}/uploadImage\": {\"post\": {\"tags\": [\"pet\"], \"summary\": \"uploads an image\", \"description\": \"\", \"operationId\": \"uploadFile\", \"parameters\": [{\"name\": \"petId\", \"in\": \"path\", \"description\": \"ID of pet to update\", \"required\": true, \"schema\": {\"type\": \"integer\", \"format\": \"int64\"}}, {\"name\": \"additionalMetadata\", \"in\": \"query\", \"description\": \"Additional Metadata\", \"required\": false, \"schema\": {\"type\": \"string\"}}], \"requestBody\": {\"content\": {\"application/octet-stream\": {\"schema\": {\"type\": \"string\", \"format\": \"binary\"}}}}, \"responses\": {\"200\": {\"description\": \"successful operation\", \"content\": {\"application/json\": {\"schema\": {\"$ref\": \"#/components/schemas/ApiResponse\"}}}}}, \"security\": [{\"petstore_auth\": [\"write:pets\", \"read:pets\"]}]}}, \"/store/inventory\": {\"get\": {\"tags\": [\"store\"], \"summary\": \"Returns pet inventories by status\", \"description\": \"Returns a map of status codes to quantities\", \"operationId\": \"getInventory\", \"responses\": {\"200\": {\"description\": \"successful operation\", \"content\": {\"application/json\": {\"schema\": {\"type\": \"object\", \"additionalProperties\": {\"type\": \"integer\", \"format\": \"int32\"}}}}}}, \"security\": [{\"api_key\": []}]}}, \"/store/order\": {\"post\": {\"tags\": [\"store\"], \"summary\": \"Place an order for a pet\", \"description\": \"Place a new order in the store\", \"operationId\": \"placeOrder\", \"requestBody\": {\"content\": {\"application/json\": {\"schema\": {\"$ref\": \"#/components/schemas/Order\"}}, \"application/xml\": {\"schema\": {\"$ref\": \"#/components/schemas/Order\"}}, \"application/x-www-form-urlencoded\": {\"schema\": {\"$ref\": \"#/components/schemas/Order\"}}}}, \"responses\": {\"200\": {\"description\": \"successful operation\", \"content\": {\"application/json\": {\"schema\": {\"$ref\": \"#/components/schemas/Order\"}}}}, \"405\": {\"description\": \"Invalid input\"}}}}, \"/store/order/{orderId}\": {\"get\": {\"tags\": [\"store\"], \"summary\": \"Find purchase order by ID\", \"description\": \"For valid response try integer IDs with value <= 5 or > 10. Other values will generate exceptions.\", \"operationId\": \"getOrderById\", \"parameters\": [{\"name\": \"orderId\", \"in\": \"path\", \"description\": \"ID of order that needs to be fetched\", \"required\": true, \"schema\": {\"type\": \"integer\", \"format\": \"int64\"}}], \"responses\": {\"200\": {\"description\": \"successful operation\", \"content\": {\"application/xml\": {\"schema\": {\"$ref\": \"#/components/schemas/Order\"}}, \"application/json\": {\"schema\": {\"$ref\": \"#/components/schemas/Order\"}}}}, \"400\": {\"description\": \"Invalid ID supplied\"}, \"404\": {\"description\": \"Order not found\"}}}, \"delete\": {\"tags\": [\"store\"], \"summary\": \"Delete purchase order by ID\", \"description\": \"For valid response try integer IDs with value < 1000. Anything above 1000 or nonintegers will generate API errors\", \"operationId\": \"deleteOrder\", \"parameters\": [{\"name\": \"orderId\", \"in\": \"path\", \"description\": \"ID of the order that needs to be deleted\", \"required\": true, \"schema\": {\"type\": \"integer\", \"format\": \"int64\"}}], \"responses\": {\"400\": {\"description\": \"Invalid ID supplied\"}, \"404\": {\"description\": \"Order not found\"}}}}, \"/user\": {\"post\": {\"tags\": [\"user\"], \"summary\": \"Create user\", \"description\": \"This can only be done by the logged in user.\", \"operationId\": \"createUser\", \"requestBody\": {\"description\": \"Created user object\", \"content\": {\"application/json\": {\"schema\": {\"$ref\": \"#/components/schemas/User\"}}, \"application/xml\": {\"schema\": {\"$ref\": \"#/components/schemas/User\"}}, \"application/x-www-form-urlencoded\": {\"schema\": {\"$ref\": \"#/components/schemas/User\"}}}}, \"responses\": {\"default\": {\"description\": \"successful operation\", \"content\": {\"application/json\": {\"schema\": {\"$ref\": \"#/components/schemas/User\"}}, \"application/xml\": {\"schema\": {\"$ref\": \"#/components/schemas/User\"}}}}}}}, \"/user/createWithList\": {\"post\": {\"tags\": [\"user\"], \"summary\": \"Creates list of users with given input array\", \"description\": \"Creates list of users with given input array\", \"operationId\": \"createUsersWithListInput\", \"requestBody\": {\"content\": {\"application/json\": {\"schema\": {\"type\": \"array\", \"items\": {\"$ref\": \"#/components/schemas/User\"}}}}}, \"responses\": {\"200\": {\"description\": \"Successful operation\", \"content\": {\"application/xml\": {\"schema\": {\"$ref\": \"#/components/schemas/User\"}}, \"application/json\": {\"schema\": {\"$ref\": \"#/components/schemas/User\"}}}}, \"default\": {\"description\": \"successful operation\"}}}}, \"/user/login\": {\"get\": {\"tags\": [\"user\"], \"summary\": \"Logs user into the system\", \"description\": \"\", \"operationId\": \"loginUser\", \"parameters\": [{\"name\": \"username\", \"in\": \"query\", \"description\": \"The user name for login\", \"required\": false, \"schema\": {\"type\": \"string\"}}, {\"name\": \"password\", \"in\": \"query\", \"description\": \"The password for login in clear text\", \"required\": false, \"schema\": {\"type\": \"string\"}}], \"responses\": {\"200\": {\"description\": \"successful operation\", \"headers\": {\"X-Rate-Limit\": {\"description\": \"calls per hour allowed by the user\", \"schema\": {\"type\": \"integer\", \"format\": \"int32\"}}, \"X-Expires-After\": {\"description\": \"date in UTC when token expires\", \"schema\": {\"type\": \"string\", \"format\": \"date-time\"}}}, \"content\": {\"application/xml\": {\"schema\": {\"type\": \"string\"}}, \"application/json\": {\"schema\": {\"type\": \"string\"}}}}, \"400\": {\"description\": \"Invalid username/password supplied\"}}}}, \"/user/logout\": {\"get\": {\"tags\": [\"user\"], \"summary\": \"Logs out current logged in user session\", \"description\": \"\", \"operationId\": \"logoutUser\", \"parameters\": [], \"responses\": {\"default\": {\"description\": \"successful operation\"}}}}, \"/user/{username}\": {\"get\": {\"tags\": [\"user\"], \"summary\": \"Get user by user name\", \"description\": \"\", \"operationId\": \"getUserByName\", \"parameters\": [{\"name\": \"username\", \"in\": \"path\", \"description\": \"The name that needs to be fetched. Use user1 for testing. \", \"required\": true, \"schema\": {\"type\": \"string\"}}], \"responses\": {\"200\": {\"description\": \"successful operation\", \"content\": {\"application/xml\": {\"schema\": {\"$ref\": \"#/components/schemas/User\"}}, \"application/json\": {\"schema\": {\"$ref\": \"#/components/schemas/User\"}}}}, \"400\": {\"description\": \"Invalid username supplied\"}, \"404\": {\"description\": \"User not found\"}}}, \"put\": {\"tags\": [\"user\"], \"summary\": \"Update user\", \"description\": \"This can only be done by the logged in user.\", \"operationId\": \"updateUser\", \"parameters\": [{\"name\": \"username\", \"in\": \"path\", \"description\": \"name that needs to be updated\", \"required\": true, \"schema\": {\"type\": \"string\"}}], \"requestBody\": {\"description\": \"Update an existent user in the store\", \"content\": {\"application/json\": {\"schema\": {\"$ref\": \"#/components/schemas/User\"}}, \"application/xml\": {\"schema\": {\"$ref\": \"#/components/schemas/User\"}}, \"application/x-www-form-urlencoded\": {\"schema\": {\"$ref\": \"#/components/schemas/User\"}}}}, \"responses\": {\"default\": {\"description\": \"successful operation\"}}}, \"delete\": {\"tags\": [\"user\"], \"summary\": \"Delete user\", \"description\": \"This can only be done by the logged in user.\", \"operationId\": \"deleteUser\", \"parameters\": [{\"name\": \"username\", \"in\": \"path\", \"description\": \"The name that needs to be deleted\", \"required\": true, \"schema\": {\"type\": \"string\"}}], \"responses\": {\"400\": {\"description\": \"Invalid username supplied\"}, \"404\": {\"description\": \"User not found\"}}}}}, \"components\": {\"schemas\": {\"Order\": {\"type\": \"object\", \"properties\": {\"id\": {\"type\": \"integer\", \"format\": \"int64\", \"example\": 10}, \"petId\": {\"type\": \"integer\", \"format\": \"int64\", \"example\": 198772}, \"quantity\": {\"type\": \"integer\", \"format\": \"int32\", \"example\": 7}, \"shipDate\": {\"type\": \"string\", \"format\": \"date-time\"}, \"status\": {\"type\": \"string\", \"description\": \"Order Status\", \"example\": \"approved\", \"enum\": [\"placed\", \"approved\", \"delivered\"]}, \"complete\": {\"type\": \"boolean\"}}, \"xml\": {\"name\": \"order\"}}, \"Customer\": {\"type\": \"object\", \"properties\": {\"id\": {\"type\": \"integer\", \"format\": \"int64\", \"example\": 100000}, \"username\": {\"type\": \"string\", \"example\": \"fehguy\"}, \"address\": {\"type\": \"array\", \"xml\": {\"name\": \"addresses\", \"wrapped\": true}, \"items\": {\"$ref\": \"#/components/schemas/Address\"}}}, \"xml\": {\"name\": \"customer\"}}, \"Address\": {\"type\": \"object\", \"properties\": {\"street\": {\"type\": \"string\", \"example\": \"437 Lytton\"}, \"city\": {\"type\": \"string\", \"example\": \"Palo Alto\"}, \"state\": {\"type\": \"string\", \"example\": \"CA\"}, \"zip\": {\"type\": \"string\", \"example\": \"94301\"}}, \"xml\": {\"name\": \"address\"}}, \"Category\": {\"type\": \"object\", \"properties\": {\"id\": {\"type\": \"integer\", \"format\": \"int64\", \"example\": 1}, \"name\": {\"type\": \"string\", \"example\": \"Dogs\"}}, \"xml\": {\"name\": \"category\"}}, \"User\": {\"type\": \"object\", \"properties\": {\"id\": {\"type\": \"integer\", \"format\": \"int64\", \"example\": 10}, \"username\": {\"type\": \"string\", \"example\": \"theUser\"}, \"firstName\": {\"type\": \"string\", \"example\": \"John\"}, \"lastName\": {\"type\": \"string\", \"example\": \"James\"}, \"email\": {\"type\": \"string\", \"example\": \"john@email.com\"}, \"password\": {\"type\": \"string\", \"example\": \"12345\"}, \"phone\": {\"type\": \"string\", \"example\": \"12345\"}, \"userStatus\": {\"type\": \"integer\", \"description\": \"User Status\", \"format\": \"int32\", \"example\": 1}}, \"xml\": {\"name\": \"user\"}}, \"Tag\": {\"type\": \"object\", \"properties\": {\"id\": {\"type\": \"integer\", \"format\": \"int64\"}, \"name\": {\"type\": \"string\"}}, \"xml\": {\"name\": \"tag\"}}, \"Pet\": {\"required\": [\"name\", \"photoUrls\"], \"type\": \"object\", \"properties\": {\"id\": {\"type\": \"integer\", \"format\": \"int64\", \"example\": 10}, \"name\": {\"type\": \"string\", \"example\": \"doggie\"}, \"category\": {\"$ref\": \"#/components/schemas/Category\"}, \"photoUrls\": {\"type\": \"array\", \"xml\": {\"wrapped\": true}, \"items\": {\"type\": \"string\", \"xml\": {\"name\": \"photoUrl\"}}}, \"tags\": {\"type\": \"array\", \"xml\": {\"wrapped\": true}, \"items\": {\"$ref\": \"#/components/schemas/Tag\"}}, \"status\": {\"type\": \"string\", \"description\": \"pet status in the store\", \"enum\": [\"available\", \"pending\", \"sold\"]}}, \"xml\": {\"name\": \"pet\"}}, \"ApiResponse\": {\"type\": \"object\", \"properties\": {\"code\": {\"type\": \"integer\", \"format\": \"int32\"}, \"type\": {\"type\": \"string\"}, \"message\": {\"type\": \"string\"}}, \"xml\": {\"name\": \"##default\"}}}, \"requestBodies\": {\"Pet\": {\"description\": \"Pet object that needs to be added to the store\", \"content\": {\"application/json\": {\"schema\": {\"$ref\": \"#/components/schemas/Pet\"}}, \"application/xml\": {\"schema\": {\"$ref\": \"#/components/schemas/Pet\"}}}}, \"UserArray\": {\"description\": \"List of user object\", \"content\": {\"application/json\": {\"schema\": {\"type\": \"array\", \"items\": {\"$ref\": \"#/components/schemas/User\"}}}}}}, \"securitySchemes\": {\"petstore_auth\": {\"type\": \"oauth2\", \"flows\": {\"implicit\": {\"authorizationUrl\": \"https://petstore3.swagger.io/oauth/authorize\", \"scopes\": {\"write:pets\": \"modify pets in your account\", \"read:pets\": \"read your pets\"}}}}, \"api_key\": {\"type\": \"apiKey\", \"name\": \"api_key\", \"in\": \"header\"}}}}" + "definition": "{\"openapi\": \"3.0.2\", \"info\": {\"title\": \"Swagger Petstore - OpenAPI 3.0\", \"description\": \"This is a sample Pet Store Server based on the OpenAPI 3.0 specification. You can find out more about\\nSwagger at [http://swagger.io](http://swagger.io). In the third iteration of the pet store, we've switched to the design first approach!\\nYou can now help us improve the API whether it's by making changes to the definition itself or to the code.\\nThat way, with time, we can improve the API in general, and expose some of the new features in OAS3.\\n\\nSome useful links:\\n- [The Pet Store repository](https://github.com/swagger-api/swagger-petstore)\\n- [The source API definition for the Pet Store](https://github.com/swagger-api/swagger-petstore/blob/master/src/main/resources/openapi.yaml)\", \"termsOfService\": \"http://swagger.io/terms/\", \"contact\": {\"email\": \"apiteam@swagger.io\"}, \"license\": {\"name\": \"Apache 2.0\", \"url\": \"http://www.apache.org/licenses/LICENSE-2.0.html\"}, \"version\": \"1.0.19\"}, \"externalDocs\": {\"description\": \"Find out more about Swagger\", \"url\": \"http://swagger.io\"}, \"servers\": [{\"url\": \"/api/v3\"}], \"tags\": [{\"name\": \"pet\", \"description\": \"Everything about your Pets\", \"externalDocs\": {\"description\": \"Find out more\", \"url\": \"http://swagger.io\"}}, {\"name\": \"store\", \"description\": \"Access to Petstore orders\", \"externalDocs\": {\"description\": \"Find out more about our store\", \"url\": \"http://swagger.io\"}}, {\"name\": \"user\", \"description\": \"Operations about user\"}], \"paths\": {\"/pet\": {\"put\": {\"tags\": [\"pet\"], \"summary\": \"Update an existing pet\", \"description\": \"Update an existing pet by Id\", \"operationId\": \"updatePet\", \"requestBody\": {\"description\": \"Update an existent pet in the store\", \"content\": {\"application/json\": {\"schema\": {\"$ref\": \"#/components/schemas/Pet\"}}, \"application/xml\": {\"schema\": {\"$ref\": \"#/components/schemas/Pet\"}}, \"application/x-www-form-urlencoded\": {\"schema\": {\"$ref\": \"#/components/schemas/Pet\"}}}, \"required\": true}, \"responses\": {\"200\": {\"description\": \"Successful operation\", \"content\": {\"application/xml\": {\"schema\": {\"$ref\": \"#/components/schemas/Pet\"}}, \"application/json\": {\"schema\": {\"$ref\": \"#/components/schemas/Pet\"}}}}, \"400\": {\"description\": \"Invalid ID supplied\"}, \"404\": {\"description\": \"Pet not found\"}, \"405\": {\"description\": \"Validation exception\"}}, \"security\": [{\"petstore_auth\": [\"write:pets\", \"read:pets\"]}]}, \"post\": {\"tags\": [\"pet\"], \"summary\": \"Add a new pet to the store\", \"description\": \"Add a new pet to the store\", \"operationId\": \"addPet\", \"requestBody\": {\"description\": \"Create a new pet in the store\", \"content\": {\"application/json\": {\"schema\": {\"$ref\": \"#/components/schemas/Pet\"}}, \"application/xml\": {\"schema\": {\"$ref\": \"#/components/schemas/Pet\"}}, \"application/x-www-form-urlencoded\": {\"schema\": {\"$ref\": \"#/components/schemas/Pet\"}}}, \"required\": true}, \"responses\": {\"200\": {\"description\": \"Successful operation\", \"content\": {\"application/xml\": {\"schema\": {\"$ref\": \"#/components/schemas/Pet\"}}, \"application/json\": {\"schema\": {\"$ref\": \"#/components/schemas/Pet\"}}}}, \"405\": {\"description\": \"Invalid input\"}}, \"security\": [{\"petstore_auth\": [\"write:pets\", \"read:pets\"]}]}}, \"/pet/findByStatus\": {\"get\": {\"tags\": [\"pet\"], \"summary\": \"Finds Pets by status\", \"description\": \"Multiple status values can be provided with comma separated strings\", \"operationId\": \"findPetsByStatus\", \"parameters\": [{\"name\": \"status\", \"in\": \"query\", \"description\": \"Status values that need to be considered for filter\", \"required\": false, \"explode\": true, \"schema\": {\"type\": \"string\", \"default\": \"available\", \"enum\": [\"available\", \"pending\", \"sold\"]}}], \"responses\": {\"200\": {\"description\": \"successful operation\", \"content\": {\"application/xml\": {\"schema\": {\"type\": \"array\", \"items\": {\"$ref\": \"#/components/schemas/Pet\"}}}, \"application/json\": {\"schema\": {\"type\": \"array\", \"items\": {\"$ref\": \"#/components/schemas/Pet\"}}}}}, \"400\": {\"description\": \"Invalid status value\"}}, \"security\": [{\"petstore_auth\": [\"write:pets\", \"read:pets\"]}]}}, \"/pet/findByTags\": {\"get\": {\"tags\": [\"pet\"], \"summary\": \"Finds Pets by tags\", \"description\": \"Multiple tags can be provided with comma separated strings. Use tag1, tag2, tag3 for testing.\", \"operationId\": \"findPetsByTags\", \"parameters\": [{\"name\": \"tags\", \"in\": \"query\", \"description\": \"Tags to filter by\", \"required\": false, \"explode\": true, \"schema\": {\"type\": \"array\", \"items\": {\"type\": \"string\"}}}], \"responses\": {\"200\": {\"description\": \"successful operation\", \"content\": {\"application/xml\": {\"schema\": {\"type\": \"array\", \"items\": {\"$ref\": \"#/components/schemas/Pet\"}}}, \"application/json\": {\"schema\": {\"type\": \"array\", \"items\": {\"$ref\": \"#/components/schemas/Pet\"}}}}}, \"400\": {\"description\": \"Invalid tag value\"}}, \"security\": [{\"petstore_auth\": [\"write:pets\", \"read:pets\"]}]}}, \"/pet/{petId}\": {\"parameters\": [{\"name\": \"petId\", \"in\": \"path\", \"description\": \"ID of pet to return\", \"required\": true, \"schema\": {\"type\": \"integer\", \"format\": \"int64\"}}], \"get\": {\"tags\": [\"pet\"], \"summary\": \"Find pet by ID\", \"description\": \"Returns a single pet\", \"operationId\": \"getPetById\", \"responses\": {\"200\": {\"description\": \"successful operation\", \"content\": {\"application/xml\": {\"schema\": {\"$ref\": \"#/components/schemas/Pet\"}}, \"application/json\": {\"schema\": {\"$ref\": \"#/components/schemas/Pet\"}}}}, \"400\": {\"description\": \"Invalid ID supplied\"}, \"404\": {\"description\": \"Pet not found\"}}, \"security\": [{\"api_key\": []}, {\"petstore_auth\": [\"write:pets\", \"read:pets\"]}]}, \"post\": {\"tags\": [\"pet\"], \"summary\": \"Updates a pet in the store with form data\", \"description\": \"\", \"operationId\": \"updatePetWithForm\", \"parameters\": [{\"name\": \"petId\", \"in\": \"path\", \"description\": \"ID of pet that needs to be updated\", \"required\": true, \"schema\": {\"type\": \"integer\", \"format\": \"int64\"}}, {\"name\": \"name\", \"in\": \"query\", \"description\": \"Name of pet that needs to be updated\", \"schema\": {\"type\": \"string\"}}, {\"name\": \"status\", \"in\": \"query\", \"description\": \"Status of pet that needs to be updated\", \"schema\": {\"type\": \"string\"}}], \"responses\": {\"405\": {\"description\": \"Invalid input\"}}, \"security\": [{\"petstore_auth\": [\"write:pets\", \"read:pets\"]}]}, \"delete\": {\"tags\": [\"pet\"], \"summary\": \"Deletes a pet\", \"description\": \"\", \"operationId\": \"deletePet\", \"parameters\": [{\"name\": \"api_key\", \"in\": \"header\", \"description\": \"\", \"required\": false, \"schema\": {\"type\": \"string\"}}, {\"name\": \"petId\", \"in\": \"path\", \"description\": \"Pet id to delete\", \"required\": true, \"schema\": {\"type\": \"integer\", \"format\": \"int64\"}}], \"responses\": {\"400\": {\"description\": \"Invalid pet value\"}}, \"security\": [{\"petstore_auth\": [\"write:pets\", \"read:pets\"]}]}}, \"/pet/{petId}/uploadImage\": {\"post\": {\"tags\": [\"pet\"], \"summary\": \"uploads an image\", \"description\": \"\", \"operationId\": \"uploadFile\", \"parameters\": [{\"name\": \"petId\", \"in\": \"path\", \"description\": \"ID of pet to update\", \"required\": true, \"schema\": {\"type\": \"integer\", \"format\": \"int64\"}}, {\"name\": \"additionalMetadata\", \"in\": \"query\", \"description\": \"Additional Metadata\", \"required\": false, \"schema\": {\"type\": \"string\"}}], \"requestBody\": {\"content\": {\"application/octet-stream\": {\"schema\": {\"type\": \"string\", \"format\": \"binary\"}}}}, \"responses\": {\"200\": {\"description\": \"successful operation\", \"content\": {\"application/json\": {\"schema\": {\"$ref\": \"#/components/schemas/ApiResponse\"}}}}}, \"security\": [{\"petstore_auth\": [\"write:pets\", \"read:pets\"]}]}}, \"/store/inventory\": {\"get\": {\"tags\": [\"store\"], \"summary\": \"Returns pet inventories by status\", \"description\": \"Returns a map of status codes to quantities\", \"operationId\": \"getInventory\", \"responses\": {\"200\": {\"description\": \"successful operation\", \"content\": {\"application/json\": {\"schema\": {\"type\": \"object\", \"additionalProperties\": {\"type\": \"integer\", \"format\": \"int32\"}}}}}}, \"security\": [{\"api_key\": []}]}}, \"/store/order\": {\"post\": {\"tags\": [\"store\"], \"summary\": \"Place an order for a pet\", \"description\": \"Place a new order in the store\", \"operationId\": \"placeOrder\", \"requestBody\": {\"content\": {\"application/json\": {\"schema\": {\"$ref\": \"#/components/schemas/Order\"}}, \"application/xml\": {\"schema\": {\"$ref\": \"#/components/schemas/Order\"}}, \"application/x-www-form-urlencoded\": {\"schema\": {\"$ref\": \"#/components/schemas/Order\"}}}}, \"responses\": {\"200\": {\"description\": \"successful operation\", \"content\": {\"application/json\": {\"schema\": {\"$ref\": \"#/components/schemas/Order\"}}}}, \"405\": {\"description\": \"Invalid input\"}}}}, \"/store/order/{orderId}\": {\"get\": {\"tags\": [\"store\"], \"summary\": \"Find purchase order by ID\", \"description\": \"For valid response try integer IDs with value <= 5 or > 10. Other values will generate exceptions.\", \"operationId\": \"getOrderById\", \"parameters\": [{\"name\": \"orderId\", \"in\": \"path\", \"description\": \"ID of order that needs to be fetched\", \"required\": true, \"schema\": {\"type\": \"integer\", \"format\": \"int64\"}}], \"responses\": {\"200\": {\"description\": \"successful operation\", \"content\": {\"application/xml\": {\"schema\": {\"$ref\": \"#/components/schemas/Order\"}}, \"application/json\": {\"schema\": {\"$ref\": \"#/components/schemas/Order\"}}}}, \"400\": {\"description\": \"Invalid ID supplied\"}, \"404\": {\"description\": \"Order not found\"}}}, \"delete\": {\"tags\": [\"store\"], \"summary\": \"Delete purchase order by ID\", \"description\": \"For valid response try integer IDs with value < 1000. Anything above 1000 or nonintegers will generate API errors\", \"operationId\": \"deleteOrder\", \"parameters\": [{\"name\": \"orderId\", \"in\": \"path\", \"description\": \"ID of the order that needs to be deleted\", \"required\": true, \"schema\": {\"type\": \"integer\", \"format\": \"int64\"}}], \"responses\": {\"400\": {\"description\": \"Invalid ID supplied\"}, \"404\": {\"description\": \"Order not found\"}}}}, \"/user\": {\"post\": {\"tags\": [\"user\"], \"summary\": \"Create user\", \"description\": \"This can only be done by the logged in user.\", \"operationId\": \"createUser\", \"requestBody\": {\"description\": \"Created user object\", \"content\": {\"application/json\": {\"schema\": {\"$ref\": \"#/components/schemas/User\"}}, \"application/xml\": {\"schema\": {\"$ref\": \"#/components/schemas/User\"}}, \"application/x-www-form-urlencoded\": {\"schema\": {\"$ref\": \"#/components/schemas/User\"}}}}, \"responses\": {\"default\": {\"description\": \"successful operation\", \"content\": {\"application/json\": {\"schema\": {\"$ref\": \"#/components/schemas/User\"}}, \"application/xml\": {\"schema\": {\"$ref\": \"#/components/schemas/User\"}}}}}}}, \"/user/createWithList\": {\"post\": {\"tags\": [\"user\"], \"summary\": \"Creates list of users with given input array\", \"description\": \"Creates list of users with given input array\", \"operationId\": \"createUsersWithListInput\", \"requestBody\": {\"content\": {\"application/json\": {\"schema\": {\"type\": \"array\", \"items\": {\"$ref\": \"#/components/schemas/User\"}}}}}, \"responses\": {\"200\": {\"description\": \"Successful operation\", \"content\": {\"application/xml\": {\"schema\": {\"$ref\": \"#/components/schemas/User\"}}, \"application/json\": {\"schema\": {\"$ref\": \"#/components/schemas/User\"}}}}, \"default\": {\"description\": \"successful operation\"}}}}, \"/user/login\": {\"get\": {\"tags\": [\"user\"], \"summary\": \"Logs user into the system\", \"description\": \"\", \"operationId\": \"loginUser\", \"parameters\": [{\"name\": \"username\", \"in\": \"query\", \"description\": \"The user name for login\", \"required\": false, \"schema\": {\"type\": \"string\"}}, {\"name\": \"password\", \"in\": \"query\", \"description\": \"The password for login in clear text\", \"required\": false, \"schema\": {\"type\": \"string\"}}], \"responses\": {\"200\": {\"description\": \"successful operation\", \"headers\": {\"X-Rate-Limit\": {\"description\": \"calls per hour allowed by the user\", \"schema\": {\"type\": \"integer\", \"format\": \"int32\"}}, \"X-Expires-After\": {\"description\": \"date in UTC when token expires\", \"schema\": {\"type\": \"string\", \"format\": \"date-time\"}}}, \"content\": {\"application/xml\": {\"schema\": {\"type\": \"string\"}}, \"application/json\": {\"schema\": {\"type\": \"string\"}}}}, \"400\": {\"description\": \"Invalid username/password supplied\"}}}}, \"/user/logout\": {\"get\": {\"tags\": [\"user\"], \"summary\": \"Logs out current logged in user session\", \"description\": \"\", \"operationId\": \"logoutUser\", \"parameters\": [], \"responses\": {\"default\": {\"description\": \"successful operation\"}}}}, \"/user/{username}\": {\"get\": {\"tags\": [\"user\"], \"summary\": \"Get user by user name\", \"description\": \"\", \"operationId\": \"getUserByName\", \"parameters\": [{\"name\": \"username\", \"in\": \"path\", \"description\": \"The name that needs to be fetched. Use user1 for testing. \", \"required\": true, \"schema\": {\"type\": \"string\"}}], \"responses\": {\"200\": {\"description\": \"successful operation\", \"content\": {\"application/xml\": {\"schema\": {\"$ref\": \"#/components/schemas/User\"}}, \"application/json\": {\"schema\": {\"$ref\": \"#/components/schemas/User\"}}}}, \"400\": {\"description\": \"Invalid username supplied\"}, \"404\": {\"description\": \"User not found\"}}}, \"put\": {\"tags\": [\"user\"], \"summary\": \"Update user\", \"description\": \"This can only be done by the logged in user.\", \"operationId\": \"updateUser\", \"parameters\": [{\"name\": \"username\", \"in\": \"path\", \"description\": \"name that needs to be updated\", \"required\": true, \"schema\": {\"type\": \"string\"}}], \"requestBody\": {\"description\": \"Update an existent user in the store\", \"content\": {\"application/json\": {\"schema\": {\"$ref\": \"#/components/schemas/User\"}}, \"application/xml\": {\"schema\": {\"$ref\": \"#/components/schemas/User\"}}, \"application/x-www-form-urlencoded\": {\"schema\": {\"$ref\": \"#/components/schemas/User\"}}}}, \"responses\": {\"default\": {\"description\": \"successful operation\"}}}, \"delete\": {\"tags\": [\"user\"], \"summary\": \"Delete user\", \"description\": \"This can only be done by the logged in user.\", \"operationId\": \"deleteUser\", \"parameters\": [{\"name\": \"username\", \"in\": \"path\", \"description\": \"The name that needs to be deleted\", \"required\": true, \"schema\": {\"type\": \"string\"}}], \"responses\": {\"400\": {\"description\": \"Invalid username supplied\"}, \"404\": {\"description\": \"User not found\"}}}}}, \"components\": {\"schemas\": {\"Order\": {\"type\": \"object\", \"properties\": {\"id\": {\"type\": \"integer\", \"format\": \"int64\", \"example\": 10}, \"petId\": {\"type\": \"integer\", \"format\": \"int64\", \"example\": 198772}, \"quantity\": {\"type\": \"integer\", \"format\": \"int32\", \"example\": 7}, \"shipDate\": {\"type\": \"string\", \"format\": \"date-time\"}, \"status\": {\"type\": \"string\", \"description\": \"Order Status\", \"example\": \"approved\", \"enum\": [\"placed\", \"approved\", \"delivered\"]}, \"complete\": {\"type\": \"boolean\"}}, \"xml\": {\"name\": \"order\"}}, \"Customer\": {\"type\": \"object\", \"properties\": {\"id\": {\"type\": \"integer\", \"format\": \"int64\", \"example\": 100000}, \"username\": {\"type\": \"string\", \"example\": \"fehguy\"}, \"address\": {\"type\": \"array\", \"xml\": {\"name\": \"addresses\", \"wrapped\": true}, \"items\": {\"$ref\": \"#/components/schemas/Address\"}}}, \"xml\": {\"name\": \"customer\"}}, \"Address\": {\"type\": \"object\", \"properties\": {\"street\": {\"type\": \"string\", \"example\": \"437 Lytton\"}, \"city\": {\"type\": \"string\", \"example\": \"Palo Alto\"}, \"state\": {\"type\": \"string\", \"example\": \"CA\"}, \"zip\": {\"type\": \"string\", \"example\": \"94301\"}}, \"xml\": {\"name\": \"address\"}}, \"Category\": {\"type\": \"object\", \"properties\": {\"id\": {\"type\": \"integer\", \"format\": \"int64\", \"example\": 1}, \"name\": {\"type\": \"string\", \"example\": \"Dogs\"}}, \"xml\": {\"name\": \"category\"}}, \"User\": {\"type\": \"object\", \"properties\": {\"id\": {\"type\": \"integer\", \"format\": \"int64\", \"example\": 10}, \"username\": {\"type\": \"string\", \"example\": \"theUser\"}, \"firstName\": {\"type\": \"string\", \"example\": \"John\"}, \"lastName\": {\"type\": \"string\", \"example\": \"James\"}, \"email\": {\"type\": \"string\", \"example\": \"john@email.com\"}, \"password\": {\"type\": \"string\", \"example\": \"12345\"}, \"phone\": {\"type\": \"string\", \"example\": \"12345\"}, \"userStatus\": {\"type\": \"integer\", \"description\": \"User Status\", \"format\": \"int32\", \"example\": 1}}, \"xml\": {\"name\": \"user\"}}, \"Tag\": {\"type\": \"object\", \"properties\": {\"id\": {\"type\": \"integer\", \"format\": \"int64\"}, \"name\": {\"type\": \"string\"}}, \"xml\": {\"name\": \"tag\"}}, \"Pet\": {\"required\": [\"name\", \"photoUrls\"], \"type\": \"object\", \"properties\": {\"id\": {\"type\": \"integer\", \"format\": \"int64\", \"example\": 10}, \"name\": {\"type\": \"string\", \"example\": \"doggie\"}, \"category\": {\"$ref\": \"#/components/schemas/Category\"}, \"photoUrls\": {\"type\": \"array\", \"xml\": {\"wrapped\": true}, \"items\": {\"type\": \"string\", \"xml\": {\"name\": \"photoUrl\"}}}, \"tags\": {\"type\": \"array\", \"xml\": {\"wrapped\": true}, \"items\": {\"$ref\": \"#/components/schemas/Tag\"}}, \"status\": {\"type\": \"string\", \"description\": \"pet status in the store\", \"enum\": [\"available\", \"pending\", \"sold\"]}}, \"xml\": {\"name\": \"pet\"}}, \"ApiResponse\": {\"type\": \"object\", \"properties\": {\"code\": {\"type\": \"integer\", \"format\": \"int32\"}, \"type\": {\"type\": \"string\"}, \"message\": {\"type\": \"string\"}}, \"xml\": {\"name\": \"##default\"}}}, \"requestBodies\": {\"Pet\": {\"description\": \"Pet object that needs to be added to the store\", \"content\": {\"application/json\": {\"schema\": {\"$ref\": \"#/components/schemas/Pet\"}}, \"application/xml\": {\"schema\": {\"$ref\": \"#/components/schemas/Pet\"}}}}, \"UserArray\": {\"description\": \"List of user object\", \"content\": {\"application/json\": {\"schema\": {\"type\": \"array\", \"items\": {\"$ref\": \"#/components/schemas/User\"}}}}}}, \"securitySchemes\": {\"petstore_auth\": {\"type\": \"oauth2\", \"flows\": {\"implicit\": {\"authorizationUrl\": \"https://petstore3.swagger.io/oauth/authorize\", \"scopes\": {\"write:pets\": \"modify pets in your account\", \"read:pets\": \"read your pets\"}}}}, \"api_key\": {\"type\": \"apiKey\", \"name\": \"api_key\", \"in\": \"header\"}}}}" }, "type": "OPEN_API" }, diff --git a/tests/openapi/data/pet_store_30/openapi.json b/tests/openapi/data/pet_store_30/openapi.json index d84de512..f2fef089 100644 --- a/tests/openapi/data/pet_store_30/openapi.json +++ b/tests/openapi/data/pet_store_30/openapi.json @@ -289,6 +289,18 @@ } }, "/pet/{petId}": { + "parameters": [ + { + "name": "petId", + "in": "path", + "description": "ID of pet to return", + "required": true, + "schema": { + "type": "integer", + "format": "int64" + } + } + ], "get": { "tags": [ "pet" @@ -296,18 +308,6 @@ "summary": "Find pet by ID", "description": "Returns a single pet", "operationId": "getPetById", - "parameters": [ - { - "name": "petId", - "in": "path", - "description": "ID of pet to return", - "required": true, - "schema": { - "type": "integer", - "format": "int64" - } - } - ], "responses": { "200": { "description": "successful operation", From 9c08b674b602575b90708bd3f4c55212529e4cd9 Mon Sep 17 00:00:00 2001 From: Scott Huang Date: Fri, 25 Oct 2024 17:50:54 +0800 Subject: [PATCH 06/19] Add more tests --- tests/common/test_event_utils.py | 2 ++ tests/openapi/test_extractor.py | 55 ++++++++++++++++++++++++++++++++ 2 files changed, 57 insertions(+) diff --git a/tests/common/test_event_utils.py b/tests/common/test_event_utils.py index d8245161..d0aeb195 100644 --- a/tests/common/test_event_utils.py +++ b/tests/common/test_event_utils.py @@ -1,5 +1,6 @@ from metaphor.common.event_util import EventUtil from metaphor.models.metadata_change_event import ( + API, Dashboard, Dataset, ExternalSearchDocument, @@ -19,6 +20,7 @@ def test_build_event(): event_utils = EventUtil() + assert event_utils.build_event(API()) == MetadataChangeEvent(api=API()) assert event_utils.build_event(Dashboard()) == MetadataChangeEvent( dashboard=Dashboard() ) diff --git a/tests/openapi/test_extractor.py b/tests/openapi/test_extractor.py index 3a296d43..b387b68b 100644 --- a/tests/openapi/test_extractor.py +++ b/tests/openapi/test_extractor.py @@ -1,4 +1,8 @@ +from collections import OrderedDict +from unittest.mock import MagicMock, call, patch + import pytest +import requests from metaphor.common.base_config import OutputConfig from metaphor.common.event_util import EventUtil @@ -33,3 +37,54 @@ async def test_extractor(data_folder, base_url, test_root_dir): ) events = [EventUtil.trim_event(e) for e in await extractor.extract()] assert events == load_json(f"{data_folder}/expected.json") + + +class MockResponse: + def __init__(self, json_data, status_code=200): + self.json_data = json_data + self.status_code = status_code + + def json(self): + return self.json_data + + def raise_for_status(self): + return + + +@patch.object(requests.Session, "get") +def test_get_openapi_json(mock_get_method: MagicMock, test_root_dir: str): + mock_get_method.side_effect = [ + MockResponse({}, 403), + MockResponse({}, 200), + ] + extractor = OpenAPIExtractor(get_dummy_config("http://foo.bar/openapi.json", "")) + + assert extractor._get_openapi_json() is None + assert extractor._get_openapi_json() == {} + + mock_get_method.assert_has_calls( + [ + call( + "http://foo.bar/openapi.json", + headers=OrderedDict( + [ + ("User-Agent", None), + ("Accept", None), + ("Connection", None), + ("Accept-Encoding", None), + ] + ), + ), + call( + "http://foo.bar/openapi.json", + headers=OrderedDict( + [ + ("User-Agent", None), + ("Accept", None), + ("Connection", None), + ("Accept-Encoding", None), + ] + ), + ), + ] + ) From 1ff7d7bab9dc56a1476bc55e909fbfddb2c07c49 Mon Sep 17 00:00:00 2001 From: Scott Huang Date: Mon, 28 Oct 2024 15:42:03 +0800 Subject: [PATCH 07/19] Tag hierarchy --- metaphor/openapi/config.py | 11 ++- metaphor/openapi/extractor.py | 84 ++++++++++++++----- tests/openapi/data/pet_store_20/expected.json | 84 +++++++++++++++---- tests/openapi/data/pet_store_30/expected.json | 81 +++++++++++++++--- tests/openapi/data/pet_store_31/expected.json | 50 ++++++++++- tests/openapi/data/pet_store_31/openapi.json | 2 +- .../expected.json | 50 ++++++++++- .../pet_store_31_absolute_server/openapi.json | 2 +- tests/openapi/test_extractor.py | 18 ++-- 9 files changed, 317 insertions(+), 65 deletions(-) diff --git a/metaphor/openapi/config.py b/metaphor/openapi/config.py index cfdf0689..6878bb4a 100644 --- a/metaphor/openapi/config.py +++ b/metaphor/openapi/config.py @@ -1,9 +1,11 @@ from typing import Optional +from pydantic import model_validator from pydantic.dataclasses import dataclass from metaphor.common.base_config import BaseConfig from metaphor.common.dataclass import ConnectorConfig +from metaphor.common.utils import must_set_exactly_one @dataclass(config=ConnectorConfig) @@ -19,7 +21,12 @@ class OpenAPIAuthConfig: @dataclass(config=ConnectorConfig) class OpenAPIRunConfig(BaseConfig): - openapi_json_path: str # URL or file path base_url: str # base_url of endpoints - + openapi_json_path: Optional[str] = None + openapi_json_url: Optional[str] = None auth: Optional[OpenAPIAuthConfig] = None + + @model_validator(mode="after") + def have_path_or_url(self) -> "OpenAPIRunConfig": + must_set_exactly_one(self.__dict__, ["openapi_json_path", "openapi_json_url"]) + return self diff --git a/metaphor/openapi/extractor.py b/metaphor/openapi/extractor.py index a60d14f1..2ea31caf 100644 --- a/metaphor/openapi/extractor.py +++ b/metaphor/openapi/extractor.py @@ -46,13 +46,15 @@ def __init__(self, config: OpenAPIRunConfig): self._base_url = config.base_url self._api_id = md5_digest(config.base_url.encode("utf-8")) self._openapi_json_path = config.openapi_json_path + self._openapi_json_url = config.openapi_json_url self._auth = config.auth - self._requests_session = requests.sessions.Session() + self._init_session() async def extract(self) -> Collection[ENTITY_TYPES]: - logger.info(f"Fetching metadata from {self._openapi_json_path}") + logger.info( + f"Fetching metadata from {self._openapi_json_path or self._openapi_json_url}" + ) - self._initial_session() openapi_json = self._get_openapi_json() if not openapi_json: @@ -64,7 +66,9 @@ async def extract(self) -> Collection[ENTITY_TYPES]: return hierarchies + endpoints - def _initial_session(self): + def _init_session(self): + self._requests_session = requests.sessions.Session() + if not self._auth: return @@ -73,19 +77,20 @@ def _initial_session(self): self._requests_session.auth = (basic_auth.user, basic_auth.password) def _get_openapi_json(self) -> Optional[dict]: - if not self._openapi_json_path.startswith("http"): + if self._openapi_json_path: with open(self._openapi_json_path, "r") as f: return json.load(f) + # to have full control of HTTP header headers = OrderedDict( { "User-Agent": None, - "Accept": None, + "Accept": "application/json", "Connection": None, "Accept-Encoding": None, } ) - resp = self._requests_session.get(self._openapi_json_path, headers=headers) + resp = self._requests_session.get(self._openapi_json_url, headers=headers) if resp.status_code != 200: return None @@ -98,27 +103,38 @@ def _extract_paths(self, openapi: dict) -> List[API]: for path, path_item in openapi["paths"].items(): path_servers = path_item.get("servers") - base_path = ( + server = ( path_servers[0]["url"] if path_servers else servers[0]["url"] if servers else "" ) - if not base_path.startswith("http"): - endpoint_url = urljoin(self._base_url, base_path + path) + if not server.startswith("http"): + endpoint_url = urljoin(self._base_url, server + path) else: - endpoint_url = urljoin(base_path + "/", f"./{path}") + endpoint_url = urljoin(server + "/", f"./{path}") + + first_tag = self._get_first_tag(path_item) endpoint = API( logical_id=APILogicalID( name=endpoint_url, platform=APIPlatform.OPEN_API ), open_api=OpenAPI(path=path, methods=self._extract_methods(path_item)), - structure=AssetStructure(directories=[self._api_id], name=path), + structure=AssetStructure( + directories=[self._api_id] + [first_tag] if first_tag else [], + name=path, + ), ) endpoints.append(endpoint) return endpoints + def _get_first_tag(self, path_item: dict) -> Optional[str]: + for item in path_item.values(): + if "tags" in item and len(item["tags"]) > 0: + return item["tags"][0] + return None + def _extract_methods(self, path_item: dict) -> List[OpenAPIMethod]: def to_operation_type(method: str) -> Optional[OperationType]: try: @@ -144,16 +160,40 @@ def to_operation_type(method: str) -> Optional[OperationType]: return methods def _build_hierarchies(self, openapi: dict) -> List[Hierarchy]: + hierarchies: List[Hierarchy] = [] + title = openapi["info"]["title"] - hierarchy = Hierarchy( - logical_id=HierarchyLogicalID( - path=[AssetPlatform.OPEN_API.value] + [self._api_id], - ), - hierarchy_info=HierarchyInfo( - name=title, - open_api=OpenAPISpecification(definition=json.dumps(openapi)), - type=HierarchyType.OPEN_API, - ), + hierarchies.append( + Hierarchy( + logical_id=HierarchyLogicalID( + path=[AssetPlatform.OPEN_API.value, self._api_id], + ), + hierarchy_info=HierarchyInfo( + name=title, + open_api=OpenAPISpecification(definition=json.dumps(openapi)), + type=HierarchyType.OPEN_API, + ), + ) ) - return [hierarchy] + for tag in openapi.get("tags") or []: + name = tag.get("name") + description = tag.get("description") + + if not name: + continue + + hierarchies.append( + Hierarchy( + logical_id=HierarchyLogicalID( + path=[AssetPlatform.OPEN_API.value, self._api_id, name], + ), + hierarchy_info=HierarchyInfo( + name=title, + description=description, + type=HierarchyType.OPEN_API, + ), + ) + ) + + return hierarchies diff --git a/tests/openapi/data/pet_store_20/expected.json b/tests/openapi/data/pet_store_20/expected.json index 216e555d..19e50e5c 100644 --- a/tests/openapi/data/pet_store_20/expected.json +++ b/tests/openapi/data/pet_store_20/expected.json @@ -14,6 +14,48 @@ ] } }, + { + "hierarchyInfo": { + "description": "Everything about your Pets", + "name": "Swagger Petstore", + "type": "OPEN_API" + }, + "logicalId": { + "path": [ + "OPEN_API", + "3a45981ff1a3539f49317040f895e979", + "pet" + ] + } + }, + { + "hierarchyInfo": { + "description": "Access to Petstore orders", + "name": "Swagger Petstore", + "type": "OPEN_API" + }, + "logicalId": { + "path": [ + "OPEN_API", + "3a45981ff1a3539f49317040f895e979", + "store" + ] + } + }, + { + "hierarchyInfo": { + "description": "Operations about user", + "name": "Swagger Petstore", + "type": "OPEN_API" + }, + "logicalId": { + "path": [ + "OPEN_API", + "3a45981ff1a3539f49317040f895e979", + "user" + ] + } + }, { "logicalId": { "name": "https://foo.bar/pet/{petId}/uploadImage", @@ -30,7 +72,8 @@ }, "structure": { "directories": [ - "3a45981ff1a3539f49317040f895e979" + "3a45981ff1a3539f49317040f895e979", + "pet" ], "name": "/pet/{petId}/uploadImage" } @@ -55,7 +98,8 @@ }, "structure": { "directories": [ - "3a45981ff1a3539f49317040f895e979" + "3a45981ff1a3539f49317040f895e979", + "pet" ], "name": "/pet" } @@ -77,7 +121,8 @@ }, "structure": { "directories": [ - "3a45981ff1a3539f49317040f895e979" + "3a45981ff1a3539f49317040f895e979", + "pet" ], "name": "/pet/findByStatus" } @@ -99,7 +144,8 @@ }, "structure": { "directories": [ - "3a45981ff1a3539f49317040f895e979" + "3a45981ff1a3539f49317040f895e979", + "pet" ], "name": "/pet/findByTags" } @@ -129,7 +175,8 @@ }, "structure": { "directories": [ - "3a45981ff1a3539f49317040f895e979" + "3a45981ff1a3539f49317040f895e979", + "pet" ], "name": "/pet/{petId}" } @@ -151,7 +198,8 @@ }, "structure": { "directories": [ - "3a45981ff1a3539f49317040f895e979" + "3a45981ff1a3539f49317040f895e979", + "store" ], "name": "/store/inventory" } @@ -172,7 +220,8 @@ }, "structure": { "directories": [ - "3a45981ff1a3539f49317040f895e979" + "3a45981ff1a3539f49317040f895e979", + "store" ], "name": "/store/order" } @@ -199,7 +248,8 @@ }, "structure": { "directories": [ - "3a45981ff1a3539f49317040f895e979" + "3a45981ff1a3539f49317040f895e979", + "store" ], "name": "/store/order/{orderId}" } @@ -220,7 +270,8 @@ }, "structure": { "directories": [ - "3a45981ff1a3539f49317040f895e979" + "3a45981ff1a3539f49317040f895e979", + "user" ], "name": "/user/createWithList" } @@ -251,7 +302,8 @@ }, "structure": { "directories": [ - "3a45981ff1a3539f49317040f895e979" + "3a45981ff1a3539f49317040f895e979", + "user" ], "name": "/user/{username}" } @@ -272,7 +324,8 @@ }, "structure": { "directories": [ - "3a45981ff1a3539f49317040f895e979" + "3a45981ff1a3539f49317040f895e979", + "user" ], "name": "/user/login" } @@ -293,7 +346,8 @@ }, "structure": { "directories": [ - "3a45981ff1a3539f49317040f895e979" + "3a45981ff1a3539f49317040f895e979", + "user" ], "name": "/user/logout" } @@ -314,7 +368,8 @@ }, "structure": { "directories": [ - "3a45981ff1a3539f49317040f895e979" + "3a45981ff1a3539f49317040f895e979", + "user" ], "name": "/user/createWithArray" } @@ -336,7 +391,8 @@ }, "structure": { "directories": [ - "3a45981ff1a3539f49317040f895e979" + "3a45981ff1a3539f49317040f895e979", + "user" ], "name": "/user" } diff --git a/tests/openapi/data/pet_store_30/expected.json b/tests/openapi/data/pet_store_30/expected.json index 289dad8b..afc4ce99 100644 --- a/tests/openapi/data/pet_store_30/expected.json +++ b/tests/openapi/data/pet_store_30/expected.json @@ -14,6 +14,48 @@ ] } }, + { + "hierarchyInfo": { + "description": "Everything about your Pets", + "name": "Swagger Petstore - OpenAPI 3.0", + "type": "OPEN_API" + }, + "logicalId": { + "path": [ + "OPEN_API", + "3a45981ff1a3539f49317040f895e979", + "pet" + ] + } + }, + { + "hierarchyInfo": { + "description": "Access to Petstore orders", + "name": "Swagger Petstore - OpenAPI 3.0", + "type": "OPEN_API" + }, + "logicalId": { + "path": [ + "OPEN_API", + "3a45981ff1a3539f49317040f895e979", + "store" + ] + } + }, + { + "hierarchyInfo": { + "description": "Operations about user", + "name": "Swagger Petstore - OpenAPI 3.0", + "type": "OPEN_API" + }, + "logicalId": { + "path": [ + "OPEN_API", + "3a45981ff1a3539f49317040f895e979", + "user" + ] + } + }, { "logicalId": { "name": "https://foo.bar/api/v3/pet", @@ -36,7 +78,8 @@ }, "structure": { "directories": [ - "3a45981ff1a3539f49317040f895e979" + "3a45981ff1a3539f49317040f895e979", + "pet" ], "name": "/pet" } @@ -58,7 +101,8 @@ }, "structure": { "directories": [ - "3a45981ff1a3539f49317040f895e979" + "3a45981ff1a3539f49317040f895e979", + "pet" ], "name": "/pet/findByStatus" } @@ -80,7 +124,8 @@ }, "structure": { "directories": [ - "3a45981ff1a3539f49317040f895e979" + "3a45981ff1a3539f49317040f895e979", + "pet" ], "name": "/pet/findByTags" } @@ -110,7 +155,8 @@ }, "structure": { "directories": [ - "3a45981ff1a3539f49317040f895e979" + "3a45981ff1a3539f49317040f895e979", + "pet" ], "name": "/pet/{petId}" } @@ -131,7 +177,8 @@ }, "structure": { "directories": [ - "3a45981ff1a3539f49317040f895e979" + "3a45981ff1a3539f49317040f895e979", + "pet" ], "name": "/pet/{petId}/uploadImage" } @@ -153,7 +200,8 @@ }, "structure": { "directories": [ - "3a45981ff1a3539f49317040f895e979" + "3a45981ff1a3539f49317040f895e979", + "store" ], "name": "/store/inventory" } @@ -175,7 +223,8 @@ }, "structure": { "directories": [ - "3a45981ff1a3539f49317040f895e979" + "3a45981ff1a3539f49317040f895e979", + "store" ], "name": "/store/order" } @@ -202,7 +251,8 @@ }, "structure": { "directories": [ - "3a45981ff1a3539f49317040f895e979" + "3a45981ff1a3539f49317040f895e979", + "store" ], "name": "/store/order/{orderId}" } @@ -224,7 +274,8 @@ }, "structure": { "directories": [ - "3a45981ff1a3539f49317040f895e979" + "3a45981ff1a3539f49317040f895e979", + "user" ], "name": "/user" } @@ -246,7 +297,8 @@ }, "structure": { "directories": [ - "3a45981ff1a3539f49317040f895e979" + "3a45981ff1a3539f49317040f895e979", + "user" ], "name": "/user/createWithList" } @@ -267,7 +319,8 @@ }, "structure": { "directories": [ - "3a45981ff1a3539f49317040f895e979" + "3a45981ff1a3539f49317040f895e979", + "user" ], "name": "/user/login" } @@ -288,7 +341,8 @@ }, "structure": { "directories": [ - "3a45981ff1a3539f49317040f895e979" + "3a45981ff1a3539f49317040f895e979", + "user" ], "name": "/user/logout" } @@ -319,7 +373,8 @@ }, "structure": { "directories": [ - "3a45981ff1a3539f49317040f895e979" + "3a45981ff1a3539f49317040f895e979", + "user" ], "name": "/user/{username}" } diff --git a/tests/openapi/data/pet_store_31/expected.json b/tests/openapi/data/pet_store_31/expected.json index 0873f0bf..c37784f0 100644 --- a/tests/openapi/data/pet_store_31/expected.json +++ b/tests/openapi/data/pet_store_31/expected.json @@ -3,7 +3,7 @@ "hierarchyInfo": { "name": "Swagger Petstore - OpenAPI 3.1", "openAPI": { - "definition": "{\"openapi\": \"3.1.0\", \"info\": {\"title\": \"Swagger Petstore - OpenAPI 3.1\", \"description\": \"This is a sample Pet Store Server based on the OpenAPI 3.1 specification.\\nYou can find out more about\\nSwagger at [http://swagger.io](http://swagger.io).\", \"termsOfService\": \"http://swagger.io/terms/\", \"contact\": {\"email\": \"apiteam@swagger.io\"}, \"license\": {\"name\": \"Apache 2.0 AND (MIT OR GPL-2.0-only)\", \"identifier\": \"Apache-2.0 AND (MIT OR GPL-2.0-only)\"}, \"version\": \"1.0.6\", \"summary\": \"Pet Store 3.1\", \"x-namespace\": \"swagger\"}, \"externalDocs\": {\"description\": \"Find out more about Swagger\", \"url\": \"http://swagger.io\"}, \"servers\": [{\"url\": \"/api/v31\"}], \"tags\": [{\"name\": \"pet\", \"description\": \"Everything about your Pets\", \"externalDocs\": {\"description\": \"Find out more\", \"url\": \"http://swagger.io\"}}, {\"name\": \"store\", \"description\": \"Access to Petstore orders\", \"externalDocs\": {\"description\": \"Find out more about our store\", \"url\": \"http://swagger.io\"}}, {\"name\": \"user\", \"description\": \"Operations about user\"}], \"paths\": {\"/pet\": {\"put\": {\"tags\": [\"pet\"], \"summary\": \"Update an existing pet\", \"description\": \"Update an existing pet by Id\", \"operationId\": \"updatePet\", \"requestBody\": {\"description\": \"Pet object that needs to be updated in the store\", \"content\": {\"application/json\": {\"schema\": {\"$ref\": \"#/components/schemas/Pet\", \"description\": \"A Pet in JSON Format\", \"required\": [\"id\"], \"writeOnly\": true}}, \"application/xml\": {\"schema\": {\"$ref\": \"#/components/schemas/Pet\", \"description\": \"A Pet in XML Format\", \"required\": [\"id\"], \"writeOnly\": true}}}, \"required\": true}, \"responses\": {\"200\": {\"description\": \"Successful operation\", \"content\": {\"application/xml\": {\"schema\": {\"$ref\": \"#/components/schemas/Pet\", \"description\": \"A Pet in XML Format\", \"readOnly\": true}}, \"application/json\": {\"schema\": {\"$ref\": \"#/components/schemas/Pet\", \"description\": \"A Pet in JSON Format\", \"readOnly\": true}}}}, \"400\": {\"description\": \"Invalid ID supplied\"}, \"404\": {\"description\": \"Pet not found\"}, \"405\": {\"description\": \"Validation exception\"}}, \"security\": [{\"petstore_auth\": [\"write:pets\", \"read:pets\"]}]}, \"post\": {\"tags\": [\"pet\"], \"summary\": \"Add a new pet to the store\", \"description\": \"Add a new pet to the store\", \"operationId\": \"addPet\", \"requestBody\": {\"description\": \"Create a new pet in the store\", \"content\": {\"application/json\": {\"schema\": {\"$ref\": \"#/components/schemas/Pet\", \"description\": \"A Pet in JSON Format\", \"required\": [\"id\"], \"writeOnly\": true}}, \"application/xml\": {\"schema\": {\"$ref\": \"#/components/schemas/Pet\", \"description\": \"A Pet in XML Format\", \"required\": [\"id\"], \"writeOnly\": true}}}, \"required\": true}, \"responses\": {\"200\": {\"description\": \"Successful operation\", \"content\": {\"application/xml\": {\"schema\": {\"$ref\": \"#/components/schemas/Pet\", \"description\": \"A Pet in XML Format\", \"readOnly\": true}}, \"application/json\": {\"schema\": {\"$ref\": \"#/components/schemas/Pet\", \"description\": \"A Pet in JSON Format\", \"readOnly\": true}}}}, \"405\": {\"description\": \"Invalid input\"}}, \"security\": [{\"petstore_auth\": [\"write:pets\", \"read:pets\"]}]}}, \"/pet/{petId}\": {\"get\": {\"tags\": [\"pets\"], \"summary\": \"Find pet by ID\", \"description\": \"Returns a pet when 0 < ID <= 10. ID > 10 or nonintegers will simulate API error conditions\", \"operationId\": \"getPetById\", \"parameters\": [{\"name\": \"petId\", \"in\": \"path\", \"description\": \"ID of pet that needs to be fetched\", \"required\": true, \"schema\": {\"type\": \"integer\", \"format\": \"int64\", \"description\": \"param ID of pet that needs to be fetched\", \"exclusiveMaximum\": 10, \"exclusiveMinimum\": 1}}], \"responses\": {\"default\": {\"description\": \"The pet\", \"content\": {\"application/json\": {\"schema\": {\"$ref\": \"#/components/schemas/Pet\", \"description\": \"A Pet in JSON format\"}}, \"application/xml\": {\"schema\": {\"$ref\": \"#/components/schemas/Pet\", \"description\": \"A Pet in XML format\"}}}}, \"400\": {\"description\": \"Invalid ID supplied\"}, \"404\": {\"description\": \"Pet not found\"}}, \"security\": [{\"petstore_auth\": [\"write:pets\", \"read:pets\"]}, {\"api_key\": []}]}}}, \"components\": {\"schemas\": {\"Category\": {\"$id\": \"/components/schemas/category\", \"description\": \"Category\", \"properties\": {\"id\": {\"type\": \"integer\", \"format\": \"int64\", \"example\": 1}, \"name\": {\"type\": \"string\", \"example\": \"Dogs\"}}, \"xml\": {\"name\": \"Category\"}}, \"Pet\": {\"$schema\": \"https://json-schema.org/draft/2020-12/schema\", \"description\": \"Pet\", \"properties\": {\"id\": {\"type\": \"integer\", \"format\": \"int64\", \"example\": 10}, \"category\": {\"$ref\": \"#/components/schemas/Category\", \"description\": \"Pet Category\"}, \"name\": {\"type\": \"string\", \"example\": \"doggie\"}, \"photoUrls\": {\"type\": \"array\", \"items\": {\"type\": \"string\", \"xml\": {\"name\": \"photoUrl\"}}, \"xml\": {\"wrapped\": true}}, \"tags\": {\"type\": \"array\", \"items\": {\"$ref\": \"#/components/schemas/Tag\"}, \"xml\": {\"wrapped\": true}}, \"status\": {\"type\": \"string\", \"description\": \"pet status in the store\", \"enum\": [\"available\", \"pending\", \"sold\"]}, \"availableInstances\": {\"type\": \"integer\", \"format\": \"int32\", \"example\": 7, \"exclusiveMaximum\": 10, \"exclusiveMinimum\": 1, \"swagger-extension\": true}, \"petDetailsId\": {\"type\": \"integer\", \"format\": \"int64\", \"$ref\": \"/components/schemas/petdetails#pet_details_id\"}, \"petDetails\": {\"$ref\": \"/components/schemas/petdetails\"}}, \"required\": [\"name\", \"photoUrls\"], \"xml\": {\"name\": \"Pet\"}}, \"PetDetails\": {\"$id\": \"/components/schemas/petdetails\", \"$schema\": \"https://json-schema.org/draft/2020-12/schema\", \"$vocabulary\": \"https://spec.openapis.org/oas/3.1/schema-base\", \"properties\": {\"id\": {\"type\": \"integer\", \"format\": \"int64\", \"$anchor\": \"pet_details_id\", \"example\": 10}, \"category\": {\"$ref\": \"/components/schemas/category\", \"description\": \"PetDetails Category\"}, \"tag\": {\"$ref\": \"/components/schemas/tag\"}}, \"xml\": {\"name\": \"PetDetails\"}}, \"Tag\": {\"$id\": \"/components/schemas/tag\", \"properties\": {\"id\": {\"type\": \"integer\", \"format\": \"int64\"}, \"name\": {\"type\": \"string\"}}, \"xml\": {\"name\": \"Tag\"}}}, \"securitySchemes\": {\"petstore_auth\": {\"type\": \"oauth2\", \"flows\": {\"implicit\": {\"authorizationUrl\": \"https://petstore3.swagger.io/oauth/authorize\", \"scopes\": {\"write:pets\": \"modify pets in your account\", \"read:pets\": \"read your pets\"}}}}, \"mutual_tls\": {\"type\": \"mutualTLS\"}, \"api_key\": {\"type\": \"apiKey\", \"name\": \"api_key\", \"in\": \"header\"}}}, \"webhooks\": {\"newPet\": {\"post\": {\"requestBody\": {\"description\": \"Information about a new pet in the system\", \"content\": {\"application/json\": {\"schema\": {\"$ref\": \"#/components/schemas/Pet\", \"description\": \"Webhook Pet\"}}}}, \"responses\": {\"200\": {\"description\": \"Return a 200 status to indicate that the data was received successfully\"}}}}}}" + "definition": "{\"openapi\": \"3.1.0\", \"info\": {\"title\": \"Swagger Petstore - OpenAPI 3.1\", \"description\": \"This is a sample Pet Store Server based on the OpenAPI 3.1 specification.\\nYou can find out more about\\nSwagger at [http://swagger.io](http://swagger.io).\", \"termsOfService\": \"http://swagger.io/terms/\", \"contact\": {\"email\": \"apiteam@swagger.io\"}, \"license\": {\"name\": \"Apache 2.0 AND (MIT OR GPL-2.0-only)\", \"identifier\": \"Apache-2.0 AND (MIT OR GPL-2.0-only)\"}, \"version\": \"1.0.6\", \"summary\": \"Pet Store 3.1\", \"x-namespace\": \"swagger\"}, \"externalDocs\": {\"description\": \"Find out more about Swagger\", \"url\": \"http://swagger.io\"}, \"servers\": [{\"url\": \"/api/v31\"}], \"tags\": [{\"name\": \"pet\", \"description\": \"Everything about your Pets\", \"externalDocs\": {\"description\": \"Find out more\", \"url\": \"http://swagger.io\"}}, {\"name\": \"store\", \"description\": \"Access to Petstore orders\", \"externalDocs\": {\"description\": \"Find out more about our store\", \"url\": \"http://swagger.io\"}}, {\"name\": \"user\", \"description\": \"Operations about user\"}], \"paths\": {\"/pet\": {\"put\": {\"tags\": [\"pet\"], \"summary\": \"Update an existing pet\", \"description\": \"Update an existing pet by Id\", \"operationId\": \"updatePet\", \"requestBody\": {\"description\": \"Pet object that needs to be updated in the store\", \"content\": {\"application/json\": {\"schema\": {\"$ref\": \"#/components/schemas/Pet\", \"description\": \"A Pet in JSON Format\", \"required\": [\"id\"], \"writeOnly\": true}}, \"application/xml\": {\"schema\": {\"$ref\": \"#/components/schemas/Pet\", \"description\": \"A Pet in XML Format\", \"required\": [\"id\"], \"writeOnly\": true}}}, \"required\": true}, \"responses\": {\"200\": {\"description\": \"Successful operation\", \"content\": {\"application/xml\": {\"schema\": {\"$ref\": \"#/components/schemas/Pet\", \"description\": \"A Pet in XML Format\", \"readOnly\": true}}, \"application/json\": {\"schema\": {\"$ref\": \"#/components/schemas/Pet\", \"description\": \"A Pet in JSON Format\", \"readOnly\": true}}}}, \"400\": {\"description\": \"Invalid ID supplied\"}, \"404\": {\"description\": \"Pet not found\"}, \"405\": {\"description\": \"Validation exception\"}}, \"security\": [{\"petstore_auth\": [\"write:pets\", \"read:pets\"]}]}, \"post\": {\"tags\": [\"pet\"], \"summary\": \"Add a new pet to the store\", \"description\": \"Add a new pet to the store\", \"operationId\": \"addPet\", \"requestBody\": {\"description\": \"Create a new pet in the store\", \"content\": {\"application/json\": {\"schema\": {\"$ref\": \"#/components/schemas/Pet\", \"description\": \"A Pet in JSON Format\", \"required\": [\"id\"], \"writeOnly\": true}}, \"application/xml\": {\"schema\": {\"$ref\": \"#/components/schemas/Pet\", \"description\": \"A Pet in XML Format\", \"required\": [\"id\"], \"writeOnly\": true}}}, \"required\": true}, \"responses\": {\"200\": {\"description\": \"Successful operation\", \"content\": {\"application/xml\": {\"schema\": {\"$ref\": \"#/components/schemas/Pet\", \"description\": \"A Pet in XML Format\", \"readOnly\": true}}, \"application/json\": {\"schema\": {\"$ref\": \"#/components/schemas/Pet\", \"description\": \"A Pet in JSON Format\", \"readOnly\": true}}}}, \"405\": {\"description\": \"Invalid input\"}}, \"security\": [{\"petstore_auth\": [\"write:pets\", \"read:pets\"]}]}}, \"/pet/{petId}\": {\"get\": {\"tags\": [\"pet\"], \"summary\": \"Find pet by ID\", \"description\": \"Returns a pet when 0 < ID <= 10. ID > 10 or nonintegers will simulate API error conditions\", \"operationId\": \"getPetById\", \"parameters\": [{\"name\": \"petId\", \"in\": \"path\", \"description\": \"ID of pet that needs to be fetched\", \"required\": true, \"schema\": {\"type\": \"integer\", \"format\": \"int64\", \"description\": \"param ID of pet that needs to be fetched\", \"exclusiveMaximum\": 10, \"exclusiveMinimum\": 1}}], \"responses\": {\"default\": {\"description\": \"The pet\", \"content\": {\"application/json\": {\"schema\": {\"$ref\": \"#/components/schemas/Pet\", \"description\": \"A Pet in JSON format\"}}, \"application/xml\": {\"schema\": {\"$ref\": \"#/components/schemas/Pet\", \"description\": \"A Pet in XML format\"}}}}, \"400\": {\"description\": \"Invalid ID supplied\"}, \"404\": {\"description\": \"Pet not found\"}}, \"security\": [{\"petstore_auth\": [\"write:pets\", \"read:pets\"]}, {\"api_key\": []}]}}}, \"components\": {\"schemas\": {\"Category\": {\"$id\": \"/components/schemas/category\", \"description\": \"Category\", \"properties\": {\"id\": {\"type\": \"integer\", \"format\": \"int64\", \"example\": 1}, \"name\": {\"type\": \"string\", \"example\": \"Dogs\"}}, \"xml\": {\"name\": \"Category\"}}, \"Pet\": {\"$schema\": \"https://json-schema.org/draft/2020-12/schema\", \"description\": \"Pet\", \"properties\": {\"id\": {\"type\": \"integer\", \"format\": \"int64\", \"example\": 10}, \"category\": {\"$ref\": \"#/components/schemas/Category\", \"description\": \"Pet Category\"}, \"name\": {\"type\": \"string\", \"example\": \"doggie\"}, \"photoUrls\": {\"type\": \"array\", \"items\": {\"type\": \"string\", \"xml\": {\"name\": \"photoUrl\"}}, \"xml\": {\"wrapped\": true}}, \"tags\": {\"type\": \"array\", \"items\": {\"$ref\": \"#/components/schemas/Tag\"}, \"xml\": {\"wrapped\": true}}, \"status\": {\"type\": \"string\", \"description\": \"pet status in the store\", \"enum\": [\"available\", \"pending\", \"sold\"]}, \"availableInstances\": {\"type\": \"integer\", \"format\": \"int32\", \"example\": 7, \"exclusiveMaximum\": 10, \"exclusiveMinimum\": 1, \"swagger-extension\": true}, \"petDetailsId\": {\"type\": \"integer\", \"format\": \"int64\", \"$ref\": \"/components/schemas/petdetails#pet_details_id\"}, \"petDetails\": {\"$ref\": \"/components/schemas/petdetails\"}}, \"required\": [\"name\", \"photoUrls\"], \"xml\": {\"name\": \"Pet\"}}, \"PetDetails\": {\"$id\": \"/components/schemas/petdetails\", \"$schema\": \"https://json-schema.org/draft/2020-12/schema\", \"$vocabulary\": \"https://spec.openapis.org/oas/3.1/schema-base\", \"properties\": {\"id\": {\"type\": \"integer\", \"format\": \"int64\", \"$anchor\": \"pet_details_id\", \"example\": 10}, \"category\": {\"$ref\": \"/components/schemas/category\", \"description\": \"PetDetails Category\"}, \"tag\": {\"$ref\": \"/components/schemas/tag\"}}, \"xml\": {\"name\": \"PetDetails\"}}, \"Tag\": {\"$id\": \"/components/schemas/tag\", \"properties\": {\"id\": {\"type\": \"integer\", \"format\": \"int64\"}, \"name\": {\"type\": \"string\"}}, \"xml\": {\"name\": \"Tag\"}}}, \"securitySchemes\": {\"petstore_auth\": {\"type\": \"oauth2\", \"flows\": {\"implicit\": {\"authorizationUrl\": \"https://petstore3.swagger.io/oauth/authorize\", \"scopes\": {\"write:pets\": \"modify pets in your account\", \"read:pets\": \"read your pets\"}}}}, \"mutual_tls\": {\"type\": \"mutualTLS\"}, \"api_key\": {\"type\": \"apiKey\", \"name\": \"api_key\", \"in\": \"header\"}}}, \"webhooks\": {\"newPet\": {\"post\": {\"requestBody\": {\"description\": \"Information about a new pet in the system\", \"content\": {\"application/json\": {\"schema\": {\"$ref\": \"#/components/schemas/Pet\", \"description\": \"Webhook Pet\"}}}}, \"responses\": {\"200\": {\"description\": \"Return a 200 status to indicate that the data was received successfully\"}}}}}}" }, "type": "OPEN_API" }, @@ -14,6 +14,48 @@ ] } }, + { + "hierarchyInfo": { + "description": "Everything about your Pets", + "name": "Swagger Petstore - OpenAPI 3.1", + "type": "OPEN_API" + }, + "logicalId": { + "path": [ + "OPEN_API", + "3a45981ff1a3539f49317040f895e979", + "pet" + ] + } + }, + { + "hierarchyInfo": { + "description": "Access to Petstore orders", + "name": "Swagger Petstore - OpenAPI 3.1", + "type": "OPEN_API" + }, + "logicalId": { + "path": [ + "OPEN_API", + "3a45981ff1a3539f49317040f895e979", + "store" + ] + } + }, + { + "hierarchyInfo": { + "description": "Operations about user", + "name": "Swagger Petstore - OpenAPI 3.1", + "type": "OPEN_API" + }, + "logicalId": { + "path": [ + "OPEN_API", + "3a45981ff1a3539f49317040f895e979", + "user" + ] + } + }, { "logicalId": { "name": "https://foo.bar/api/v31/pet", @@ -36,7 +78,8 @@ }, "structure": { "directories": [ - "3a45981ff1a3539f49317040f895e979" + "3a45981ff1a3539f49317040f895e979", + "pet" ], "name": "/pet" } @@ -58,7 +101,8 @@ }, "structure": { "directories": [ - "3a45981ff1a3539f49317040f895e979" + "3a45981ff1a3539f49317040f895e979", + "pet" ], "name": "/pet/{petId}" } diff --git a/tests/openapi/data/pet_store_31/openapi.json b/tests/openapi/data/pet_store_31/openapi.json index 9068da95..749de9ee 100644 --- a/tests/openapi/data/pet_store_31/openapi.json +++ b/tests/openapi/data/pet_store_31/openapi.json @@ -190,7 +190,7 @@ "/pet/{petId}": { "get": { "tags": [ - "pets" + "pet" ], "summary": "Find pet by ID", "description": "Returns a pet when 0 < ID <= 10. ID > 10 or nonintegers will simulate API error conditions", diff --git a/tests/openapi/data/pet_store_31_absolute_server/expected.json b/tests/openapi/data/pet_store_31_absolute_server/expected.json index ef7c7621..3b6b72f1 100644 --- a/tests/openapi/data/pet_store_31_absolute_server/expected.json +++ b/tests/openapi/data/pet_store_31_absolute_server/expected.json @@ -3,7 +3,7 @@ "hierarchyInfo": { "name": "Swagger Petstore - OpenAPI 3.1", "openAPI": { - "definition": "{\"openapi\": \"3.1.0\", \"info\": {\"title\": \"Swagger Petstore - OpenAPI 3.1\", \"description\": \"This is a sample Pet Store Server based on the OpenAPI 3.1 specification.\\nYou can find out more about\\nSwagger at [http://swagger.io](http://swagger.io).\", \"termsOfService\": \"http://swagger.io/terms/\", \"contact\": {\"email\": \"apiteam@swagger.io\"}, \"license\": {\"name\": \"Apache 2.0 AND (MIT OR GPL-2.0-only)\", \"identifier\": \"Apache-2.0 AND (MIT OR GPL-2.0-only)\"}, \"version\": \"1.0.6\", \"summary\": \"Pet Store 3.1\", \"x-namespace\": \"swagger\"}, \"externalDocs\": {\"description\": \"Find out more about Swagger\", \"url\": \"http://swagger.io\"}, \"servers\": [{\"url\": \"https://xyz.bar/api/v31\"}], \"tags\": [{\"name\": \"pet\", \"description\": \"Everything about your Pets\", \"externalDocs\": {\"description\": \"Find out more\", \"url\": \"http://swagger.io\"}}, {\"name\": \"store\", \"description\": \"Access to Petstore orders\", \"externalDocs\": {\"description\": \"Find out more about our store\", \"url\": \"http://swagger.io\"}}, {\"name\": \"user\", \"description\": \"Operations about user\"}], \"paths\": {\"/pet\": {\"put\": {\"tags\": [\"pet\"], \"summary\": \"Update an existing pet\", \"description\": \"Update an existing pet by Id\", \"operationId\": \"updatePet\", \"requestBody\": {\"description\": \"Pet object that needs to be updated in the store\", \"content\": {\"application/json\": {\"schema\": {\"$ref\": \"#/components/schemas/Pet\", \"description\": \"A Pet in JSON Format\", \"required\": [\"id\"], \"writeOnly\": true}}, \"application/xml\": {\"schema\": {\"$ref\": \"#/components/schemas/Pet\", \"description\": \"A Pet in XML Format\", \"required\": [\"id\"], \"writeOnly\": true}}}, \"required\": true}, \"responses\": {\"200\": {\"description\": \"Successful operation\", \"content\": {\"application/xml\": {\"schema\": {\"$ref\": \"#/components/schemas/Pet\", \"description\": \"A Pet in XML Format\", \"readOnly\": true}}, \"application/json\": {\"schema\": {\"$ref\": \"#/components/schemas/Pet\", \"description\": \"A Pet in JSON Format\", \"readOnly\": true}}}}, \"400\": {\"description\": \"Invalid ID supplied\"}, \"404\": {\"description\": \"Pet not found\"}, \"405\": {\"description\": \"Validation exception\"}}, \"security\": [{\"petstore_auth\": [\"write:pets\", \"read:pets\"]}]}, \"post\": {\"tags\": [\"pet\"], \"summary\": \"Add a new pet to the store\", \"description\": \"Add a new pet to the store\", \"operationId\": \"addPet\", \"requestBody\": {\"description\": \"Create a new pet in the store\", \"content\": {\"application/json\": {\"schema\": {\"$ref\": \"#/components/schemas/Pet\", \"description\": \"A Pet in JSON Format\", \"required\": [\"id\"], \"writeOnly\": true}}, \"application/xml\": {\"schema\": {\"$ref\": \"#/components/schemas/Pet\", \"description\": \"A Pet in XML Format\", \"required\": [\"id\"], \"writeOnly\": true}}}, \"required\": true}, \"responses\": {\"200\": {\"description\": \"Successful operation\", \"content\": {\"application/xml\": {\"schema\": {\"$ref\": \"#/components/schemas/Pet\", \"description\": \"A Pet in XML Format\", \"readOnly\": true}}, \"application/json\": {\"schema\": {\"$ref\": \"#/components/schemas/Pet\", \"description\": \"A Pet in JSON Format\", \"readOnly\": true}}}}, \"405\": {\"description\": \"Invalid input\"}}, \"security\": [{\"petstore_auth\": [\"write:pets\", \"read:pets\"]}]}}, \"/pet/{petId}\": {\"get\": {\"tags\": [\"pets\"], \"summary\": \"Find pet by ID\", \"description\": \"Returns a pet when 0 < ID <= 10. ID > 10 or nonintegers will simulate API error conditions\", \"operationId\": \"getPetById\", \"parameters\": [{\"name\": \"petId\", \"in\": \"path\", \"description\": \"ID of pet that needs to be fetched\", \"required\": true, \"schema\": {\"type\": \"integer\", \"format\": \"int64\", \"description\": \"param ID of pet that needs to be fetched\", \"exclusiveMaximum\": 10, \"exclusiveMinimum\": 1}}], \"responses\": {\"default\": {\"description\": \"The pet\", \"content\": {\"application/json\": {\"schema\": {\"$ref\": \"#/components/schemas/Pet\", \"description\": \"A Pet in JSON format\"}}, \"application/xml\": {\"schema\": {\"$ref\": \"#/components/schemas/Pet\", \"description\": \"A Pet in XML format\"}}}}, \"400\": {\"description\": \"Invalid ID supplied\"}, \"404\": {\"description\": \"Pet not found\"}}, \"security\": [{\"petstore_auth\": [\"write:pets\", \"read:pets\"]}, {\"api_key\": []}]}}}, \"components\": {\"schemas\": {\"Category\": {\"$id\": \"/components/schemas/category\", \"description\": \"Category\", \"properties\": {\"id\": {\"type\": \"integer\", \"format\": \"int64\", \"example\": 1}, \"name\": {\"type\": \"string\", \"example\": \"Dogs\"}}, \"xml\": {\"name\": \"Category\"}}, \"Pet\": {\"$schema\": \"https://json-schema.org/draft/2020-12/schema\", \"description\": \"Pet\", \"properties\": {\"id\": {\"type\": \"integer\", \"format\": \"int64\", \"example\": 10}, \"category\": {\"$ref\": \"#/components/schemas/Category\", \"description\": \"Pet Category\"}, \"name\": {\"type\": \"string\", \"example\": \"doggie\"}, \"photoUrls\": {\"type\": \"array\", \"items\": {\"type\": \"string\", \"xml\": {\"name\": \"photoUrl\"}}, \"xml\": {\"wrapped\": true}}, \"tags\": {\"type\": \"array\", \"items\": {\"$ref\": \"#/components/schemas/Tag\"}, \"xml\": {\"wrapped\": true}}, \"status\": {\"type\": \"string\", \"description\": \"pet status in the store\", \"enum\": [\"available\", \"pending\", \"sold\"]}, \"availableInstances\": {\"type\": \"integer\", \"format\": \"int32\", \"example\": 7, \"exclusiveMaximum\": 10, \"exclusiveMinimum\": 1, \"swagger-extension\": true}, \"petDetailsId\": {\"type\": \"integer\", \"format\": \"int64\", \"$ref\": \"/components/schemas/petdetails#pet_details_id\"}, \"petDetails\": {\"$ref\": \"/components/schemas/petdetails\"}}, \"required\": [\"name\", \"photoUrls\"], \"xml\": {\"name\": \"Pet\"}}, \"PetDetails\": {\"$id\": \"/components/schemas/petdetails\", \"$schema\": \"https://json-schema.org/draft/2020-12/schema\", \"$vocabulary\": \"https://spec.openapis.org/oas/3.1/schema-base\", \"properties\": {\"id\": {\"type\": \"integer\", \"format\": \"int64\", \"$anchor\": \"pet_details_id\", \"example\": 10}, \"category\": {\"$ref\": \"/components/schemas/category\", \"description\": \"PetDetails Category\"}, \"tag\": {\"$ref\": \"/components/schemas/tag\"}}, \"xml\": {\"name\": \"PetDetails\"}}, \"Tag\": {\"$id\": \"/components/schemas/tag\", \"properties\": {\"id\": {\"type\": \"integer\", \"format\": \"int64\"}, \"name\": {\"type\": \"string\"}}, \"xml\": {\"name\": \"Tag\"}}}, \"securitySchemes\": {\"petstore_auth\": {\"type\": \"oauth2\", \"flows\": {\"implicit\": {\"authorizationUrl\": \"https://petstore3.swagger.io/oauth/authorize\", \"scopes\": {\"write:pets\": \"modify pets in your account\", \"read:pets\": \"read your pets\"}}}}, \"mutual_tls\": {\"type\": \"mutualTLS\"}, \"api_key\": {\"type\": \"apiKey\", \"name\": \"api_key\", \"in\": \"header\"}}}, \"webhooks\": {\"newPet\": {\"post\": {\"requestBody\": {\"description\": \"Information about a new pet in the system\", \"content\": {\"application/json\": {\"schema\": {\"$ref\": \"#/components/schemas/Pet\", \"description\": \"Webhook Pet\"}}}}, \"responses\": {\"200\": {\"description\": \"Return a 200 status to indicate that the data was received successfully\"}}}}}}" + "definition": "{\"openapi\": \"3.1.0\", \"info\": {\"title\": \"Swagger Petstore - OpenAPI 3.1\", \"description\": \"This is a sample Pet Store Server based on the OpenAPI 3.1 specification.\\nYou can find out more about\\nSwagger at [http://swagger.io](http://swagger.io).\", \"termsOfService\": \"http://swagger.io/terms/\", \"contact\": {\"email\": \"apiteam@swagger.io\"}, \"license\": {\"name\": \"Apache 2.0 AND (MIT OR GPL-2.0-only)\", \"identifier\": \"Apache-2.0 AND (MIT OR GPL-2.0-only)\"}, \"version\": \"1.0.6\", \"summary\": \"Pet Store 3.1\", \"x-namespace\": \"swagger\"}, \"externalDocs\": {\"description\": \"Find out more about Swagger\", \"url\": \"http://swagger.io\"}, \"servers\": [{\"url\": \"https://xyz.bar/api/v31\"}], \"tags\": [{\"name\": \"pet\", \"description\": \"Everything about your Pets\", \"externalDocs\": {\"description\": \"Find out more\", \"url\": \"http://swagger.io\"}}, {\"name\": \"store\", \"description\": \"Access to Petstore orders\", \"externalDocs\": {\"description\": \"Find out more about our store\", \"url\": \"http://swagger.io\"}}, {\"name\": \"user\", \"description\": \"Operations about user\"}], \"paths\": {\"/pet\": {\"put\": {\"tags\": [\"pet\"], \"summary\": \"Update an existing pet\", \"description\": \"Update an existing pet by Id\", \"operationId\": \"updatePet\", \"requestBody\": {\"description\": \"Pet object that needs to be updated in the store\", \"content\": {\"application/json\": {\"schema\": {\"$ref\": \"#/components/schemas/Pet\", \"description\": \"A Pet in JSON Format\", \"required\": [\"id\"], \"writeOnly\": true}}, \"application/xml\": {\"schema\": {\"$ref\": \"#/components/schemas/Pet\", \"description\": \"A Pet in XML Format\", \"required\": [\"id\"], \"writeOnly\": true}}}, \"required\": true}, \"responses\": {\"200\": {\"description\": \"Successful operation\", \"content\": {\"application/xml\": {\"schema\": {\"$ref\": \"#/components/schemas/Pet\", \"description\": \"A Pet in XML Format\", \"readOnly\": true}}, \"application/json\": {\"schema\": {\"$ref\": \"#/components/schemas/Pet\", \"description\": \"A Pet in JSON Format\", \"readOnly\": true}}}}, \"400\": {\"description\": \"Invalid ID supplied\"}, \"404\": {\"description\": \"Pet not found\"}, \"405\": {\"description\": \"Validation exception\"}}, \"security\": [{\"petstore_auth\": [\"write:pets\", \"read:pets\"]}]}, \"post\": {\"tags\": [\"pet\"], \"summary\": \"Add a new pet to the store\", \"description\": \"Add a new pet to the store\", \"operationId\": \"addPet\", \"requestBody\": {\"description\": \"Create a new pet in the store\", \"content\": {\"application/json\": {\"schema\": {\"$ref\": \"#/components/schemas/Pet\", \"description\": \"A Pet in JSON Format\", \"required\": [\"id\"], \"writeOnly\": true}}, \"application/xml\": {\"schema\": {\"$ref\": \"#/components/schemas/Pet\", \"description\": \"A Pet in XML Format\", \"required\": [\"id\"], \"writeOnly\": true}}}, \"required\": true}, \"responses\": {\"200\": {\"description\": \"Successful operation\", \"content\": {\"application/xml\": {\"schema\": {\"$ref\": \"#/components/schemas/Pet\", \"description\": \"A Pet in XML Format\", \"readOnly\": true}}, \"application/json\": {\"schema\": {\"$ref\": \"#/components/schemas/Pet\", \"description\": \"A Pet in JSON Format\", \"readOnly\": true}}}}, \"405\": {\"description\": \"Invalid input\"}}, \"security\": [{\"petstore_auth\": [\"write:pets\", \"read:pets\"]}]}}, \"/pet/{petId}\": {\"get\": {\"tags\": [\"pet\"], \"summary\": \"Find pet by ID\", \"description\": \"Returns a pet when 0 < ID <= 10. ID > 10 or nonintegers will simulate API error conditions\", \"operationId\": \"getPetById\", \"parameters\": [{\"name\": \"petId\", \"in\": \"path\", \"description\": \"ID of pet that needs to be fetched\", \"required\": true, \"schema\": {\"type\": \"integer\", \"format\": \"int64\", \"description\": \"param ID of pet that needs to be fetched\", \"exclusiveMaximum\": 10, \"exclusiveMinimum\": 1}}], \"responses\": {\"default\": {\"description\": \"The pet\", \"content\": {\"application/json\": {\"schema\": {\"$ref\": \"#/components/schemas/Pet\", \"description\": \"A Pet in JSON format\"}}, \"application/xml\": {\"schema\": {\"$ref\": \"#/components/schemas/Pet\", \"description\": \"A Pet in XML format\"}}}}, \"400\": {\"description\": \"Invalid ID supplied\"}, \"404\": {\"description\": \"Pet not found\"}}, \"security\": [{\"petstore_auth\": [\"write:pets\", \"read:pets\"]}, {\"api_key\": []}]}}}, \"components\": {\"schemas\": {\"Category\": {\"$id\": \"/components/schemas/category\", \"description\": \"Category\", \"properties\": {\"id\": {\"type\": \"integer\", \"format\": \"int64\", \"example\": 1}, \"name\": {\"type\": \"string\", \"example\": \"Dogs\"}}, \"xml\": {\"name\": \"Category\"}}, \"Pet\": {\"$schema\": \"https://json-schema.org/draft/2020-12/schema\", \"description\": \"Pet\", \"properties\": {\"id\": {\"type\": \"integer\", \"format\": \"int64\", \"example\": 10}, \"category\": {\"$ref\": \"#/components/schemas/Category\", \"description\": \"Pet Category\"}, \"name\": {\"type\": \"string\", \"example\": \"doggie\"}, \"photoUrls\": {\"type\": \"array\", \"items\": {\"type\": \"string\", \"xml\": {\"name\": \"photoUrl\"}}, \"xml\": {\"wrapped\": true}}, \"tags\": {\"type\": \"array\", \"items\": {\"$ref\": \"#/components/schemas/Tag\"}, \"xml\": {\"wrapped\": true}}, \"status\": {\"type\": \"string\", \"description\": \"pet status in the store\", \"enum\": [\"available\", \"pending\", \"sold\"]}, \"availableInstances\": {\"type\": \"integer\", \"format\": \"int32\", \"example\": 7, \"exclusiveMaximum\": 10, \"exclusiveMinimum\": 1, \"swagger-extension\": true}, \"petDetailsId\": {\"type\": \"integer\", \"format\": \"int64\", \"$ref\": \"/components/schemas/petdetails#pet_details_id\"}, \"petDetails\": {\"$ref\": \"/components/schemas/petdetails\"}}, \"required\": [\"name\", \"photoUrls\"], \"xml\": {\"name\": \"Pet\"}}, \"PetDetails\": {\"$id\": \"/components/schemas/petdetails\", \"$schema\": \"https://json-schema.org/draft/2020-12/schema\", \"$vocabulary\": \"https://spec.openapis.org/oas/3.1/schema-base\", \"properties\": {\"id\": {\"type\": \"integer\", \"format\": \"int64\", \"$anchor\": \"pet_details_id\", \"example\": 10}, \"category\": {\"$ref\": \"/components/schemas/category\", \"description\": \"PetDetails Category\"}, \"tag\": {\"$ref\": \"/components/schemas/tag\"}}, \"xml\": {\"name\": \"PetDetails\"}}, \"Tag\": {\"$id\": \"/components/schemas/tag\", \"properties\": {\"id\": {\"type\": \"integer\", \"format\": \"int64\"}, \"name\": {\"type\": \"string\"}}, \"xml\": {\"name\": \"Tag\"}}}, \"securitySchemes\": {\"petstore_auth\": {\"type\": \"oauth2\", \"flows\": {\"implicit\": {\"authorizationUrl\": \"https://petstore3.swagger.io/oauth/authorize\", \"scopes\": {\"write:pets\": \"modify pets in your account\", \"read:pets\": \"read your pets\"}}}}, \"mutual_tls\": {\"type\": \"mutualTLS\"}, \"api_key\": {\"type\": \"apiKey\", \"name\": \"api_key\", \"in\": \"header\"}}}, \"webhooks\": {\"newPet\": {\"post\": {\"requestBody\": {\"description\": \"Information about a new pet in the system\", \"content\": {\"application/json\": {\"schema\": {\"$ref\": \"#/components/schemas/Pet\", \"description\": \"Webhook Pet\"}}}}, \"responses\": {\"200\": {\"description\": \"Return a 200 status to indicate that the data was received successfully\"}}}}}}" }, "type": "OPEN_API" }, @@ -14,6 +14,48 @@ ] } }, + { + "hierarchyInfo": { + "description": "Everything about your Pets", + "name": "Swagger Petstore - OpenAPI 3.1", + "type": "OPEN_API" + }, + "logicalId": { + "path": [ + "OPEN_API", + "3a45981ff1a3539f49317040f895e979", + "pet" + ] + } + }, + { + "hierarchyInfo": { + "description": "Access to Petstore orders", + "name": "Swagger Petstore - OpenAPI 3.1", + "type": "OPEN_API" + }, + "logicalId": { + "path": [ + "OPEN_API", + "3a45981ff1a3539f49317040f895e979", + "store" + ] + } + }, + { + "hierarchyInfo": { + "description": "Operations about user", + "name": "Swagger Petstore - OpenAPI 3.1", + "type": "OPEN_API" + }, + "logicalId": { + "path": [ + "OPEN_API", + "3a45981ff1a3539f49317040f895e979", + "user" + ] + } + }, { "logicalId": { "name": "https://xyz.bar/api/v31/pet", @@ -36,7 +78,8 @@ }, "structure": { "directories": [ - "3a45981ff1a3539f49317040f895e979" + "3a45981ff1a3539f49317040f895e979", + "pet" ], "name": "/pet" } @@ -58,7 +101,8 @@ }, "structure": { "directories": [ - "3a45981ff1a3539f49317040f895e979" + "3a45981ff1a3539f49317040f895e979", + "pet" ], "name": "/pet/{petId}" } diff --git a/tests/openapi/data/pet_store_31_absolute_server/openapi.json b/tests/openapi/data/pet_store_31_absolute_server/openapi.json index d26eb74a..77dab1b5 100644 --- a/tests/openapi/data/pet_store_31_absolute_server/openapi.json +++ b/tests/openapi/data/pet_store_31_absolute_server/openapi.json @@ -190,7 +190,7 @@ "/pet/{petId}": { "get": { "tags": [ - "pets" + "pet" ], "summary": "Find pet by ID", "description": "Returns a pet when 0 < ID <= 10. ID > 10 or nonintegers will simulate API error conditions", diff --git a/tests/openapi/test_extractor.py b/tests/openapi/test_extractor.py index b387b68b..37c21f95 100644 --- a/tests/openapi/test_extractor.py +++ b/tests/openapi/test_extractor.py @@ -11,10 +11,11 @@ from tests.test_utils import load_json -def get_dummy_config(openapi_json_path: str, base_url: str): +def get_dummy_config(base_url: str, path: str = "", url: str = ""): return OpenAPIRunConfig( - openapi_json_path=openapi_json_path, base_url=base_url, + openapi_json_path=path or None, + openapi_json_url=url or None, output=OutputConfig(), ) @@ -33,7 +34,10 @@ async def test_extractor(data_folder, base_url, test_root_dir): data_folder = f"{test_root_dir}/openapi/data/{data_folder}" extractor = OpenAPIExtractor( - config=get_dummy_config(f"{data_folder}/openapi.json", base_url) + config=get_dummy_config( + base_url, + path=f"{data_folder}/openapi.json", + ) ) events = [EventUtil.trim_event(e) for e in await extractor.extract()] assert events == load_json(f"{data_folder}/expected.json") @@ -57,7 +61,9 @@ def test_get_openapi_json(mock_get_method: MagicMock, test_root_dir: str): MockResponse({}, 403), MockResponse({}, 200), ] - extractor = OpenAPIExtractor(get_dummy_config("http://foo.bar/openapi.json", "")) + extractor = OpenAPIExtractor( + get_dummy_config("", url="http://foo.bar/openapi.json") + ) assert extractor._get_openapi_json() is None assert extractor._get_openapi_json() == {} @@ -69,7 +75,7 @@ def test_get_openapi_json(mock_get_method: MagicMock, test_root_dir: str): headers=OrderedDict( [ ("User-Agent", None), - ("Accept", None), + ("Accept", "application/json"), ("Connection", None), ("Accept-Encoding", None), ] @@ -80,7 +86,7 @@ def test_get_openapi_json(mock_get_method: MagicMock, test_root_dir: str): headers=OrderedDict( [ ("User-Agent", None), - ("Accept", None), + ("Accept", "application/json"), ("Connection", None), ("Accept-Encoding", None), ] From e1811558216f34949c70a5c7af9a569f3232b4ae Mon Sep 17 00:00:00 2001 From: Scott Huang Date: Mon, 28 Oct 2024 15:46:51 +0800 Subject: [PATCH 08/19] Update readme --- metaphor/openapi/README.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/metaphor/openapi/README.md b/metaphor/openapi/README.md index b56560e7..31e5e9e0 100644 --- a/metaphor/openapi/README.md +++ b/metaphor/openapi/README.md @@ -1,6 +1,10 @@ # OpenAPI Connector -This connector extracts APIs from an OpenAPI Specification JSON. +This connector extracts APIs from an OpenAPI Specification JSON. The following OAS versions are supported: + +- OpenAPI Specification 3.1.0 +- OpenAPI Specification 3.0.0 +- Swagger 2.0 ## Config File From 3437dc677032571c3068464236894b4f87b5cd2a Mon Sep 17 00:00:00 2001 From: Scott Ssuyi Huang Date: Mon, 28 Oct 2024 16:10:05 +0800 Subject: [PATCH 09/19] Update metaphor/openapi/extractor.py Co-authored-by: Tsung-Ju Lii --- metaphor/openapi/extractor.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/metaphor/openapi/extractor.py b/metaphor/openapi/extractor.py index 2ea31caf..4a5b012c 100644 --- a/metaphor/openapi/extractor.py +++ b/metaphor/openapi/extractor.py @@ -138,8 +138,7 @@ def _get_first_tag(self, path_item: dict) -> Optional[str]: def _extract_methods(self, path_item: dict) -> List[OpenAPIMethod]: def to_operation_type(method: str) -> Optional[OperationType]: try: - operation_type = OperationType(method.upper()) - return operation_type + return OperationType(method.upper()) except ValueError: return None From 94a54ffee0d4b9b4190e6c5fb3441a2be90d573f Mon Sep 17 00:00:00 2001 From: Scott Huang Date: Mon, 28 Oct 2024 16:12:59 +0800 Subject: [PATCH 10/19] Fix hierarchy name --- metaphor/openapi/extractor.py | 2 +- tests/openapi/data/pet_store_20/expected.json | 6 +++--- tests/openapi/data/pet_store_30/expected.json | 6 +++--- tests/openapi/data/pet_store_31/expected.json | 6 +++--- .../openapi/data/pet_store_31_absolute_server/expected.json | 6 +++--- 5 files changed, 13 insertions(+), 13 deletions(-) diff --git a/metaphor/openapi/extractor.py b/metaphor/openapi/extractor.py index 2ea31caf..5d6af462 100644 --- a/metaphor/openapi/extractor.py +++ b/metaphor/openapi/extractor.py @@ -189,7 +189,7 @@ def _build_hierarchies(self, openapi: dict) -> List[Hierarchy]: path=[AssetPlatform.OPEN_API.value, self._api_id, name], ), hierarchy_info=HierarchyInfo( - name=title, + name=name, description=description, type=HierarchyType.OPEN_API, ), diff --git a/tests/openapi/data/pet_store_20/expected.json b/tests/openapi/data/pet_store_20/expected.json index 19e50e5c..d9bdee34 100644 --- a/tests/openapi/data/pet_store_20/expected.json +++ b/tests/openapi/data/pet_store_20/expected.json @@ -17,7 +17,7 @@ { "hierarchyInfo": { "description": "Everything about your Pets", - "name": "Swagger Petstore", + "name": "pet", "type": "OPEN_API" }, "logicalId": { @@ -31,7 +31,7 @@ { "hierarchyInfo": { "description": "Access to Petstore orders", - "name": "Swagger Petstore", + "name": "store", "type": "OPEN_API" }, "logicalId": { @@ -45,7 +45,7 @@ { "hierarchyInfo": { "description": "Operations about user", - "name": "Swagger Petstore", + "name": "user", "type": "OPEN_API" }, "logicalId": { diff --git a/tests/openapi/data/pet_store_30/expected.json b/tests/openapi/data/pet_store_30/expected.json index afc4ce99..d0a16a6e 100644 --- a/tests/openapi/data/pet_store_30/expected.json +++ b/tests/openapi/data/pet_store_30/expected.json @@ -17,7 +17,7 @@ { "hierarchyInfo": { "description": "Everything about your Pets", - "name": "Swagger Petstore - OpenAPI 3.0", + "name": "pet", "type": "OPEN_API" }, "logicalId": { @@ -31,7 +31,7 @@ { "hierarchyInfo": { "description": "Access to Petstore orders", - "name": "Swagger Petstore - OpenAPI 3.0", + "name": "store", "type": "OPEN_API" }, "logicalId": { @@ -45,7 +45,7 @@ { "hierarchyInfo": { "description": "Operations about user", - "name": "Swagger Petstore - OpenAPI 3.0", + "name": "user", "type": "OPEN_API" }, "logicalId": { diff --git a/tests/openapi/data/pet_store_31/expected.json b/tests/openapi/data/pet_store_31/expected.json index c37784f0..b4985ca7 100644 --- a/tests/openapi/data/pet_store_31/expected.json +++ b/tests/openapi/data/pet_store_31/expected.json @@ -17,7 +17,7 @@ { "hierarchyInfo": { "description": "Everything about your Pets", - "name": "Swagger Petstore - OpenAPI 3.1", + "name": "pet", "type": "OPEN_API" }, "logicalId": { @@ -31,7 +31,7 @@ { "hierarchyInfo": { "description": "Access to Petstore orders", - "name": "Swagger Petstore - OpenAPI 3.1", + "name": "store", "type": "OPEN_API" }, "logicalId": { @@ -45,7 +45,7 @@ { "hierarchyInfo": { "description": "Operations about user", - "name": "Swagger Petstore - OpenAPI 3.1", + "name": "user", "type": "OPEN_API" }, "logicalId": { diff --git a/tests/openapi/data/pet_store_31_absolute_server/expected.json b/tests/openapi/data/pet_store_31_absolute_server/expected.json index 3b6b72f1..9e595afd 100644 --- a/tests/openapi/data/pet_store_31_absolute_server/expected.json +++ b/tests/openapi/data/pet_store_31_absolute_server/expected.json @@ -17,7 +17,7 @@ { "hierarchyInfo": { "description": "Everything about your Pets", - "name": "Swagger Petstore - OpenAPI 3.1", + "name": "pet", "type": "OPEN_API" }, "logicalId": { @@ -31,7 +31,7 @@ { "hierarchyInfo": { "description": "Access to Petstore orders", - "name": "Swagger Petstore - OpenAPI 3.1", + "name": "store", "type": "OPEN_API" }, "logicalId": { @@ -45,7 +45,7 @@ { "hierarchyInfo": { "description": "Operations about user", - "name": "Swagger Petstore - OpenAPI 3.1", + "name": "user", "type": "OPEN_API" }, "logicalId": { From 73f4db027364b89c8235564853ade039d40c5961 Mon Sep 17 00:00:00 2001 From: Scott Huang Date: Mon, 28 Oct 2024 16:19:45 +0800 Subject: [PATCH 11/19] Update readme --- metaphor/openapi/README.md | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/metaphor/openapi/README.md b/metaphor/openapi/README.md index 31e5e9e0..260cb294 100644 --- a/metaphor/openapi/README.md +++ b/metaphor/openapi/README.md @@ -12,9 +12,18 @@ Create a YAML config file based on the following template. ### Required Configurations +Configure the connector by either + +```yaml +base_url: # BaseUrl for endpoints in OAS +openapi_json_path: # path to OAS JSON file +``` + +or + ```yaml base_url: # BaseUrl for endpoints in OAS -openapi_json_path: # URL or path of OAS +openapi_json_url: # URL of OAS ``` ### Optional Configurations From 442b378c500a9372a7f9c4295de023a6ab64ef29 Mon Sep 17 00:00:00 2001 From: Scott Huang Date: Mon, 28 Oct 2024 16:27:06 +0800 Subject: [PATCH 12/19] Update config model --- metaphor/openapi/config.py | 6 ++-- metaphor/openapi/extractor.py | 6 ++-- tests/openapi/data/pet_store_20/expected.json | 36 +++++++++---------- tests/openapi/data/pet_store_30/expected.json | 34 +++++++++--------- tests/openapi/data/pet_store_31/expected.json | 12 +++---- .../expected.json | 12 +++---- tests/openapi/test_extractor.py | 9 ++--- 7 files changed, 58 insertions(+), 57 deletions(-) diff --git a/metaphor/openapi/config.py b/metaphor/openapi/config.py index 6878bb4a..5b06b50a 100644 --- a/metaphor/openapi/config.py +++ b/metaphor/openapi/config.py @@ -1,6 +1,6 @@ from typing import Optional -from pydantic import model_validator +from pydantic import HttpUrl, model_validator from pydantic.dataclasses import dataclass from metaphor.common.base_config import BaseConfig @@ -21,9 +21,9 @@ class OpenAPIAuthConfig: @dataclass(config=ConnectorConfig) class OpenAPIRunConfig(BaseConfig): - base_url: str # base_url of endpoints + base_url: HttpUrl openapi_json_path: Optional[str] = None - openapi_json_url: Optional[str] = None + openapi_json_url: Optional[HttpUrl] = None auth: Optional[OpenAPIAuthConfig] = None @model_validator(mode="after") diff --git a/metaphor/openapi/extractor.py b/metaphor/openapi/extractor.py index 5d6af462..3c5a9c37 100644 --- a/metaphor/openapi/extractor.py +++ b/metaphor/openapi/extractor.py @@ -43,10 +43,10 @@ def from_config_file(config_file: str) -> "OpenAPIExtractor": def __init__(self, config: OpenAPIRunConfig): super().__init__(config) - self._base_url = config.base_url - self._api_id = md5_digest(config.base_url.encode("utf-8")) + self._base_url = str(config.base_url) + self._api_id = md5_digest(self._base_url.encode("utf-8")) self._openapi_json_path = config.openapi_json_path - self._openapi_json_url = config.openapi_json_url + self._openapi_json_url = str(config.openapi_json_url) self._auth = config.auth self._init_session() diff --git a/tests/openapi/data/pet_store_20/expected.json b/tests/openapi/data/pet_store_20/expected.json index d9bdee34..93b2baf3 100644 --- a/tests/openapi/data/pet_store_20/expected.json +++ b/tests/openapi/data/pet_store_20/expected.json @@ -10,7 +10,7 @@ "logicalId": { "path": [ "OPEN_API", - "3a45981ff1a3539f49317040f895e979" + "5a3c97a89f1d0f3edc5d35bfa96170b4" ] } }, @@ -23,7 +23,7 @@ "logicalId": { "path": [ "OPEN_API", - "3a45981ff1a3539f49317040f895e979", + "5a3c97a89f1d0f3edc5d35bfa96170b4", "pet" ] } @@ -37,7 +37,7 @@ "logicalId": { "path": [ "OPEN_API", - "3a45981ff1a3539f49317040f895e979", + "5a3c97a89f1d0f3edc5d35bfa96170b4", "store" ] } @@ -51,7 +51,7 @@ "logicalId": { "path": [ "OPEN_API", - "3a45981ff1a3539f49317040f895e979", + "5a3c97a89f1d0f3edc5d35bfa96170b4", "user" ] } @@ -72,7 +72,7 @@ }, "structure": { "directories": [ - "3a45981ff1a3539f49317040f895e979", + "5a3c97a89f1d0f3edc5d35bfa96170b4", "pet" ], "name": "/pet/{petId}/uploadImage" @@ -98,7 +98,7 @@ }, "structure": { "directories": [ - "3a45981ff1a3539f49317040f895e979", + "5a3c97a89f1d0f3edc5d35bfa96170b4", "pet" ], "name": "/pet" @@ -121,7 +121,7 @@ }, "structure": { "directories": [ - "3a45981ff1a3539f49317040f895e979", + "5a3c97a89f1d0f3edc5d35bfa96170b4", "pet" ], "name": "/pet/findByStatus" @@ -144,7 +144,7 @@ }, "structure": { "directories": [ - "3a45981ff1a3539f49317040f895e979", + "5a3c97a89f1d0f3edc5d35bfa96170b4", "pet" ], "name": "/pet/findByTags" @@ -175,7 +175,7 @@ }, "structure": { "directories": [ - "3a45981ff1a3539f49317040f895e979", + "5a3c97a89f1d0f3edc5d35bfa96170b4", "pet" ], "name": "/pet/{petId}" @@ -198,7 +198,7 @@ }, "structure": { "directories": [ - "3a45981ff1a3539f49317040f895e979", + "5a3c97a89f1d0f3edc5d35bfa96170b4", "store" ], "name": "/store/inventory" @@ -220,7 +220,7 @@ }, "structure": { "directories": [ - "3a45981ff1a3539f49317040f895e979", + "5a3c97a89f1d0f3edc5d35bfa96170b4", "store" ], "name": "/store/order" @@ -248,7 +248,7 @@ }, "structure": { "directories": [ - "3a45981ff1a3539f49317040f895e979", + "5a3c97a89f1d0f3edc5d35bfa96170b4", "store" ], "name": "/store/order/{orderId}" @@ -270,7 +270,7 @@ }, "structure": { "directories": [ - "3a45981ff1a3539f49317040f895e979", + "5a3c97a89f1d0f3edc5d35bfa96170b4", "user" ], "name": "/user/createWithList" @@ -302,7 +302,7 @@ }, "structure": { "directories": [ - "3a45981ff1a3539f49317040f895e979", + "5a3c97a89f1d0f3edc5d35bfa96170b4", "user" ], "name": "/user/{username}" @@ -324,7 +324,7 @@ }, "structure": { "directories": [ - "3a45981ff1a3539f49317040f895e979", + "5a3c97a89f1d0f3edc5d35bfa96170b4", "user" ], "name": "/user/login" @@ -346,7 +346,7 @@ }, "structure": { "directories": [ - "3a45981ff1a3539f49317040f895e979", + "5a3c97a89f1d0f3edc5d35bfa96170b4", "user" ], "name": "/user/logout" @@ -368,7 +368,7 @@ }, "structure": { "directories": [ - "3a45981ff1a3539f49317040f895e979", + "5a3c97a89f1d0f3edc5d35bfa96170b4", "user" ], "name": "/user/createWithArray" @@ -391,7 +391,7 @@ }, "structure": { "directories": [ - "3a45981ff1a3539f49317040f895e979", + "5a3c97a89f1d0f3edc5d35bfa96170b4", "user" ], "name": "/user" diff --git a/tests/openapi/data/pet_store_30/expected.json b/tests/openapi/data/pet_store_30/expected.json index d0a16a6e..acae5021 100644 --- a/tests/openapi/data/pet_store_30/expected.json +++ b/tests/openapi/data/pet_store_30/expected.json @@ -10,7 +10,7 @@ "logicalId": { "path": [ "OPEN_API", - "3a45981ff1a3539f49317040f895e979" + "5a3c97a89f1d0f3edc5d35bfa96170b4" ] } }, @@ -23,7 +23,7 @@ "logicalId": { "path": [ "OPEN_API", - "3a45981ff1a3539f49317040f895e979", + "5a3c97a89f1d0f3edc5d35bfa96170b4", "pet" ] } @@ -37,7 +37,7 @@ "logicalId": { "path": [ "OPEN_API", - "3a45981ff1a3539f49317040f895e979", + "5a3c97a89f1d0f3edc5d35bfa96170b4", "store" ] } @@ -51,7 +51,7 @@ "logicalId": { "path": [ "OPEN_API", - "3a45981ff1a3539f49317040f895e979", + "5a3c97a89f1d0f3edc5d35bfa96170b4", "user" ] } @@ -78,7 +78,7 @@ }, "structure": { "directories": [ - "3a45981ff1a3539f49317040f895e979", + "5a3c97a89f1d0f3edc5d35bfa96170b4", "pet" ], "name": "/pet" @@ -101,7 +101,7 @@ }, "structure": { "directories": [ - "3a45981ff1a3539f49317040f895e979", + "5a3c97a89f1d0f3edc5d35bfa96170b4", "pet" ], "name": "/pet/findByStatus" @@ -124,7 +124,7 @@ }, "structure": { "directories": [ - "3a45981ff1a3539f49317040f895e979", + "5a3c97a89f1d0f3edc5d35bfa96170b4", "pet" ], "name": "/pet/findByTags" @@ -155,7 +155,7 @@ }, "structure": { "directories": [ - "3a45981ff1a3539f49317040f895e979", + "5a3c97a89f1d0f3edc5d35bfa96170b4", "pet" ], "name": "/pet/{petId}" @@ -177,7 +177,7 @@ }, "structure": { "directories": [ - "3a45981ff1a3539f49317040f895e979", + "5a3c97a89f1d0f3edc5d35bfa96170b4", "pet" ], "name": "/pet/{petId}/uploadImage" @@ -200,7 +200,7 @@ }, "structure": { "directories": [ - "3a45981ff1a3539f49317040f895e979", + "5a3c97a89f1d0f3edc5d35bfa96170b4", "store" ], "name": "/store/inventory" @@ -223,7 +223,7 @@ }, "structure": { "directories": [ - "3a45981ff1a3539f49317040f895e979", + "5a3c97a89f1d0f3edc5d35bfa96170b4", "store" ], "name": "/store/order" @@ -251,7 +251,7 @@ }, "structure": { "directories": [ - "3a45981ff1a3539f49317040f895e979", + "5a3c97a89f1d0f3edc5d35bfa96170b4", "store" ], "name": "/store/order/{orderId}" @@ -274,7 +274,7 @@ }, "structure": { "directories": [ - "3a45981ff1a3539f49317040f895e979", + "5a3c97a89f1d0f3edc5d35bfa96170b4", "user" ], "name": "/user" @@ -297,7 +297,7 @@ }, "structure": { "directories": [ - "3a45981ff1a3539f49317040f895e979", + "5a3c97a89f1d0f3edc5d35bfa96170b4", "user" ], "name": "/user/createWithList" @@ -319,7 +319,7 @@ }, "structure": { "directories": [ - "3a45981ff1a3539f49317040f895e979", + "5a3c97a89f1d0f3edc5d35bfa96170b4", "user" ], "name": "/user/login" @@ -341,7 +341,7 @@ }, "structure": { "directories": [ - "3a45981ff1a3539f49317040f895e979", + "5a3c97a89f1d0f3edc5d35bfa96170b4", "user" ], "name": "/user/logout" @@ -373,7 +373,7 @@ }, "structure": { "directories": [ - "3a45981ff1a3539f49317040f895e979", + "5a3c97a89f1d0f3edc5d35bfa96170b4", "user" ], "name": "/user/{username}" diff --git a/tests/openapi/data/pet_store_31/expected.json b/tests/openapi/data/pet_store_31/expected.json index b4985ca7..dfcfe0d3 100644 --- a/tests/openapi/data/pet_store_31/expected.json +++ b/tests/openapi/data/pet_store_31/expected.json @@ -10,7 +10,7 @@ "logicalId": { "path": [ "OPEN_API", - "3a45981ff1a3539f49317040f895e979" + "5a3c97a89f1d0f3edc5d35bfa96170b4" ] } }, @@ -23,7 +23,7 @@ "logicalId": { "path": [ "OPEN_API", - "3a45981ff1a3539f49317040f895e979", + "5a3c97a89f1d0f3edc5d35bfa96170b4", "pet" ] } @@ -37,7 +37,7 @@ "logicalId": { "path": [ "OPEN_API", - "3a45981ff1a3539f49317040f895e979", + "5a3c97a89f1d0f3edc5d35bfa96170b4", "store" ] } @@ -51,7 +51,7 @@ "logicalId": { "path": [ "OPEN_API", - "3a45981ff1a3539f49317040f895e979", + "5a3c97a89f1d0f3edc5d35bfa96170b4", "user" ] } @@ -78,7 +78,7 @@ }, "structure": { "directories": [ - "3a45981ff1a3539f49317040f895e979", + "5a3c97a89f1d0f3edc5d35bfa96170b4", "pet" ], "name": "/pet" @@ -101,7 +101,7 @@ }, "structure": { "directories": [ - "3a45981ff1a3539f49317040f895e979", + "5a3c97a89f1d0f3edc5d35bfa96170b4", "pet" ], "name": "/pet/{petId}" diff --git a/tests/openapi/data/pet_store_31_absolute_server/expected.json b/tests/openapi/data/pet_store_31_absolute_server/expected.json index 9e595afd..0205620a 100644 --- a/tests/openapi/data/pet_store_31_absolute_server/expected.json +++ b/tests/openapi/data/pet_store_31_absolute_server/expected.json @@ -10,7 +10,7 @@ "logicalId": { "path": [ "OPEN_API", - "3a45981ff1a3539f49317040f895e979" + "5a3c97a89f1d0f3edc5d35bfa96170b4" ] } }, @@ -23,7 +23,7 @@ "logicalId": { "path": [ "OPEN_API", - "3a45981ff1a3539f49317040f895e979", + "5a3c97a89f1d0f3edc5d35bfa96170b4", "pet" ] } @@ -37,7 +37,7 @@ "logicalId": { "path": [ "OPEN_API", - "3a45981ff1a3539f49317040f895e979", + "5a3c97a89f1d0f3edc5d35bfa96170b4", "store" ] } @@ -51,7 +51,7 @@ "logicalId": { "path": [ "OPEN_API", - "3a45981ff1a3539f49317040f895e979", + "5a3c97a89f1d0f3edc5d35bfa96170b4", "user" ] } @@ -78,7 +78,7 @@ }, "structure": { "directories": [ - "3a45981ff1a3539f49317040f895e979", + "5a3c97a89f1d0f3edc5d35bfa96170b4", "pet" ], "name": "/pet" @@ -101,7 +101,7 @@ }, "structure": { "directories": [ - "3a45981ff1a3539f49317040f895e979", + "5a3c97a89f1d0f3edc5d35bfa96170b4", "pet" ], "name": "/pet/{petId}" diff --git a/tests/openapi/test_extractor.py b/tests/openapi/test_extractor.py index 37c21f95..56446670 100644 --- a/tests/openapi/test_extractor.py +++ b/tests/openapi/test_extractor.py @@ -3,6 +3,7 @@ import pytest import requests +from pydantic import HttpUrl from metaphor.common.base_config import OutputConfig from metaphor.common.event_util import EventUtil @@ -13,9 +14,9 @@ def get_dummy_config(base_url: str, path: str = "", url: str = ""): return OpenAPIRunConfig( - base_url=base_url, + base_url=HttpUrl(base_url), openapi_json_path=path or None, - openapi_json_url=url or None, + openapi_json_url=HttpUrl(url) if url else None, output=OutputConfig(), ) @@ -56,13 +57,13 @@ def raise_for_status(self): @patch.object(requests.Session, "get") -def test_get_openapi_json(mock_get_method: MagicMock, test_root_dir: str): +def test_get_openapi_json(mock_get_method: MagicMock): mock_get_method.side_effect = [ MockResponse({}, 403), MockResponse({}, 200), ] extractor = OpenAPIExtractor( - get_dummy_config("", url="http://foo.bar/openapi.json") + get_dummy_config("http://baz", url="http://foo.bar/openapi.json") ) assert extractor._get_openapi_json() is None From 01cd1ec036363384edfe64e4edfb18383727957a Mon Sep 17 00:00:00 2001 From: Scott Huang Date: Mon, 28 Oct 2024 16:34:41 +0800 Subject: [PATCH 13/19] Bump version --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 12ec542d..282fac8f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "metaphor-connectors" -version = "0.14.136" +version = "0.14.137" license = "Apache-2.0" description = "A collection of Python-based 'connectors' that extract metadata from various sources to ingest into the Metaphor app." authors = ["Metaphor "] From 184588730536da1dc662062bc40fdc293fd8cf9f Mon Sep 17 00:00:00 2001 From: Scott Ssuyi Huang Date: Mon, 28 Oct 2024 17:21:56 +0800 Subject: [PATCH 14/19] Update metaphor/openapi/extractor.py Co-authored-by: Tsung-Ju Lii --- metaphor/openapi/extractor.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/metaphor/openapi/extractor.py b/metaphor/openapi/extractor.py index a5c1da23..aab2bd11 100644 --- a/metaphor/openapi/extractor.py +++ b/metaphor/openapi/extractor.py @@ -61,7 +61,7 @@ async def extract(self) -> Collection[ENTITY_TYPES]: logger.error("Unable to get OAS json") return [] - endpoints = self._extract_paths(openapi_json) + apis = self._extract_apis(openapi_json) hierarchies = self._build_hierarchies(openapi_json) return hierarchies + endpoints From 5bb7aabbc44d0089083e9193824595983171a37b Mon Sep 17 00:00:00 2001 From: Scott Ssuyi Huang Date: Mon, 28 Oct 2024 17:22:24 +0800 Subject: [PATCH 15/19] Update metaphor/openapi/extractor.py Co-authored-by: Tsung-Ju Lii --- metaphor/openapi/extractor.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/metaphor/openapi/extractor.py b/metaphor/openapi/extractor.py index aab2bd11..68f7a5b8 100644 --- a/metaphor/openapi/extractor.py +++ b/metaphor/openapi/extractor.py @@ -62,7 +62,7 @@ async def extract(self) -> Collection[ENTITY_TYPES]: return [] apis = self._extract_apis(openapi_json) - hierarchies = self._build_hierarchies(openapi_json) + hierarchies = self._extract_hierarchies(openapi_json) return hierarchies + endpoints From 3be1246928a6a75444a6fb107ebe1e2cf3b3580f Mon Sep 17 00:00:00 2001 From: Scott Ssuyi Huang Date: Mon, 28 Oct 2024 17:22:33 +0800 Subject: [PATCH 16/19] Update metaphor/openapi/extractor.py Co-authored-by: Tsung-Ju Lii --- metaphor/openapi/extractor.py | 18 +++++++----------- 1 file changed, 7 insertions(+), 11 deletions(-) diff --git a/metaphor/openapi/extractor.py b/metaphor/openapi/extractor.py index 68f7a5b8..8ef3a7de 100644 --- a/metaphor/openapi/extractor.py +++ b/metaphor/openapi/extractor.py @@ -144,18 +144,14 @@ def to_operation_type(method: str) -> Optional[OperationType]: methods: List[OpenAPIMethod] = [] for method, item in path_item.items(): - operation_type = to_operation_type(method) - - if not operation_type: - continue - - methods.append( - OpenAPIMethod( - summary=item.get("summary") or None, - description=item.get("description") or None, - type=operation_type, + if operation_type := to_operation_type(method): + methods.append( + OpenAPIMethod( + summary=item.get("summary"), + description=item.get("description"), + type=operation_type, + ) ) - ) return methods def _build_hierarchies(self, openapi: dict) -> List[Hierarchy]: From 3bcf89c2db89efd9b67e45738bc7f25446ca5a39 Mon Sep 17 00:00:00 2001 From: Scott Huang Date: Mon, 28 Oct 2024 17:22:39 +0800 Subject: [PATCH 17/19] Address comments --- metaphor/openapi/config.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/metaphor/openapi/config.py b/metaphor/openapi/config.py index 5b06b50a..d3474c58 100644 --- a/metaphor/openapi/config.py +++ b/metaphor/openapi/config.py @@ -1,6 +1,6 @@ from typing import Optional -from pydantic import HttpUrl, model_validator +from pydantic import FilePath, HttpUrl, model_validator from pydantic.dataclasses import dataclass from metaphor.common.base_config import BaseConfig @@ -22,7 +22,7 @@ class OpenAPIAuthConfig: @dataclass(config=ConnectorConfig) class OpenAPIRunConfig(BaseConfig): base_url: HttpUrl - openapi_json_path: Optional[str] = None + openapi_json_path: Optional[FilePath] = None openapi_json_url: Optional[HttpUrl] = None auth: Optional[OpenAPIAuthConfig] = None From af8171b31239ffefd30ddd971f1f4b7d2507c5ec Mon Sep 17 00:00:00 2001 From: Scott Huang Date: Mon, 28 Oct 2024 17:26:35 +0800 Subject: [PATCH 18/19] Address comments --- metaphor/openapi/extractor.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/metaphor/openapi/extractor.py b/metaphor/openapi/extractor.py index 8ef3a7de..c91aaef2 100644 --- a/metaphor/openapi/extractor.py +++ b/metaphor/openapi/extractor.py @@ -64,7 +64,7 @@ async def extract(self) -> Collection[ENTITY_TYPES]: apis = self._extract_apis(openapi_json) hierarchies = self._extract_hierarchies(openapi_json) - return hierarchies + endpoints + return hierarchies + apis def _init_session(self): self._requests_session = requests.sessions.Session() @@ -97,8 +97,8 @@ def _get_openapi_json(self) -> Optional[dict]: return resp.json() - def _extract_paths(self, openapi: dict) -> List[API]: - endpoints: List[API] = [] + def _extract_apis(self, openapi: dict) -> List[API]: + apis: List[API] = [] servers = openapi.get("servers") for path, path_item in openapi["paths"].items(): @@ -126,8 +126,8 @@ def _extract_paths(self, openapi: dict) -> List[API]: name=path, ), ) - endpoints.append(endpoint) - return endpoints + apis.append(endpoint) + return apis def _get_first_tag(self, path_item: dict) -> Optional[str]: for item in path_item.values(): @@ -147,14 +147,14 @@ def to_operation_type(method: str) -> Optional[OperationType]: if operation_type := to_operation_type(method): methods.append( OpenAPIMethod( - summary=item.get("summary"), - description=item.get("description"), + summary=item.get("summary") or None, + description=item.get("description") or None, type=operation_type, ) ) return methods - def _build_hierarchies(self, openapi: dict) -> List[Hierarchy]: + def _extract_hierarchies(self, openapi: dict) -> List[Hierarchy]: hierarchies: List[Hierarchy] = [] title = openapi["info"]["title"] From fb09e488c91594d2fc9d84186519d03dcf31f8c6 Mon Sep 17 00:00:00 2001 From: Scott Huang Date: Mon, 28 Oct 2024 17:33:59 +0800 Subject: [PATCH 19/19] Fix type --- tests/openapi/test_extractor.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/openapi/test_extractor.py b/tests/openapi/test_extractor.py index 56446670..8ad6d13f 100644 --- a/tests/openapi/test_extractor.py +++ b/tests/openapi/test_extractor.py @@ -3,7 +3,7 @@ import pytest import requests -from pydantic import HttpUrl +from pydantic import FilePath, HttpUrl from metaphor.common.base_config import OutputConfig from metaphor.common.event_util import EventUtil @@ -15,7 +15,7 @@ def get_dummy_config(base_url: str, path: str = "", url: str = ""): return OpenAPIRunConfig( base_url=HttpUrl(base_url), - openapi_json_path=path or None, + openapi_json_path=FilePath(path) if path else None, openapi_json_url=HttpUrl(url) if url else None, output=OutputConfig(), )