OpenAPI (fka Swagger) 3 support in Eclipse Vert.x now in test stage!

Now on upstream!

We have pub­lished this pack­age with name vertx-web-api-contract

As GSoC 2017’s stu­dent, I’m ac­tu­ally work­ing on an em­bed­ded sup­port to Ope­nAPI 3 stan­dard in­side Eclipse Vert.x frame­work. Now, after a lot of work, you can try it!

Why OpenAPI 3?

Ope­nAPI 2 is the most im­por­tant industry-​grade stan­dard for API Spec­i­fi­ca­tions. As you can see on of­fi­cial blog of Ope­nAPI Ini­tia­tive, the re­lease of ver­sion 3 is be­hind the cor­ner, so we want to give to our com­mu­nity the lat­est tools for the lat­est stan­dards!

Vert.x project ob­jec­tive is to give you more in­te­grated tools. With this new sup­port, it gives you the abil­ity to use the De­sign Dri­ven (or De­sign First) ap­proach with­out load­ing any thirds par­ties li­braries.

Features

The ac­tu­ally sup­ported fea­tures are the fol­low­ing (we reefer to Ope­nAPI ver­sion 3.0.0):

  • Ope­nAPI 3 com­pli­ant API spec­i­fi­ca­tion val­i­da­tion with load­ing of ex­ter­nal Json schemas
  • Au­to­matic re­quest val­i­da­tion
  • Au­to­matic mount of se­cu­rity val­i­da­tion han­dlers
  • Au­to­matic 501 re­sponse for not im­ple­mented op­er­a­tions
  • Router fac­tory to pro­vide all this fea­tures to users

Au­to­matic re­quest val­i­da­tion is pro­vided by a new han­dler: ValidationHandler. You can also de­fine your own ValidationHandler with­out API spec­i­fi­ca­tions, but I will dis­cuss it later.

The re­quest val­i­da­tion (pro­vided by sub­class OpenAPI3RequestValidationHandler) ac­tu­ally sup­ports:

  • Pa­ra­me­ters de­fined in Pa­ra­me­ter ob­ject. We sup­port every type of pa­ra­me­ter, in­clud­ing object and array. We also sup­port every type de­scrip­tion field (for ex­am­ple format, minimum, maximum, etc). Also, at the mo­ment, we sup­port every com­bi­na­tion of style and explode field (ex­cluded styles matrix and label)
  • Body de­fined in new Re­quest­Body ob­ject. In par­tic­u­lar:
    • For application/json the val­i­da­tion han­dler will take schema that you have de­fined in schema ob­ject and will val­i­date json bod­ies with it
    • For application/x-www-form-urlencoded and multipart/form-data the val­i­da­tion han­dler will take care of val­i­date every pa­ra­me­ters in form at­trib­utes. It ac­tu­ally sup­ports only comma sep­a­rated val­ues for object and arrays
    • For other pa­ra­me­ter types it will check Content-Type header

Re­quest val­i­da­tion er­rors will be car­ried with RoutingContext en­cap­su­lated in an ob­ject called ValidationHandler, so you have to at­tach fail­ure han­dler to check if some­thing went wrong dur­ing val­i­da­tion. Also the RoutingContext carry a new ob­ject called RequestParameters that en­cap­su­late all re­quest pa­ra­me­ters de­se­ri­al­ized and parsed.

Router fac­tory is in­tended to give you a re­ally sim­ple user in­ter­face to use Ope­nAPI 3 sup­port. Most im­por­tant fea­tures are:

  • Async load­ing of spec­i­fi­ca­tion and its schema de­pen­den­cies
  • Au­to­matic con­vert Ope­nAPI style paths to Vert.x style paths
  • Lazy meth­ods: op­er­a­tions (com­bi­na­tion of paths and HTTP meth­ods) are mounted in de­f­i­n­i­tion order in­side spec­i­fi­ca­tion
  • Au­to­matic mount of se­cu­rity val­i­da­tion han­dlers

Also, it’s planned to re­lease a project skele­ton gen­er­a­tor based on API spec.

Startup your project

We are in a test­ing stage, so the vertx-​web of­fi­cial repo doesn’t con­tain it. To in­clude the mod­i­fied ver­sion of vertx-​web re­place your vertx-​web maven de­pen­dency with this one:

<dependency>
    <groupId>io.vertx</groupId>
    <artifactId>vertx-web-api-contract</artifactId>
    <version>3.6.0</version>
</dependency>

Now you can start using Ope­nAPI 3 in­side your Vert.x pow­ered app!

First of all you need to load the spec­i­fi­ca­tion and con­struct the router fac­tory:

// Load the api spec. This operation is asynchronous
OpenAPI3RouterFactory.create(this.vertx, "src/main/resources/petstore.yaml", ar -> {
    if (ar.succeeded()) {
        // Spec loaded with success
        OpenAPI3RouterFactory routerFactory = ar.result();
    } else {
        // Something went wrong during router factory initialization
        Throwable exception = ar.cause();
        logger.error("Ops!", exception);
    }
});

Handlers mounting

Now load han­dlers to your op­er­a­tions. Use addHandlerByOperationId(String operationId, Handler<RoutingContext> handler) to add an han­dler to a route that matches the operationId. To add a fail­ure han­dler use addFailureHandlerByOperationId(String operationId, Handler<RoutingContext> failureHandler)

You can, of course, add mul­ti­ple han­dlers to same op­er­a­tion, with­out over­writ­ing the ex­ist­ing ones.

This is an ex­am­ple of addHandlerByOperationId():

// Add an handler with operationId
routerFactory.addHandlerByOperationId("listPets", routingContext -> {
    // Handle listPets operation (GET /pets)
}, routingContext -> {
    // Handle failure
});

Request parameters

Now you can freely use re­quest pa­ra­me­ters. To get the RequestParameters ob­ject:

RequestParameters params = routingContext.get("parsedParameters");

The RequestParameters ob­ject pro­vides all meth­ods to ac­cess to query, cookie, header, path, form and en­tire body pa­ra­me­ters. Here are some ex­am­ples of how to use this ob­ject.

Pa­ra­me­ter with name awesomeParameter with type integer in query:

RequestParameter awesomeParameter = params.queryParameter("awesomeParameter");
if (awesomeParameter != null) {
    // awesomeParameter parameter exists, but we are not sure that is empty or not (query parameters can be empty with allowEmptyValue: true)
    if (!awesomeParameter.isEmpty()) {
      // Now we are sure that it exists and it's not empty, so we can extract it
      Integer awesome = awesomeParameter.getInteger();
    } else {
      // Parameter exists, but it's empty value
    }
} else {
    // Parameter doesn't exist (it's not required)
}

As you can see, every pa­ra­me­ter is mapped in re­spec­tive ob­jects (integer in Integer, integer with format: int64 in Long, float in Float and so on)

Comma sep­a­rated array with name awesomeParameters with type integer in query:

RequestParameter awesomeParameters = params.queryParameter("awesomeParameters");
if (awesomeParameters != null && !awesomeParameters.isEmpty()) {
    List<RequestParameter> awesomeList = awesomeParameters.getArray();
    for (RequestParameter awesome : awesomeList) {
      Integer a = awesome.getInteger();
    }
} else {
  // awesomeParameters not found or empty string
}

JSON Body:

RequestParameter body = params.body();
if (body != null)
  JsonObject jsonBody = body.getJsonObject();

Security handling

You can mount only one se­cu­rity han­dler for a com­bi­na­tion of schema and scope.

To add a se­cu­rity han­dler only with a schema name:

routerFactory.addSecurityHandler("security_scheme_name", routingContext -> {
    // Handle security here and then call next()
    routingContext.next();
});

To add a se­cu­rity han­dler with a com­bi­na­tion of schema name and scope:

routerFactory.addSecuritySchemaScopeValidator("security_scheme_name", "scope_name", routingContext -> {
    // Handle security here and then call next()
    routingContext.next();
});

You can de­fine se­cu­rity han­dlers where you want but de­fine it! | Dur­ing Router in­stan­ti­a­tion, if fac­tory finds a path that re­quire a se­cu­rity schema with­out an as­signed han­dler, It will throw a RouterFactoryException

Error handling

Every time you add an han­dler for an op­er­a­tion you can add a fail­ure han­dler. To han­dle a ValidationException:

Throwable failure = routingContext.failure();
if (failure instanceof ValidationException)
    // Handle Validation Exception
    routingContext.response().setStatusCode(400).setStatusMessage("ValidationError").end(failure.getMessage());

Also the router fac­tory pro­vides two other tools:

  • It au­to­mat­i­cally mounts a 501 Not Implemented han­dler for op­er­a­tions where you haven’t mounted any han­dler
  • It can load a de­fault ValidationException fail­ure han­dler

Both these op­tions are con­fig­urable with RouterFactoryOptions

And now use it!

Now you are ready to gen­er­ate the Router!

Router router = routerFactory.getRouter();

// Now you can use your Router instance
HttpServer server = vertx.createHttpServer(new HttpServerOptions().setPort(8080).setHost("localhost"));
server.requestHandler(router::accept).listen();
Lazy methods!

getRouter() gen­er­ate the Router ob­ject, so you don’t have to care about code de­f­i­n­i­tion order

And now?

You can find a com­plete ex­am­ple on vertx-examples

You can ac­cess to doc­u­men­ta­tion here and Javadoc here

We want you!

Please give us your feed­back open­ing an issue here

Next post

Vert.x 3.5.0.Beta1

It's summer time and we have just released Vert.x 3.5.0.Beta1!

Read more
Previous post

Preview of a guide for Java developers

We are introducing the book “A gentle guide to asynchronous programming with Eclipse Vert.x for enterprise application developers”.

Read more
Related posts

Vert.x Web API Service Introduction

This blog post teaches you how to use the new module vertx-web-api-service to combine the Web Router and the OpenAPI Router Factory with service proxies.

Read more

Real-time bidding with Websockets and Vert.x

The expectations of users for interactivity with web applications have changed over the past few years. Users during bidding in auction no longer want to press the refresh button.

Read more

Eclipse Vert.x 4 milestone 5 released!

We are extremely pleased to announce the fifth 4.0 milestone release of Eclipse Vert.x. This release aims to provide a reliable distribution of the current development of Vert.x 4.

Read more