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 8eac2405fff CAMEL-20825: camel-rest - Contract first for api-doc 
should include the spec
8eac2405fff is described below

commit 8eac2405fff282c7b37a43fc6c53a91421083f7a
Author: Claus Ibsen <[email protected]>
AuthorDate: Sat Jun 1 20:03:49 2024 +0200

    CAMEL-20825: camel-rest - Contract first for api-doc should include the spec
---
 .../camel/catalog/models/restConfiguration.json    |  2 +-
 .../apache/camel/catalog/schemas/camel-spring.xsd  |  1 +
 components/camel-openapi-java/pom.xml              |  4 ++
 .../apache/camel/openapi/RestOpenApiReader.java    | 48 +++++++++++++++++++++-
 .../org/apache/camel/spi/RestConfiguration.java    |  3 +-
 .../apache/camel/model/rest/restConfiguration.json |  2 +-
 .../camel/model/rest/RestHostNameResolver.java     |  3 +-
 .../modules/ROOT/pages/rest-dsl-openapi.adoc       | 33 ++++++++++++++-
 .../dsl/yaml/deserializers/ModelDeserializers.java |  2 +-
 .../generated/resources/schema/camelYamlDsl.json   |  2 +-
 10 files changed, 91 insertions(+), 9 deletions(-)

diff --git 
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/models/restConfiguration.json
 
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/models/restConfiguration.json
index 357b2846746..740a4708e86 100644
--- 
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/models/restConfiguration.json
+++ 
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/models/restConfiguration.json
@@ -25,7 +25,7 @@
     "apiContextPath": { "index": 10, "kind": "attribute", "displayName": "Api 
Context Path", "group": "consumer", "label": "consumer", "required": false, 
"type": "string", "javaType": "java.lang.String", "deprecated": false, 
"autowired": false, "secret": false, "description": "Sets a leading 
context-path the REST API will be using. This can be used when using components 
such as camel-servlet where the deployed web application is deployed using a 
context-path." },
     "apiContextRouteId": { "index": 11, "kind": "attribute", "displayName": 
"Api Context Route Id", "group": "consumer (advanced)", "label": 
"consumer,advanced", "required": false, "type": "string", "javaType": 
"java.lang.String", "deprecated": false, "autowired": false, "secret": false, 
"description": "Sets the route id to use for the route that services the REST 
API. The route will by default use an auto assigned route id." },
     "apiVendorExtension": { "index": 12, "kind": "attribute", "displayName": 
"Api Vendor Extension", "group": "consumer (advanced)", "label": 
"consumer,advanced", "required": false, "type": "boolean", "javaType": 
"java.lang.Boolean", "deprecated": false, "autowired": false, "secret": false, 
"defaultValue": false, "description": "Whether vendor extension is enabled in 
the Rest APIs. If enabled then Camel will include additional information as 
vendor extension (eg keys starting with x-) su [...]
-    "hostNameResolver": { "index": 13, "kind": "attribute", "displayName": 
"Host Name Resolver", "group": "consumer (advanced)", "label": 
"consumer,advanced", "required": false, "type": "enum", "javaType": 
"org.apache.camel.model.rest.RestHostNameResolver", "enum": [ "allLocalIp", 
"localHostName", "localIp" ], "deprecated": false, "autowired": false, 
"secret": false, "defaultValue": "allLocalIp", "description": "If no hostname 
has been explicit configured, then this resolver is used to c [...]
+    "hostNameResolver": { "index": 13, "kind": "attribute", "displayName": 
"Host Name Resolver", "group": "consumer (advanced)", "label": 
"consumer,advanced", "required": false, "type": "enum", "javaType": 
"org.apache.camel.model.rest.RestHostNameResolver", "enum": [ "allLocalIp", 
"localHostName", "localIp", "none" ], "deprecated": false, "autowired": false, 
"secret": false, "defaultValue": "allLocalIp", "description": "If no hostname 
has been explicit configured, then this resolver is u [...]
     "bindingMode": { "index": 14, "kind": "attribute", "displayName": "Binding 
Mode", "group": "common", "required": false, "type": "enum", "javaType": 
"org.apache.camel.model.rest.RestBindingMode", "enum": [ "off", "auto", "json", 
"xml", "json_xml" ], "deprecated": false, "autowired": false, "secret": false, 
"defaultValue": "off", "description": "Sets the binding mode to use. The 
default value is off" },
     "bindingPackageScan": { "index": 15, "kind": "attribute", "displayName": 
"Binding Package Scan", "group": "consumer (advanced)", "label": 
"consumer,advanced", "required": false, "type": "string", "javaType": 
"java.lang.String", "deprecated": false, "autowired": false, "secret": false, 
"description": "Package name to use as base (offset) for classpath scanning of 
POJO classes are located when using binding mode is enabled for JSon or XML. 
Multiple package names can be separated by comma." },
     "skipBindingOnErrorCode": { "index": 16, "kind": "attribute", 
"displayName": "Skip Binding On Error Code", "group": "advanced", "label": 
"advanced", "required": false, "type": "boolean", "javaType": 
"java.lang.Boolean", "deprecated": false, "autowired": false, "secret": false, 
"defaultValue": false, "description": "Whether to skip binding on output if 
there is a custom HTTP error code header. This allows to build custom error 
messages that do not bind to json \/ xml etc, as success m [...]
diff --git 
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/schemas/camel-spring.xsd
 
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/schemas/camel-spring.xsd
index 61209fbf610..e92a71aeca2 100644
--- 
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/schemas/camel-spring.xsd
+++ 
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/schemas/camel-spring.xsd
@@ -18201,6 +18201,7 @@ An optional certificate alias to use. This is useful 
when the keystore has multi
       <xs:enumeration value="allLocalIp"/>
       <xs:enumeration value="localIp"/>
       <xs:enumeration value="localHostName"/>
+      <xs:enumeration value="none"/>
     </xs:restriction>
   </xs:simpleType>
   <xs:simpleType name="restBindingMode">
diff --git a/components/camel-openapi-java/pom.xml 
b/components/camel-openapi-java/pom.xml
index 98fb640c7e5..dc34c7720c9 100644
--- a/components/camel-openapi-java/pom.xml
+++ b/components/camel-openapi-java/pom.xml
@@ -45,6 +45,10 @@
             <groupId>org.apache.camel</groupId>
             <artifactId>camel-core-engine</artifactId>
         </dependency>
+        <dependency>
+            <groupId>org.apache.camel</groupId>
+            <artifactId>camel-platform-http</artifactId>
+        </dependency>
         <dependency>
             <groupId>org.apache.camel</groupId>
             <artifactId>camel-xml-io</artifactId>
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 7140fcf4cb5..81887163462 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
@@ -21,6 +21,7 @@ import java.io.InputStream;
 import java.lang.invoke.MethodHandle;
 import java.lang.invoke.MethodType;
 import java.lang.reflect.Method;
+import java.net.UnknownHostException;
 import java.util.*;
 import java.util.function.Function;
 import java.util.stream.Collectors;
@@ -61,10 +62,13 @@ import io.swagger.v3.oas.models.security.OAuthFlows;
 import io.swagger.v3.oas.models.security.Scopes;
 import io.swagger.v3.oas.models.security.SecurityRequirement;
 import io.swagger.v3.oas.models.security.SecurityScheme;
+import io.swagger.v3.oas.models.servers.Server;
 import io.swagger.v3.oas.models.tags.Tag;
 import io.swagger.v3.parser.OpenAPIV3Parser;
 import io.swagger.v3.parser.core.models.SwaggerParseResult;
 import org.apache.camel.CamelContext;
+import org.apache.camel.component.platform.http.PlatformHttpComponent;
+import org.apache.camel.component.platform.http.spi.PlatformHttpEngine;
 import org.apache.camel.model.rest.ApiKeyDefinition;
 import org.apache.camel.model.rest.BasicAuthDefinition;
 import org.apache.camel.model.rest.BearerTokenDefinition;
@@ -76,6 +80,7 @@ import org.apache.camel.model.rest.ParamDefinition;
 import org.apache.camel.model.rest.ResponseHeaderDefinition;
 import org.apache.camel.model.rest.ResponseMessageDefinition;
 import org.apache.camel.model.rest.RestDefinition;
+import org.apache.camel.model.rest.RestHostNameResolver;
 import org.apache.camel.model.rest.RestPropertyDefinition;
 import org.apache.camel.model.rest.RestSecuritiesDefinition;
 import org.apache.camel.model.rest.RestSecurityDefinition;
@@ -84,10 +89,13 @@ import org.apache.camel.model.rest.VerbDefinition;
 import org.apache.camel.spi.ClassResolver;
 import org.apache.camel.spi.NodeIdFactory;
 import org.apache.camel.spi.Resource;
+import org.apache.camel.spi.RestConfiguration;
 import org.apache.camel.support.CamelContextHelper;
 import org.apache.camel.support.ObjectHelper;
 import org.apache.camel.support.PluginHelper;
+import org.apache.camel.support.RestComponentHelper;
 import org.apache.camel.util.FileUtil;
+import org.apache.camel.util.HostUtils;
 import org.apache.camel.util.IOHelper;
 import org.apache.commons.lang3.ClassUtils;
 import org.slf4j.Logger;
@@ -135,11 +143,12 @@ public class RestOpenApiReader {
      * @param  classResolver          class resolver to use @return the 
openApi model
      * @throws ClassNotFoundException is thrown if error loading class
      * @throws IOException            is thrown if error loading openapi 
specification
+     * @throws UnknownHostException   is thrown if error resolving local 
hostname
      */
     public OpenAPI read(
             CamelContext camelContext, List<RestDefinition> rests, BeanConfig 
config,
             String camelContextId, ClassResolver classResolver)
-            throws ClassNotFoundException, IOException {
+            throws ClassNotFoundException, IOException, UnknownHostException {
 
         // contract first, then load the specification as-is and use as 
response
         for (RestDefinition rest : rests) {
@@ -151,7 +160,42 @@ public class RestOpenApiReader {
                     IOHelper.close(is);
                     OpenAPIV3Parser parser = new OpenAPIV3Parser();
                     SwaggerParseResult out = parser.readContents(data);
-                    return out.getOpenAPI();
+                    OpenAPI answer = out.getOpenAPI();
+
+                    String host = null;
+                    RestConfiguration restConfig = 
camelContext.getRestConfiguration();
+                    if (restConfig.getHostNameResolver() != 
RestConfiguration.RestHostNameResolver.none) {
+                        host = 
camelContext.getRestConfiguration().getApiHost();
+                        if (host == null || host.isEmpty()) {
+                            String scheme = "http://";;
+                            host = 
RestComponentHelper.resolveRestHostName(host, restConfig);
+                            PlatformHttpComponent http = 
camelContext.getComponent("platform-http", PlatformHttpComponent.class);
+                            if (http != null) {
+                                int port = http.getEngine().getServerPort();
+                                if (port > 0) {
+                                    host = host + ":" + port;
+                                    if (port == 443) {
+                                        scheme = "https://";;
+                                    }
+                                }
+                            }
+                            host = scheme + host;
+                        }
+                    }
+                    if (host != null) {
+                        String basePath = 
RestOpenApiSupport.getBasePathFromOasDocument(answer);
+                        if (basePath == null) {
+                            basePath = "/";
+                        }
+                        if (!basePath.startsWith("/")) {
+                            basePath = "/" + basePath;
+                        }
+                        Server server = new Server();
+                        server.setUrl(host + basePath);
+                        answer.setServers(null);
+                        answer.addServersItem(server);
+                    }
+                    return answer;
                 }
             }
         }
diff --git 
a/core/camel-api/src/main/java/org/apache/camel/spi/RestConfiguration.java 
b/core/camel-api/src/main/java/org/apache/camel/spi/RestConfiguration.java
index dc46c6987b3..0ecd481b5a9 100644
--- a/core/camel-api/src/main/java/org/apache/camel/spi/RestConfiguration.java
+++ b/core/camel-api/src/main/java/org/apache/camel/spi/RestConfiguration.java
@@ -45,7 +45,8 @@ public class RestConfiguration {
     public enum RestHostNameResolver {
         allLocalIp,
         localIp,
-        localHostName
+        localHostName,
+        none;
     }
 
     private String component;
diff --git 
a/core/camel-core-model/src/generated/resources/META-INF/org/apache/camel/model/rest/restConfiguration.json
 
b/core/camel-core-model/src/generated/resources/META-INF/org/apache/camel/model/rest/restConfiguration.json
index 357b2846746..740a4708e86 100644
--- 
a/core/camel-core-model/src/generated/resources/META-INF/org/apache/camel/model/rest/restConfiguration.json
+++ 
b/core/camel-core-model/src/generated/resources/META-INF/org/apache/camel/model/rest/restConfiguration.json
@@ -25,7 +25,7 @@
     "apiContextPath": { "index": 10, "kind": "attribute", "displayName": "Api 
Context Path", "group": "consumer", "label": "consumer", "required": false, 
"type": "string", "javaType": "java.lang.String", "deprecated": false, 
"autowired": false, "secret": false, "description": "Sets a leading 
context-path the REST API will be using. This can be used when using components 
such as camel-servlet where the deployed web application is deployed using a 
context-path." },
     "apiContextRouteId": { "index": 11, "kind": "attribute", "displayName": 
"Api Context Route Id", "group": "consumer (advanced)", "label": 
"consumer,advanced", "required": false, "type": "string", "javaType": 
"java.lang.String", "deprecated": false, "autowired": false, "secret": false, 
"description": "Sets the route id to use for the route that services the REST 
API. The route will by default use an auto assigned route id." },
     "apiVendorExtension": { "index": 12, "kind": "attribute", "displayName": 
"Api Vendor Extension", "group": "consumer (advanced)", "label": 
"consumer,advanced", "required": false, "type": "boolean", "javaType": 
"java.lang.Boolean", "deprecated": false, "autowired": false, "secret": false, 
"defaultValue": false, "description": "Whether vendor extension is enabled in 
the Rest APIs. If enabled then Camel will include additional information as 
vendor extension (eg keys starting with x-) su [...]
-    "hostNameResolver": { "index": 13, "kind": "attribute", "displayName": 
"Host Name Resolver", "group": "consumer (advanced)", "label": 
"consumer,advanced", "required": false, "type": "enum", "javaType": 
"org.apache.camel.model.rest.RestHostNameResolver", "enum": [ "allLocalIp", 
"localHostName", "localIp" ], "deprecated": false, "autowired": false, 
"secret": false, "defaultValue": "allLocalIp", "description": "If no hostname 
has been explicit configured, then this resolver is used to c [...]
+    "hostNameResolver": { "index": 13, "kind": "attribute", "displayName": 
"Host Name Resolver", "group": "consumer (advanced)", "label": 
"consumer,advanced", "required": false, "type": "enum", "javaType": 
"org.apache.camel.model.rest.RestHostNameResolver", "enum": [ "allLocalIp", 
"localHostName", "localIp", "none" ], "deprecated": false, "autowired": false, 
"secret": false, "defaultValue": "allLocalIp", "description": "If no hostname 
has been explicit configured, then this resolver is u [...]
     "bindingMode": { "index": 14, "kind": "attribute", "displayName": "Binding 
Mode", "group": "common", "required": false, "type": "enum", "javaType": 
"org.apache.camel.model.rest.RestBindingMode", "enum": [ "off", "auto", "json", 
"xml", "json_xml" ], "deprecated": false, "autowired": false, "secret": false, 
"defaultValue": "off", "description": "Sets the binding mode to use. The 
default value is off" },
     "bindingPackageScan": { "index": 15, "kind": "attribute", "displayName": 
"Binding Package Scan", "group": "consumer (advanced)", "label": 
"consumer,advanced", "required": false, "type": "string", "javaType": 
"java.lang.String", "deprecated": false, "autowired": false, "secret": false, 
"description": "Package name to use as base (offset) for classpath scanning of 
POJO classes are located when using binding mode is enabled for JSon or XML. 
Multiple package names can be separated by comma." },
     "skipBindingOnErrorCode": { "index": 16, "kind": "attribute", 
"displayName": "Skip Binding On Error Code", "group": "advanced", "label": 
"advanced", "required": false, "type": "boolean", "javaType": 
"java.lang.Boolean", "deprecated": false, "autowired": false, "secret": false, 
"defaultValue": false, "description": "Whether to skip binding on output if 
there is a custom HTTP error code header. This allows to build custom error 
messages that do not bind to json \/ xml etc, as success m [...]
diff --git 
a/core/camel-core-model/src/main/java/org/apache/camel/model/rest/RestHostNameResolver.java
 
b/core/camel-core-model/src/main/java/org/apache/camel/model/rest/RestHostNameResolver.java
index 5e6e9249f21..f9f0c08a4ea 100644
--- 
a/core/camel-core-model/src/main/java/org/apache/camel/model/rest/RestHostNameResolver.java
+++ 
b/core/camel-core-model/src/main/java/org/apache/camel/model/rest/RestHostNameResolver.java
@@ -31,6 +31,7 @@ public enum RestHostNameResolver {
 
     allLocalIp,
     localIp,
-    localHostName
+    localHostName,
+    none
 
 }
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 62ae96d5cfa..af23014f4b5 100644
--- a/docs/user-manual/modules/ROOT/pages/rest-dsl-openapi.adoc
+++ b/docs/user-manual/modules/ROOT/pages/rest-dsl-openapi.adoc
@@ -289,11 +289,42 @@ Here Camel will detect the `schema` part:
 }
 ----
 
-And compute the class name as `Pet` and attempt to disover this class from 
classpath scanning specified via the `bindingPackageScan` option.
+And compute the class name as `Pet` and attempt to discover this class from 
classpath scanning specified via the `bindingPackageScan` option.
 
 You can source code generate Java POJO classes from an OpenAPI specification 
via tooling such as the `swagger-codegen-maven-plugin` Maven plugin.
 For more details see this 
https://github.com/apache/camel-spring-boot-examples/tree/main/openapi-contract-first[Spring
 Boot example].
 
+=== Expose API specification
+
+The OpenAPI specification is by default not exposed on the HTTP endpoint. You 
can make this happen by setting the rest-configuration as follows:
+
+[source,yaml]
+----
+- restConfiguration:
+    apiContextPath: /api-doc
+----
+
+Then the specification is accessible on `/api-doc` on the embedded HTTP 
server, so typically that would be `http://localhost:8080/api-doc`.
+
+In the returned API specification the `server` section has been modified to 
return the IP of the current server. This can be controlled via:
+
+
+[source,yaml]
+----
+- restConfiguration:
+    apiContextPath: /api-doc
+    hostNameResolver: localIp
+----
+
+And you can turn this off by setting the value to `none` so the server part is 
taken verbatim from the specification file.
+
+[source,yaml]
+----
+- restConfiguration:
+    apiContextPath: /api-doc
+    hostNameResolver: none
+----
+
 == Examples
 
 You can find a few examples such as:
diff --git 
a/dsl/camel-yaml-dsl/camel-yaml-dsl-deserializers/src/generated/java/org/apache/camel/dsl/yaml/deserializers/ModelDeserializers.java
 
b/dsl/camel-yaml-dsl/camel-yaml-dsl-deserializers/src/generated/java/org/apache/camel/dsl/yaml/deserializers/ModelDeserializers.java
index 2478efad9cb..e3157f6b076 100644
--- 
a/dsl/camel-yaml-dsl/camel-yaml-dsl-deserializers/src/generated/java/org/apache/camel/dsl/yaml/deserializers/ModelDeserializers.java
+++ 
b/dsl/camel-yaml-dsl/camel-yaml-dsl-deserializers/src/generated/java/org/apache/camel/dsl/yaml/deserializers/ModelDeserializers.java
@@ -13913,7 +13913,7 @@ public final class ModelDeserializers extends 
YamlDeserializerSupport {
                     @YamlProperty(name = "enableNoContentResponse", type = 
"boolean", description = "Whether to return HTTP 204 with an empty body when a 
response contains an empty JSON object or XML root object. The default value is 
false.", displayName = "Enable No Content Response"),
                     @YamlProperty(name = "endpointProperty", type = 
"array:org.apache.camel.model.rest.RestPropertyDefinition", description = 
"Allows to configure as many additional properties for the rest endpoint in 
use.", displayName = "Endpoint Property"),
                     @YamlProperty(name = "host", type = "string", description 
= "The hostname to use for exposing the REST service.", displayName = "Host"),
-                    @YamlProperty(name = "hostNameResolver", type = 
"enum:allLocalIp,localHostName,localIp", defaultValue = "allLocalIp", 
description = "If no hostname has been explicit configured, then this resolver 
is used to compute the hostname the REST service will be using.", displayName = 
"Host Name Resolver"),
+                    @YamlProperty(name = "hostNameResolver", type = 
"enum:allLocalIp,localHostName,localIp,none", defaultValue = "allLocalIp", 
description = "If no hostname has been explicit configured, then this resolver 
is used to compute the hostname the REST service will be using.", displayName = 
"Host Name Resolver"),
                     @YamlProperty(name = "inlineRoutes", type = "boolean", 
description = "Inline routes in rest-dsl which are linked using direct 
endpoints. Each service in Rest DSL is an individual route, meaning that you 
would have at least two routes per service (rest-dsl, and the route linked from 
rest-dsl). By inlining (default) allows Camel to optimize and inline this as a 
single route, however this requires to use direct endpoints, which must be 
unique per service. If a route is n [...]
                     @YamlProperty(name = "jsonDataFormat", type = "string", 
description = "Name of specific json data format to use. By default jackson 
will be used. Important: This option is only for setting a custom name of the 
data format, not to refer to an existing data format instance.", displayName = 
"Json Data Format"),
                     @YamlProperty(name = "port", type = "string", description 
= "The port number to use for exposing the REST service. Notice if you use 
servlet component then the port number configured here does not apply, as the 
port number in use is the actual port number the servlet component is using. eg 
if using Apache Tomcat its the tomcat http port, if using Apache Karaf its the 
HTTP service in Karaf that uses port 8181 by default etc. Though in those 
situations setting the port  [...]
diff --git 
a/dsl/camel-yaml-dsl/camel-yaml-dsl/src/generated/resources/schema/camelYamlDsl.json
 
b/dsl/camel-yaml-dsl/camel-yaml-dsl/src/generated/resources/schema/camelYamlDsl.json
index 749086b3a97..b5f23143d54 100644
--- 
a/dsl/camel-yaml-dsl/camel-yaml-dsl/src/generated/resources/schema/camelYamlDsl.json
+++ 
b/dsl/camel-yaml-dsl/camel-yaml-dsl/src/generated/resources/schema/camelYamlDsl.json
@@ -15393,7 +15393,7 @@
             "title" : "Host Name Resolver",
             "description" : "If no hostname has been explicit configured, then 
this resolver is used to compute the hostname the REST service will be using.",
             "default" : "allLocalIp",
-            "enum" : [ "allLocalIp", "localHostName", "localIp" ]
+            "enum" : [ "allLocalIp", "localHostName", "localIp", "none" ]
           },
           "inlineRoutes" : {
             "type" : "boolean",

Reply via email to