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 fcacbcbeb7f CAMEL-20652: camel-rest - Contract First - Make it 
possible to build … (#14007)
fcacbcbeb7f is described below

commit fcacbcbeb7fedc9b0e45f96bed58749f0e2d134a
Author: Claus Ibsen <claus.ib...@gmail.com>
AuthorDate: Wed May 1 11:52:41 2024 +0200

    CAMEL-20652: camel-rest - Contract First - Make it possible to build … 
(#14007)
    
    * CAMEL-20652: camel-rest - Contract First - Make it possible to build 
response from example in the openapi spec file
    
    * CAMEL-20652: camel-rest - Contract First - Make it possible to build 
response from example in the openapi spec file
---
 .../DefaultRestOpenapiProcessorStrategy.java       | 82 ++++++++++++++++++++--
 .../rest/openapi/RestOpenApiProcessor.java         |  2 +-
 .../rest/openapi/RestOpenapiProcessorStrategy.java |  3 +-
 .../modules/ROOT/pages/rest-dsl-openapi.adoc       | 48 ++++++++++++-
 4 files changed, 125 insertions(+), 10 deletions(-)

diff --git 
a/components/camel-rest-openapi/src/main/java/org/apache/camel/component/rest/openapi/DefaultRestOpenapiProcessorStrategy.java
 
b/components/camel-rest-openapi/src/main/java/org/apache/camel/component/rest/openapi/DefaultRestOpenapiProcessorStrategy.java
index 360bc123b45..e0bff0f9672 100644
--- 
a/components/camel-rest-openapi/src/main/java/org/apache/camel/component/rest/openapi/DefaultRestOpenapiProcessorStrategy.java
+++ 
b/components/camel-rest-openapi/src/main/java/org/apache/camel/component/rest/openapi/DefaultRestOpenapiProcessorStrategy.java
@@ -25,7 +25,10 @@ import java.util.stream.Collectors;
 
 import io.swagger.v3.oas.models.OpenAPI;
 import io.swagger.v3.oas.models.Operation;
+import io.swagger.v3.oas.models.examples.Example;
 import io.swagger.v3.oas.models.media.Content;
+import io.swagger.v3.oas.models.media.MediaType;
+import io.swagger.v3.oas.models.responses.ApiResponse;
 import org.apache.camel.AsyncCallback;
 import org.apache.camel.AsyncProducer;
 import org.apache.camel.CamelContext;
@@ -48,6 +51,7 @@ import org.apache.camel.support.service.ServiceHelper;
 import org.apache.camel.support.service.ServiceSupport;
 import org.apache.camel.util.FileUtil;
 import org.apache.camel.util.IOHelper;
+import org.apache.camel.util.StringHelper;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -59,6 +63,8 @@ public class DefaultRestOpenapiProcessorStrategy extends 
ServiceSupport
 
     private static final Logger LOG = 
LoggerFactory.getLogger(DefaultRestOpenapiProcessorStrategy.class);
 
+    private static final String BODY_VERBS = "DELETE,PUT,POST,PATCH";
+
     private CamelContext camelContext;
     private ProducerCache producerCache;
     private String component = "direct";
@@ -164,7 +170,7 @@ public class DefaultRestOpenapiProcessorStrategy extends 
ServiceSupport
 
     @Override
     public boolean process(
-            Operation operation, String path,
+            Operation operation, String verb, String path,
             RestBindingAdvice binding,
             Exchange exchange, AsyncCallback callback) {
 
@@ -174,7 +180,7 @@ public class DefaultRestOpenapiProcessorStrategy extends 
ServiceSupport
             if (e == null) {
                 if ("mock".equalsIgnoreCase(missingOperation)) {
                     // no route then try to load mock data as the answer
-                    loadMockData(operation, path, exchange);
+                    loadMockData(operation, verb, path, exchange);
                 }
                 callback.done(true);
                 return true;
@@ -205,7 +211,7 @@ public class DefaultRestOpenapiProcessorStrategy extends 
ServiceSupport
         });
     }
 
-    private void loadMockData(Operation operation, String path, Exchange 
exchange) {
+    private void loadMockData(Operation operation, String verb, String path, 
Exchange exchange) {
         final PackageScanResourceResolver resolver = 
PluginHelper.getPackageScanResourceResolver(camelContext);
         final String[] includes = mockIncludePattern != null ? 
mockIncludePattern.split(",") : null;
 
@@ -256,9 +262,73 @@ public class DefaultRestOpenapiProcessorStrategy extends 
ServiceSupport
                 // ignore
             }
         } else {
-            // no mock data, so return an empty response
-            exchange.getMessage().setHeader(Exchange.HTTP_RESPONSE_CODE, 204);
-            exchange.getMessage().setBody("");
+            // no mock data, so return data as-is for PUT,POST,DELETE,PATCH
+            if (BODY_VERBS.contains(verb)) {
+                // return input data as-is
+                exchange.getMessage().setHeader(Exchange.HTTP_RESPONSE_CODE, 
200);
+            } else {
+                // no mock data (such as for GET)
+                // then try to see if there is an example in the openapi spec 
response we can use,
+                // otherwise use an empty body
+                Object body = "";
+
+                String contentType = ExchangeHelper.getContentType(exchange);
+                String accept = exchange.getMessage().getHeader("Accept", 
String.class);
+                if (operation.getResponses() != null) {
+                    ApiResponse a = operation.getResponses().get("200");
+                    Content c = a.getContent();
+                    if (c != null && !c.isEmpty()) {
+                        // prefer media-type that is the same as the incoming 
content-type
+                        // if none found, then find first matching 
content-type from the HTTP Accept header
+                        MediaType mt = contentType != null ? 
c.get(contentType) : null;
+                        if (mt == null && accept != null) {
+                            // find best match accept
+                            for (String acc : accept.split(",")) {
+                                acc = StringHelper.before(acc, ";", acc);
+                                acc = acc.trim();
+                                mt = c.get(acc);
+                                if (mt != null) {
+                                    // update content-type
+                                    contentType = acc;
+                                    break;
+                                }
+                            }
+                            // fallback to grab json or xml if we accept 
anything
+                            if (mt == null && "*/*".equals(accept)) {
+                                mt = c.get("application/json");
+                                if (mt != null) {
+                                    contentType = "application/json";
+                                }
+                            }
+                            // fallback to grab json or xml if we accept 
anything
+                            if (mt == null && "*/*".equals(accept)) {
+                                mt = c.get("application/xml");
+                                if (mt != null) {
+                                    contentType = "application/xml";
+                                }
+                            }
+                        }
+                        if (mt != null) {
+                            if (mt.getExample() != null) {
+                                body = mt.getExample();
+                            } else if (mt.getExamples() != null) {
+                                // grab first example
+                                Example ex = 
mt.getExamples().values().iterator().next();
+                                body = ex.getValue();
+                            }
+                        }
+                    }
+                }
+                boolean empty = body == null || body.toString().isBlank();
+                if (empty) {
+                    
exchange.getMessage().setHeader(Exchange.HTTP_RESPONSE_CODE, 204);
+                    exchange.getMessage().setBody("");
+                } else {
+                    
exchange.getMessage().setHeader(Exchange.HTTP_RESPONSE_CODE, 200);
+                    exchange.getMessage().setHeader(Exchange.CONTENT_TYPE, 
contentType);
+                    exchange.getMessage().setBody(body);
+                }
+            }
         }
     }
 
diff --git 
a/components/camel-rest-openapi/src/main/java/org/apache/camel/component/rest/openapi/RestOpenApiProcessor.java
 
b/components/camel-rest-openapi/src/main/java/org/apache/camel/component/rest/openapi/RestOpenApiProcessor.java
index 8b556c0beb0..d3c5c1de632 100644
--- 
a/components/camel-rest-openapi/src/main/java/org/apache/camel/component/rest/openapi/RestOpenApiProcessor.java
+++ 
b/components/camel-rest-openapi/src/main/java/org/apache/camel/component/rest/openapi/RestOpenApiProcessor.java
@@ -140,7 +140,7 @@ public class RestOpenApiProcessor extends 
DelegateAsyncProcessor implements Came
             }
 
             // process the incoming request
-            return restOpenapiProcessorStrategy.process(o, uri, 
rcp.getBinding(), exchange, callback);
+            return restOpenapiProcessorStrategy.process(o, verb, uri, 
rcp.getBinding(), exchange, callback);
         }
 
         // is it the api-context path
diff --git 
a/components/camel-rest-openapi/src/main/java/org/apache/camel/component/rest/openapi/RestOpenapiProcessorStrategy.java
 
b/components/camel-rest-openapi/src/main/java/org/apache/camel/component/rest/openapi/RestOpenapiProcessorStrategy.java
index bff1e714e42..52c71a7c671 100644
--- 
a/components/camel-rest-openapi/src/main/java/org/apache/camel/component/rest/openapi/RestOpenapiProcessorStrategy.java
+++ 
b/components/camel-rest-openapi/src/main/java/org/apache/camel/component/rest/openapi/RestOpenapiProcessorStrategy.java
@@ -67,6 +67,7 @@ public interface RestOpenapiProcessorStrategy {
      * Strategy for processing the Rest DSL operation
      *
      * @param  operation the rest operation
+     * @param  verb      the HTTP verb (GET, POST etc.)
      * @param  path      the context-path
      * @param  binding   binding advice
      * @param  exchange  the exchange
@@ -77,7 +78,7 @@ public interface RestOpenapiProcessorStrategy {
      *                   asynchronously
      */
     boolean process(
-            Operation operation, String path,
+            Operation operation, String verb, String path,
             RestBindingAdvice binding,
             Exchange exchange, AsyncCallback callback);
 
diff --git a/docs/user-manual/modules/ROOT/pages/rest-dsl-openapi.adoc 
b/docs/user-manual/modules/ROOT/pages/rest-dsl-openapi.adoc
index 88bfad8408e..62ae96d5cfa 100644
--- a/docs/user-manual/modules/ROOT/pages/rest-dsl-openapi.adoc
+++ b/docs/user-manual/modules/ROOT/pages/rest-dsl-openapi.adoc
@@ -149,8 +149,14 @@ This is similar to ignoring missing API operations, as you 
can tell Camel to moc
     rest().openApi("petstore-v3.json").missingOperation("mock");
 ----
 
-When using _mock_ then Camel will (for missing operations) simulate a 
successful response, by attempting to load
-canned responses from file system. This allows you to have a set of files that 
you can use for development and testing purposes.
+When using _mock_ then Camel will (for missing operations) simulate a 
successful response:
+
+1. attempting to load canned responses from file system.
+2. for GET verbs then attempt to use example inlined in the OpenAPI `response` 
section.
+3. for other verbs (DELETE, PUT, POST, PATCH) then return the input body as 
response.
+4. if none of above then return empty body.
+
+This allows you to have a set of files that you can use for development and 
testing purposes.
 
 The files should be stored in `camel-mock` when using Camel JBang, and 
`src/main/resources/camel-mock` for Maven/Gradle based projects.
 
@@ -187,6 +193,44 @@ $ curl http://0.0.0.0:8080/api/v3/pet/123
 }
 ----
 
+If no file is found, then Camel will attempt to find an example from the 
_response_ section in the OpenAPI specification.
+
+In the response section below, then for success GET response (200) then for 
the `application/json` content-type, we have
+an inlined example. Note if there are multiple examples for the same 
content-type, then Camel will pick the first example,
+so make sure it's the best example you want to let Camel use as mocked 
response body.
+
+[source,json]
+----
+"responses": {
+    "200": {
+        "description": "successful operation",
+        "content": {
+            "application/xml": {
+                "schema": {
+                    "$ref": "#/components/schemas/Pet"
+                }
+            },
+            "application/json": {
+                "schema": {
+                    "$ref": "#/components/schemas/Pet"
+                },
+                "examples": {
+                    "success": {
+                        "summary": "A cat",
+                        "value": "{\"pet\": \"Jack the cat\"}"
+                    }
+                }
+            }
+        }
+    },
+    "400": {
+        "description": "Invalid ID supplied"
+    },
+    "404": {
+        "description": "Pet not found"
+    }
+----
+
 === Binding to POJO classes
 
 _contract first_ Rest DSL with OpenAPI also support binding mode to JSon and 
XML.

Reply via email to