REST DSL with contract-first OpenAPI
From Camel 4.6 onwards, the Rest DSL has been improved with a contract-first approach using vanilla OpenAPI specification.
How it works
The Rest DSL OpenAPI is a facade that builds Rest OpenAPI endpoint as consumer for Camel routes. The actual HTTP transport is leveraged by using the Platform HTTP, which makes it plugin to Camel Spring Boot, Camel Quarkus or can run standalone with Camel Main.
Contract first
The contract-first approach requires you to have an existing OpenAPI v3 specification file. This contract is a standard OpenAPI contract, and you can use any existing API design tool to build such contracts.
Camel support OpenAPI v3.0 and v3.1. |
In Camel, you then use the Rest DSL in contract-first mode. For example, having a contract in a file named my-contract.json
, you can then copy this file to src/main/resources
so it’s loaded from classpath.
In Camel Rest DSL you can then very easily define contract-first as shown below:
-
Java
-
XML
-
YAML
@Override
public void configure() throws Exception {
rest().openApi("petstore-v3.json");
}
<rest>
<openApi specification="petstore-v3.json"/>
</rest>
- rest:
openApi:
specification: petstore-v3.json
When Camel startup the OpenAPI specification file is loaded and parsed. For every API Camel builds HTTP REST endpoint, which are routed 1:1 to Camel routes using the direct:operationId
naming convention.
The pestore has 18 APIs here we look at the 5 user APIs:
http://0.0.0.0:8080/api/v3/user (POST) (accept:application/json,application/x-www-form-urlencoded,application/xml produce:application/json,application/xml)
http://0.0.0.0:8080/api/v3/user/createWithList (POST) (accept:application/json produce:application/json,application/xml)
http://0.0.0.0:8080/api/v3/user/login (GET) (produce:application/json,application/xml)
http://0.0.0.0:8080/api/v3/user/logout (GET)
http://0.0.0.0:8080/api/v3/user/{username} (DELETE,GET,PUT)
These APIs are outputted using the URI that clients can use to call the service. Each of these APIs has a unique operation id which is what Camel uses for calling the route. This gives:
http://0.0.0.0:8080/api/v3/user direct:createUser
http://0.0.0.0:8080/api/v3/user/createWithList direct:createUsersWithListInput
http://0.0.0.0:8080/api/v3/user/login direct:loginUser
http://0.0.0.0:8080/api/v3/user/logout direct:logoutUser
http://0.0.0.0:8080/api/v3/user/{username} direct:getUserByName
You should then implement a route for each API that starts from those direct endpoints listed above, such as:
-
Java
-
XML
-
YAML
@Override
public void configure() throws Exception {
rest().openApi("petstore-v3.json");
from("direct:getUserByName")
... // do something here
}
<rest>
<openApi specification="petstore-v3.json"/>
</rest>
<route>
<from uri="direct:getUserByName"/>
// do something here
</route>
- rest:
openApi:
specification: petstore-v3.json
- route:
from:
uri: direct:getUserByName
steps:
- log:
message: "do something here"
Ignoring missing API operations
When using OpenAPI with contract-first, then Camel will on startup check if there is a corresponding direct:operationId
route for every API service. If some operations are missing, then Camel will fail on startup with an error.
During development, you can use missingOperation
to ignore this as shown:
rest().openApi("petstore-v3.json").missingOperation("ignore");
This allows you to implement the APIs one by one over time.
Mocking API operations
This is similar to ignoring missing API operations, as you can tell Camel to mock instead, as shown:
rest().openApi("petstore-v3.json").missingOperation("mock");
When using mock, then Camel will (for missing operations) simulate a successful response:
-
attempting to load canned responses from the file system.
-
for GET verbs then attempt to use example inlined in the OpenAPI
response
section. -
for other verbs (DELETE, PUT, POST, PATCH) then return the input body as response.
-
if none of the 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.
For example, the following Camel JBang example is structured as:
README.md
camel-mock/pet/123.json
petstore-v3.json
petstore.camel.yaml
And the Camel route:
- restConfiguration:
clientRequestValidation: true
- rest:
openApi:
missingOperation: mock
specification: petstore-v3.json
When running this example, you can call the APIs and have an empty successful response. However, for the url pet/123
the file camel-mock/pet/123.json
will be loaded as the response as shown below:
$ curl http://0.0.0.0:8080/api/v3/pet/123
{
"pet": "donald the dock"
}
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.
"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 supports binding mode to JSON and XML. This works the same as code first Rest DSL.
However, we have added the bindingPackageScan
configuration to make it possible for Camel to automatically discover POJO classes from classpath.
When using Spring Boot or Quarkus, then you must configure the package names (base) such as follows:
// turn on json binding and scan for POJO classes in the model package
restConfiguration().bindingMode(RestBindingMode.json)
.bindingPackageScan("sample.petstore.model");
You can also configure this in application.properties
:
camel.rest.bindingMode = json
camel.rest.bindingPackageScan = sample.petstore.model
Then Camel will automatically for every OpenAPI operation detect the specified schemas for incoming and outgoing responses, and map that to Java POJO classes by class name.
For example, the getPetById
operation in the OpenAPI contract:
"responses": {
"200": {
"description": "successful operation",
"content": {
"application/xml": {
"schema": {
"$ref": "#/components/schemas/Pet"
}
},
"application/json": {
"schema": {
"$ref": "#/components/schemas/Pet"
}
}
}
},
Here Camel will detect the schema
part:
"schema": {
"$ref": "#/components/schemas/Pet"
}
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 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:
- 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:
- 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.
- restConfiguration:
apiContextPath: /api-doc
hostNameResolver: none