[v2.0] Request & reponse bodies fail validation when using oneOf to specify alternate schemas
I believe the following payload conforms to my pretend schema, but fails validation with the attached stacktrace...
openapi: "3.0.1"
info:
version: 1.0.0
title: My Application
servers:
- url: http://localhost:8080/api
paths:
/pets:
post:
summary: Add a new pet
requestBody:
description: A JSON object containing pet information
content:
application/json:
schema:
oneOf:
- $ref: '#/components/schemas/Cat'
- $ref: '#/components/schemas/Dog'
responses:
'201':
description: Created
components:
schemas:
Cat:
type: object
properties:
scientificName:
type: string
meow:
type: string
Dog:
type: object
properties:
scientificName:
type: string
bark:
type: string
{
"scientificName": "Canis lupus familiaris",
"bark": "very loud"
}
@Test
public void testAdd() throws IOException {
InputStream json = getClass().getResourceAsStream("/pets-add.json");
given()
.filter(filter)
.when()
.body(IOUtils.toString(json, "UTF-8"))
.contentType(ContentType.JSON)
.post("/Pets")
.then()
.log().all()
.assertThat()
.statusCode(200)
.contentType(ContentType.JSON);
}
com.atlassian.oai.validator.restassured.SwaggerValidationFilter$SwaggerValidationException: Validation failed.
[ERROR] Object instance has properties which are not allowed by the schema: ["bark","scientificName"]
at com.atlassian.oai.validator.restassured.SwaggerValidationFilter.filter(SwaggerValidationFilter.java:61)
at io.restassured.filter.Filter$filter.call(Unknown Source)
at org.codehaus.groovy.runtime.callsite.CallSiteArray.defaultCall(CallSiteArray.java:48)
at org.codehaus.groovy.runtime.callsite.AbstractCallSite.call(AbstractCallSite.java:113)
at org.codehaus.groovy.runtime.callsite.AbstractCallSite.call(AbstractCallSite.java:141)
at io.restassured.internal.filter.FilterContextImpl.next(FilterContextImpl.groovy:72)
at io.restassured.filter.FilterContext$next.call(Unknown Source)
at org.codehaus.groovy.runtime.callsite.CallSiteArray.defaultCall(CallSiteArray.java:48)
at org.codehaus.groovy.runtime.callsite.AbstractCallSite.call(AbstractCallSite.java:113)
at org.codehaus.groovy.runtime.callsite.AbstractCallSite.call(AbstractCallSite.java:133)
at io.restassured.internal.RequestSpecificationImpl.applyPathParamsAndSendRequest(RequestSpecificationImpl.groovy:1731)
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:483)
at org.codehaus.groovy.reflection.CachedMethod.invoke(CachedMethod.java:93)
at groovy.lang.MetaMethod.doMethodInvoke(MetaMethod.java:325)
at groovy.lang.MetaClassImpl.invokeMethod(MetaClassImpl.java:1213)
at groovy.lang.MetaClassImpl.invokeMethod(MetaClassImpl.java:1022)
at groovy.lang.MetaClassImpl.invokeMethod(MetaClassImpl.java:810)
at io.restassured.internal.RequestSpecificationImpl.invokeMethod(RequestSpecificationImpl.groovy)
at org.codehaus.groovy.runtime.callsite.PogoInterceptableSite.call(PogoInterceptableSite.java:48)
at org.codehaus.groovy.runtime.callsite.PogoInterceptableSite.callCurrent(PogoInterceptableSite.java:58)
at org.codehaus.groovy.runtime.callsite.CallSiteArray.defaultCallCurrent(CallSiteArray.java:52)
at org.codehaus.groovy.runtime.callsite.AbstractCallSite.callCurrent(AbstractCallSite.java:154)
at org.codehaus.groovy.runtime.callsite.AbstractCallSite.callCurrent(AbstractCallSite.java:182)
at io.restassured.internal.RequestSpecificationImpl.applyPathParamsAndSendRequest(RequestSpecificationImpl.groovy:1737)
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:483)
at org.codehaus.groovy.reflection.CachedMethod.invoke(CachedMethod.java:93)
at groovy.lang.MetaMethod.doMethodInvoke(MetaMethod.java:325)
at groovy.lang.MetaClassImpl.invokeMethod(MetaClassImpl.java:1213)
at groovy.lang.MetaClassImpl.invokeMethod(MetaClassImpl.java:1022)
at groovy.lang.MetaClassImpl.invokeMethod(MetaClassImpl.java:810)
at io.restassured.internal.RequestSpecificationImpl.invokeMethod(RequestSpecificationImpl.groovy)
at org.codehaus.groovy.runtime.callsite.PogoInterceptableSite.call(PogoInterceptableSite.java:48)
at org.codehaus.groovy.runtime.callsite.PogoInterceptableSite.callCurrent(PogoInterceptableSite.java:58)
at org.codehaus.groovy.runtime.callsite.CallSiteArray.defaultCallCurrent(CallSiteArray.java:52)
at org.codehaus.groovy.runtime.callsite.AbstractCallSite.callCurrent(AbstractCallSite.java:154)
at org.codehaus.groovy.runtime.callsite.AbstractCallSite.callCurrent(AbstractCallSite.java:182)
at io.restassured.internal.RequestSpecificationImpl.post(RequestSpecificationImpl.groovy:174)
at io.restassured.internal.RequestSpecificationImpl.post(RequestSpecificationImpl.groovy)
at com.atlassian.oai.validator.examples.restassured.PetOperationsTest.testAdd(PetOperationsTest.java:35)
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:483)
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.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.eclipse.jdt.internal.junit4.runner.JUnit4TestReference.run(JUnit4TestReference.java:86)
at org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java:38)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:538)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:760)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:460)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:206)
Comments (15)
-
reporter -
reporter - changed title to Request & reponse bodies fail validation when using oneOf to specify alternate schemas
Note that the same seems to be true with response bodies. e.g., the following response passes when replacing the "oneOf" block with either of its items
#! responses: '200': description: OK content: 'application/json': schema: type: array items: # this passes $ref: "#/components/schemas/ResponseBodyModel" # this passes # - type: array # items: # $ref: "#/components/schemas/ResponseBodyModel" # this fails # oneOf: # - $ref: "#/components/schemas/ResponseBodyModel" # - type: array # items: # $ref: "#/components/schemas/ResponseBodyModel"
-
- changed title to [v2.0] Request & reponse bodies fail validation when using oneOf to specify alternate schemas
-
Thanks for raising this. I haven't done any work to support
oneOf
oranyOf
as yet, but its on the backlog. -
I think this is the same root cause as the
allOf
problem mentioned in the FAQ section of the README. The problem is to do with how JSON Schema composition plays with theadditionalProperties: false
directive (which is automatically inserted by the validator).https://bitbucket.org/atlassian/swagger-request-validator/pull-requests/105 has tests that exercise the
anyOf
,oneOf
andallOf
composition - all require disabling theadditionalProperties
validation.Can you try disabling the additional properties validation and see if that fixes your problem (see the FAQ for details on how to do that).
-
- changed status to open
-
reporter That changes the error, but still seems to result in a failure:
#! com.atlassian.oai.validator.restassured.SwaggerValidationFilter$SwaggerValidationException: Validation failed. [ERROR] Instance failed to match exactly one schema (matched 2 out of 2) at com.atlassian.oai.validator.restassured.SwaggerValidationFilter.filter(SwaggerValidationFilter.java:61) at io.restassured.filter.Filter$filter.call(Unknown Source) at org.codehaus.groovy.runtime.callsite.CallSiteArray.defaultCall(CallSiteArray.java:48) at org.codehaus.groovy.runtime.callsite.AbstractCallSite.call(AbstractCallSite.java:113) at org.codehaus.groovy.runtime.callsite.AbstractCallSite.call(AbstractCallSite.java:141) at io.restassured.internal.filter.FilterContextImpl.next(FilterContextImpl.groovy:72) at io.restassured.filter.FilterContext$next.call(Unknown Source) at org.codehaus.groovy.runtime.callsite.CallSiteArray.defaultCall(CallSiteArray.java:48) at org.codehaus.groovy.runtime.callsite.AbstractCallSite.call(AbstractCallSite.java:113) at org.codehaus.groovy.runtime.callsite.AbstractCallSite.call(AbstractCallSite.java:133) at io.restassured.internal.RequestSpecificationImpl.applyPathParamsAndSendRequest(RequestSpecificationImpl.groovy:1731) 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:483) at org.codehaus.groovy.reflection.CachedMethod.invoke(CachedMethod.java:93) at groovy.lang.MetaMethod.doMethodInvoke(MetaMethod.java:325) at groovy.lang.MetaClassImpl.invokeMethod(MetaClassImpl.java:1213) at groovy.lang.MetaClassImpl.invokeMethod(MetaClassImpl.java:1022) at groovy.lang.MetaClassImpl.invokeMethod(MetaClassImpl.java:810) at io.restassured.internal.RequestSpecificationImpl.invokeMethod(RequestSpecificationImpl.groovy) at org.codehaus.groovy.runtime.callsite.PogoInterceptableSite.call(PogoInterceptableSite.java:48) at org.codehaus.groovy.runtime.callsite.PogoInterceptableSite.callCurrent(PogoInterceptableSite.java:58) at org.codehaus.groovy.runtime.callsite.CallSiteArray.defaultCallCurrent(CallSiteArray.java:52) at org.codehaus.groovy.runtime.callsite.AbstractCallSite.callCurrent(AbstractCallSite.java:154) at org.codehaus.groovy.runtime.callsite.AbstractCallSite.callCurrent(AbstractCallSite.java:182) at io.restassured.internal.RequestSpecificationImpl.applyPathParamsAndSendRequest(RequestSpecificationImpl.groovy:1737) 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:483) at org.codehaus.groovy.reflection.CachedMethod.invoke(CachedMethod.java:93) at groovy.lang.MetaMethod.doMethodInvoke(MetaMethod.java:325) at groovy.lang.MetaClassImpl.invokeMethod(MetaClassImpl.java:1213) at groovy.lang.MetaClassImpl.invokeMethod(MetaClassImpl.java:1022) at groovy.lang.MetaClassImpl.invokeMethod(MetaClassImpl.java:810) at io.restassured.internal.RequestSpecificationImpl.invokeMethod(RequestSpecificationImpl.groovy) at org.codehaus.groovy.runtime.callsite.PogoInterceptableSite.call(PogoInterceptableSite.java:48) at org.codehaus.groovy.runtime.callsite.PogoInterceptableSite.callCurrent(PogoInterceptableSite.java:58) at org.codehaus.groovy.runtime.callsite.CallSiteArray.defaultCallCurrent(CallSiteArray.java:52) at org.codehaus.groovy.runtime.callsite.AbstractCallSite.callCurrent(AbstractCallSite.java:154) at org.codehaus.groovy.runtime.callsite.AbstractCallSite.callCurrent(AbstractCallSite.java:182) at io.restassured.internal.RequestSpecificationImpl.post(RequestSpecificationImpl.groovy:174) at io.restassured.internal.RequestSpecificationImpl.post(RequestSpecificationImpl.groovy) at com.atlassian.oai.validator.examples.restassured.CustomerOperationsTest.testAdd(CustomerOperationsTest.java:47) 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:483) 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.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.eclipse.jdt.internal.junit4.runner.JUnit4TestReference.run(JUnit4TestReference.java:86) at org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java:38) at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:538) at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:760) at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:460) at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:206)
-
That does seem odd.
The
SwaggerValidationException
that's being generated should include a copy of the validation report on it. Can you try catching the exception and the logging the full details of the report with e.g.catch (SwaggerValidationException e) { log.info(JsonValidationReportFormat.getInstance().apply(e.getValidationReport()); }
-
reporter #! { "messages" : [ { "key" : "validation.request.body.schema.oneOf", "level" : "ERROR", "message" : "Instance failed to match exactly one schema (matched 2 out of 2)", "context" : { "requestPath" : "/api/Customers", "apiRequestContentType" : "application/json", "location" : "REQUEST", "requestMethod" : "POST" } } ] }
-
Im still struggling to reproduce the problem.
Perhaps if you could give an example schema and request that shows the problem I can use that directly in my tests.
-
reporter An example schema, payload, and test method are in the original post... are you saying that you see a different behavior with versions 2.0.0-RC2 and RC3?
-
reporter For clarity:
Example Schema (http://localhost:8080/api/Pets.yaml):
#! openapi: "3.0.1" info: version: 1.0.0 title: My Application servers: - url: http://localhost:8080/api paths: /Pets: post: summary: Add a new pet requestBody: description: A JSON object containing pet information content: application/json: schema: oneOf: - $ref: '#/components/schemas/Cat' - $ref: '#/components/schemas/Dog' responses: '201': description: Created default: description: Error components: schemas: Cat: type: object properties: scientificName: type: string meow: type: string Dog: type: object properties: scientificName: type: string bark: type: string
Example request (com.atlassian.oai.validator.restassured.PetsOperationsTest.testAdd() - note by the way that OpenApiValidationException has default visibility so I had to change my own packaging to make that go. Separate bug there?)
package com.atlassian.oai.validator.restassured; import static io.restassured.RestAssured.given; import java.io.IOException; import java.io.InputStream; import org.apache.commons.io.IOUtils; import org.junit.Test; import com.atlassian.oai.validator.OpenApiInteractionValidator; import com.atlassian.oai.validator.report.JsonValidationReportFormat; import com.atlassian.oai.validator.report.LevelResolver; import com.atlassian.oai.validator.report.ValidationReport.Level; import com.atlassian.oai.validator.restassured.OpenApiValidationFilter.OpenApiValidationException; import io.restassured.RestAssured; import io.restassured.http.ContentType; public class PetsOperationsTest { private static final String BASE_URI = "http://localhost:8080/api"; private static final String SPEC_URI = BASE_URI + "/Pets.yaml"; private static final OpenApiInteractionValidator validator = OpenApiInteractionValidator .createFor(SPEC_URI) .withLevelResolver( LevelResolver.create().withLevel("validation.schema.additionalProperties", Level.IGNORE).build()) .build(); private static final OpenApiValidationFilter filter = new OpenApiValidationFilter(validator); static { RestAssured.baseURI = BASE_URI; } @Test public void testAdd() throws IOException { InputStream json = getClass().getResourceAsStream("/pets-add.json"); try { given() .filter(filter) .when() .body(IOUtils.toString(json, "UTF-8")) .contentType(ContentType.JSON) .post("/Pets") .then() .log().all() .assertThat() .statusCode(200) .contentType(ContentType.JSON); } catch (OpenApiValidationException e) { System.out.println((JsonValidationReportFormat.getInstance().apply(e.getValidationReport()))); } } }
Example payload (/pets-add.json)
#! { "scientificName": "Canis lupus familiaris", "bark": "very loud" }
Result
#! { "messages" : [ { "key" : "validation.request.body.schema.oneOf", "level" : "ERROR", "message" : "Instance failed to match exactly one schema (matched 2 out of 2)", "context" : { "requestPath" : "/api/Pets", "apiRequestContentType" : "application/json", "location" : "REQUEST", "requestMethod" : "POST" } } ] }
-
Ah - I think I see now. Neither of the Dog or Cat schemas define required properties, so the request you’re making technically matches both schemas and so fails validation.
Try making the properties required in both schemas and see if that fixes the problem.
And yes - the visibility of the exception is a bug - I’ll fix that next week (thanks for pointing it out!)
-
reporter Marked 'bark' and 'meow' properties required, and that (along with disabling the additionalProperties validation suggested above) works around it.
-
- changed status to resolved
- Log in to comment