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
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
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.
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 OKcontentType: text/*, OKcontentType: text/plain still does not workWhat 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
@drakkan I'm not able to reproduce the bug, look at this test i updated in pr #1214: https://github.com/vert-x3/vertx-web/blob/b48ceb5b6e375b84f81c9a270df6cc7796427d82/vertx-web-api-contract/src/test/java/io/vertx/ext/web/api/contract/openapi3/OpenAPI3ValidationTest.java#L436 and the contract https://github.com/vert-x3/vertx-web/blob/b48ceb5b6e375b84f81c9a270df6cc7796427d82/vertx-web-api-contract/src/test/resources/swaggers/validation_test.yaml#L402
Maybe I'm missing some simple point :smile:
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
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!