-
Notifications
You must be signed in to change notification settings - Fork 16
Description
Version
5.0.0 to 5.0.5
Context
While migrating to Vert.x 5.x.x a test case involving a numeric path parameter stopped working.
Consider the following OpenAPI descriptor fragment:
/entities/{id}:
get:
summary: Get an entity by its id
operationId: getEntity
parameters:
- description: Unique entity identifier
in: path
name: id
required: true
schema:
type: integer
findPathSegment(String, String) static method in class io.vertx.openapi.validation.RequestUtils analyzes the path declared in the contract (/entities/{id}) and determines id parameter has to be looked up at segment number 2.
However, when the router built upon this contranct is mounted as a subrouter (e.g. a context root router) the segment is no more 2. Suppose the context root is my-service; the actual call to get entity with id 5 would be:
GET http://<HOST>:<PORT>/my-service/entities/5
The problem is that segment number 2 in the above uri (/my-service/entities/5) is string entities and not the number 5, causing validation code to crash due to incompatible types:
io.vertx.ext.web.handler.HttpException: Bad Request
Caused by: io.vertx.openapi.validation.SchemaValidationException: The value of path parameter id is invalid. Reason: Instance type string is invalid. Expected integer
at io.vertx.openapi.validation.SchemaValidationException.createInvalidValueParameter(SchemaValidationException.java:40)
at io.vertx.openapi.validation.SchemaValidationException.createErrorFromOutputUnitType(SchemaValidationException.java:65)
at io.vertx.openapi.validation.impl.RequestValidatorImpl.validateParameter(RequestValidatorImpl.java:135)
at io.vertx.openapi.validation.impl.RequestValidatorImpl.lambda$validate$2(RequestValidatorImpl.java:98)
at io.vertx.core.impl.ExecuteBlocking$1.execute(ExecuteBlocking.java:36)
at io.vertx.core.impl.WorkerTask.run(WorkerTask.java:57)
at io.vertx.core.impl.TaskQueue.run(TaskQueue.java:80)
at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1136)
at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:635)
at io.netty.util.concurrent.FastThreadLocalRunnable.run(FastThreadLocalRunnable.java:30)
at java.base/java.lang.Thread.run(Thread.java:833)
Caused by: io.vertx.json.schema.JsonSchemaValidationException: Instance type string is invalid. Expected integer
at [#/type].<#>(Unknown Source)
The validation code calculates the path parameter position based only on the contract and is unaware of the context.
If the type of the id path parameter was java.lang.String, validation would probably succeed, which is even worse.
This used to work just fine up to version 4.5.x.
As a workaround, validation could be disabled on a per-route basis by calling OpenAPIRoute.setDoValidation(boolean) passing in false.
Steps to reproduce
API contract (openapi.yml)
---
info:
description: Minimal API for unit testing purposes
license:
name: Apache 2.0
url: http://www.apache.org/licenses/LICENSE-2.0.html
termsOfService: http://swagger.io/terms/
title: API for unit testing
version: 1.0.0
openapi: 3.0.0
paths:
/entities/{id}:
get:
summary: Get an entity by its id
operationId: getEntity
parameters:
- description: Unique entity identifier
in: path
name: id
required: true
schema:
type: integer
responses:
'200':
description: successful operation
content:
application/json:
schema:
$ref: '#/components/schemas/Entity'
components:
schemas:
Entity:
type: object
description: An entity descriptor
required:
- description
properties:
id:
type: integer
description:
type: string
Relevant pom.xml entries
<properties>
<io.vertx.version>5.0.5</io.vertx.version>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>io.vertx</groupId>
<artifactId>vertx-stack-depchain</artifactId>
<version>${io.vertx.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>io.vertx</groupId>
<artifactId>vertx-core</artifactId>
</dependency>
<dependency>
<groupId>io.vertx</groupId>
<artifactId>vertx-web</artifactId>
</dependency>
<dependency>
<groupId>io.vertx</groupId>
<artifactId>vertx-openapi</artifactId>
</dependency>
<dependency>
<groupId>io.vertx</groupId>
<artifactId>vertx-web-openapi-router</artifactId>
</dependency>
</dependencies>
HTTP server setup code
private Future<HttpServer> setupHttpServer() {
return OpenAPIContract.from(this.vertx, "openapi.yml")
.map(contract -> RouterBuilder.create(this.vertx, contract))
.onSuccess(rb -> rb.getRoute("getEntity")
.setDoValidation(true)
.addHandler(this.getEntityHandler)
)
.map(RouterBuilder::createRouter)
.compose(router -> {
final Router mainRouter = Router.router(this.vertx);
mainRouter.route("/my-service/*").subRouter(router);
return this.vertx.createHttpServer()
.requestHandler(mainRouter)
.listen(80);
});
}
getEntity operation handler
private final Handler<RoutingContext> getEntityHandler = rc -> {
// Irrelevant: do something and send a response back.
}
Do you have a reproducer?
No response