You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@camel.apache.org by ja...@apache.org on 2023/03/22 14:39:47 UTC

[camel] branch main updated: CAMEL-19183: Add an option to set the default content types used in the generated OpenAPI document

This is an automated email from the ASF dual-hosted git repository.

jamesnetherton 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 d4c385d98e8 CAMEL-19183: Add an option to set the default content types used in the generated OpenAPI document
d4c385d98e8 is described below

commit d4c385d98e88200a8e9680093e9f26bef8aa25e3
Author: James Netherton <ja...@gmail.com>
AuthorDate: Wed Mar 22 10:24:50 2023 +0000

    CAMEL-19183: Add an option to set the default content types used in the generated OpenAPI document
---
 .../src/main/docs/openapi-java.adoc                |  10 +-
 .../java/org/apache/camel/openapi/BeanConfig.java  |  20 ++
 .../apache/camel/openapi/RestOpenApiReader.java    |  47 ++--
 .../apache/camel/openapi/RestOpenApiSupport.java   |  10 +
 .../RestOpenApiDefaultProducesConsumesTest.java    | 280 +++++++++++++++++++++
 .../camel/openapi/RestOpenApiReaderTest.java       | 103 ++++++--
 6 files changed, 427 insertions(+), 43 deletions(-)

diff --git a/components/camel-openapi-java/src/main/docs/openapi-java.adoc b/components/camel-openapi-java/src/main/docs/openapi-java.adoc
index b6ad0184dd8..24a8b5823ed 100644
--- a/components/camel-openapi-java/src/main/docs/openapi-java.adoc
+++ b/components/camel-openapi-java/src/main/docs/openapi-java.adoc
@@ -82,7 +82,7 @@ with `api.xxx` is configured using `apiProperty` dsl.
 |Option |Type |Description
 
 |cors |Boolean |Whether to enable CORS. Notice this only enables CORS for the api
-browser, and not the actual access to the REST services. Is default
+browser, and not the actual access to the REST services. The default is
 false.
 
 |openapi.version |String |OpenApi spec version. Is default 3.0.
@@ -120,6 +120,14 @@ So using relative paths is much easier. See above for an example.
 |api.license.name |String |The license name used for the API.
 
 |api.license.url |String |A URL to the license used for the API.
+
+|api.default.consumes |String |Comma separated list of default media types when `RestParamType.body` is used
+without providing any `.consumes()` configuration. The default value is `application/json`. Note that this applies only
+to the generated OpenAPI document and not to the actual REST services.
+
+|api.default.produces |String |Comma separated list of default media types when `outType` is used
+without providing any `.produces()` configuration. The default value is `application/json`. Note that this applies only
+to the generated OpenAPI document and not to the actual REST services
 |===
 
 == Adding Security Definitions in API doc
diff --git a/components/camel-openapi-java/src/main/java/org/apache/camel/openapi/BeanConfig.java b/components/camel-openapi-java/src/main/java/org/apache/camel/openapi/BeanConfig.java
index 071a71a3a56..cb90922a050 100644
--- a/components/camel-openapi-java/src/main/java/org/apache/camel/openapi/BeanConfig.java
+++ b/components/camel-openapi-java/src/main/java/org/apache/camel/openapi/BeanConfig.java
@@ -26,6 +26,8 @@ import io.apicurio.datamodels.openapi.v2.models.Oas20Document;
 import io.apicurio.datamodels.openapi.v3.models.Oas30Document;
 
 public class BeanConfig {
+    public static final String DEFAULT_MEDIA_TYPE = "application/json";
+
     String[] schemes;
     String title;
     String version;
@@ -35,6 +37,8 @@ public class BeanConfig {
     Info info;
     String host;
     String basePath;
+    String defaultConsumes = DEFAULT_MEDIA_TYPE;
+    String defaultProduces = DEFAULT_MEDIA_TYPE;
 
     public String[] getSchemes() {
         return schemes;
@@ -106,6 +110,22 @@ public class BeanConfig {
         }
     }
 
+    public String getDefaultConsumes() {
+        return defaultConsumes;
+    }
+
+    public void setDefaultConsumes(String defaultConsumes) {
+        this.defaultConsumes = defaultConsumes;
+    }
+
+    public String getDefaultProduces() {
+        return defaultProduces;
+    }
+
+    public void setDefaultProduces(String defaultProduces) {
+        this.defaultProduces = defaultProduces;
+    }
+
     public OasDocument configure(OasDocument openApi) {
         if (openApi instanceof Oas20Document) {
             configureOas20((Oas20Document) openApi);
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 319300f803c..61a59573b33 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
@@ -158,7 +158,7 @@ public class RestOpenApiReader {
         for (RestDefinition rest : rests) {
             Boolean disabled = CamelContextHelper.parseBoolean(camelContext, rest.getDisabled());
             if (disabled == null || !disabled) {
-                parse(camelContext, openApi, rest, camelContextId, classResolver);
+                parse(camelContext, openApi, rest, camelContextId, classResolver, config);
             }
         }
 
@@ -190,7 +190,7 @@ public class RestOpenApiReader {
 
     private void parse(
             CamelContext camelContext, OasDocument openApi, RestDefinition rest, String camelContextId,
-            ClassResolver classResolver)
+            ClassResolver classResolver, BeanConfig config)
             throws ClassNotFoundException {
 
         // only include enabled verbs
@@ -269,7 +269,7 @@ public class RestOpenApiReader {
             appendModels(clazz, openApi);
         }
 
-        doParseVerbs(camelContext, openApi, rest, camelContextId, verbs, pathAsTags);
+        doParseVerbs(camelContext, openApi, rest, camelContextId, verbs, pathAsTags, config);
 
         // setup root security node if necessary
         List<SecurityDefinition> securityRequirements = rest.getSecurityRequirements();
@@ -495,7 +495,7 @@ public class RestOpenApiReader {
 
     private void doParseVerbs(
             CamelContext camelContext, OasDocument openApi, RestDefinition rest, String camelContextId,
-            List<VerbDefinition> verbs, String[] pathAsTags) {
+            List<VerbDefinition> verbs, String[] pathAsTags, BeanConfig config) {
 
         String basePath = buildBasePath(camelContext, rest);
 
@@ -556,7 +556,15 @@ public class RestOpenApiReader {
             path = setPathOperation(path, op, method);
 
             String consumes = getValue(camelContext, verb.getConsumes() != null ? verb.getConsumes() : rest.getConsumes());
+            if (consumes == null) {
+                consumes = config.defaultConsumes;
+            }
+
             String produces = getValue(camelContext, verb.getProduces() != null ? verb.getProduces() : rest.getProduces());
+            if (produces == null) {
+                produces = config.defaultProduces;
+            }
+
             if (openApi instanceof Oas20Document) {
                 doParseVerbOas20(camelContext, (Oas20Document) openApi, verb, (Oas20Operation) op, consumes, produces);
             } else if (openApi instanceof Oas30Document) {
@@ -800,26 +808,11 @@ public class RestOpenApiReader {
     private void doParseVerbOas20(
             CamelContext camelContext, Oas20Document openApi, VerbDefinition verb, Oas20Operation op, String consumes,
             String produces) {
-        if (consumes != null) {
-            String[] parts = consumes.split(",");
-            if (op.consumes == null) {
-                op.consumes = new ArrayList<>();
-            }
-            op.consumes.addAll(Arrays.asList(parts));
-        }
 
         if ("true".equals(verb.getDeprecated())) {
             op.deprecated = Boolean.TRUE;
         }
 
-        if (produces != null) {
-            String[] parts = produces.split(",");
-            if (op.produces == null) {
-                op.produces = new ArrayList<>();
-            }
-            op.produces.addAll(Arrays.asList(parts));
-        }
-
         if (verb.getDescriptionText() != null) {
             op.summary = getValue(camelContext, verb.getDescriptionText());
         }
@@ -931,6 +924,14 @@ public class RestOpenApiReader {
 
                 // set schema on body parameter
                 if (parameter.in.equals("body")) {
+                    if (consumes != null) {
+                        String[] parts = consumes.split(",");
+                        if (op.consumes == null) {
+                            op.consumes = new ArrayList<>();
+                        }
+                        op.consumes.addAll(Arrays.asList(parts));
+                    }
+
                     Oas20Parameter bp = (Oas20Parameter) parameter;
                     String type = getValue(camelContext, param.getDataType() != null ? param.getDataType() : verb.getType());
                     if (type != null) {
@@ -982,6 +983,14 @@ public class RestOpenApiReader {
 
         // if we have an out type then set that as response message
         if (verb.getOutType() != null) {
+            if (produces != null) {
+                String[] parts = produces.split(",");
+                if (op.produces == null) {
+                    op.produces = new ArrayList<>();
+                }
+                op.produces.addAll(Arrays.asList(parts));
+            }
+
             if (op.responses == null) {
                 op.responses = op.createResponses();
             }
diff --git a/components/camel-openapi-java/src/main/java/org/apache/camel/openapi/RestOpenApiSupport.java b/components/camel-openapi-java/src/main/java/org/apache/camel/openapi/RestOpenApiSupport.java
index 3fae7fabc0f..446f95c46bc 100644
--- a/components/camel-openapi-java/src/main/java/org/apache/camel/openapi/RestOpenApiSupport.java
+++ b/components/camel-openapi-java/src/main/java/org/apache/camel/openapi/RestOpenApiSupport.java
@@ -270,6 +270,16 @@ public class RestOpenApiSupport {
             openApiConfig.setSchemes(new String[] { "http" });
         }
 
+        String defaultConsumes = (String) config.get("api.default.consumes");
+        if (defaultConsumes != null) {
+            openApiConfig.setDefaultConsumes(defaultConsumes);
+        }
+
+        String defaultProduces = (String) config.get("api.default.produces");
+        if (defaultProduces != null) {
+            openApiConfig.setDefaultProduces(defaultProduces);
+        }
+
         String version = (String) config.get("api.version");
         String title = (String) config.get("api.title");
         String description = (String) config.get("api.description");
diff --git a/components/camel-openapi-java/src/test/java/org/apache/camel/openapi/RestOpenApiDefaultProducesConsumesTest.java b/components/camel-openapi-java/src/test/java/org/apache/camel/openapi/RestOpenApiDefaultProducesConsumesTest.java
new file mode 100644
index 00000000000..e55ba45fe64
--- /dev/null
+++ b/components/camel-openapi-java/src/test/java/org/apache/camel/openapi/RestOpenApiDefaultProducesConsumesTest.java
@@ -0,0 +1,280 @@
+/*
+ * 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.util.List;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import org.apache.camel.CamelContext;
+import org.apache.camel.Exchange;
+import org.apache.camel.builder.RouteBuilder;
+import org.apache.camel.impl.DefaultCamelContext;
+import org.apache.camel.model.rest.RestParamType;
+import org.apache.camel.spi.RestConfiguration;
+import org.apache.camel.support.DefaultExchange;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.MethodSource;
+
+import static org.apache.camel.openapi.BeanConfig.DEFAULT_MEDIA_TYPE;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+public class RestOpenApiDefaultProducesConsumesTest {
+
+    private static final ObjectMapper MAPPER = new ObjectMapper();
+    private static final String[] OPENAPI_VERSIONS = { "3.0", "2.0" };
+
+    @ParameterizedTest
+    @MethodSource("getOpenApiVersions")
+    void testDefaultConsumesConfig(String openApiVersion) throws Exception {
+        CamelContext context = new DefaultCamelContext();
+        context.addRoutes(new RouteBuilder() {
+            @Override
+            public void configure() {
+                restConfiguration()
+                        .apiProperty("openapi.version", openApiVersion)
+                        .apiProperty("api.default.consumes", "application/xml");
+
+                rest("/api")
+                        .post("/test")
+                        .param()
+                        .name("body")
+                        .description("The request body")
+                        .type(RestParamType.body)
+                        .endParam()
+                        .to("direct:api");
+
+                from("direct:api").setBody().constant("Hello World");
+            }
+        });
+
+        RestConfiguration restConfiguration = context.getRestConfiguration();
+        RestOpenApiProcessor processor
+                = new RestOpenApiProcessor(restConfiguration.getApiProperties(), restConfiguration);
+        Exchange exchange = new DefaultExchange(context);
+        processor.process(exchange);
+
+        String json = exchange.getMessage().getBody(String.class);
+        assertNotNull(json);
+
+        JsonNode root = MAPPER.readTree(json);
+        assertConsumesMatches(root, openApiVersion, "application/xml");
+    }
+
+    @ParameterizedTest
+    @MethodSource("getOpenApiVersions")
+    void testDefaultProducesConfig(String openApiVersion) throws Exception {
+        CamelContext context = new DefaultCamelContext();
+        context.addRoutes(new RouteBuilder() {
+            @Override
+            public void configure() {
+                restConfiguration()
+                        .apiProperty("openapi.version", openApiVersion)
+                        .apiProperty("api.default.produces", "application/xml");
+
+                rest("/api")
+                        .post("/test")
+                        .outType(String.class)
+                        .to("direct:api");
+
+                from("direct:api").setBody().constant("Hello World");
+            }
+        });
+
+        RestConfiguration restConfiguration = context.getRestConfiguration();
+        RestOpenApiProcessor processor
+                = new RestOpenApiProcessor(restConfiguration.getApiProperties(), restConfiguration);
+        Exchange exchange = new DefaultExchange(context);
+        processor.process(exchange);
+
+        String json = exchange.getMessage().getBody(String.class);
+        assertNotNull(json);
+
+        JsonNode root = MAPPER.readTree(json);
+        assertProducesMatches(root, openApiVersion, "application/xml");
+    }
+
+    @ParameterizedTest
+    @MethodSource("getOpenApiVersions")
+    void testDefaultConfigMediaType(String openApiVersion) throws Exception {
+        CamelContext context = new DefaultCamelContext();
+        context.addRoutes(new RouteBuilder() {
+            @Override
+            public void configure() {
+                restConfiguration().apiProperty("openapi.version", openApiVersion);
+
+                rest("/api")
+                        .post("/test")
+                        .outType(String.class)
+                        .param()
+                        .name("body")
+                        .description("The request body")
+                        .type(RestParamType.body)
+                        .endParam()
+                        .to("direct:api");
+
+                from("direct:api").setBody().constant("Hello World");
+            }
+        });
+
+        RestConfiguration restConfiguration = context.getRestConfiguration();
+        RestOpenApiProcessor processor
+                = new RestOpenApiProcessor(restConfiguration.getApiProperties(), restConfiguration);
+        Exchange exchange = new DefaultExchange(context);
+        processor.process(exchange);
+
+        String json = exchange.getMessage().getBody(String.class);
+        assertNotNull(json);
+
+        JsonNode root = MAPPER.readTree(json);
+        if (openApiVersion.equals("3.0")) {
+            JsonNode requestBody = root.findValue("requestBody");
+            assertConsumesMatches(requestBody, openApiVersion, DEFAULT_MEDIA_TYPE);
+
+            JsonNode responses = root.findValue("responses");
+            assertProducesMatches(responses, openApiVersion, DEFAULT_MEDIA_TYPE);
+        } else {
+            assertConsumesMatches(root, openApiVersion, DEFAULT_MEDIA_TYPE);
+            assertProducesMatches(root, openApiVersion, DEFAULT_MEDIA_TYPE);
+        }
+    }
+
+    @ParameterizedTest
+    @MethodSource("getOpenApiVersions")
+    void testVerbConfigOverridesDefaultConfig(String openApiVersion) throws Exception {
+        CamelContext context = new DefaultCamelContext();
+        context.addRoutes(new RouteBuilder() {
+            @Override
+            public void configure() {
+                restConfiguration().apiProperty("openapi.version", openApiVersion);
+
+                rest("/api")
+                        .post("/test")
+                        .consumes("text/plain")
+                        .produces("application/xml")
+                        .outType(String.class)
+                        .param()
+                        .name("body")
+                        .description("The request body")
+                        .type(RestParamType.body)
+                        .endParam()
+                        .to("direct:api");
+
+                from("direct:api").setBody().constant("Hello World");
+            }
+        });
+
+        RestConfiguration restConfiguration = context.getRestConfiguration();
+        RestOpenApiProcessor processor
+                = new RestOpenApiProcessor(restConfiguration.getApiProperties(), restConfiguration);
+        Exchange exchange = new DefaultExchange(context);
+        processor.process(exchange);
+
+        String json = exchange.getMessage().getBody(String.class);
+        assertNotNull(json);
+
+        JsonNode root = MAPPER.readTree(json);
+        if (openApiVersion.equals("3.0")) {
+            JsonNode requestBody = root.findValue("requestBody");
+            assertConsumesMatches(requestBody, openApiVersion, "text/plain");
+
+            JsonNode responses = root.findValue("responses");
+            assertProducesMatches(responses, openApiVersion, "application/xml");
+        } else {
+            assertConsumesMatches(root, openApiVersion, "text/plain");
+            assertProducesMatches(root, openApiVersion, "application/xml");
+        }
+    }
+
+    @ParameterizedTest
+    @MethodSource("getOpenApiVersions")
+    void testGlobalConfigOverridesDefaultConfig(String openApiVersion) throws Exception {
+        CamelContext context = new DefaultCamelContext();
+        context.addRoutes(new RouteBuilder() {
+            @Override
+            public void configure() {
+                restConfiguration().apiProperty("openapi.version", openApiVersion);
+
+                rest("/api")
+                        .consumes("text/plain")
+                        .produces("application/xml")
+                        .post("/test")
+                        .outType(String.class)
+                        .param()
+                        .name("body")
+                        .description("The request body")
+                        .type(RestParamType.body)
+                        .endParam()
+                        .to("direct:api");
+
+                from("direct:api").setBody().constant("Hello World");
+            }
+        });
+
+        RestConfiguration restConfiguration = context.getRestConfiguration();
+        RestOpenApiProcessor processor
+                = new RestOpenApiProcessor(restConfiguration.getApiProperties(), restConfiguration);
+        Exchange exchange = new DefaultExchange(context);
+        processor.process(exchange);
+
+        String json = exchange.getMessage().getBody(String.class);
+        assertNotNull(json);
+
+        JsonNode root = MAPPER.readTree(json);
+        if (openApiVersion.equals("3.0")) {
+            JsonNode requestBody = root.findValue("requestBody");
+            assertConsumesMatches(requestBody, openApiVersion, "text/plain");
+
+            JsonNode responses = root.findValue("responses");
+            assertProducesMatches(responses, openApiVersion, "application/xml");
+        } else {
+            assertConsumesMatches(root, openApiVersion, "text/plain");
+            assertProducesMatches(root, openApiVersion, "application/xml");
+        }
+    }
+
+    static String[] getOpenApiVersions() {
+        return OPENAPI_VERSIONS;
+    }
+
+    private void assertProducesMatches(JsonNode json, String openApiVersion, String match) {
+        String fieldName = openApiVersion.equals("3.0") ? "content" : "produces";
+        assertContentTypeMatches(json, openApiVersion, fieldName, match);
+    }
+
+    private void assertConsumesMatches(JsonNode json, String openApiVersion, String match) {
+        String fieldName = openApiVersion.equals("3.0") ? "content" : "consumes";
+        assertContentTypeMatches(json, openApiVersion, fieldName, match);
+    }
+
+    private void assertContentTypeMatches(JsonNode json, String openApiVersion, String fieldName, String match) {
+        List<JsonNode> content = json.findValues(fieldName);
+        assertNotNull(content);
+        assertEquals(1, content.size());
+
+        JsonNode contentNode = content.get(0);
+        if (openApiVersion.equals("3.0")) {
+            assertTrue(contentNode.has(match));
+        } else {
+            assertEquals(1, contentNode.size());
+            JsonNode mediaType = contentNode.get(0);
+            assertEquals(match, mediaType.asText());
+        }
+    }
+}
diff --git a/components/camel-openapi-java/src/test/java/org/apache/camel/openapi/RestOpenApiReaderTest.java b/components/camel-openapi-java/src/test/java/org/apache/camel/openapi/RestOpenApiReaderTest.java
index f18c8cef161..e1e5c878274 100644
--- a/components/camel-openapi-java/src/test/java/org/apache/camel/openapi/RestOpenApiReaderTest.java
+++ b/components/camel-openapi-java/src/test/java/org/apache/camel/openapi/RestOpenApiReaderTest.java
@@ -47,40 +47,95 @@ public class RestOpenApiReaderTest extends CamelTestSupport {
         return new RouteBuilder() {
             @Override
             public void configure() {
-                rest("/hello").consumes("application/json").produces("application/json").get("/hi/{name}")
-                        .description("Saying hi").param().name("name").type(RestParamType.path)
-                        .dataType("string").description("Who is it").example("Donald Duck").endParam()
-                        .param().name("filter").description("Filters to apply to the entity.").type(RestParamType.query)
-                        .dataType("array").arrayType("date-time").endParam().to("log:hi")
-                        .get("/bye/{name}").description("Saying bye").param().name("name")
-                        .type(RestParamType.path).dataType("string").description("Who is it").example("Donald Duck").endParam()
-                        .responseMessage().code(200).message("A reply number")
-                        .responseModel(float.class).example("success", "123").example("error", "-1").endResponseMessage()
-                        .to("log:bye").post("/bye")
-                        .description("To update the greeting message").consumes("application/xml").produces("application/xml")
-                        .param().name("greeting").type(RestParamType.body)
-                        .dataType("string").description("Message to use as greeting")
-                        .example("application/xml", "<hello>Hi</hello>").endParam().to("log:bye");
+                rest("/hello")
+                        .consumes("application/json")
+                        .produces("application/json")
+
+                        .get("/hi/{name}")
+                        .description("Saying hi")
+                        .param()
+                        .name("name")
+                        .type(RestParamType.path)
+                        .dataType("string")
+                        .description("Who is it")
+                        .example("Donald Duck")
+                        .endParam()
+                        .param()
+                        .name("filter")
+                        .description("Filters to apply to the entity.")
+                        .type(RestParamType.query)
+                        .dataType("array")
+                        .arrayType("date-time")
+                        .endParam()
+                        .to("log:hi")
+
+                        .get("/bye/{name}")
+                        .description("Saying bye")
+                        .param()
+                        .name("name")
+                        .type(RestParamType.path)
+                        .dataType("string")
+                        .description("Who is it")
+                        .example("Donald Duck")
+                        .endParam()
+                        .responseMessage()
+                        .code(200)
+                        .message("A reply number")
+                        .responseModel(float.class)
+                        .example("success", "123")
+                        .example("error", "-1")
+                        .endResponseMessage()
+                        .to("log:bye")
+
+                        .post("/bye")
+                        .description("To update the greeting message")
+                        .consumes("application/xml")
+                        .produces("application/xml")
+                        .outType(String.class)
+                        .param()
+                        .name("greeting")
+                        .type(RestParamType.body)
+                        .dataType("string")
+                        .description("Message to use as greeting")
+                        .example("application/xml", "<hello>Hi</hello>")
+                        .endParam()
+                        .to("log:bye");
 
                 rest("/tag")
                         .get("single")
                         .tag("Organisation")
-                        .consumes("application/json")
-                        .produces("application/json")
+                        .outType(String.class)
+                        .param()
+                        .name("body")
+                        .type(RestParamType.body)
+                        .dataType("string")
+                        .description("Message body")
+                        .endParam()
                         .to("log:bye");
 
                 rest("/tag")
                         .get("multiple/a")
                         .tag("Organisation,Group A")
-                        .consumes("application/json")
-                        .produces("application/json")
+                        .outType(String.class)
+                        .param()
+                        .name("body")
+                        .type(RestParamType.body)
+                        .dataType("string")
+                        .description("Message body")
+                        .endParam()
+
                         .to("log:bye");
 
                 rest("/tag")
                         .get("multiple/b")
                         .tag("Organisation,Group B")
-                        .consumes("application/json")
-                        .produces("application/json")
+                        .outType(String.class)
+                        .param()
+                        .name("body")
+                        .type(RestParamType.body)
+                        .dataType("string")
+                        .description("Message body")
+                        .endParam()
                         .to("log:bye");
 
             }
@@ -177,9 +232,11 @@ public class RestOpenApiReaderTest extends CamelTestSupport {
         assertTrue(json.contains("\"format\" : \"date-time\""));
 
         assertTrue(flatJson.contains("\"/hello/bye/{name}\" : { \"get\" : { \"tags\" : [ \"/hello\" ],"));
-        assertTrue(flatJson.contains("\"/tag/single\" : { \"get\" : { \"tags\" : [ \"Organisation\" ],"));
-        assertTrue(flatJson.contains("\"/tag/multiple/a\" : { \"get\" : { \"tags\" : [ \"Organisation\", \"Group A\" ],"));
-        assertTrue(flatJson.contains("\"/tag/multiple/b\" : { \"get\" : { \"tags\" : [ \"Organisation\", \"Group B\" ],"));
+        assertTrue(flatJson.matches(".*\"/tag/single\" : \\{ \"get\" : .* \"tags\" : \\[ \"Organisation\" ],.*"));
+        assertTrue(
+                flatJson.matches(".*\"/tag/multiple/a\" : \\{ \"get\" : .* \"tags\" : \\[ \"Organisation\", \"Group A\" ],.*"));
+        assertTrue(
+                flatJson.matches(".*\"/tag/multiple/b\" : \\{ \"get\" : .*\"tags\" : \\[ \"Organisation\", \"Group B\" ],.*"));
         assertTrue(flatJson.contains(
                 "\"tags\" : [ { \"name\" : \"Group B\" }, { \"name\" : \"Organisation\" }, { \"name\" : \"Group A\" }, { \"name\" : \"/hello\" } ]"));