I'm creating a simple Rest API using Apache Camel 4, and I'm having some issues after adding camel-openapi-validator dependency. My idea is create the Rest API using contract first, so, I created an OpenAPI 3 file describing my operations (just part of file is here):
{
"openapi": "3.0.1",
"info": {
"title": "Representante API",
"description": "API for managing users in the Representante application",
"version": "1.0.0"
},
"paths": {
"/api/register": {
"post": {
"summary": "Create a new user",
"description": "Creates a new user with the provided information",
"operationId": "createUser",
"requestBody": {
"description": "User to be created",
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/UserCreate"
}
}
}
},
"responses": {
"201": {
"description": "User created successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/User"
}
}
}
},
"400": {
"description": "Invalid input"
},
"409": {
"description": "User already exists"
}
}
}
},
"components": {
"schemas": {
"UserCreate": {
"type": "object",
"required": [
"phoneNumber",
"password"
],
"properties": {
"phoneNumber": {
"type": "string",
"description": "User phone number",
"minLength": 10,
"maxLength": 20,
"pattern": "^[+0-9]*$"
},
"password": {
"type": "string",
"description": "User password",
"minLength": 8,
"maxLength": 60,
"format": "password"
},
"firstName": {
"type": "string",
"description": "User first name",
"maxLength": 50
},
"lastName": {
"type": "string",
"description": "User last name",
"maxLength": 50
},
"email": {
"type": "string",
"description": "User email address",
"format": "email",
"minLength": 5,
"maxLength": 100,
"pattern": "^[a-zA-Z0-9_!#$%&'*+/=?`{|}~^.-]+@[a-zA-Z0-9.-]+$"
},
"langKey": {
"type": "string",
"description": "User language preference",
"minLength": 2,
"maxLength": 5
},
"imageUrl": {
"type": "string",
"description": "URL to user profile image",
"maxLength": 256
}
}
}
}
}
I configured the routes directly in java:
@Component
@RequiredArgsConstructor
public class CamelRouteConfiguration extends RouteBuilder {
private final Logger log = LoggerFactory.getLogger(CamelRouteConfiguration.class);
private final JwtAuthenticationFilter jwtAuthenticationFilter;
@Override
public void configure() {
// Configure REST DSL
restConfiguration()
.contextPath("/")
.bindingMode(RestBindingMode.json)
.dataFormatProperty("prettyPrint", "true")
.enableCORS(true);
getContext().setTracing(true);
getContext().getGlobalOptions().put("CamelJacksonEnableTypeConverter", "true");
getContext().getGlobalOptions().put("CamelJacksonTypeConverterToPojo", "true");
// Load OpenAPI specification
rest().openApi().specification("representante-openapi.json").missingOperation("ignore");
}
}
Finally I declared the routes in a routes.xml file:
?xml version="1.0" encoding="UTF-8"?>
<routes xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://camel.apache.org/schema/spring"
xsi:schemaLocation="http://camel.apache.org/schema/spring http://camel.apache.org/schema/spring/camel-spring.xsd">
<route>
<from uri="direct:createUser"/>
<log message="Processing create user request"/>
<bean ref="createUserProcessor"/>
</route>
</routes>
Everything works fine, until I activate client-request-validation. After this, all requests returns the same problem:
{
"messages": [
{
"key": "validation.request.path.missing",
"level": "ERROR",
"message": "No API path found that matches request ''.",
"context": {
"requestMethod": "POST"
}
}
]
}
I did some debbugging and found this happens because CamelHttpPath header is empty. Am I missing something?
When you use rest().openApi()
in Camel, it expects your REST routes to be created via the Camel REST DSL or directly from HTTP endpoints like rest:post:/api/register
and for the client-request-validation to work, Camel needs the CamelHttpPath header populated when a request comes in via an HTTP component (like jetty, undertow, or servlet).
But in your case, you've declared the REST API contract with rest().openApi()
but you didn't create matching rest routes in code. Instead, you wired your processor routes via direct:
routes in your routes.xml
, which are internal Camel endpoints and don't carry the CamelHttpPath
header from an HTTP request.
That's why when validation happen, it doesn't see a path, and throws below error.
{
"key": "validation.request.path.missing",
"message": "No API path found that matches request ''"
}
You got two options to solve.
Instead of routing from direct:createUser
only, declare your HTTP POST binding via the REST DSL like this:
rest("/api")
.post("/register")
.id("createUserRoute")
.to("direct:createUser");
So your configure()
would have:
@Override
public void configure() {
restConfiguration()
.contextPath("/")
.bindingMode(RestBindingMode.json)
.dataFormatProperty("prettyPrint", "true")
.enableCORS(true);
getContext().setTracing(true);
getContext().getGlobalOptions().put("CamelJacksonEnableTypeConverter", "true");
getContext().getGlobalOptions().put("CamelJacksonTypeConverterToPojo", "true");
rest().openApi().specification("representante-openapi.json").missingOperation("ignore");
rest("/api")
.post("/register")
.id("createUserRoute")
.to("direct:createUser");
}
Now Camel will correctly map the incoming HTTP request to the OpenAPI operation, populate the CamelHttpPath header, and your client-request-validation
will work.
If for some reason you want to continue routing directly from a servlet endpoint to direct:createUser
, you could add a pre-processor to set the header before validation.
from("servlet:/api/register?httpMethodRestrict=POST")
.setHeader(Exchange.HTTP_PATH, constant("/api/register"))
.to("direct:createUser");
OR
from("servlet:/api/register?httpMethodRestrict=POST")
.process(exchange -> {
exchange.getIn().setHeader(Exchange.HTTP_PATH, "/api/register");
})
.to("direct:createUser");
Note : This is kind of a workaround. Better use Option 1