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.

Limitations

Camel does not support websockets from the OpenAPI 3.1 specification. Neither is (at this time of writing) any security aspects from the OpenAPI specification in use.

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 contracted 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 APIs 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, by attempting to load canned responses from file system. 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"
}

Binding to POJO classes

contract first Rest DSL with OpenAPI also support 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 automatic 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 disover 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.