From ce5f35639bdd0cd9af0dc373c1ef759c1aca495d Mon Sep 17 00:00:00 2001 From: Dmitrii Tikhomirov Date: Wed, 19 Nov 2025 16:57:31 -0800 Subject: [PATCH] Add Swagger support to OpenAPI defition processor Signed-off-by: Dmitrii Tikhomirov --- impl/openapi/pom.xml | 10 + .../executors/openapi/OpenAPIExecutor.java | 5 +- .../executors/openapi/OpenAPIProcessor.java | 25 +- .../openapi/OperationDefinition.java | 106 ++--- .../openapi/ParameterDefinition.java | 58 +++ .../openapi/OpenAPIProcessorTest.java | 216 ++++++++++ .../resources/schema/openapi/petstore.json | 382 ++++++++++++++++++ .../resources/schema/swagger/petstore.json | 360 +++++++++++++++++ 8 files changed, 1103 insertions(+), 59 deletions(-) create mode 100644 impl/openapi/src/main/java/io/serverlessworkflow/impl/executors/openapi/ParameterDefinition.java create mode 100644 impl/openapi/src/test/java/io/serverlessworkflow/impl/executors/openapi/OpenAPIProcessorTest.java create mode 100644 impl/openapi/src/test/resources/schema/openapi/petstore.json create mode 100644 impl/openapi/src/test/resources/schema/swagger/petstore.json diff --git a/impl/openapi/pom.xml b/impl/openapi/pom.xml index 39e3cce8f..10b91025e 100644 --- a/impl/openapi/pom.xml +++ b/impl/openapi/pom.xml @@ -25,5 +25,15 @@ swagger-parser ${version.io.swagger.parser.v3} + + org.junit.jupiter + junit-jupiter-engine + test + + + org.junit.jupiter + junit-jupiter-params + test + diff --git a/impl/openapi/src/main/java/io/serverlessworkflow/impl/executors/openapi/OpenAPIExecutor.java b/impl/openapi/src/main/java/io/serverlessworkflow/impl/executors/openapi/OpenAPIExecutor.java index 7b2a7d11a..4d0c1a01b 100644 --- a/impl/openapi/src/main/java/io/serverlessworkflow/impl/executors/openapi/OpenAPIExecutor.java +++ b/impl/openapi/src/main/java/io/serverlessworkflow/impl/executors/openapi/OpenAPIExecutor.java @@ -29,7 +29,6 @@ import io.serverlessworkflow.impl.executors.http.HttpExecutor.HttpExecutorBuilder; import io.serverlessworkflow.impl.resources.ResourceLoaderUtils; import io.swagger.v3.oas.models.media.Schema; -import io.swagger.v3.oas.models.parameters.Parameter; import java.util.Collection; import java.util.HashMap; import java.util.HashSet; @@ -110,7 +109,7 @@ private void fillHttpBuilder(WorkflowApplication application, OperationDefinitio Set missingParams = new HashSet<>(); Map bodyParameters = new HashMap<>(parameters); - for (Parameter parameter : operation.getParameters()) { + for (ParameterDefinition parameter : operation.getParameters()) { switch (parameter.getIn()) { case "header": param(parameter, bodyParameters, headersMap, missingParams); @@ -141,7 +140,7 @@ private void fillHttpBuilder(WorkflowApplication application, OperationDefinitio } private void param( - Parameter parameter, + ParameterDefinition parameter, Map origMap, Map collectorMap, Set missingParams) { diff --git a/impl/openapi/src/main/java/io/serverlessworkflow/impl/executors/openapi/OpenAPIProcessor.java b/impl/openapi/src/main/java/io/serverlessworkflow/impl/executors/openapi/OpenAPIProcessor.java index e65ebc2fa..f8da99942 100644 --- a/impl/openapi/src/main/java/io/serverlessworkflow/impl/executors/openapi/OpenAPIProcessor.java +++ b/impl/openapi/src/main/java/io/serverlessworkflow/impl/executors/openapi/OpenAPIProcessor.java @@ -15,11 +15,12 @@ */ package io.serverlessworkflow.impl.executors.openapi; +import io.swagger.parser.OpenAPIParser; import io.swagger.v3.oas.models.OpenAPI; import io.swagger.v3.oas.models.Operation; import io.swagger.v3.oas.models.PathItem; -import io.swagger.v3.parser.OpenAPIV3Parser; import io.swagger.v3.parser.core.models.ParseOptions; +import io.swagger.v3.parser.core.models.SwaggerParseResult; import java.util.Set; class OpenAPIProcessor { @@ -31,14 +32,22 @@ class OpenAPIProcessor { } public OperationDefinition parse(String content) { - OpenAPIV3Parser parser = new OpenAPIV3Parser(); + OpenAPIParser parser = new OpenAPIParser(); ParseOptions opts = new ParseOptions(); opts.setResolve(true); - opts.setResolveFully(false); - return getOperation(parser.readContents(content).getOpenAPI()); + opts.setResolveFully(true); + + SwaggerParseResult result = parser.readContents(content, null, opts); + + if (result.getMessages() != null && !result.getMessages().isEmpty()) { + throw new IllegalArgumentException( + "Failed to parse OpenAPI document: " + String.join(", ", result.getMessages())); + } + return getOperation(result.getOpenAPI(), !result.isOpenapi31()); } - private OperationDefinition getOperation(OpenAPI openAPI) { + private OperationDefinition getOperation( + OpenAPI openAPI, boolean emulateSwaggerV2BodyParameters) { if (openAPI == null || openAPI.getPaths() == null) { throw new IllegalArgumentException("Invalid OpenAPI document"); } @@ -50,7 +59,11 @@ private OperationDefinition getOperation(OpenAPI openAPI) { OperationAndMethod operationAndMethod = findInPathItem(pathItem, operationId); if (operationAndMethod != null) { return new OperationDefinition( - openAPI, operationAndMethod.operation, path, operationAndMethod.method); + openAPI, + operationAndMethod.operation, + path, + operationAndMethod.method, + emulateSwaggerV2BodyParameters); } } throw new IllegalArgumentException( diff --git a/impl/openapi/src/main/java/io/serverlessworkflow/impl/executors/openapi/OperationDefinition.java b/impl/openapi/src/main/java/io/serverlessworkflow/impl/executors/openapi/OperationDefinition.java index 0ffc36b4a..5111a38b7 100644 --- a/impl/openapi/src/main/java/io/serverlessworkflow/impl/executors/openapi/OperationDefinition.java +++ b/impl/openapi/src/main/java/io/serverlessworkflow/impl/executors/openapi/OperationDefinition.java @@ -17,26 +17,33 @@ import io.swagger.v3.oas.models.OpenAPI; import io.swagger.v3.oas.models.Operation; -import io.swagger.v3.oas.models.media.Content; import io.swagger.v3.oas.models.media.MediaType; import io.swagger.v3.oas.models.media.Schema; -import io.swagger.v3.oas.models.parameters.Parameter; -import io.swagger.v3.oas.models.responses.ApiResponse; import io.swagger.v3.oas.models.servers.Server; +import java.util.ArrayList; +import java.util.HashSet; import java.util.List; import java.util.Map; +import java.util.Set; class OperationDefinition { private final Operation operation; private final String method; private final OpenAPI openAPI; private final String path; + private final boolean emulateSwaggerV2BodyParameters; - OperationDefinition(OpenAPI openAPI, Operation operation, String path, String method) { + OperationDefinition( + OpenAPI openAPI, + Operation operation, + String path, + String method, + boolean emulateSwaggerV2BodyParameters) { this.openAPI = openAPI; this.operation = operation; this.path = path; this.method = method; + this.emulateSwaggerV2BodyParameters = emulateSwaggerV2BodyParameters; } String getMethod() { @@ -52,67 +59,66 @@ Operation getOperation() { } List getServers() { + if (openAPI.getServers() == null) { + return List.of(); + } return openAPI.getServers().stream().map(Server::getUrl).toList(); } - List getParameters() { + List getParameters() { + return emulateSwaggerV2BodyParameters ? getSwaggerV2Parameters() : getOpenApiParameters(); + } + + private List getOpenApiParameters() { if (operation.getParameters() == null) { return List.of(); } - return operation.getParameters(); + return operation.getParameters().stream().map(ParameterDefinition::new).toList(); } - @SuppressWarnings({"rawtypes", "unchecked"}) - Map getBody() { - if (operation.getRequestBody() != null && operation.getRequestBody().getContent() != null) { - Content content = operation.getRequestBody().getContent(); - if (content.containsKey("application/json")) { - MediaType mt = content.get("application/json"); - if (mt.getSchema().get$ref() != null && !mt.getSchema().get$ref().isEmpty()) { - Schema schema = resolveSchema(mt.getSchema().get$ref()); - return schema.getProperties(); - } else if (mt.getSchema().getProperties() != null) { - return mt.getSchema().getProperties(); - } else { - throw new IllegalArgumentException( - "Can't resolve schema for request body of operation " + operation.getOperationId()); - } - } else { - throw new IllegalArgumentException("Only 'application/json' content type is supported"); - } + @SuppressWarnings({"rawtypes"}) + private List getSwaggerV2Parameters() { + if (operation.getParameters() != null && !operation.getParameters().isEmpty()) { + return operation.getParameters().stream().map(ParameterDefinition::new).toList(); } - return Map.of(); - } - - String getContentType() { - String method = getMethod().toUpperCase(); - - if (method.equals("POST") || method.equals("PUT") || method.equals("PATCH")) { - if (operation.getRequestBody() != null && operation.getRequestBody().getContent() != null) { - Content content = operation.getRequestBody().getContent(); - if (!content.isEmpty()) { - return content.keySet().iterator().next(); - } + if (operation.getRequestBody() != null) { + Schema schema = null; + if (operation.getRequestBody().getContent() != null + && operation + .getRequestBody() + .getContent() + .containsKey(jakarta.ws.rs.core.MediaType.APPLICATION_JSON)) { + MediaType mt = + operation + .getRequestBody() + .getContent() + .get(jakarta.ws.rs.core.MediaType.APPLICATION_JSON); + schema = mt.getSchema(); + } else if (operation.getRequestBody().get$ref() != null) { + schema = resolveSchema(operation.getRequestBody().get$ref()); } - } - if (operation.getResponses() != null) { - for (String code : new String[] {"200", "201", "204"}) { - ApiResponse resp = operation.getResponses().get(code); - if (resp != null && resp.getContent() != null && !resp.getContent().isEmpty()) { - return resp.getContent().keySet().iterator().next(); - } + if (schema == null) { + return List.of(); } - for (Map.Entry e : operation.getResponses().entrySet()) { - Content content = e.getValue().getContent(); - if (content != null && !content.isEmpty()) { - return content.keySet().iterator().next(); + + Set required = + schema.getRequired() != null ? new HashSet<>(schema.getRequired()) : new HashSet<>(); + + Map properties = schema.getProperties(); + if (properties != null) { + List result = new ArrayList<>(); + for (Map.Entry prop : properties.entrySet()) { + String fieldName = prop.getKey(); + ParameterDefinition fieldParam = + new ParameterDefinition( + fieldName, "body", required.contains(fieldName), prop.getValue()); + result.add(fieldParam); } + return result; } } - - throw new IllegalStateException( - "No content type found for operation " + operation.getOperationId() + " [" + method + "]"); + return List.of(); } Schema resolveSchema(String ref) { diff --git a/impl/openapi/src/main/java/io/serverlessworkflow/impl/executors/openapi/ParameterDefinition.java b/impl/openapi/src/main/java/io/serverlessworkflow/impl/executors/openapi/ParameterDefinition.java new file mode 100644 index 000000000..542900966 --- /dev/null +++ b/impl/openapi/src/main/java/io/serverlessworkflow/impl/executors/openapi/ParameterDefinition.java @@ -0,0 +1,58 @@ +/* + * Copyright 2020-Present The Serverless Workflow Specification Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.serverlessworkflow.impl.executors.openapi; + +import io.swagger.v3.oas.models.media.Schema; +import io.swagger.v3.oas.models.parameters.Parameter; + +class ParameterDefinition { + + private final String name; + private final String in; + private final boolean required; + private final Schema schema; + + ParameterDefinition(Parameter parameter) { + this( + parameter.getName(), + parameter.getIn(), + parameter.getRequired() != null && parameter.getRequired(), + parameter.getSchema()); + } + + ParameterDefinition(String name, String in, boolean required, Schema schema) { + this.name = name; + this.in = in; + this.required = required; + this.schema = schema; + } + + public String getIn() { + return in; + } + + public String getName() { + return name; + } + + public boolean getRequired() { + return required; + } + + public Schema getSchema() { + return schema; + } +} diff --git a/impl/openapi/src/test/java/io/serverlessworkflow/impl/executors/openapi/OpenAPIProcessorTest.java b/impl/openapi/src/test/java/io/serverlessworkflow/impl/executors/openapi/OpenAPIProcessorTest.java new file mode 100644 index 000000000..c89df6b71 --- /dev/null +++ b/impl/openapi/src/test/java/io/serverlessworkflow/impl/executors/openapi/OpenAPIProcessorTest.java @@ -0,0 +1,216 @@ +/* + * Copyright 2020-Present The Serverless Workflow Specification Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.serverlessworkflow.impl.executors.openapi; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.io.IOException; +import java.io.InputStream; +import java.nio.charset.StandardCharsets; +import java.util.List; +import org.junit.jupiter.api.Test; + +public class OpenAPIProcessorTest { + + @Test + public void testGetPetByIdSwaggerV2() { + String json = readResource("schema/swagger/petstore.json"); + testGetPetById(json); + } + + @Test + public void testGetPetByIdOpenAPI() { + String json = readResource("schema/openapi/petstore.json"); + testGetPetById(json); + } + + public void testGetPetById(String json) { + OperationDefinition definition = new OpenAPIProcessor("getPetById").parse(json); + assertEquals("GET", definition.getMethod()); + assertEquals("/pet/{petId}", definition.getPath()); + assertTrue(checkServer(definition.getServers(), "https://petstore.swagger.io/v2")); + assertEquals(1, definition.getParameters().size()); + ParameterDefinition param = definition.getParameters().get(0); + assertEquals("path", param.getIn()); + assertEquals("petId", param.getName()); + assertTrue(param.getRequired()); + } + + @Test + public void testAddPetByIdSwaggerV2() { + String swaggerJson = readResource("schema/swagger/petstore.json"); + testAddPetById(swaggerJson); + } + + @Test + public void testAddPetByIdOpenAPI() { + String json = readResource("schema/openapi/petstore.json"); + testAddPetById(json); + } + + public void testAddPetById(String json) { + OperationDefinition definition = new OpenAPIProcessor("addPet").parse(json); + + assertEquals("POST", definition.getMethod()); + assertEquals("/pet", definition.getPath()); + assertTrue(checkServer(definition.getServers(), "https://petstore.swagger.io/v2")); + assertEquals(6, definition.getParameters().size()); + + ParameterDefinition param = definition.getParameters().get(0); + assertEquals("body", param.getIn()); + assertEquals("id", param.getName()); + assertFalse(param.getRequired()); + param = definition.getParameters().get(1); + assertEquals("body", param.getIn()); + assertEquals("category", param.getName()); + assertFalse(param.getRequired()); + param = definition.getParameters().get(2); + assertEquals("body", param.getIn()); + assertEquals("name", param.getName()); + assertTrue(param.getRequired()); + param = definition.getParameters().get(3); + assertEquals("body", param.getIn()); + assertEquals("photoUrls", param.getName()); + assertTrue(param.getRequired()); + param = definition.getParameters().get(4); + assertEquals("body", param.getIn()); + assertEquals("tags", param.getName()); + assertFalse(param.getRequired()); + param = definition.getParameters().get(5); + assertEquals("body", param.getIn()); + assertEquals("status", param.getName()); + assertFalse(param.getRequired()); + } + + @Test + public void testGetInventorySwaggerV2() { + String swaggerJson = readResource("schema/swagger/petstore.json"); + testGetInventory(swaggerJson); + } + + @Test + public void testGetInventoryOpenAPI() { + String json = readResource("schema/openapi/petstore.json"); + testGetInventory(json); + } + + public void testGetInventory(String json) { + OperationDefinition definition = new OpenAPIProcessor("getInventory").parse(json); + + assertEquals("GET", definition.getMethod()); + assertEquals("/store/inventory", definition.getPath()); + assertTrue(checkServer(definition.getServers(), "https://petstore.swagger.io/v2")); + assertEquals(0, definition.getParameters().size()); + } + + @Test + public void testPlaceOrderSwaggerV2() { + String json = readResource("schema/swagger/petstore.json"); + testPlaceOrder(json); + } + + @Test + public void testPlaceOrderOpenAPI() { + String json = readResource("schema/openapi/petstore.json"); + testPlaceOrder(json); + } + + public void testPlaceOrder(String json) { + OperationDefinition definition = new OpenAPIProcessor("placeOrder").parse(json); + + assertEquals("POST", definition.getMethod()); + assertEquals("/store/order", definition.getPath()); + assertTrue(checkServer(definition.getServers(), "https://petstore.swagger.io/v2")); + assertEquals(6, definition.getParameters().size()); + ParameterDefinition param = definition.getParameters().get(0); + assertEquals("body", param.getIn()); + assertEquals("id", param.getName()); + assertFalse(param.getRequired()); + + param = definition.getParameters().get(1); + assertEquals("body", param.getIn()); + assertEquals("petId", param.getName()); + assertFalse(param.getRequired()); + param = definition.getParameters().get(2); + assertEquals("body", param.getIn()); + assertEquals("quantity", param.getName()); + assertFalse(param.getRequired()); + param = definition.getParameters().get(3); + assertEquals("body", param.getIn()); + assertEquals("shipDate", param.getName()); + assertFalse(param.getRequired()); + param = definition.getParameters().get(4); + assertEquals("body", param.getIn()); + assertEquals("status", param.getName()); + assertFalse(param.getRequired()); + param = definition.getParameters().get(5); + assertEquals("body", param.getIn()); + assertEquals("complete", param.getName()); + assertFalse(param.getRequired()); + } + + @Test + public void testLoginUserSwaggerV2() { + String json = readResource("schema/swagger/petstore.json"); + testLoginUser(json); + } + + @Test + public void testLoginUserOpenAPI() { + String json = readResource("schema/openapi/petstore.json"); + testLoginUser(json); + } + + public void testLoginUser(String json) { + OperationDefinition definition = new OpenAPIProcessor("loginUser").parse(json); + + assertEquals("GET", definition.getMethod()); + assertEquals("/user/login", definition.getPath()); + assertTrue(checkServer(definition.getServers(), "https://petstore.swagger.io/v2")); + assertEquals(2, definition.getParameters().size()); + ParameterDefinition param1 = definition.getParameters().get(0); + assertEquals("query", param1.getIn()); + assertEquals("username", param1.getName()); + assertTrue(param1.getRequired()); + ParameterDefinition param2 = definition.getParameters().get(1); + assertEquals("query", param2.getIn()); + assertEquals("password", param2.getName()); + assertTrue(param2.getRequired()); + } + + private boolean checkServer(List servers, String expected) { + for (String server : servers) { + if (server.equals(expected)) { + return true; + } + } + return false; + } + + public static String readResource(String path) { + try (InputStream is = + Thread.currentThread().getContextClassLoader().getResourceAsStream(path)) { + if (is == null) { + throw new IllegalArgumentException("Resource not found: " + path); + } + return new String(is.readAllBytes(), StandardCharsets.UTF_8); + } catch (IOException e) { + throw new RuntimeException("Failed to read resource: " + path, e); + } + } +} diff --git a/impl/openapi/src/test/resources/schema/openapi/petstore.json b/impl/openapi/src/test/resources/schema/openapi/petstore.json new file mode 100644 index 000000000..3da786fe2 --- /dev/null +++ b/impl/openapi/src/test/resources/schema/openapi/petstore.json @@ -0,0 +1,382 @@ +{ + "openapi": "3.0.3", + "info": { + "version": "1.0.0", + "title": "Swagger Petstore (simplified)" + }, + "servers": [ + { + "url": "https://petstore.swagger.io/v2" + } + ], + "paths": { + "/pet": { + "post": { + "tags": [ + "pet" + ], + "summary": "Add a new pet to the store", + "operationId": "addPet", + "requestBody": { + "description": "Pet object that needs to be added to the store", + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Pet" + } + }, + "application/xml": { + "schema": { + "$ref": "#/components/schemas/Pet" + } + } + } + }, + "responses": { + "405": { + "description": "Invalid input" + } + } + } + }, + "/pet/{petId}": { + "get": { + "tags": [ + "pet" + ], + "summary": "Find pet by ID", + "description": "Returns a single pet", + "operationId": "getPetById", + "parameters": [ + { + "name": "petId", + "in": "path", + "description": "ID of pet to return", + "required": true, + "schema": { + "type": "integer", + "format": "int64" + } + } + ], + "responses": { + "200": { + "description": "successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Pet" + } + }, + "application/xml": { + "schema": { + "$ref": "#/components/schemas/Pet" + } + } + } + }, + "400": { + "description": "Invalid ID supplied" + }, + "404": { + "description": "Pet not found" + } + } + } + }, + "/store/inventory": { + "get": { + "tags": [ + "store" + ], + "summary": "Returns pet inventories by status", + "description": "Returns a map of status codes to quantities", + "operationId": "getInventory", + "parameters": [], + "responses": { + "200": { + "description": "successful operation", + "content": { + "application/json": { + "schema": { + "type": "object", + "additionalProperties": { + "type": "integer", + "format": "int32" + } + } + } + } + } + } + } + }, + "/store/order": { + "post": { + "tags": [ + "store" + ], + "summary": "Place an order for a pet", + "operationId": "placeOrder", + "requestBody": { + "description": "order placed for purchasing the pet", + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Order" + } + } + } + }, + "responses": { + "200": { + "description": "successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Order" + } + }, + "application/xml": { + "schema": { + "$ref": "#/components/schemas/Order" + } + } + } + }, + "400": { + "description": "Invalid Order" + } + } + } + }, + "/user/login": { + "get": { + "tags": [ + "user" + ], + "summary": "Logs user into the system", + "operationId": "loginUser", + "parameters": [ + { + "name": "username", + "in": "query", + "description": "The user name for login", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "password", + "in": "query", + "description": "The password for login in clear text", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "successful operation", + "headers": { + "X-Expires-After": { + "description": "date in UTC when token expires", + "schema": { + "type": "string", + "format": "date-time" + } + }, + "X-Rate-Limit": { + "description": "calls per hour allowed by the user", + "schema": { + "type": "integer", + "format": "int32" + } + } + }, + "content": { + "application/json": { + "schema": { + "type": "string" + } + }, + "application/xml": { + "schema": { + "type": "string" + } + } + } + }, + "400": { + "description": "Invalid username/password supplied" + } + } + } + } + }, + "components": { + "schemas": { + "Category": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "format": "int64" + }, + "name": { + "type": "string" + } + }, + "xml": { + "name": "Category" + } + }, + "Tag": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "format": "int64" + }, + "name": { + "type": "string" + } + }, + "xml": { + "name": "Tag" + } + }, + "Pet": { + "type": "object", + "required": [ + "name", + "photoUrls" + ], + "properties": { + "id": { + "type": "integer", + "format": "int64" + }, + "category": { + "$ref": "#/components/schemas/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": "#/components/schemas/Tag" + } + }, + "status": { + "type": "string", + "description": "pet status in the store", + "enum": [ + "available", + "pending", + "sold" + ] + } + }, + "xml": { + "name": "Pet" + } + }, + "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" + } + } + } + } +} diff --git a/impl/openapi/src/test/resources/schema/swagger/petstore.json b/impl/openapi/src/test/resources/schema/swagger/petstore.json new file mode 100644 index 000000000..e2b8a08e1 --- /dev/null +++ b/impl/openapi/src/test/resources/schema/swagger/petstore.json @@ -0,0 +1,360 @@ +{ + "swagger": "2.0", + "info": { + "version": "1.0.0", + "title": "Swagger Petstore (simplified)" + }, + "host": "petstore.swagger.io", + "basePath": "/v2", + "schemes": [ + "https" + ], + "paths": { + "/pet": { + "post": { + "tags": [ + "pet" + ], + "summary": "Add a new pet to the store", + "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" + } + } + } + }, + "/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" + } + } + } + }, + "/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" + } + } + } + } + } + }, + "/store/order": { + "post": { + "tags": [ + "store" + ], + "summary": "Place an order for a pet", + "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" + } + } + } + }, + "/user/login": { + "get": { + "tags": [ + "user" + ], + "summary": "Logs user into the system", + "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" + } + } + } + } + }, + "definitions": { + "Category": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "format": "int64" + }, + "name": { + "type": "string" + } + }, + "xml": { + "name": "Category" + } + }, + "Tag": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "format": "int64" + }, + "name": { + "type": "string" + } + }, + "xml": { + "name": "Tag" + } + }, + "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" + } + }, + "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" + } + } + } +}