OpenAPI 3 support
Vert.x allows you to use your OpenApi 3 specification directly inside your code using the design first approach.
Vert.x-Web provides:
-
OpenAPI 3 compliant API specification validation with automatic loading of external Json schemas
-
Automatic request validation
-
Automatic mount of security validation handlers
-
Automatic 501 response for not implemented operations
-
Router factory to provide all these features to users
The router factory
You can create your web service based on OpenAPI3 specification with OpenAPI3RouterFactory.
This class, as name says, is a router factory based on your OpenAPI 3 specification.
OpenAPI3RouterFactory is intended to give you a really simple user interface to use OpenAPI 3 support. It includes:
-
Async loading of specification and its schema dependencies
-
Mount path with operationId or with combination of path and HTTP method
-
Automatic request parameters validation
-
Automatic convert OpenAPI style paths to Vert.x style paths
-
Lazy methods: operations (combination of paths and HTTP methods) are mounted in declaration order inside specification
-
Automatic mount of security validation handlers
Create a new router factory
To create a new router factory, you can use methods inside OpenAPI3RouterFactory:
-
OpenAPI3RouterFactory.createRouterFactoryFromFileto create a router factory from local file -
OpenAPI3RouterFactory.createRouterFactoryFromURLto create a router factory from url
For example:
OpenAPI3RouterFactory.createRouterFactoryFromFile(vertx, "src/main/resources/petstore.yaml", { ar ->
if (ar.succeeded()) {
// Spec loaded with success
def routerFactory = ar.result()
} else {
// Something went wrong during router factory initialization
def exception = ar.cause()
}
})
Mount the handlers
Now load your first path. There are two functions to load the handlers:
And, of course, two functions to load failure handlers
You can, of course, add multiple handlers to same operation, without overwrite the existing ones.
|
Important
|
Path in OpenAPI format
If you want to use addHandler or addFailureHandler pay attention: You can provide a path only in OpenAPI styles (for example path /hello/:param doesn’t work)
|
For example:
routerFactory.addHandlerByOperationId("awesomeOperation", { routingContext ->
def params = routingContext.get("parsedParameters")
def body = params.body()
def jsonBody = body.getJsonObject()
// Do something with body
})
routerFactory.addFailureHandlerByOperationId("awesomeOperation", { routingContext ->
// Handle failure
})
|
Important
|
Add operations with operationId
Usage of combination of path and HTTP method is allowed, but it’s better to add operations handlers with operationId, for performance reasons and to avoid paths nomenclature errors
|
Now you can use parameter values as described in vertx-web documentation
Define security handlers
A security handler is defined by a combination of schema name and scope. You can mount only one security handler for a combination. For example:
routerFactory.addSecurityHandler("security_scheme_name", securityHandler)
You can of course use included Vert.x security handlers, for example:
routerFactory.addSecurityHandler("jwt_auth", JWTAuthHandler.create(jwtAuthProvider))
Error handling
The router factory allows you to manage errors efficiently:
-
It automatically mounts a 501
Not Implementedhandler for operations where you haven’t mounted any handler -
It automatically mounts a 400
Bad Requesthandler that managesValidationException(You can enable/disable this feature viaenableValidationFailureHandler)
Generate the router
When you are ready, generate the router and use it:
def router = routerFactory.getRouter()
def server = vertx.createHttpServer([
port:8080,
host:"localhost"
])
server.requestHandler(router.&accept).listen()
Requests validation
Vert.x provides a validation framework that will validate requests for you and will put results of validation inside a container. To define a HTTPRequestValidationHandler:
// Create Validation Handler with some stuff
def validationHandler = HTTPRequestValidationHandler.create().addQueryParam("parameterName", ParameterType.INT, true).addFormParamWithPattern("formParameterName", "a{4}", true).addPathParam("pathParam", ParameterType.FLOAT)
Then you can mount your validation handler:
// BodyHandler is required to manage body parameters like forms or json body
router.route().handler(BodyHandler.create())
router.get("/awesome/:pathParam").handler(validationHandler).handler({ routingContext ->
// Get Request parameters container
def params = routingContext.get("parsedParameters")
// Get parameters
def parameterName = params.queryParameter("parameterName").getInteger()
def formParameterName = params.formParameter("formParameterName").getString()
def pathParam = params.pathParameter("pathParam").getFloat()
}).failureHandler({ routingContext ->
def failure = routingContext.failure()
if (failure instanceof io.vertx.ext.web.api.validation.ValidationException) {
// Something went wrong during validation!
def validationErrorMessage = failure.getMessage()
}
})
If validation succeeds, It returns request parameters inside RequestParameters, otherwise It will throw a ValidationException
Types of request parameters
Every parameter has a type validator, a class that describes the expected type of parameter.
A type validator validates the value, casts it in required language type and then loads it inside a RequestParameter object. There are three ways to describe the type of your parameter:
-
There is a set of prebuilt types that you can use:
ParameterType -
You can instantiate a custom instance of prebuilt type validators using static methods of
ParameterTypeValidatorand then load it intoHTTPRequestValidationHandlerusing functions ending withWithCustomTypeValidator -
You can create your own
ParameterTypeValidatorimplementingParameterTypeValidatorinterface
Handling parameters
Now you can handle parameter values:
def params = routingContext.get("parsedParameters")
def awesomeParameter = params.queryParameter("awesomeParameter")
if (awesomeParameter != null) {
if (!awesomeParameter.isEmpty()) {
// Parameter exists and isn't empty
// ParameterTypeValidator mapped the parameter in equivalent language object
def awesome = awesomeParameter.getInteger()
} else {
// Parameter exists, but it's empty
}
} else {
// Parameter doesn't exist (it's not required)
}
As you can see, every parameter is mapped in respective language objects. You can also get a json body:
def body = params.body()
if (body != null) {
def jsonBody = body.getJsonObject()
}