Vert.x OpenAPI

Preview

Vert.x OpenAPI extends Vert.x to support OpenAPI 3 in version 3.0 and 3.1.

Vert.x OpenAPI can:

  • parse and validate your OpenAPI contract.

  • parse and validate incoming requests according to your OpenAPI contract.

  • parse and validate outgoing responses according to your OpenAPI contract.

Using Vert.x OpenAPI

To use Vert.x OpenAPI, add the following dependency to the dependencies section of your build descriptor:

  • Maven (in your pom.xml):

<dependency>
  <groupId>io.vertx</groupId>
  <artifactId>vertx-openapi</artifactId>
  <version>5.1.0</version>
</dependency>
  • Gradle (in your build.gradle file):

dependencies {
  compile 'io.vertx:vertx-openapi:5.1.0'
}

OpenAPIContract

When using Vert.x OpenAPI you always start with creating an instance of OpenAPIContract from your contract.

String pathToContract = "../../myContract.json"; // json or yaml
Future<OpenAPIContract> contract = OpenAPIContract.from(vertx, pathToContract);
Due to security reasons this library is not downloading external references from your contract. In case your contract requires external resources, they must be downloaded upfront and also provided to the OpenAPIContract.

The example below shows a snippet from an example OpenAPI contract that includes a reference to an external resource and how to create an instance of OpenAPIContract.

paths:
  /pets:
    get:
      operationId: listPets
      parameters:
        - name: query
          schema:
            $ref: 'https://example.com/pet-components#/components/schemas/Query'
String pathToContract = "../../myContract.json"; // json or yaml
String pathToComponents = "../../myComponents.json"; // json or yaml
Map<String, String> additionalContractFiles = new HashMap<>();
additionalContractFiles.put("https://example.com/pet-components",
  pathToComponents);

Future<OpenAPIContract> contract =
  OpenAPIContract.from(vertx, pathToContract, additionalContractFiles);
During the instantiation of OpenAPIContract the contract gets validated. In case your contract does not match the OpenAPI specification or uses features which are not yet supported an error is thrown.

Path, Operation, Parameter

The OpenAPIContract interface offers methods to navigate to the Path, Operation and Parameter objects of the OpenAPI contract.

OpenAPIContract contract = getContract();

for (Path path : contract.getPaths()) {
  for (Parameter pathParameter : path.getParameters()) {
    // example methods of a OpenAPI parameter object
    pathParameter.isRequired();
    pathParameter.getSchema();
  }
  for (Operation operation : path.getOperations()) {
    // example methods of a OpenAPI operation object
    operation.getOperationId();
    operation.getRequestBody();
    operation.getParameters();
  }
}

Extensions

Vert.x OpenAPI supports the extension x-vertx-openapi-urldecode to specify if a cookie or header parameter should be URL decoded. The default is false. Query parameters and path parameters gets always URL decoded.

paths:
  /forceEncoding:
    get:
      operationId: forceEncoding
      parameters:
        - in: header
          name: headerWithEncodedContent
          x-vertx-openapi-urldecode: true
          schema:
            type: string

Validation

Vert.x OpenAPI checks both whether the content is syntactically correct and whether it corresponds to the schema. If no schema is defined, or the content is binary no schema validation is performed. By Default, the following media types are supported:

  • application/json

  • application/json+hal

  • application/octet-stream

  • multipart/form-data

  • Vendor specific json that matches the following regular expression /vnd\.[\w.-]\+json

Unknown media types are rejected and the contract will load with an exception.

You can add additional media types when you construct the contract via the OpenAPIContractBuilder. You need to provide a MediaTypeRegistry, either by starting from the default one MediaTypeRegistry.createDefault or an empty one MediaTypeRegistry.createEmpty. On the registry you can register new media types via a MediaTypeRegistration that consist of a MediaTypePredicate that checks whether the registration can handle a given media type and a ContentAnalyserFactory that creates a new ContentAnalyser for each validation.

Vert.x OpenAPI contains ready to use predicates for static media type strings (../../apidocs/io/vertx/openapi/mediatype/MediaTypePredicate.html#ofExactTypes-java.lang.String-[MediaTypePredicate.ofExactTypes]) and regular expressions (MediaTypePredicate.ofRegexp). If those do not match your need, you must provide your own predicate implementations.

Vert.x OpenAPI contains ready to use ContentAnalysers that perform schema validation for json bodies (ContentAnalyserFactory.json), multipart bodies (ContentAnalyserFactory.multipart). Additionally a noop ContentAnalyser is available that does not perform any validation (ContentAnalyserFactory.noop). If those do not fit your needs, you need to provide your own implementation.

Example:

String pathToContract = "../../myContract.json"; // json or yaml
String pathToComponents = "../../myComponents.json"; // json or yaml

Future<OpenAPIContract> contract =
  OpenAPIContract.builder(vertx)
    .setContractPath(pathToContract)
    .setAdditionalContractPartPaths(Map.of(
      "https://example.com/pet-components", pathToComponents))
    .mediaTypeRegistry(
      MediaTypeRegistry.createDefault()
        .register(
          new DefaultMediaTypeRegistration(
            MediaTypePredicate.ofExactTypes("text/my-custom-type+json"),
            ContentAnalyserFactory.json()))
    )
    .build();

Validation of Requests

The RequestValidator offers multiple validate methods to validate incoming requests.

OpenAPIContract contract = getContract();
RequestValidator validator = RequestValidator.create(vertx, contract);

vertx.createHttpServer().requestHandler(httpServerRequest -> {
  // Operation id must be determined for every request which is inefficient
  validator.validate(httpServerRequest).onSuccess(validatedRequest -> {
    validatedRequest.getBody(); // returns the body
    validatedRequest.getHeaders(); // returns the header
    // ..
    // ..
  });

  // Operation id will be passed to save effort for determining
  validator.validate(httpServerRequest, "yourOperationId")
    .onSuccess(validatedRequest -> {
      // do something
    });
}).listen(0);

The RequestValidator also offers a signature of the validate method that consumes a ValidatableRequest.

OpenAPIContract contract = getContract();
RequestValidator validator = RequestValidator.create(vertx, contract);

ValidatableRequest request = getValidatableRequest();
validator.validate(request, "yourOperationId").onSuccess(validatedRequest -> {
  validatedRequest.getBody(); // returns the body
  validatedRequest.getHeaders(); // returns the header
  // ..
  // ..
});
The parameters in a ValidatableRequest must be stored in a specific format depending on the style, location and if they are exploded or not, otherwise the RequestValidator can’t validate the request. The required format MUST exactly look like as described in the JavaDoc of RequestValidator.

Validation of Responses

The ResponseValidator offers a validate method to validate responses. ValidatableResponse offers multiple create methods to build validatable responses easily.

In case that the validation of a response has passed, the returned ValidatedResponse can directly be sent back to the client.

OpenAPIContract contract = getContract();
ResponseValidator validator = ResponseValidator.create(vertx, contract);

JsonObject cat = new JsonObject().put("name", "foo");
ValidatableResponse response =
  ValidatableResponse.create(200, cat.toBuffer(), APPLICATION_JSON.toString());

vertx.createHttpServer().requestHandler(httpServerRequest -> {
  validator.validate(response, "yourOperationId")
    .onSuccess(validatedResponse -> {
      validatedResponse.getBody(); // returns the body
      validatedResponse.getHeaders(); // returns the header
      // ..
      // ..
      // send back the validated response
      validatedResponse.send(httpServerRequest.response());
    });
});
The parameters in a ValidatableResponse must be stored in a specific format depending on the style, location and if they are exploded or not, otherwise the ResponseValidator can’t validate the response. The required format MUST exactly look like as described in the JavaDoc of ResponseValidator.

Handle Validation Exceptions

A ValidatorException is thrown, if the validation of a request or response fails. The validation can fail for formal reasons, such as the wrong format for a parameter or the absence of a required parameter. However, validation can of course also fail because the content does not match the defined schema. In this case a SchemaValidationException is thrown. It is a subclass of ValidatorException and provides access to the related OutputUnit to allow further analysis of the error.