You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@camel.apache.org by da...@apache.org on 2024/03/28 14:40:29 UTC
(camel) 15/38: CAMEL-20557: Rest DSL to use openapi spec directly
This is an automated email from the ASF dual-hosted git repository.
davsclaus pushed a commit to branch openapi2
in repository https://gitbox.apache.org/repos/asf/camel.git
commit c104cc1762c3ba0446bc562660082c6fa88e276b
Author: Claus Ibsen <cl...@gmail.com>
AuthorDate: Mon Mar 25 10:59:32 2024 +0100
CAMEL-20557: Rest DSL to use openapi spec directly
---
.../vertx/PlatformHttpRestOpenApiConsumerTest.java | 28 +++++++++++
.../rest/openapi/RestOpenApiComponent.java | 6 +--
.../rest/openapi/RestOpenApiEndpoint.java | 2 +-
.../rest/openapi/RestOpenApiProcessor.java | 55 +++++++++++++++++-----
.../validator/RequestValidationCustomizer.java | 1 +
.../rest/openapi/validator/RequestValidator.java | 31 ++++++------
6 files changed, 90 insertions(+), 33 deletions(-)
diff --git a/components/camel-platform-http-vertx/src/test/java/org/apache/camel/component/platform/http/vertx/PlatformHttpRestOpenApiConsumerTest.java b/components/camel-platform-http-vertx/src/test/java/org/apache/camel/component/platform/http/vertx/PlatformHttpRestOpenApiConsumerTest.java
index 6c9865ecb59..1ea0a976913 100644
--- a/components/camel-platform-http-vertx/src/test/java/org/apache/camel/component/platform/http/vertx/PlatformHttpRestOpenApiConsumerTest.java
+++ b/components/camel-platform-http-vertx/src/test/java/org/apache/camel/component/platform/http/vertx/PlatformHttpRestOpenApiConsumerTest.java
@@ -110,4 +110,32 @@ public class PlatformHttpRestOpenApiConsumerTest {
}
}
+ @Test
+ public void testRestOpenApiValidate() throws Exception {
+ final CamelContext context = VertxPlatformHttpEngineTest.createCamelContext();
+
+ try {
+ context.addRoutes(new RouteBuilder() {
+ @Override
+ public void configure() {
+ from("rest-openapi:classpath:openapi-v3.json?requestValidationEnabled=true")
+ .log("dummy");
+
+ from("direct:updatePet")
+ .setBody().constant("{\"pet\": \"tony the tiger\"}");
+ }
+ });
+
+ context.start();
+
+ given()
+ .when()
+ .put("/api/v3/pet")
+ .then()
+ .statusCode(405); // no request body
+ } finally {
+ context.stop();
+ }
+ }
+
}
diff --git a/components/camel-rest-openapi/src/main/java/org/apache/camel/component/rest/openapi/RestOpenApiComponent.java b/components/camel-rest-openapi/src/main/java/org/apache/camel/component/rest/openapi/RestOpenApiComponent.java
index b405b1466c5..98ea9e91fe4 100644
--- a/components/camel-rest-openapi/src/main/java/org/apache/camel/component/rest/openapi/RestOpenApiComponent.java
+++ b/components/camel-rest-openapi/src/main/java/org/apache/camel/component/rest/openapi/RestOpenApiComponent.java
@@ -128,8 +128,7 @@ public final class RestOpenApiComponent extends DefaultComponent implements SSLC
+ " configuration.",
defaultValue = DEFAULT_SPECIFICATION_URI_STR, label = "producer")
private URI specificationUri;
- @Metadata(description = "Enable validation of requests against the configured OpenAPI specification",
- defaultValue = "false")
+ @Metadata(description = "Enable validation of requests against the configured OpenAPI specification")
private boolean requestValidationEnabled;
@Metadata(description = "If request validation is enabled, this option provides the capability to customize"
+ " the creation of OpenApiInteractionValidator used to validate requests.",
@@ -252,8 +251,7 @@ public final class RestOpenApiComponent extends DefaultComponent implements SSLC
return this.requestValidationEnabled;
}
- public void setRequestValidationCustomizer(
- RequestValidationCustomizer requestValidationCustomizer) {
+ public void setRequestValidationCustomizer(RequestValidationCustomizer requestValidationCustomizer) {
this.requestValidationCustomizer = requestValidationCustomizer;
}
diff --git a/components/camel-rest-openapi/src/main/java/org/apache/camel/component/rest/openapi/RestOpenApiEndpoint.java b/components/camel-rest-openapi/src/main/java/org/apache/camel/component/rest/openapi/RestOpenApiEndpoint.java
index 034a7e3e56b..2c55358dacf 100644
--- a/components/camel-rest-openapi/src/main/java/org/apache/camel/component/rest/openapi/RestOpenApiEndpoint.java
+++ b/components/camel-rest-openapi/src/main/java/org/apache/camel/component/rest/openapi/RestOpenApiEndpoint.java
@@ -206,7 +206,7 @@ public final class RestOpenApiEndpoint extends DefaultEndpoint {
public Consumer createConsumer(final Processor processor) throws Exception {
OpenAPI doc = loadSpecificationFrom(getCamelContext(), specificationUri);
String path = determineBasePath(doc);
- Processor target = new RestOpenApiProcessor(doc, path, processor);
+ Processor target = new RestOpenApiProcessor(this, doc, path, processor);
CamelContextAware.trySetCamelContext(target, getCamelContext());
return createConsumerFor(path, target);
}
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 5e3291d82c9..b1000f26ccc 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
@@ -18,10 +18,16 @@ package org.apache.camel.component.rest.openapi;
import java.util.ArrayList;
import java.util.Arrays;
+import java.util.HashMap;
import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.function.Function;
+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.parameters.Parameter;
import org.apache.camel.AsyncCallback;
import org.apache.camel.CamelContext;
import org.apache.camel.CamelContextAware;
@@ -38,14 +44,15 @@ public class RestOpenApiProcessor extends DelegateAsyncProcessor implements Came
= Arrays.asList("GET", "HEAD", "POST", "PUT", "DELETE", "TRACE", "OPTIONS", "CONNECT", "PATCH");
private CamelContext camelContext;
+ private final RestOpenApiEndpoint endpoint;
private final OpenAPI openAPI;
private final String basePath;
private final List<RestConsumerContextPathMatcher.ConsumerPath<Operation>> paths = new ArrayList<>();
private RestOpenapiProcessorStrategy restOpenapiProcessorStrategy;
- private RequestValidator requestValidator;
- public RestOpenApiProcessor(OpenAPI openAPI, String basePath, Processor processor) {
+ public RestOpenApiProcessor(RestOpenApiEndpoint endpoint, OpenAPI openAPI, String basePath, Processor processor) {
super(processor);
+ this.endpoint = endpoint;
this.basePath = basePath;
this.openAPI = openAPI;
this.restOpenapiProcessorStrategy = new DefaultRestOpenapiProcessorStrategy();
@@ -69,17 +76,8 @@ public class RestOpenApiProcessor extends DelegateAsyncProcessor implements Came
this.restOpenapiProcessorStrategy = restOpenapiProcessorStrategy;
}
- public RequestValidator getRequestValidator() {
- return requestValidator;
- }
-
- public void setRequestValidator(RequestValidator requestValidator) {
- this.requestValidator = requestValidator;
- }
-
@Override
public boolean process(Exchange exchange, AsyncCallback callback) {
- // TODO: RequestValidator
// TODO: binding
String path = exchange.getMessage().getHeader(Exchange.HTTP_PATH, String.class);
@@ -91,8 +89,39 @@ public class RestOpenApiProcessor extends DelegateAsyncProcessor implements Came
RestConsumerContextPathMatcher.ConsumerPath<Operation> m
= RestConsumerContextPathMatcher.matchBestPath(verb, path, paths);
if (m != null) {
- Operation o = m.getConsumer();
- return restOpenapiProcessorStrategy.process(o, path, exchange, callback);
+ Operation operation = m.getConsumer();
+
+ // we have found the operation to call, but if validation is enabled then we need
+ // to validate the incoming request first
+ if (endpoint.isRequestValidationEnabled()) {
+ Map<String, Parameter> pathParameters;
+ if (operation.getParameters() != null) {
+ pathParameters = operation.getParameters().stream()
+ .filter(p -> "path".equals(p.getIn()))
+ .collect(Collectors.toMap(Parameter::getName, Function.identity()));
+ } else {
+ pathParameters = new HashMap<>();
+ }
+ try {
+ final String uriTemplate = endpoint.resolveUri(path, pathParameters);
+ RequestValidator validator = endpoint.configureRequestValidator(openAPI, operation, verb, uriTemplate);
+ Set<String> errors = validator.validate(exchange);
+ if (!errors.isEmpty()) {
+ RestOpenApiValidationException exception = new RestOpenApiValidationException(errors);
+ exchange.setException(exception);
+ // validation error should be 405
+ exchange.getMessage().setHeader(Exchange.HTTP_RESPONSE_CODE, 405);
+ callback.done(true);
+ return true;
+ }
+ } catch (Exception e) {
+ exchange.setException(e);
+ callback.done(true);
+ return true;
+ }
+ }
+
+ return restOpenapiProcessorStrategy.process(operation, path, exchange, callback);
}
// okay we cannot process this requires so return either 404 or 405.
diff --git a/components/camel-rest-openapi/src/main/java/org/apache/camel/component/rest/openapi/validator/RequestValidationCustomizer.java b/components/camel-rest-openapi/src/main/java/org/apache/camel/component/rest/openapi/validator/RequestValidationCustomizer.java
index 5ac2c99d286..42a5913bab6 100644
--- a/components/camel-rest-openapi/src/main/java/org/apache/camel/component/rest/openapi/validator/RequestValidationCustomizer.java
+++ b/components/camel-rest-openapi/src/main/java/org/apache/camel/component/rest/openapi/validator/RequestValidationCustomizer.java
@@ -24,6 +24,7 @@ import org.apache.camel.Exchange;
* An abstraction for customizing the behavior of OpenApi request validation.
*/
public interface RequestValidationCustomizer {
+
/**
* Customizes the creation of a {@link OpenApiInteractionValidator}. The default implementation enables validation
* of only the request body.
diff --git a/components/camel-rest-openapi/src/main/java/org/apache/camel/component/rest/openapi/validator/RequestValidator.java b/components/camel-rest-openapi/src/main/java/org/apache/camel/component/rest/openapi/validator/RequestValidator.java
index 7636f668527..56724f71d37 100644
--- a/components/camel-rest-openapi/src/main/java/org/apache/camel/component/rest/openapi/validator/RequestValidator.java
+++ b/components/camel-rest-openapi/src/main/java/org/apache/camel/component/rest/openapi/validator/RequestValidator.java
@@ -39,18 +39,19 @@ import org.apache.camel.util.ObjectHelper;
import org.apache.camel.util.UnsafeUriCharactersEncoder;
public class RequestValidator {
+
private static final Pattern REST_PATH_PARAM_PATTERN = Pattern.compile("\\{([^}]+)}");
- private final OpenApiInteractionValidator openApiInteractionValidator;
- private final RestOpenApiOperation restOpenApiOperation;
- private final RequestValidationCustomizer requestValidationCustomizer;
+ private final OpenApiInteractionValidator validator;
+ private final RestOpenApiOperation operation;
+ private final RequestValidationCustomizer customizer;
- public RequestValidator(OpenApiInteractionValidator openApiInteractionValidator,
- RestOpenApiOperation restOpenApiOperation,
- RequestValidationCustomizer requestValidationCustomizer) {
- this.openApiInteractionValidator = openApiInteractionValidator;
- this.restOpenApiOperation = restOpenApiOperation;
- this.requestValidationCustomizer = requestValidationCustomizer;
+ public RequestValidator(OpenApiInteractionValidator validator,
+ RestOpenApiOperation operation,
+ RequestValidationCustomizer customizer) {
+ this.validator = validator;
+ this.operation = operation;
+ this.customizer = customizer;
}
public Set<String> validate(Exchange exchange) {
@@ -63,7 +64,7 @@ public class RequestValidator {
}
SimpleRequest.Builder builder
- = new SimpleRequest.Builder(restOpenApiOperation.getMethod(), resolvePathParams(exchange));
+ = new SimpleRequest.Builder(operation.getMethod(), resolvePathParams(exchange));
builder.withContentType(contentType);
// Validate request body if available
@@ -81,7 +82,7 @@ public class RequestValidator {
}
// Validate required operation query params
- restOpenApiOperation.getQueryParams()
+ operation.getQueryParams()
.stream()
.filter(parameter -> Objects.nonNull(parameter.getRequired()) && parameter.getRequired())
.forEach(parameter -> {
@@ -96,7 +97,7 @@ public class RequestValidator {
});
// Validate operation required headers
- restOpenApiOperation.getHeaders()
+ operation.getHeaders()
.stream()
.filter(parameter -> Objects.nonNull(parameter.getRequired()) && parameter.getRequired())
.forEach(parameter -> {
@@ -111,11 +112,11 @@ public class RequestValidator {
});
// Apply any extra customizations to the validation request
- requestValidationCustomizer.customizeSimpleRequestBuilder(builder, restOpenApiOperation, exchange);
+ customizer.customizeSimpleRequestBuilder(builder, operation, exchange);
// Perform validation and capture errors
Set<String> validationErrors = new LinkedHashSet<>();
- openApiInteractionValidator.validateRequest(builder.build())
+ validator.validateRequest(builder.build())
.getMessages()
.stream()
.filter(validationMessage -> validationMessage.getLevel().equals(ValidationReport.Level.ERROR))
@@ -129,7 +130,7 @@ public class RequestValidator {
}
protected String resolvePathParams(Exchange exchange) {
- String path = restOpenApiOperation.getUriTemplate();
+ String path = operation.getUriTemplate();
Matcher matcher = REST_PATH_PARAM_PATTERN.matcher(path);
String pathToProcess = path;
while (matcher.find()) {