This is an automated email from the ASF dual-hosted git repository.
davsclaus pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/camel.git
The following commit(s) were added to refs/heads/main by this push:
new 0ad9e02 CAMEL-15503 camel-openapi-java - Schema Definitions not
generating correctly (#5477)
0ad9e02 is described below
commit 0ad9e0242eede44bc8dce2c843fd021be6d7b7c5
Author: electrosaur <[email protected]>
AuthorDate: Tue May 11 03:54:52 2021 -0700
CAMEL-15503 camel-openapi-java - Schema Definitions not generating
correctly (#5477)
---
components/camel-openapi-java/pom.xml | 6 +
.../apache/camel/openapi/RestModelConverters.java | 148 +++++++++++++++---
.../apache/camel/openapi/RestOpenApiReader.java | 133 ++++++++++++++--
.../org/apache/camel/openapi/ComplexTypesTest.java | 169 +++++++++++++++++++++
.../RestOpenApiReaderModelBookOrderTest.java | 10 ++
.../org/apache/camel/openapi/model/CustomData.java | 26 ++++
.../openapi/model/GenericComplexRequestType.java | 52 +++++++
.../apache/camel/openapi/model/GenericData.java | 23 +++
.../openapi/model/SampleComplexRequestType.java | 71 +++++++++
.../openapi/model/SampleComplexResponseType.java | 59 +++++++
.../openapi/V2SchemaForComplexTypesRequest.json | 159 +++++++++++++++++++
.../openapi/V2SchemaForComplexTypesResponse.json | 102 +++++++++++++
.../openapi/V3SchemaForComplexTypesRequest.json | 169 +++++++++++++++++++++
.../openapi/V3SchemaForComplexTypesResponse.json | 112 ++++++++++++++
14 files changed, 1209 insertions(+), 30 deletions(-)
diff --git a/components/camel-openapi-java/pom.xml
b/components/camel-openapi-java/pom.xml
index 2a20972..203f836 100644
--- a/components/camel-openapi-java/pom.xml
+++ b/components/camel-openapi-java/pom.xml
@@ -82,6 +82,12 @@
<artifactId>apicurio-data-models</artifactId>
</dependency>
+ <dependency>
+ <groupId>io.swagger.core.v3</groupId>
+ <artifactId>swagger-core</artifactId>
+ <version>2.1.9</version>
+ </dependency>
+
<!-- servlet api -->
<dependency>
diff --git
a/components/camel-openapi-java/src/main/java/org/apache/camel/openapi/RestModelConverters.java
b/components/camel-openapi-java/src/main/java/org/apache/camel/openapi/RestModelConverters.java
index eb8460f..1223624 100644
---
a/components/camel-openapi-java/src/main/java/org/apache/camel/openapi/RestModelConverters.java
+++
b/components/camel-openapi-java/src/main/java/org/apache/camel/openapi/RestModelConverters.java
@@ -16,25 +16,42 @@
*/
package org.apache.camel.openapi;
+import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
+import com.fasterxml.jackson.databind.ObjectMapper;
import io.apicurio.datamodels.core.models.Extension;
import io.apicurio.datamodels.openapi.models.OasDocument;
import io.apicurio.datamodels.openapi.models.OasSchema;
-import io.apicurio.datamodels.openapi.v2.models.Oas20Definitions;
import io.apicurio.datamodels.openapi.v2.models.Oas20Document;
+import io.apicurio.datamodels.openapi.v2.models.Oas20Schema;
import io.apicurio.datamodels.openapi.v2.models.Oas20SchemaDefinition;
import io.apicurio.datamodels.openapi.v3.models.Oas30Document;
import io.apicurio.datamodels.openapi.v3.models.Oas30SchemaDefinition;
+import io.swagger.v3.core.converter.ModelConverters;
+import io.swagger.v3.core.jackson.ModelResolver;
+import io.swagger.v3.oas.models.media.ArraySchema;
+import io.swagger.v3.oas.models.media.Schema;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
/**
* A Camel extended {@link ModelConverters} where we appending vendor
extensions to include the java class name of the
* model classes.
*/
+@SuppressWarnings("rawtypes")
public class RestModelConverters {
+ private static final Logger LOG =
LoggerFactory.getLogger(RestModelConverters.class);
+ private static final ModelConverters MODEL_CONVERTERS;
+
+ static {
+ MODEL_CONVERTERS = ModelConverters.getInstance();
+ MODEL_CONVERTERS.addConverter(new FqnModelResolver());
+ }
+
public List<? extends OasSchema> readClass(OasDocument oasDocument,
Class<?> clazz) {
if (clazz.equals(java.io.File.class)) {
// File is a special type in OAS2 / OAS3 (no model)
@@ -53,19 +70,20 @@ public class RestModelConverters {
if (!name.contains(".")) {
return null;
}
+
if (oasDocument.components == null) {
oasDocument.components = oasDocument.createComponents();
}
- Oas30SchemaDefinition model =
oasDocument.components.createSchemaDefinition(clazz.getSimpleName());
- oasDocument.components.addSchemaDefinition(clazz.getSimpleName(),
model);
- model.type = clazz.getSimpleName();
- Extension extension = model.createExtension();
- extension.name = "x-className";
- Map<String, String> value = new HashMap<String, String>();
- value.put("type", "string");
- value.put("format", name);
- extension.value = value;
- model.addExtension("x-className", extension);
+
+ Map<String, Schema> swaggerModel = MODEL_CONVERTERS.readAll(clazz);
+ swaggerModel.forEach((key, schema) -> {
+ Oas30SchemaDefinition model =
oasDocument.components.createSchemaDefinition(key);
+ oasDocument.components.addSchemaDefinition(key, model);
+ processSchema(model, schema);
+
+ addClassNameExtension(model, key);
+ });
+
return oasDocument.components.getSchemaDefinitions();
}
@@ -74,20 +92,112 @@ public class RestModelConverters {
if (!name.contains(".")) {
return null;
}
+
if (oasDocument.definitions == null) {
oasDocument.definitions = oasDocument.createDefinitions();
}
- Oas20Definitions resolved = oasDocument.definitions;
- Oas20SchemaDefinition model =
resolved.createSchemaDefinition(clazz.getSimpleName());
- resolved.addDefinition(clazz.getSimpleName(), model);
- model.type = clazz.getSimpleName();
- Extension extension = model.createExtension();
+
+ Map<String, Schema> swaggerModel =
MODEL_CONVERTERS.getInstance().readAll(clazz);
+ swaggerModel.forEach((key, schema) -> {
+ Oas20SchemaDefinition model =
oasDocument.definitions.createSchemaDefinition(key);
+ oasDocument.definitions.addDefinition(key, model);
+ processSchema(model, schema);
+
+ addClassNameExtension(model, key);
+ });
+
+ return oasDocument.definitions.getDefinitions();
+ }
+
+ private void processSchema(OasSchema model, Schema schema) {
+ String type = schema.getType();
+ model.type = type;
+ model.format = schema.getFormat();
+
+ String ref = schema.get$ref();
+ if (ref != null) {
+ if (model instanceof Oas20Schema) {
+ // Change the prefix from 3.x to 2.x
+ model.$ref = RestOpenApiReader.OAS20_SCHEMA_DEFINITION_PREFIX +
+
ref.substring(RestOpenApiReader.OAS30_SCHEMA_DEFINITION_PREFIX.length());
+ } else {
+ model.$ref = ref;
+ }
+ }
+
+ if (type != null) {
+ switch (type) {
+ case "object":
+ if (schema.getProperties() != null) {
+ //noinspection unchecked
+ schema.getProperties().forEach((p, v) -> {
+ OasSchema property =
model.createPropertySchema((String) p);
+ model.addProperty((String) p, property);
+ processSchema(property, (Schema) v);
+ });
+ }
+ break;
+ case "array":
+ Schema items = ((ArraySchema) schema).getItems();
+ OasSchema modelItems = model.createItemsSchema();
+ model.items = modelItems;
+ processSchema(modelItems, items);
+ break;
+ case "string":
+ if (schema.getEnum() != null) {
+ //noinspection unchecked
+ model.enum_ = new ArrayList<String>(schema.getEnum());
+ }
+ break;
+ case "number":
+ case "integer":
+ break;
+ default:
+ LOG.warn("Encountered unexpected type " + type + " in
processing schema.");
+ break;
+ }
+ }
+
+ if (schema.getRequired() != null) {
+ //noinspection unchecked
+ model.required = new ArrayList<String>(schema.getRequired());
+ }
+
+ if (schema.getAdditionalProperties() instanceof Schema) {
+ OasSchema additionalProperties =
model.createAdditionalPropertiesSchema();
+ model.additionalProperties = additionalProperties;
+ processSchema(additionalProperties, (Schema)
schema.getAdditionalProperties());
+ }
+
+ if (schema.getExtensions() != null) {
+ //noinspection unchecked
+ schema.getExtensions().forEach((key, value) -> {
+ Extension extension = model.createExtension();
+ extension.name = (String) key;
+ extension.value = value;
+ });
+ }
+ }
+
+ private void addClassNameExtension(OasSchema schema, String name) {
+ Extension extension = schema.createExtension();
extension.name = "x-className";
- Map<String, String> value = new HashMap<String, String>();
+ Map<String, String> value = new HashMap<>();
value.put("type", "string");
value.put("format", name);
extension.value = value;
- model.addExtension("x-className", extension);
- return resolved.getDefinitions();
+ schema.addExtension("x-className", extension);
+ }
+
+ private static class FqnModelResolver extends ModelResolver {
+ public FqnModelResolver() {
+ this(new ObjectMapper());
+ }
+
+ public FqnModelResolver(ObjectMapper mapper) {
+ super(mapper);
+ this._typeNameResolver.setUseFqn(true);
+ }
}
+
}
diff --git
a/components/camel-openapi-java/src/main/java/org/apache/camel/openapi/RestOpenApiReader.java
b/components/camel-openapi-java/src/main/java/org/apache/camel/openapi/RestOpenApiReader.java
index 3ad4d5c..004d80d 100644
---
a/components/camel-openapi-java/src/main/java/org/apache/camel/openapi/RestOpenApiReader.java
+++
b/components/camel-openapi-java/src/main/java/org/apache/camel/openapi/RestOpenApiReader.java
@@ -20,22 +20,29 @@ import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodType;
import java.util.ArrayList;
import java.util.Arrays;
-import java.util.Collections;
import java.util.Comparator;
+import java.util.HashMap;
+import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
+import java.util.function.Function;
import java.util.stream.Collectors;
+import java.util.stream.Stream;
+import io.apicurio.datamodels.Library;
import io.apicurio.datamodels.core.models.ExtensibleNode;
import io.apicurio.datamodels.core.models.Extension;
+import io.apicurio.datamodels.core.models.Node;
import io.apicurio.datamodels.core.models.common.AuthorizationCodeOAuthFlow;
import io.apicurio.datamodels.core.models.common.ImplicitOAuthFlow;
import io.apicurio.datamodels.core.models.common.OAuthFlow;
import io.apicurio.datamodels.core.models.common.SecurityRequirement;
+import io.apicurio.datamodels.core.models.common.Tag;
+import io.apicurio.datamodels.core.visitors.TraverserDirection;
import io.apicurio.datamodels.openapi.models.OasDocument;
import io.apicurio.datamodels.openapi.models.OasOperation;
import io.apicurio.datamodels.openapi.models.OasParameter;
@@ -50,6 +57,7 @@ import io.apicurio.datamodels.openapi.v2.models.Oas20Response;
import io.apicurio.datamodels.openapi.v2.models.Oas20Schema;
import io.apicurio.datamodels.openapi.v2.models.Oas20SchemaDefinition;
import io.apicurio.datamodels.openapi.v2.models.Oas20SecurityScheme;
+import io.apicurio.datamodels.openapi.v2.visitors.Oas20AllNodeVisitor;
import io.apicurio.datamodels.openapi.v3.models.Oas30Document;
import io.apicurio.datamodels.openapi.v3.models.Oas30Header;
import io.apicurio.datamodels.openapi.v3.models.Oas30MediaType;
@@ -59,6 +67,7 @@ import io.apicurio.datamodels.openapi.v3.models.Oas30Response;
import io.apicurio.datamodels.openapi.v3.models.Oas30Schema;
import io.apicurio.datamodels.openapi.v3.models.Oas30SchemaDefinition;
import io.apicurio.datamodels.openapi.v3.models.Oas30SecurityScheme;
+import io.apicurio.datamodels.openapi.v3.visitors.Oas30AllNodeVisitor;
import org.apache.camel.CamelContext;
import org.apache.camel.model.rest.RestDefinition;
import org.apache.camel.model.rest.RestOperationParamDefinition;
@@ -87,6 +96,15 @@ import static java.lang.invoke.MethodHandles.publicLookup;
*/
public class RestOpenApiReader {
+ public static final String OAS20_SCHEMA_DEFINITION_PREFIX =
"#/definitions/";
+ public static final String OAS30_SCHEMA_DEFINITION_PREFIX =
"#/components/schemas/";
+ // Types that are not allowed in references.
+ private static final Set<String> NO_REFERENCE_TYPE_NAMES = new HashSet<>(
+ Arrays.asList(
+ "byte", "char", "short", "int", "java.lang.Integer",
"long", "java.lang.Long", "float", "java.lang.Float",
+ "double", "java.lang.Double", "string",
"java.lang.String", "boolean", "java.lang.Boolean",
+ "file", "java.io.File"));
+
private static String getValue(CamelContext camelContext, String text) {
return camelContext.resolvePropertyPlaceholders(text);
}
@@ -136,6 +154,27 @@ public class RestOpenApiReader {
parse(camelContext, openApi, rest, camelContextId, classResolver);
}
+ shortenClassNames(openApi);
+
+ /*
+ * Fixes the problem of not generating the "paths" section when no
rest route is defined.
+ * A schema with no paths is considered invalid.
+ */
+ if (openApi.paths == null) {
+ openApi.paths = openApi.createPaths();
+ }
+
+ /*
+ * Fixes the problem of generating duplicated tags which is invalid
per the specification
+ */
+ if (openApi.tags != null) {
+ openApi.tags = new ArrayList<>(
+ openApi.tags
+ .stream()
+ .collect(Collectors.toMap(Tag::getName,
Function.identity(), (prev, current) -> prev))
+ .values());
+ }
+
// configure before returning
openApi = config.configure(openApi);
return openApi;
@@ -148,7 +187,7 @@ public class RestOpenApiReader {
List<VerbDefinition> verbs = new ArrayList<>(rest.getVerbs());
// must sort the verbs by uri so we group them together when an uri
has multiple operations
- Collections.sort(verbs, new VerbOrdering(camelContext));
+ verbs.sort(new VerbOrdering(camelContext));
// we need to group the operations within the same tag, so use the
path as default if not
// configured
String pathAsTag = getValue(camelContext, rest.getTag() != null
@@ -567,7 +606,7 @@ public class RestOpenApiReader {
String ref = modelTypeAsRef(type, openApi);
if (ref != null) {
Oas30Schema refModel = (Oas30Schema)
bp.createSchema();
- refModel.$ref = "#/components/schemas/" + ref;
+ refModel.$ref = OAS30_SCHEMA_DEFINITION_PREFIX
+ ref;
bp.schema = refModel;
} else {
OasSchema model = (Oas30Schema)
bp.createSchema();
@@ -790,7 +829,7 @@ public class RestOpenApiReader {
String ref = modelTypeAsRef(type, openApi);
if (ref != null) {
Oas20Schema refModel = (Oas20Schema)
bp.createSchema();
- refModel.$ref = "#/definitions/" + ref;
+ refModel.$ref = OAS20_SCHEMA_DEFINITION_PREFIX
+ ref;
bp.schema = refModel;
} else {
OasSchema model = (Oas20Schema)
bp.createSchema();
@@ -1216,13 +1255,11 @@ public class RestOpenApiReader {
typeName = typeName.substring(0, typeName.length() - 2);
}
- OasSchema model = asModel(typeName, openApi);
- if (model != null) {
- typeName = model.type;
- return typeName;
+ if (NO_REFERENCE_TYPE_NAMES.contains(typeName)) {
+ return null;
}
- return null;
+ return typeName;
}
private OasSchema modelTypeAsProperty(String typeName, OasDocument
openApi, OasSchema prop) {
@@ -1235,9 +1272,9 @@ public class RestOpenApiReader {
if (ref != null) {
if (openApi instanceof Oas20Document) {
- prop.$ref = "#/definitions/" + ref;
+ prop.$ref = OAS20_SCHEMA_DEFINITION_PREFIX + ref;
} else if (openApi instanceof Oas30Document) {
- prop.$ref = "#/components/schemas/" + ref;
+ prop.$ref = OAS30_SCHEMA_DEFINITION_PREFIX + ref;
}
} else {
// special for byte arrays
@@ -1372,4 +1409,78 @@ public class RestOpenApiReader {
}
}
+ private void shortenClassNames(OasDocument document) {
+ if (document instanceof Oas30Document) {
+ Oas30Document oas30Document = (Oas30Document) document;
+ if (oas30Document.components == null ||
oas30Document.components.schemas == null) {
+ return;
+ }
+ } else {
+ Oas20Document oas20Document = (Oas20Document) document;
+ if (oas20Document.definitions == null ||
oas20Document.definitions.getDefinitions() == null) {
+ return;
+ }
+ }
+
+ // Make a mapping from full name to possibly shortened name.
+ Map<String, String> names = new HashMap<>();
+ Stream<String> schemaStream;
+ if (document instanceof Oas30Document) {
+ schemaStream = ((Oas30Document)
document).components.schemas.keySet().stream();
+ } else {
+ schemaStream = ((Oas20Document)
document).definitions.getDefinitions().stream()
+ .map(Oas20SchemaDefinition::getName);
+ }
+ schemaStream.forEach(key -> {
+ String s = key.replaceAll("[^a-zA-Z0-9.-_]", "_");
+ String shortName = s.substring(s.lastIndexOf('.') + 1);
+ names.put(key, names.containsValue(shortName) ? s : shortName);
+ });
+
+ if (document instanceof Oas30Document) {
+ Library.visitTree(document, new Oas30AllNodeVisitor() {
+ @Override
+ protected void visitNode(Node node) {
+ if (node instanceof Oas30SchemaDefinition) {
+ Oas30SchemaDefinition definition =
(Oas30SchemaDefinition) node;
+
definition.rename(fixSchemaReference(definition.getName(), names,
OAS30_SCHEMA_DEFINITION_PREFIX));
+ } else if (node instanceof Oas30Schema) {
+ Oas30Schema schema = (Oas30Schema) node;
+ String ref = schema.$ref;
+ if (ref != null) {
+ schema.$ref = OAS30_SCHEMA_DEFINITION_PREFIX +
+ fixSchemaReference(ref, names,
OAS30_SCHEMA_DEFINITION_PREFIX);
+ }
+ }
+ }
+ }, TraverserDirection.down);
+ } else {
+ Library.visitTree(document, new Oas20AllNodeVisitor() {
+ @Override
+ protected void visitNode(Node node) {
+ if (node instanceof Oas20SchemaDefinition) {
+ Oas20SchemaDefinition definition =
(Oas20SchemaDefinition) node;
+
definition.rename(fixSchemaReference(definition.getName(), names,
OAS20_SCHEMA_DEFINITION_PREFIX));
+ } else if (node instanceof Oas20Schema) {
+ Oas20Schema schema = (Oas20Schema) node;
+ String ref = schema.$ref;
+ if (ref != null) {
+ schema.$ref = OAS20_SCHEMA_DEFINITION_PREFIX +
+ fixSchemaReference(ref, names,
OAS20_SCHEMA_DEFINITION_PREFIX);
+ }
+ }
+ }
+ }, TraverserDirection.down);
+ }
+ }
+
+ private String fixSchemaReference(String ref, Map<String, String> names,
String prefix) {
+ if (ref.startsWith(prefix)) {
+ ref = ref.substring(prefix.length());
+ }
+
+ String name = names.get(ref);
+ return name == null ? ref : name;
+ }
+
}
diff --git
a/components/camel-openapi-java/src/test/java/org/apache/camel/openapi/ComplexTypesTest.java
b/components/camel-openapi-java/src/test/java/org/apache/camel/openapi/ComplexTypesTest.java
new file mode 100644
index 0000000..5aeacea
--- /dev/null
+++
b/components/camel-openapi-java/src/test/java/org/apache/camel/openapi/ComplexTypesTest.java
@@ -0,0 +1,169 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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 org.apache.camel.openapi;
+
+import java.io.BufferedReader;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.nio.charset.StandardCharsets;
+import java.util.List;
+import java.util.stream.Collectors;
+
+import com.fasterxml.jackson.annotation.JsonInclude;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.SerializationFeature;
+import io.apicurio.datamodels.Library;
+import io.apicurio.datamodels.openapi.models.OasDocument;
+import org.apache.camel.BindToRegistry;
+import org.apache.camel.builder.RouteBuilder;
+import org.apache.camel.impl.engine.DefaultClassResolver;
+import org.apache.camel.model.rest.RestBindingMode;
+import org.apache.camel.model.rest.RestDefinition;
+import org.apache.camel.openapi.model.SampleComplexRequestType;
+import org.apache.camel.openapi.model.SampleComplexResponseType;
+import org.apache.camel.test.junit5.CamelTestSupport;
+import org.junit.jupiter.api.Test;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+
+public class ComplexTypesTest extends CamelTestSupport {
+ private static final Logger LOG =
LoggerFactory.getLogger(ComplexTypesTest.class);
+
+ @SuppressWarnings("unused")
+ @BindToRegistry("dummy-rest")
+ private final DummyRestConsumerFactory factory = new
DummyRestConsumerFactory();
+
+ @Override
+ protected RouteBuilder createRouteBuilder() throws Exception {
+ return new RouteBuilder() {
+ @Override
+ public void configure() {
+ rest().securityDefinitions()
+ .oauth2("global")
+ .accessCode("https://AUTHORIZATION_URL",
"https://TOKEN_URL")
+ .withScope("groups", "Required scopes for Camel REST
APIs");
+
+ rest().post("/complexRequest")
+ .description("Demo complex request type")
+ .type(SampleComplexRequestType.class)
+ .consumes("application/json")
+ .produces("text/plain")
+ .bindingMode(RestBindingMode.json)
+ .responseMessage()
+ .code(200)
+ .message("Receives a complex object as parameter")
+ .endResponseMessage()
+ .outType(SampleComplexResponseType.InnerClass.class)
+ .route()
+ .routeId("complex request type")
+ .log("/complex request invoked");
+
+ rest().get("/complexResponse")
+ .description("Demo complex response type")
+ .type(SampleComplexRequestType.InnerClass.class)
+ .consumes("application/json")
+ .outType(SampleComplexResponseType.class)
+ .produces("application/json")
+ .bindingMode(RestBindingMode.json)
+ .responseMessage()
+ .code(200)
+ .message("Returns a complex object")
+ .endResponseMessage()
+ .route()
+ .routeId("complex response type")
+ .log("/complex invoked")
+ .setBody(constant(new SampleComplexResponseType()));
+ }
+ };
+ }
+
+ @Test
+ public void testV3SchemaForComplexTypesRequest() throws Exception {
+ checkSchemaGeneration("/complexRequest", "3.0",
"V3SchemaForComplexTypesRequest.json");
+ }
+
+ @Test
+ public void testV2SchemaForComplexTypesRequest() throws Exception {
+ checkSchemaGeneration("/complexRequest", "2.0",
"V2SchemaForComplexTypesRequest.json");
+ }
+
+ @Test
+ public void testV3SchemaForComplexTypesResponse() throws Exception {
+ checkSchemaGeneration("/complexResponse", "3.0",
"V3SchemaForComplexTypesResponse.json");
+ }
+
+ @Test
+ public void testV2SchemaForComplexTypesResponse() throws Exception {
+ checkSchemaGeneration("/complexResponse", "2.0",
"V2SchemaForComplexTypesResponse.json");
+ }
+
+ private void checkSchemaGeneration(String uri, String apiVersion, String
schemaResource) throws Exception {
+ BeanConfig config = getBeanConfig(apiVersion);
+
+ List<RestDefinition> rests = context.getRestDefinitions().stream()
+ // So we get the security schema and the route schema
+ .filter(def -> def.getVerbs().isEmpty() ||
def.getVerbs().get(0).getUri().equals(uri))
+ .collect(Collectors.toList());
+
+ RestOpenApiReader reader = new RestOpenApiReader();
+ OasDocument openApi = reader.read(context, rests, null, config,
context.getName(), new DefaultClassResolver());
+ assertNotNull(openApi);
+
+ ObjectMapper mapper = new ObjectMapper();
+ mapper.enable(SerializationFeature.INDENT_OUTPUT);
+ mapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
+ Object dump = Library.writeNode(openApi);
+ String json = mapper.writeValueAsString(dump);
+
+ LOG.info(json);
+
+ json = generify(json);
+
+ InputStream is =
getClass().getClassLoader().getResourceAsStream("org/apache/camel/openapi/" +
schemaResource);
+ assertNotNull(is);
+ String expected = new BufferedReader(
+ new InputStreamReader(is, StandardCharsets.UTF_8))
+ .lines()
+ .collect(Collectors.joining("\n"));
+ is.close();
+
+ assertEquals(expected, json);
+ }
+
+ private BeanConfig getBeanConfig(String apiVersion) {
+ BeanConfig config = new BeanConfig();
+ config.setHost("localhost:8080");
+ config.setSchemes(new String[] { "http" });
+ config.setBasePath("/api");
+ config.setTitle("Camel User store");
+ config.setLicense("Apache 2.0");
+
config.setLicenseUrl("https://www.apache.org/licenses/LICENSE-2.0.html");
+ config.setVersion(apiVersion);
+ return config;
+ }
+
+ private String generify(String input) {
+ input = input.replaceAll("\"openapi\" : \"3\\..*\",", "\"openapi\" :
\"3.x\",");
+ input = input.replaceAll("\"swagger\" : \"2\\..*\",", "\"swagger\" :
\"2.x\",");
+ input = input.replaceAll("\"operationId\" : \"verb.*\",",
"\"operationId\" : \"verb\",");
+ input = input.replaceAll("\"x-camelContextId\" : \"camel.*\",",
"\"x-camelContextId\" : \"camel\",");
+ return input;
+ }
+}
diff --git
a/components/camel-openapi-java/src/test/java/org/apache/camel/openapi/RestOpenApiReaderModelBookOrderTest.java
b/components/camel-openapi-java/src/test/java/org/apache/camel/openapi/RestOpenApiReaderModelBookOrderTest.java
index 2e45e5b..b8257ab 100644
---
a/components/camel-openapi-java/src/test/java/org/apache/camel/openapi/RestOpenApiReaderModelBookOrderTest.java
+++
b/components/camel-openapi-java/src/test/java/org/apache/camel/openapi/RestOpenApiReaderModelBookOrderTest.java
@@ -48,6 +48,16 @@ public class RestOpenApiReaderModelBookOrderTest extends
CamelTestSupport {
return new RouteBuilder() {
@Override
public void configure() throws Exception {
+ rest()
+ .securityDefinitions()
+ .oauth2("global")
+ .accessCode(
+ "https://AUTHORIZATION_URL",
+ "https://TOKEN_URL"
+ )
+ .withScope("groups", "Required scopes for Camel REST APIs")
+ .end();
+
// this user REST service is json only
rest("/books").tag("dude").description("Book order
service").consumes("application/json")
.produces("application/json")
diff --git
a/components/camel-openapi-java/src/test/java/org/apache/camel/openapi/model/CustomData.java
b/components/camel-openapi-java/src/test/java/org/apache/camel/openapi/model/CustomData.java
new file mode 100644
index 0000000..ff3c945
--- /dev/null
+++
b/components/camel-openapi-java/src/test/java/org/apache/camel/openapi/model/CustomData.java
@@ -0,0 +1,26 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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 org.apache.camel.openapi.model;
+
+class CustomData implements GenericData {
+
+ private String customDataField;
+
+ public String getCustomDataField() {
+ return customDataField;
+ }
+}
diff --git
a/components/camel-openapi-java/src/test/java/org/apache/camel/openapi/model/GenericComplexRequestType.java
b/components/camel-openapi-java/src/test/java/org/apache/camel/openapi/model/GenericComplexRequestType.java
new file mode 100644
index 0000000..d10c4ed
--- /dev/null
+++
b/components/camel-openapi-java/src/test/java/org/apache/camel/openapi/model/GenericComplexRequestType.java
@@ -0,0 +1,52 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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 org.apache.camel.openapi.model;
+
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Sample request POJO that uses Generics.
+ */
+public class GenericComplexRequestType<T extends GenericData> {
+
+ private T data;
+ private List<T> listOfData;
+ private List<List<T>> listOfListOfData;
+ private Map<String, T> mapOfData;
+ private Map<String, Map<String, T>> mapOfMapOfData;
+
+ public T getData() {
+ return data;
+ }
+
+ public List<T> getListOfData() {
+ return listOfData;
+ }
+
+ public Map<String, T> getMapOfData() {
+ return mapOfData;
+ }
+
+ public List<List<T>> getListOfListOfData() {
+ return listOfListOfData;
+ }
+
+ public Map<String, Map<String, T>> getMapOfMapOfData() {
+ return mapOfMapOfData;
+ }
+}
diff --git
a/components/camel-openapi-java/src/test/java/org/apache/camel/openapi/model/GenericData.java
b/components/camel-openapi-java/src/test/java/org/apache/camel/openapi/model/GenericData.java
new file mode 100644
index 0000000..6e37cdf
--- /dev/null
+++
b/components/camel-openapi-java/src/test/java/org/apache/camel/openapi/model/GenericData.java
@@ -0,0 +1,23 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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 org.apache.camel.openapi.model;
+
+/**
+ * Generic interface used to validate that inheritance and generics also works
with the camel open-api compoment
+ */
+interface GenericData {
+}
diff --git
a/components/camel-openapi-java/src/test/java/org/apache/camel/openapi/model/SampleComplexRequestType.java
b/components/camel-openapi-java/src/test/java/org/apache/camel/openapi/model/SampleComplexRequestType.java
new file mode 100644
index 0000000..16b9675
--- /dev/null
+++
b/components/camel-openapi-java/src/test/java/org/apache/camel/openapi/model/SampleComplexRequestType.java
@@ -0,0 +1,71 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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 org.apache.camel.openapi.model;
+
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.TimeUnit;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+public class SampleComplexRequestType extends
GenericComplexRequestType<CustomData> {
+ @JsonProperty(required = true)
+ private String requestField1;
+ private String requestField2;
+ private List<String> listOfStrings;
+ private String[] arrayOfString;
+ private Map<String, String> mapOfStrings;
+ private TimeUnit timeUnit;
+ private InnerClass innerClass;
+
+ public String getRequestField1() {
+ return requestField1;
+ }
+
+ public String getRequestField2() {
+ return requestField2;
+ }
+
+ public List<String> getListOfStrings() {
+ return listOfStrings;
+ }
+
+ public String[] getArrayOfString() {
+ return arrayOfString;
+ }
+
+ @JsonProperty(required = true)
+ public Map<String, String> getMapOfStrings() {
+ return mapOfStrings;
+ }
+
+ public TimeUnit getTimeUnit() {
+ return timeUnit;
+ }
+
+ public InnerClass getInnerClass() {
+ return innerClass;
+ }
+
+ public static class InnerClass {
+ private long longField;
+
+ public long getLongField() {
+ return longField;
+ }
+ }
+}
diff --git
a/components/camel-openapi-java/src/test/java/org/apache/camel/openapi/model/SampleComplexResponseType.java
b/components/camel-openapi-java/src/test/java/org/apache/camel/openapi/model/SampleComplexResponseType.java
new file mode 100644
index 0000000..ba7dcdd
--- /dev/null
+++
b/components/camel-openapi-java/src/test/java/org/apache/camel/openapi/model/SampleComplexResponseType.java
@@ -0,0 +1,59 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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 org.apache.camel.openapi.model;
+
+import java.time.Month;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+public class SampleComplexResponseType {
+ @JsonProperty(required = true)
+ private String responseField1 = "Response Field 1";
+ private String responseField2 = "Response Field 2";
+ private String[] arrayOfStrings;
+ private Month month;
+ private InnerClass innerClass;
+
+ public String getResponseField1() {
+ return responseField1;
+ }
+
+ public String getResponseField2() {
+ return responseField2;
+ }
+
+ @JsonProperty(required = true)
+ public String[] getArrayOfStrings() {
+ return arrayOfStrings;
+ }
+
+ public Month getMonth() {
+ return month;
+ }
+
+ public InnerClass getInnerClass() {
+ return innerClass;
+ }
+
+ public static class InnerClass {
+ double doubleField;
+
+ public double getDoubleField() {
+ return doubleField;
+ }
+ }
+}
diff --git
a/components/camel-openapi-java/src/test/resources/org/apache/camel/openapi/V2SchemaForComplexTypesRequest.json
b/components/camel-openapi-java/src/test/resources/org/apache/camel/openapi/V2SchemaForComplexTypesRequest.json
new file mode 100644
index 0000000..92d0fa7
--- /dev/null
+++
b/components/camel-openapi-java/src/test/resources/org/apache/camel/openapi/V2SchemaForComplexTypesRequest.json
@@ -0,0 +1,159 @@
+{
+ "swagger" : "2.x",
+ "host" : "localhost:8080",
+ "basePath" : "/api",
+ "schemes" : [ "http" ],
+ "paths" : {
+ "/complexRequest" : {
+ "post" : {
+ "consumes" : [ "application/json" ],
+ "produces" : [ "text/plain" ],
+ "parameters" : [ {
+ "name" : "body",
+ "schema" : {
+ "$ref" : "#/definitions/SampleComplexRequestType"
+ },
+ "in" : "body",
+ "required" : true
+ } ],
+ "responses" : {
+ "200" : {
+ "description" : "Receives a complex object as parameter",
+ "schema" : {
+ "$ref" : "#/definitions/SampleComplexResponseType_InnerClass"
+ }
+ }
+ },
+ "operationId" : "verb",
+ "summary" : "Demo complex request type",
+ "x-camelContextId" : "camel",
+ "x-routeId" : "complex request type"
+ }
+ }
+ },
+ "definitions" : {
+ "CustomData" : {
+ "type" : "object",
+ "properties" : {
+ "customDataField" : {
+ "type" : "string"
+ }
+ },
+ "x-className" : {
+ "format" : "org.apache.camel.openapi.model.CustomData",
+ "type" : "string"
+ }
+ },
+ "SampleComplexRequestType" : {
+ "required" : [ "mapOfStrings", "requestField1" ],
+ "type" : "object",
+ "properties" : {
+ "data" : {
+ "$ref" : "#/definitions/CustomData"
+ },
+ "listOfData" : {
+ "type" : "array",
+ "items" : {
+ "$ref" : "#/definitions/CustomData"
+ }
+ },
+ "listOfListOfData" : {
+ "type" : "array",
+ "items" : {
+ "type" : "array",
+ "items" : {
+ "$ref" : "#/definitions/CustomData"
+ }
+ }
+ },
+ "mapOfData" : {
+ "type" : "object",
+ "additionalProperties" : {
+ "$ref" : "#/definitions/CustomData"
+ }
+ },
+ "mapOfMapOfData" : {
+ "type" : "object",
+ "additionalProperties" : {
+ "type" : "object",
+ "additionalProperties" : {
+ "$ref" : "#/definitions/CustomData"
+ }
+ }
+ },
+ "requestField1" : {
+ "type" : "string"
+ },
+ "requestField2" : {
+ "type" : "string"
+ },
+ "listOfStrings" : {
+ "type" : "array",
+ "items" : {
+ "type" : "string"
+ }
+ },
+ "arrayOfString" : {
+ "type" : "array",
+ "items" : {
+ "type" : "string"
+ }
+ },
+ "mapOfStrings" : {
+ "type" : "object",
+ "additionalProperties" : {
+ "type" : "string"
+ }
+ },
+ "timeUnit" : {
+ "enum" : [ "NANOSECONDS", "MICROSECONDS", "MILLISECONDS", "SECONDS",
"MINUTES", "HOURS", "DAYS" ],
+ "type" : "string"
+ },
+ "innerClass" : {
+ "$ref" : "#/definitions/SampleComplexRequestType_InnerClass"
+ }
+ },
+ "x-className" : {
+ "format" : "org.apache.camel.openapi.model.SampleComplexRequestType",
+ "type" : "string"
+ }
+ },
+ "SampleComplexRequestType_InnerClass" : {
+ "type" : "object",
+ "properties" : {
+ "longField" : {
+ "format" : "int64",
+ "type" : "integer"
+ }
+ },
+ "x-className" : {
+ "format" :
"org.apache.camel.openapi.model.SampleComplexRequestType$InnerClass",
+ "type" : "string"
+ }
+ },
+ "SampleComplexResponseType_InnerClass" : {
+ "type" : "object",
+ "properties" : {
+ "doubleField" : {
+ "format" : "double",
+ "type" : "number"
+ }
+ },
+ "x-className" : {
+ "format" :
"org.apache.camel.openapi.model.SampleComplexResponseType$InnerClass",
+ "type" : "string"
+ }
+ }
+ },
+ "securityDefinitions" : {
+ "global" : {
+ "flow" : "accessCode",
+ "authorizationUrl" : "https://AUTHORIZATION_URL",
+ "tokenUrl" : "https://TOKEN_URL",
+ "scopes" : {
+ "groups" : "Required scopes for Camel REST APIs"
+ },
+ "type" : "oauth2"
+ }
+ }
+}
\ No newline at end of file
diff --git
a/components/camel-openapi-java/src/test/resources/org/apache/camel/openapi/V2SchemaForComplexTypesResponse.json
b/components/camel-openapi-java/src/test/resources/org/apache/camel/openapi/V2SchemaForComplexTypesResponse.json
new file mode 100644
index 0000000..752686d
--- /dev/null
+++
b/components/camel-openapi-java/src/test/resources/org/apache/camel/openapi/V2SchemaForComplexTypesResponse.json
@@ -0,0 +1,102 @@
+{
+ "swagger" : "2.x",
+ "host" : "localhost:8080",
+ "basePath" : "/api",
+ "schemes" : [ "http" ],
+ "paths" : {
+ "/complexResponse" : {
+ "get" : {
+ "consumes" : [ "application/json" ],
+ "produces" : [ "application/json" ],
+ "parameters" : [ {
+ "name" : "body",
+ "schema" : {
+ "$ref" : "#/definitions/SampleComplexRequestType_InnerClass"
+ },
+ "in" : "body",
+ "required" : true
+ } ],
+ "responses" : {
+ "200" : {
+ "description" : "Returns a complex object",
+ "schema" : {
+ "$ref" : "#/definitions/SampleComplexResponseType"
+ }
+ }
+ },
+ "operationId" : "verb",
+ "summary" : "Demo complex response type",
+ "x-camelContextId" : "camel",
+ "x-routeId" : "complex response type"
+ }
+ }
+ },
+ "definitions" : {
+ "SampleComplexRequestType_InnerClass" : {
+ "type" : "object",
+ "properties" : {
+ "longField" : {
+ "format" : "int64",
+ "type" : "integer"
+ }
+ },
+ "x-className" : {
+ "format" :
"org.apache.camel.openapi.model.SampleComplexRequestType$InnerClass",
+ "type" : "string"
+ }
+ },
+ "SampleComplexResponseType" : {
+ "required" : [ "arrayOfStrings", "responseField1" ],
+ "type" : "object",
+ "properties" : {
+ "responseField1" : {
+ "type" : "string"
+ },
+ "responseField2" : {
+ "type" : "string"
+ },
+ "arrayOfStrings" : {
+ "type" : "array",
+ "items" : {
+ "type" : "string"
+ }
+ },
+ "month" : {
+ "enum" : [ "JANUARY", "FEBRUARY", "MARCH", "APRIL", "MAY", "JUNE",
"JULY", "AUGUST", "SEPTEMBER", "OCTOBER", "NOVEMBER", "DECEMBER" ],
+ "type" : "string"
+ },
+ "innerClass" : {
+ "$ref" : "#/definitions/SampleComplexResponseType_InnerClass"
+ }
+ },
+ "x-className" : {
+ "format" : "org.apache.camel.openapi.model.SampleComplexResponseType",
+ "type" : "string"
+ }
+ },
+ "SampleComplexResponseType_InnerClass" : {
+ "type" : "object",
+ "properties" : {
+ "doubleField" : {
+ "format" : "double",
+ "type" : "number"
+ }
+ },
+ "x-className" : {
+ "format" :
"org.apache.camel.openapi.model.SampleComplexResponseType$InnerClass",
+ "type" : "string"
+ }
+ }
+ },
+ "securityDefinitions" : {
+ "global" : {
+ "flow" : "accessCode",
+ "authorizationUrl" : "https://AUTHORIZATION_URL",
+ "tokenUrl" : "https://TOKEN_URL",
+ "scopes" : {
+ "groups" : "Required scopes for Camel REST APIs"
+ },
+ "type" : "oauth2"
+ }
+ }
+}
\ No newline at end of file
diff --git
a/components/camel-openapi-java/src/test/resources/org/apache/camel/openapi/V3SchemaForComplexTypesRequest.json
b/components/camel-openapi-java/src/test/resources/org/apache/camel/openapi/V3SchemaForComplexTypesRequest.json
new file mode 100644
index 0000000..873d791
--- /dev/null
+++
b/components/camel-openapi-java/src/test/resources/org/apache/camel/openapi/V3SchemaForComplexTypesRequest.json
@@ -0,0 +1,169 @@
+{
+ "openapi" : "3.x",
+ "servers" : [ {
+ "url" : "http://localhost:8080/api"
+ } ],
+ "paths" : {
+ "/complexRequest" : {
+ "post" : {
+ "requestBody" : {
+ "description" : "",
+ "content" : {
+ "application/json" : {
+ "schema" : {
+ "$ref" : "#/components/schemas/SampleComplexRequestType"
+ }
+ }
+ },
+ "required" : true
+ },
+ "responses" : {
+ "200" : {
+ "content" : {
+ "text/plain" : {
+ "schema" : {
+ "$ref" :
"#/components/schemas/SampleComplexResponseType_InnerClass"
+ }
+ }
+ },
+ "description" : "Receives a complex object as parameter"
+ }
+ },
+ "operationId" : "verb",
+ "summary" : "Demo complex request type",
+ "x-camelContextId" : "camel",
+ "x-routeId" : "complex request type"
+ }
+ }
+ },
+ "components" : {
+ "schemas" : {
+ "CustomData" : {
+ "type" : "object",
+ "properties" : {
+ "customDataField" : {
+ "type" : "string"
+ }
+ },
+ "x-className" : {
+ "format" : "org.apache.camel.openapi.model.CustomData",
+ "type" : "string"
+ }
+ },
+ "SampleComplexRequestType" : {
+ "required" : [ "mapOfStrings", "requestField1" ],
+ "type" : "object",
+ "properties" : {
+ "data" : {
+ "$ref" : "#/components/schemas/CustomData"
+ },
+ "listOfData" : {
+ "type" : "array",
+ "items" : {
+ "$ref" : "#/components/schemas/CustomData"
+ }
+ },
+ "listOfListOfData" : {
+ "type" : "array",
+ "items" : {
+ "type" : "array",
+ "items" : {
+ "$ref" : "#/components/schemas/CustomData"
+ }
+ }
+ },
+ "mapOfData" : {
+ "type" : "object",
+ "additionalProperties" : {
+ "$ref" : "#/components/schemas/CustomData"
+ }
+ },
+ "mapOfMapOfData" : {
+ "type" : "object",
+ "additionalProperties" : {
+ "type" : "object",
+ "additionalProperties" : {
+ "$ref" : "#/components/schemas/CustomData"
+ }
+ }
+ },
+ "requestField1" : {
+ "type" : "string"
+ },
+ "requestField2" : {
+ "type" : "string"
+ },
+ "listOfStrings" : {
+ "type" : "array",
+ "items" : {
+ "type" : "string"
+ }
+ },
+ "arrayOfString" : {
+ "type" : "array",
+ "items" : {
+ "type" : "string"
+ }
+ },
+ "mapOfStrings" : {
+ "type" : "object",
+ "additionalProperties" : {
+ "type" : "string"
+ }
+ },
+ "timeUnit" : {
+ "enum" : [ "NANOSECONDS", "MICROSECONDS", "MILLISECONDS",
"SECONDS", "MINUTES", "HOURS", "DAYS" ],
+ "type" : "string"
+ },
+ "innerClass" : {
+ "$ref" : "#/components/schemas/SampleComplexRequestType_InnerClass"
+ }
+ },
+ "x-className" : {
+ "format" : "org.apache.camel.openapi.model.SampleComplexRequestType",
+ "type" : "string"
+ }
+ },
+ "SampleComplexRequestType_InnerClass" : {
+ "type" : "object",
+ "properties" : {
+ "longField" : {
+ "format" : "int64",
+ "type" : "integer"
+ }
+ },
+ "x-className" : {
+ "format" :
"org.apache.camel.openapi.model.SampleComplexRequestType$InnerClass",
+ "type" : "string"
+ }
+ },
+ "SampleComplexResponseType_InnerClass" : {
+ "type" : "object",
+ "properties" : {
+ "doubleField" : {
+ "format" : "double",
+ "type" : "number"
+ }
+ },
+ "x-className" : {
+ "format" :
"org.apache.camel.openapi.model.SampleComplexResponseType$InnerClass",
+ "type" : "string"
+ }
+ }
+ },
+ "securitySchemes" : {
+ "global" : {
+ "flows" : {
+ "authorizationCode" : {
+ "authorizationUrl" : "https://AUTHORIZATION_URL",
+ "tokenUrl" : "https://TOKEN_URL",
+ "scopes" : {
+ "groups" : "Required scopes for Camel REST APIs"
+ }
+ }
+ },
+ "type" : "oauth2"
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git
a/components/camel-openapi-java/src/test/resources/org/apache/camel/openapi/V3SchemaForComplexTypesResponse.json
b/components/camel-openapi-java/src/test/resources/org/apache/camel/openapi/V3SchemaForComplexTypesResponse.json
new file mode 100644
index 0000000..c4c691e
--- /dev/null
+++
b/components/camel-openapi-java/src/test/resources/org/apache/camel/openapi/V3SchemaForComplexTypesResponse.json
@@ -0,0 +1,112 @@
+{
+ "openapi" : "3.x",
+ "servers" : [ {
+ "url" : "http://localhost:8080/api"
+ } ],
+ "paths" : {
+ "/complexResponse" : {
+ "get" : {
+ "requestBody" : {
+ "description" : "",
+ "content" : {
+ "application/json" : {
+ "schema" : {
+ "$ref" :
"#/components/schemas/SampleComplexRequestType_InnerClass"
+ }
+ }
+ },
+ "required" : true
+ },
+ "responses" : {
+ "200" : {
+ "content" : {
+ "application/json" : {
+ "schema" : {
+ "$ref" : "#/components/schemas/SampleComplexResponseType"
+ }
+ }
+ },
+ "description" : "Returns a complex object"
+ }
+ },
+ "operationId" : "verb",
+ "summary" : "Demo complex response type",
+ "x-camelContextId" : "camel",
+ "x-routeId" : "complex response type"
+ }
+ }
+ },
+ "components" : {
+ "schemas" : {
+ "SampleComplexRequestType_InnerClass" : {
+ "type" : "object",
+ "properties" : {
+ "longField" : {
+ "format" : "int64",
+ "type" : "integer"
+ }
+ },
+ "x-className" : {
+ "format" :
"org.apache.camel.openapi.model.SampleComplexRequestType$InnerClass",
+ "type" : "string"
+ }
+ },
+ "SampleComplexResponseType" : {
+ "required" : [ "arrayOfStrings", "responseField1" ],
+ "type" : "object",
+ "properties" : {
+ "responseField1" : {
+ "type" : "string"
+ },
+ "responseField2" : {
+ "type" : "string"
+ },
+ "arrayOfStrings" : {
+ "type" : "array",
+ "items" : {
+ "type" : "string"
+ }
+ },
+ "month" : {
+ "enum" : [ "JANUARY", "FEBRUARY", "MARCH", "APRIL", "MAY", "JUNE",
"JULY", "AUGUST", "SEPTEMBER", "OCTOBER", "NOVEMBER", "DECEMBER" ],
+ "type" : "string"
+ },
+ "innerClass" : {
+ "$ref" :
"#/components/schemas/SampleComplexResponseType_InnerClass"
+ }
+ },
+ "x-className" : {
+ "format" :
"org.apache.camel.openapi.model.SampleComplexResponseType",
+ "type" : "string"
+ }
+ },
+ "SampleComplexResponseType_InnerClass" : {
+ "type" : "object",
+ "properties" : {
+ "doubleField" : {
+ "format" : "double",
+ "type" : "number"
+ }
+ },
+ "x-className" : {
+ "format" :
"org.apache.camel.openapi.model.SampleComplexResponseType$InnerClass",
+ "type" : "string"
+ }
+ }
+ },
+ "securitySchemes" : {
+ "global" : {
+ "flows" : {
+ "authorizationCode" : {
+ "authorizationUrl" : "https://AUTHORIZATION_URL",
+ "tokenUrl" : "https://TOKEN_URL",
+ "scopes" : {
+ "groups" : "Required scopes for Camel REST APIs"
+ }
+ }
+ },
+ "type" : "oauth2"
+ }
+ }
+ }
+}
\ No newline at end of file