Vertx-web: OpenAPI3 Validation error for required binary field in multipart/form data with content type != application/octet-stream

Created on 29 Mar 2019  路  11Comments  路  Source: vert-x3/vertx-web

Version

  • vert.x core: 3.6.3
  • vert.x web: 3.6.3

Context

Given a spec that contain a multipart form/data with a required binary field and a content type different from application/octet-stream a validation error occours

Do you have a reproducer?

yaml

openapi: 3.0.1
info:
  title: Test API
  description: 'Test API'
  version: 1.0.0

paths:
  /multipart:
    post:
      tags:
      - multipart
      operationId: testMultipart
      requestBody:
        content:
          multipart/form-data:
            schema:
              type: object
              required:
                - type
                - name
                - file1
              properties:
                file1:
                  type: string
                  description: File1
                  format: binary
                type:
                  type: string
                  description: Model type
                  enum:
                    - 1
                    - 2
                name:
                  type: string
                  description: Model name
            encoding:
              file1:
                contentType: application/octet-stream , text/plain
        required: true
      responses:
        200:
          description: successful operation
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ApiResponse'
        400:
          description: Invalid parameters supplied
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ApiResponse'
              example:
                code: 0
                message: "Error description if any"
                version: "1.0.0"
        500:
          description: Internal error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ApiResponse' 
              example:
                code: 0
                message: "Internal Server Error"
                version: "1.0.0"

components:
  schemas:
    ApiResponse:
      type: object
      properties:
        code:
          type: integer
          format: int32
          example: 1
          enum:
            - 0
            - 1
          description: >
            code:
              * `0` - error
              * `1` - success
        message:
          type: string
        version:
          type: string
          example: 1.0.0

Steps to reproduce

curl -X POST "http://127.0.0.1:8080/multipart" -H "Content-Type: multipart/form-data" -F "type=2" -F "name=test model" -F "file1=@testdata/classes.txt;type=application/octet-stream"

fails with this error

Error during validation of request. Parameter \"file1\" inside body form not found

It only works setting contentType: application/octet-stream in the spec file.

if in the spec you set

encoding:
    file1:
       contentType: text/plain

and you try

curl -X POST "http://127.0.0.1:8080/multipart" -H "Content-Type: multipart/form-data" -F "type=2" -F "name=test model" -F "file1=@testdata/classes.txt;type=text/plain"

the same validation error occours.

So basically the validation fails if content type is set to something different from application/octet-stream that is the default for a binary field.

componenapi-contracbug

All 11 comments

Opened a pr for it, do you want to give a look? :smile: https://github.com/vert-x3/vertx-web/pull/1214

It seems much better, thanks!

here are my test results:

  • contentType: application/octet-stream, text/plain OK
  • contentType: text/*, OK
  • contentType: text/plain still does not work

What do you mean for not working the text/plain content type? Do you mean configuring encoding as text/plain? Can you provide a reproducer for that specific case?

I mean setting something like this in the spec file

content:
          multipart/form-data:
            schema:
              type: object
              required:
                - type
                - name
                - file1
                - file2
              properties:
                file1:
                  type: string
                  description: File1
                  format: binary
                file2:
                  type: string
                  description: File1
                  format: binary
                type:
                  type: string
                  description: Model type
                  enum:
                    - 1
                    - 2
                name:
                  type: string
                  description: Model name
            encoding:
              file1:
                contentType: text/plain
              file2:
                contentType: application/octet-stream, text/plain

and then upload files like this

curl -X POST "http://127.0.0.1:8080/multipart" -H "Content-Type: multipart/form-data" -F "type=2" -F "name=test model" -F "file1=@testdata/classes.txt;type=text/plain" -F "file2=@testdata/test.pb"

this will result in a validation error

Error during validation of request. Parameter \"file1\" inside body form not found

I tested patching 3.6.3 version

thanks, the only differences on my side are these one:

requestBody:
        required: true

and the parameters must have required too

multipart/form-data:
            schema:
              type: object
              required:
                - param1
                - param2
                - param3
                - param4
                - param1binary

if I make the parameters not required it works even without the patch. This is the actual workaround in my app (I do the validation myself).

Maybe I'm doing something wrong on my side, I'll retest later today,

meantime thanks for your quick response and fix!

I just applied the patch against 3.7 branch and executed mvn test all OpenAPI3 tests passed.

Then I changed src/test/resources/swaggers/multipart.yaml and in /testMultipartWildcard I replaced:

encoding:
              file1:
                contentType: text/*

with

encoding:
              file1:
                contentType: text/plain

the csv test should now fail so I did this change

git diff
diff --git a/vertx-web-api-contract/src/test/java/io/vertx/ext/web/api/contract/openapi3/OpenAPI3RouterFactoryTest.java b/vertx-web-api-contract/src/test/java/io/vertx/ext/web/api/contract/openapi3/OpenAPI3RouterFactoryTest.java
index 1f73c179..519300ad 100644
--- a/vertx-web-api-contract/src/test/java/io/vertx/ext/web/api/contract/openapi3/OpenAPI3RouterFactoryTest.java
+++ b/vertx-web-api-contract/src/test/java/io/vertx/ext/web/api/contract/openapi3/OpenAPI3RouterFactoryTest.java
@@ -972,7 +972,7 @@ public class OpenAPI3RouterFactoryTest extends ApiWebTestBase {
         .binaryFileUpload("file1", "random.csv", "src/test/resources/random.csv", "text/csv")
         .attribute("type", "text/csv");

-    testRequestWithMultipartForm(HttpMethod.POST, "/testMultipartWildcard", form2, 200, "text/csv");
+    testRequestWithMultipartForm(HttpMethod.POST, "/testMultipartWildcard", form2, 400, "text/csv");

     MultipartForm form3 =
       MultipartForm

now tests fails

ava.lang.AssertionError: expected:<200> but was:<400>
    at org.junit.Assert.fail(Assert.java:88)
    at org.junit.Assert.failNotEquals(Assert.java:834)
    at org.junit.Assert.assertEquals(Assert.java:645)
    at org.junit.Assert.assertEquals(Assert.java:631)
    at io.vertx.test.core.AsyncTestBase.assertEquals(AsyncTestBase.java:235)
    at io.vertx.ext.web.api.ApiWebTestBase.lambda$testRequestWithMultipartForm$5(ApiWebTestBase.java:112)
    at io.vertx.ext.web.client.impl.HttpContext.handleDispatchResponse(HttpContext.java:285)
    at io.vertx.ext.web.client.impl.HttpContext.execute(HttpContext.java:272)
    at io.vertx.ext.web.client.impl.HttpContext.next(HttpContext.java:250)
    at io.vertx.ext.web.client.impl.predicate.PredicateInterceptor.handle(PredicateInterceptor.java:69)
    at io.vertx.ext.web.client.impl.predicate.PredicateInterceptor.handle(PredicateInterceptor.java:32)
    at io.vertx.ext.web.client.impl.HttpContext.next(HttpContext.java:247)
    at io.vertx.ext.web.client.impl.HttpContext.fire(HttpContext.java:257)
    at io.vertx.ext.web.client.impl.HttpContext.dispatchResponse(HttpContext.java:218)
    at io.vertx.ext.web.client.impl.HttpContext.lambda$null$2(HttpContext.java:341)
    at io.vertx.core.impl.ContextImpl.executeTask(ContextImpl.java:320)
    at io.vertx.core.impl.EventLoopContext.lambda$executeAsync$0(EventLoopContext.java:38)
    at io.netty.util.concurrent.AbstractEventExecutor.safeExecute(AbstractEventExecutor.java:163)
    at io.netty.util.concurrent.SingleThreadEventExecutor.runAllTasks(SingleThreadEventExecutor.java:404)
    at io.netty.channel.nio.NioEventLoop.run(NioEventLoop.java:462)
    at io.netty.util.concurrent.SingleThreadEventExecutor$5.run(SingleThreadEventExecutor.java:897)
    at io.netty.util.concurrent.FastThreadLocalRunnable.run(FastThreadLocalRunnable.java:30)
    at java.lang.Thread.run(Thread.java:748)

java.lang.AssertionError
    at org.junit.Assert.fail(Assert.java:86)
    at org.junit.Assert.assertTrue(Assert.java:41)
    at org.junit.Assert.assertTrue(Assert.java:52)
    at io.vertx.test.core.AsyncTestBase.assertTrue(AsyncTestBase.java:372)
    at io.vertx.test.core.AsyncTestBase.awaitLatch(AsyncTestBase.java:592)
    at io.vertx.ext.web.api.ApiWebTestBase.testRequestWithMultipartForm(ApiWebTestBase.java:116)
    at io.vertx.ext.web.api.contract.openapi3.OpenAPI3RouterFactoryTest.wildcardMultipartEncoding(OpenAPI3RouterFactoryTest.java:967)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:498)
    at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50)
    at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
    at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47)
    at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
    at org.junit.internal.runners.statements.RunBefores.evaluate(RunBefores.java:26)
    at org.junit.internal.runners.statements.RunAfters.evaluate(RunAfters.java:27)
    at org.junit.rules.TestWatcher$1.evaluate(TestWatcher.java:55)
    at org.junit.rules.RunRules.evaluate(RunRules.java:20)
    at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:325)
    at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:78)
    at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:57)
    at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290)
    at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71)
    at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
    at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58)
    at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268)
    at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
    at org.apache.maven.surefire.junit4.JUnit4Provider.execute(JUnit4Provider.java:283)
    at org.apache.maven.surefire.junit4.JUnit4Provider.executeWithRerun(JUnit4Provider.java:173)
    at org.apache.maven.surefire.junit4.JUnit4Provider.executeTestSet(JUnit4Provider.java:153)
    at org.apache.maven.surefire.junit4.JUnit4Provider.invoke(JUnit4Provider.java:128)
    at org.apache.maven.surefire.booter.ForkedBooter.invokeProviderInSameClassLoader(ForkedBooter.java:203)
    at org.apache.maven.surefire.booter.ForkedBooter.runSuitesInProcess(ForkedBooter.java:155)
    at org.apache.maven.surefire.booter.ForkedBooter.main(ForkedBooter.java:103)

at line 967 there is the text/plain test

testRequestWithMultipartForm(HttpMethod.POST, "/testMultipartWildcard", form1, 200, "text/plain");

that should work since I changed the content type to allow text plain

here the modified spec

multipart.zip

Thank you for the detailed report i will investigate more

Okay I finally reproduced it, I'm on it ;)

@drakkan can you check again the pr? The problem was about wrong inference of form attribute/file upload. In the PR I updated the documentation too to explain it

@slinkydeveloper it works fine now, grazie!

Was this page helpful?
0 / 5 - 0 ratings