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",