From bc733e2696e11d89a1064a508d7831fceeab1992 Mon Sep 17 00:00:00 2001 From: Scott Ssuyi Huang Date: Mon, 28 Oct 2024 18:29:57 +0800 Subject: [PATCH 1/2] OpenAPI crawler (#1021) * add OpenAPI crawler * Change config * Finalize * Fix tests * Update tests * Add more tests * Tag hierarchy * Update readme * Update metaphor/openapi/extractor.py Co-authored-by: Tsung-Ju Lii * Fix hierarchy name * Update readme * Update config model * Bump version * Update metaphor/openapi/extractor.py Co-authored-by: Tsung-Ju Lii * Update metaphor/openapi/extractor.py Co-authored-by: Tsung-Ju Lii * Update metaphor/openapi/extractor.py Co-authored-by: Tsung-Ju Lii * Address comments * Address comments * Fix type --------- Co-authored-by: Tsung-Ju Lii --- README.md | 1 + metaphor/common/event_util.py | 8 +- metaphor/openapi/README.md | 54 + metaphor/openapi/__init__.py | 6 + metaphor/openapi/config.py | 32 + metaphor/openapi/extractor.py | 194 +++ poetry.lock | 10 +- pyproject.toml | 4 +- tests/common/test_event_utils.py | 2 + tests/openapi/__init__.py | 0 tests/openapi/data/pet_store_20/expected.json | 400 ++++++ tests/openapi/data/pet_store_20/openapi.json | 1054 ++++++++++++++ tests/openapi/data/pet_store_30/expected.json | 382 +++++ tests/openapi/data/pet_store_30/openapi.json | 1225 +++++++++++++++++ tests/openapi/data/pet_store_31/expected.json | 110 ++ tests/openapi/data/pet_store_31/openapi.json | 428 ++++++ .../expected.json | 110 ++ .../pet_store_31_absolute_server/openapi.json | 428 ++++++ tests/openapi/test_extractor.py | 97 ++ tests/quick_sight/__init__.py | 0 20 files changed, 4536 insertions(+), 9 deletions(-) create mode 100644 metaphor/openapi/README.md create mode 100644 metaphor/openapi/__init__.py create mode 100644 metaphor/openapi/config.py create mode 100644 metaphor/openapi/extractor.py create mode 100644 tests/openapi/__init__.py 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 create mode 100644 tests/quick_sight/__init__.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/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/README.md b/metaphor/openapi/README.md new file mode 100644 index 00000000..260cb294 --- /dev/null +++ b/metaphor/openapi/README.md @@ -0,0 +1,54 @@ +# OpenAPI Connector + +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 + +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_url: # URL 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/__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..d3474c58 --- /dev/null +++ b/metaphor/openapi/config.py @@ -0,0 +1,32 @@ +from typing import Optional + +from pydantic import FilePath, HttpUrl, 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) +class BasicAuth: + user: str + password: str + + +@dataclass(config=ConnectorConfig) +class OpenAPIAuthConfig: + basic_auth: Optional[BasicAuth] = None + + +@dataclass(config=ConnectorConfig) +class OpenAPIRunConfig(BaseConfig): + base_url: HttpUrl + openapi_json_path: Optional[FilePath] = None + openapi_json_url: Optional[HttpUrl] = 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 new file mode 100644 index 00000000..c91aaef2 --- /dev/null +++ b/metaphor/openapi/extractor.py @@ -0,0 +1,194 @@ +import json +from collections import OrderedDict +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.common.utils import md5_digest +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 = 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 = str(config.openapi_json_url) + self._auth = config.auth + self._init_session() + + async def extract(self) -> Collection[ENTITY_TYPES]: + logger.info( + f"Fetching metadata from {self._openapi_json_path or self._openapi_json_url}" + ) + + openapi_json = self._get_openapi_json() + + if not openapi_json: + logger.error("Unable to get OAS json") + return [] + + apis = self._extract_apis(openapi_json) + hierarchies = self._extract_hierarchies(openapi_json) + + return hierarchies + apis + + def _init_session(self): + self._requests_session = requests.sessions.Session() + + 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) -> Optional[dict]: + 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": "application/json", + "Connection": None, + "Accept-Encoding": None, + } + ) + resp = self._requests_session.get(self._openapi_json_url, headers=headers) + + if resp.status_code != 200: + return None + + return resp.json() + + def _extract_apis(self, openapi: dict) -> List[API]: + apis: List[API] = [] + servers = openapi.get("servers") + + for path, path_item in openapi["paths"].items(): + path_servers = path_item.get("servers") + server = ( + path_servers[0]["url"] + if path_servers + else servers[0]["url"] if servers else "" + ) + + if not server.startswith("http"): + endpoint_url = urljoin(self._base_url, server + path) + else: + 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] + [first_tag] if first_tag else [], + name=path, + ), + ) + apis.append(endpoint) + return apis + + 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: + return OperationType(method.upper()) + except ValueError: + return None + + methods: List[OpenAPIMethod] = [] + for method, item in path_item.items(): + if operation_type := to_operation_type(method): + methods.append( + OpenAPIMethod( + summary=item.get("summary") or None, + description=item.get("description") or None, + type=operation_type, + ) + ) + return methods + + def _extract_hierarchies(self, openapi: dict) -> List[Hierarchy]: + hierarchies: List[Hierarchy] = [] + + title = openapi["info"]["title"] + 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, + ), + ) + ) + + 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=name, + description=description, + type=HierarchyType.OPEN_API, + ), + ) + ) + + return hierarchies diff --git a/poetry.lock b/poetry.lock index e30bb2e4..4652634d 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" @@ -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]] @@ -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 = "6db5396d65f72e18f2c9f45afd1048dc540900df1601dd77b95345bd34d9d4f0" +content-hash = "5cf4c7306334f39d8bdfde366c70b984695d832220a7ab4f1d5a524d402a499e" diff --git a/pyproject.toml b/pyproject.toml index 95f79a50..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 "] @@ -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 } 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/__init__.py b/tests/openapi/__init__.py new file mode 100644 index 00000000..e69de29b 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..93b2baf3 --- /dev/null +++ b/tests/openapi/data/pet_store_20/expected.json @@ -0,0 +1,400 @@ +[ + { + "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", + "5a3c97a89f1d0f3edc5d35bfa96170b4" + ] + } + }, + { + "hierarchyInfo": { + "description": "Everything about your Pets", + "name": "pet", + "type": "OPEN_API" + }, + "logicalId": { + "path": [ + "OPEN_API", + "5a3c97a89f1d0f3edc5d35bfa96170b4", + "pet" + ] + } + }, + { + "hierarchyInfo": { + "description": "Access to Petstore orders", + "name": "store", + "type": "OPEN_API" + }, + "logicalId": { + "path": [ + "OPEN_API", + "5a3c97a89f1d0f3edc5d35bfa96170b4", + "store" + ] + } + }, + { + "hierarchyInfo": { + "description": "Operations about user", + "name": "user", + "type": "OPEN_API" + }, + "logicalId": { + "path": [ + "OPEN_API", + "5a3c97a89f1d0f3edc5d35bfa96170b4", + "user" + ] + } + }, + { + "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": [ + "5a3c97a89f1d0f3edc5d35bfa96170b4", + "pet" + ], + "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": [ + "5a3c97a89f1d0f3edc5d35bfa96170b4", + "pet" + ], + "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": [ + "5a3c97a89f1d0f3edc5d35bfa96170b4", + "pet" + ], + "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": [ + "5a3c97a89f1d0f3edc5d35bfa96170b4", + "pet" + ], + "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": [ + "5a3c97a89f1d0f3edc5d35bfa96170b4", + "pet" + ], + "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": [ + "5a3c97a89f1d0f3edc5d35bfa96170b4", + "store" + ], + "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": [ + "5a3c97a89f1d0f3edc5d35bfa96170b4", + "store" + ], + "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": [ + "5a3c97a89f1d0f3edc5d35bfa96170b4", + "store" + ], + "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": [ + "5a3c97a89f1d0f3edc5d35bfa96170b4", + "user" + ], + "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": [ + "5a3c97a89f1d0f3edc5d35bfa96170b4", + "user" + ], + "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": [ + "5a3c97a89f1d0f3edc5d35bfa96170b4", + "user" + ], + "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": [ + "5a3c97a89f1d0f3edc5d35bfa96170b4", + "user" + ], + "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": [ + "5a3c97a89f1d0f3edc5d35bfa96170b4", + "user" + ], + "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": [ + "5a3c97a89f1d0f3edc5d35bfa96170b4", + "user" + ], + "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..acae5021 --- /dev/null +++ b/tests/openapi/data/pet_store_30/expected.json @@ -0,0 +1,382 @@ +[ + { + "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}\": {\"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" + }, + "logicalId": { + "path": [ + "OPEN_API", + "5a3c97a89f1d0f3edc5d35bfa96170b4" + ] + } + }, + { + "hierarchyInfo": { + "description": "Everything about your Pets", + "name": "pet", + "type": "OPEN_API" + }, + "logicalId": { + "path": [ + "OPEN_API", + "5a3c97a89f1d0f3edc5d35bfa96170b4", + "pet" + ] + } + }, + { + "hierarchyInfo": { + "description": "Access to Petstore orders", + "name": "store", + "type": "OPEN_API" + }, + "logicalId": { + "path": [ + "OPEN_API", + "5a3c97a89f1d0f3edc5d35bfa96170b4", + "store" + ] + } + }, + { + "hierarchyInfo": { + "description": "Operations about user", + "name": "user", + "type": "OPEN_API" + }, + "logicalId": { + "path": [ + "OPEN_API", + "5a3c97a89f1d0f3edc5d35bfa96170b4", + "user" + ] + } + }, + { + "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": [ + "5a3c97a89f1d0f3edc5d35bfa96170b4", + "pet" + ], + "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": [ + "5a3c97a89f1d0f3edc5d35bfa96170b4", + "pet" + ], + "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": [ + "5a3c97a89f1d0f3edc5d35bfa96170b4", + "pet" + ], + "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": [ + "5a3c97a89f1d0f3edc5d35bfa96170b4", + "pet" + ], + "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": [ + "5a3c97a89f1d0f3edc5d35bfa96170b4", + "pet" + ], + "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": [ + "5a3c97a89f1d0f3edc5d35bfa96170b4", + "store" + ], + "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": [ + "5a3c97a89f1d0f3edc5d35bfa96170b4", + "store" + ], + "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": [ + "5a3c97a89f1d0f3edc5d35bfa96170b4", + "store" + ], + "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": [ + "5a3c97a89f1d0f3edc5d35bfa96170b4", + "user" + ], + "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": [ + "5a3c97a89f1d0f3edc5d35bfa96170b4", + "user" + ], + "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": [ + "5a3c97a89f1d0f3edc5d35bfa96170b4", + "user" + ], + "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": [ + "5a3c97a89f1d0f3edc5d35bfa96170b4", + "user" + ], + "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": [ + "5a3c97a89f1d0f3edc5d35bfa96170b4", + "user" + ], + "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..f2fef089 --- /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}": { + "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" + } + } + } +} 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..dfcfe0d3 --- /dev/null +++ b/tests/openapi/data/pet_store_31/expected.json @@ -0,0 +1,110 @@ +[ + { + "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\": [\"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" + }, + "logicalId": { + "path": [ + "OPEN_API", + "5a3c97a89f1d0f3edc5d35bfa96170b4" + ] + } + }, + { + "hierarchyInfo": { + "description": "Everything about your Pets", + "name": "pet", + "type": "OPEN_API" + }, + "logicalId": { + "path": [ + "OPEN_API", + "5a3c97a89f1d0f3edc5d35bfa96170b4", + "pet" + ] + } + }, + { + "hierarchyInfo": { + "description": "Access to Petstore orders", + "name": "store", + "type": "OPEN_API" + }, + "logicalId": { + "path": [ + "OPEN_API", + "5a3c97a89f1d0f3edc5d35bfa96170b4", + "store" + ] + } + }, + { + "hierarchyInfo": { + "description": "Operations about user", + "name": "user", + "type": "OPEN_API" + }, + "logicalId": { + "path": [ + "OPEN_API", + "5a3c97a89f1d0f3edc5d35bfa96170b4", + "user" + ] + } + }, + { + "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": [ + "5a3c97a89f1d0f3edc5d35bfa96170b4", + "pet" + ], + "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": [ + "5a3c97a89f1d0f3edc5d35bfa96170b4", + "pet" + ], + "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..749de9ee --- /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": [ + "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" + } + } + } + } + } +} 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..0205620a --- /dev/null +++ b/tests/openapi/data/pet_store_31_absolute_server/expected.json @@ -0,0 +1,110 @@ +[ + { + "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\": [\"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" + }, + "logicalId": { + "path": [ + "OPEN_API", + "5a3c97a89f1d0f3edc5d35bfa96170b4" + ] + } + }, + { + "hierarchyInfo": { + "description": "Everything about your Pets", + "name": "pet", + "type": "OPEN_API" + }, + "logicalId": { + "path": [ + "OPEN_API", + "5a3c97a89f1d0f3edc5d35bfa96170b4", + "pet" + ] + } + }, + { + "hierarchyInfo": { + "description": "Access to Petstore orders", + "name": "store", + "type": "OPEN_API" + }, + "logicalId": { + "path": [ + "OPEN_API", + "5a3c97a89f1d0f3edc5d35bfa96170b4", + "store" + ] + } + }, + { + "hierarchyInfo": { + "description": "Operations about user", + "name": "user", + "type": "OPEN_API" + }, + "logicalId": { + "path": [ + "OPEN_API", + "5a3c97a89f1d0f3edc5d35bfa96170b4", + "user" + ] + } + }, + { + "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": [ + "5a3c97a89f1d0f3edc5d35bfa96170b4", + "pet" + ], + "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": [ + "5a3c97a89f1d0f3edc5d35bfa96170b4", + "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 new file mode 100644 index 00000000..77dab1b5 --- /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": [ + "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" + } + } + } + } + } +} diff --git a/tests/openapi/test_extractor.py b/tests/openapi/test_extractor.py new file mode 100644 index 00000000..8ad6d13f --- /dev/null +++ b/tests/openapi/test_extractor.py @@ -0,0 +1,97 @@ +from collections import OrderedDict +from unittest.mock import MagicMock, call, patch + +import pytest +import requests +from pydantic import FilePath, HttpUrl + +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(base_url: str, path: str = "", url: str = ""): + return OpenAPIRunConfig( + base_url=HttpUrl(base_url), + openapi_json_path=FilePath(path) if path else None, + openapi_json_url=HttpUrl(url) if url else None, + 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( + 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") + + +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): + mock_get_method.side_effect = [ + MockResponse({}, 403), + MockResponse({}, 200), + ] + extractor = OpenAPIExtractor( + get_dummy_config("http://baz", url="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", "application/json"), + ("Connection", None), + ("Accept-Encoding", None), + ] + ), + ), + call( + "http://foo.bar/openapi.json", + headers=OrderedDict( + [ + ("User-Agent", None), + ("Accept", "application/json"), + ("Connection", None), + ("Accept-Encoding", None), + ] + ), + ), + ] + ) diff --git a/tests/quick_sight/__init__.py b/tests/quick_sight/__init__.py new file mode 100644 index 00000000..e69de29b From db186153d984ff42f1cb2e3af04170504c8adf31 Mon Sep 17 00:00:00 2001 From: Mars Lan Date: Mon, 28 Oct 2024 06:13:56 -0700 Subject: [PATCH 2/2] Deprecate PostgreSQL usage connector (#1023) --- README.md | 1 - metaphor/postgresql/usage/extractor.py | 60 +++++--------------------- pyproject.toml | 2 +- tests/postgresql/usage/__init__.py | 0 tests/postgresql/usage/config.yml | 17 -------- tests/postgresql/usage/test_config.py | 34 --------------- 6 files changed, 11 insertions(+), 103 deletions(-) delete mode 100644 tests/postgresql/usage/__init__.py delete mode 100644 tests/postgresql/usage/config.yml delete mode 100644 tests/postgresql/usage/test_config.py diff --git a/README.md b/README.md index 73260520..c199208d 100644 --- a/README.md +++ b/README.md @@ -77,7 +77,6 @@ Each connector is placed under its own directory under [metaphor](./metaphor) an | [notion](metaphor/notion/) | Document embeddings | | [postgresql](metaphor/postgresql/) | Schema, description, statistics | | [postgresql.profile](metaphor/postgresql/profile/) | Data profile | -| [postgresql.usage](metaphor/postgresql/usage/) | Usage | | [power_bi](metaphor/power_bi/) | Dashboard, lineage | | [quick_sight](metaphor/quick_sight/) | Dashboard, lineage | | [redshift](metaphor/redshift/) | Schema, description, statistics, queries | diff --git a/metaphor/postgresql/usage/extractor.py b/metaphor/postgresql/usage/extractor.py index 2d9e8da6..a84dbd7b 100644 --- a/metaphor/postgresql/usage/extractor.py +++ b/metaphor/postgresql/usage/extractor.py @@ -1,20 +1,10 @@ from typing import Collection +from warnings import warn -from metaphor.common.entity_id import dataset_normalized_name from metaphor.common.event_util import ENTITY_TYPES -from metaphor.common.logger import get_logger -from metaphor.common.usage_util import UsageUtil -from metaphor.models.metadata_change_event import DataPlatform from metaphor.postgresql.extractor import PostgreSQLExtractor from metaphor.postgresql.usage.config import PostgreSQLUsageRunConfig -logger = get_logger() - - -USAGE_SQL = """ -SELECT schemaname, relname, seq_scan FROM pg_stat_user_tables -""" - class PostgreSQLUsageExtractor(PostgreSQLExtractor): """PostgreSQL usage metadata extractor""" @@ -27,44 +17,14 @@ def from_config_file(config_file: str) -> "PostgreSQLUsageExtractor": PostgreSQLUsageRunConfig.from_yaml_file(config_file) ) - async def extract(self) -> Collection[ENTITY_TYPES]: - logger.info(f"Fetching usage metadata from PostgreSQL host {self._host}") - - databases = ( - await self._fetch_databases() - if self._filter.includes is None - else list(self._filter.includes.keys()) + def __init__(self, config: PostgreSQLUsageRunConfig): + super().__init__(config) + warn( + "PostgreSQL usage crawler is deprecated, and is marked for removal in 0.15.0", + DeprecationWarning, + stacklevel=2, ) - datasets = [] - - for db in databases: - conn = await self._connect_database(db) - try: - results = await conn.fetch(USAGE_SQL) - for row in results: - schema = row["schemaname"] - table_name = row["relname"] - read_count = row["seq_scan"] - normalized_name = dataset_normalized_name(db, schema, table_name) - - if not self._filter.include_table(db, schema, table_name): - logger.info(f"Ignore {normalized_name} due to filter config") - continue - - dataset = UsageUtil.init_dataset( - normalized_name, DataPlatform.POSTGRESQL - ) - - # don't have exact time of query, so set all time window to be same query count - dataset.usage.query_counts.last24_hours.count = float(read_count) - dataset.usage.query_counts.last7_days.count = float(read_count) - dataset.usage.query_counts.last30_days.count = float(read_count) - dataset.usage.query_counts.last90_days.count = float(read_count) - dataset.usage.query_counts.last365_days.count = float(read_count) - datasets.append(dataset) - finally: - await conn.close() - - UsageUtil.calculate_statistics(datasets) - return datasets + async def extract(self) -> Collection[ENTITY_TYPES]: + # Deprecated connector, do nothing! + return [] diff --git a/pyproject.toml b/pyproject.toml index 282fac8f..8f15b488 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "metaphor-connectors" -version = "0.14.137" +version = "0.14.138" 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/postgresql/usage/__init__.py b/tests/postgresql/usage/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/tests/postgresql/usage/config.yml b/tests/postgresql/usage/config.yml deleted file mode 100644 index cd01c1d9..00000000 --- a/tests/postgresql/usage/config.yml +++ /dev/null @@ -1,17 +0,0 @@ ---- -host: host -database: database -user: user -password: password -filter: - includes: - db1: - schema1: - db2: - schema2: - - table1 - - table2 - excludes: - db3: -port: 1234 -output: {} diff --git a/tests/postgresql/usage/test_config.py b/tests/postgresql/usage/test_config.py deleted file mode 100644 index 7b1026bf..00000000 --- a/tests/postgresql/usage/test_config.py +++ /dev/null @@ -1,34 +0,0 @@ -from metaphor.common.base_config import OutputConfig -from metaphor.common.filter import DatasetFilter -from metaphor.postgresql.usage.config import PostgreSQLUsageRunConfig - - -def test_yaml_config(test_root_dir): - config = PostgreSQLUsageRunConfig.from_yaml_file( - f"{test_root_dir}/postgresql/usage/config.yml" - ) - - assert config == PostgreSQLUsageRunConfig( - host="host", - database="database", - user="user", - password="password", - filter=DatasetFilter( - includes={ - "db1": { - "schema1": None, - }, - "db2": { - "schema2": set( - [ - "table1", - "table2", - ] - ) - }, - }, - excludes={"db3": None}, - ), - port=1234, - output=OutputConfig(), - )