diff --git a/impl/openapi/pom.xml b/impl/openapi/pom.xml
index 39e3cce8..10b91025 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 7b2a7d11..4d0c1a01 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 e65ebc2f..f8da9994 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 0ffc36b4..5111a38b 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 00000000..54290096
--- /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 00000000..c89df6b7
--- /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 00000000..3da786fe
--- /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 00000000..e2b8a08e
--- /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"
+ }
+ }
+ }
+}