Skip to content

Request validation may fail due to incorrect path parameter extraction #120

@stefanuzzo

Description

@stefanuzzo

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions