vertx-web
vertx-web copied to clipboard
Parameter name is missing from validation exception in open api.
Version
3.x vs 4.x
Context
Hi, in Vertx 3 when using open api, if we had a validation error in the Json we used to get back the name of the Json property that failed the validation.
It looks like in Vertx 4 vertx-web-openapi this information is gone?
In vertx 3 we could use ValidationException and get the parameter name with the function parameterName(). But now with vertx 4 BodyProcessorException we don't get that info.
We use that information to create proper error messages back to the client to explain which field had the issue.
Now we just get an error as follows: "[Bad Request] Validation error for body application/json; charset=UTF-8: provided string should respect pattern ^[0-9A-Fa-f]{8}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{12}"
Which isn't any more helpful cause it tells us the pattern failed but not for what paremeter.
Hi, so just to be sure 2 things will happen 1- A function like parameterName() or similar will be added. 2- The error message will contain the field name.
@InfoSec812 are you interested to handle this case ?
@vietj I am interested in looking into this as I too have had this problem. The question is how quickly I will be able to address it. My schedule for the next few weeks is overflowing. It will likely be November before I can dig into this.
Thank you @InfoSec812
@vietj Given this is not a blocker, I believe it is fine to postpone to 4.2.1.
I have started looking at this and it does not appear to be a problem with OpenAPI support. The actual root cause is in the validation module which is used by the OpenAPI components.
@javadevmtl Could you provide an example error with stack trace of what it looks like in Vert.x 3.x please? Then I can probably put together a PR and get this resolved pretty quickly.
Hi here is a sample stack trace. But I don't think you should only rely on 1-2 stack traces that I provided below. I also provided a sample code of how I handle the validation error to output to the response.
ValidationException{parameterName='body.domain', validationRule=null, value='{
"domain": "foo.bar12345678901234567890",
"url": "http://apple.com",
"name": "First",
"description": "Testing!",
"expiresAt": "2121-10-15T00:00:00.12345Z"
}', message='$.domain: may only be 16 characters long', errorType=JSON_INVALID}
at io.vertx.ext.web.api.validation.ValidationException$ValidationExceptionFactory.generateInvalidJsonBodyException(ValidationException.java:202)
at io.vertx.ext.web.api.validation.impl.JsonTypeValidator.isValid(JsonTypeValidator.java:50)
at io.vertx.ext.web.api.validation.impl.BaseValidationHandler.validateEntireBody(BaseValidationHandler.java:296)
at io.vertx.ext.web.api.validation.impl.BaseValidationHandler.handle(BaseValidationHandler.java:82)
at io.vertx.ext.web.api.validation.impl.BaseValidationHandler.handle(BaseValidationHandler.java:21)
at io.vertx.ext.web.impl.RouteState.handleContext(RouteState.java:1038)
at io.vertx.ext.web.impl.RoutingContextImplBase.iterateNext(RoutingContextImplBase.java:101)
at io.vertx.ext.web.impl.RoutingContextWrapper.next(RoutingContextWrapper.java:176)
at io.vertx.ext.web.handler.impl.AuthHandlerImpl.lambda$authorizeUser$3(AuthHandlerImpl.java:223)
at io.vertx.ext.web.handler.impl.AuthHandlerImpl.authorize(AuthHandlerImpl.java:107)
at io.vertx.ext.web.handler.impl.AuthHandlerImpl.authorizeUser(AuthHandlerImpl.java:217)
at io.vertx.ext.web.handler.impl.AuthHandlerImpl.lambda$null$1(AuthHandlerImpl.java:161)
at io.vertx.ext.auth.jwt.impl.JWTAuthProviderImpl.authenticate(JWTAuthProviderImpl.java:150)
at io.vertx.ext.web.handler.impl.AuthHandlerImpl.lambda$handle$2(AuthHandlerImpl.java:150)
at io.vertx.ext.web.handler.impl.JWTAuthHandlerImpl.lambda$parseCredentials$0(JWTAuthHandlerImpl.java:76)
at io.vertx.ext.web.handler.impl.AuthorizationAuthHandler.parseAuthorization(AuthorizationAuthHandler.java:97)
at io.vertx.ext.web.handler.impl.JWTAuthHandlerImpl.parseCredentials(JWTAuthHandlerImpl.java:70)
at io.vertx.ext.web.handler.impl.AuthHandlerImpl.handle(AuthHandlerImpl.java:129)
at io.vertx.ext.web.handler.impl.AuthHandlerImpl.handle(AuthHandlerImpl.java:39)
at io.vertx.ext.web.impl.RouteState.handleContext(RouteState.java:1038)
at io.vertx.ext.web.impl.RoutingContextImplBase.iterateNext(RoutingContextImplBase.java:101)
at io.vertx.ext.web.impl.RoutingContextWrapper.next(RoutingContextWrapper.java:176)
at io.vertx.ext.web.handler.impl.ResponseContentTypeHandlerImpl.handle(ResponseContentTypeHandlerImpl.java:54)
at io.vertx.ext.web.handler.impl.ResponseContentTypeHandlerImpl.handle(ResponseContentTypeHandlerImpl.java:28)
at io.vertx.ext.web.impl.RouteState.handleContext(RouteState.java:1038)
at io.vertx.ext.web.impl.RoutingContextImplBase.iterateNext(RoutingContextImplBase.java:137)
at io.vertx.ext.web.impl.RoutingContextWrapper.next(RoutingContextWrapper.java:176)
at io.vertx.ext.web.handler.impl.BodyHandlerImpl$BHandler.doEnd(BodyHandlerImpl.java:296)
at io.vertx.ext.web.handler.impl.BodyHandlerImpl$BHandler.end(BodyHandlerImpl.java:276)
at io.vertx.ext.web.handler.impl.BodyHandlerImpl.lambda$handle$0(BodyHandlerImpl.java:87)
at io.vertx.core.http.impl.HttpServerRequestImpl.onEnd(HttpServerRequestImpl.java:525)
at io.vertx.core.http.impl.HttpServerRequestImpl.handleEnd(HttpServerRequestImpl.java:511)
at io.vertx.core.http.impl.Http1xServerConnection.handleEnd(Http1xServerConnection.java:176)
at io.vertx.core.http.impl.Http1xServerConnection.handleContent(Http1xServerConnection.java:163)
at io.vertx.core.http.impl.Http1xServerConnection.handleMessage(Http1xServerConnection.java:140)
at io.vertx.core.impl.ContextImpl.executeTask(ContextImpl.java:366)
at io.vertx.core.impl.EventLoopContext.execute(EventLoopContext.java:43)
at io.vertx.core.impl.ContextImpl.executeFromIO(ContextImpl.java:229)
at io.vertx.core.net.impl.VertxHandler.channelRead(VertxHandler.java:164)
at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:379)
at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:365)
at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:357)
at io.netty.channel.ChannelInboundHandlerAdapter.channelRead(ChannelInboundHandlerAdapter.java:93)
at io.netty.handler.codec.http.websocketx.extensions.WebSocketServerExtensionHandler.channelRead(WebSocketServerExtensionHandler.java:99)
at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:379)
at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:365)
at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:357)
at io.netty.handler.codec.ByteToMessageDecoder.fireChannelRead(ByteToMessageDecoder.java:324)
at io.netty.handler.codec.ByteToMessageDecoder.channelRead(ByteToMessageDecoder.java:296)
at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:379)
at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:365)
at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:357)
at io.vertx.core.http.impl.Http1xOrH2CHandler.end(Http1xOrH2CHandler.java:61)
at io.vertx.core.http.impl.Http1xOrH2CHandler.channelRead(Http1xOrH2CHandler.java:38)
at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:379)
at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:365)
at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:357)
at io.netty.channel.DefaultChannelPipeline$HeadContext.channelRead(DefaultChannelPipeline.java:1410)
at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:379)
at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:365)
at io.netty.channel.DefaultChannelPipeline.fireChannelRead(DefaultChannelPipeline.java:919)
at io.netty.channel.nio.AbstractNioByteChannel$NioByteUnsafe.read(AbstractNioByteChannel.java:166)
at io.netty.channel.nio.NioEventLoop.processSelectedKey(NioEventLoop.java:719)
at io.netty.channel.nio.NioEventLoop.processSelectedKeysOptimized(NioEventLoop.java:655)
at io.netty.channel.nio.NioEventLoop.processSelectedKeys(NioEventLoop.java:581)
at io.netty.channel.nio.NioEventLoop.run(NioEventLoop.java:493)
at io.netty.util.concurrent.SingleThreadEventExecutor$4.run(SingleThreadEventExecutor.java:986)
at io.netty.util.internal.ThreadExecutorMap$2.run(ThreadExecutorMap.java:74)
at io.netty.util.concurrent.FastThreadLocalRunnable.run(FastThreadLocalRunnable.java:30)
at java.base/java.lang.Thread.run(Thread.java:834)
One more example for required fields...
ValidationException{parameterName='body', validationRule=null, value='{
"domain": "foo.bar",
"url": "http://apple.com",
"description": "Testing!",
"expiresAt": "2121-10-15T00:00:00.12345Z"
}', message='$.name: is missing but it is required', errorType=JSON_INVALID}
at io.vertx.ext.web.api.validation.ValidationException$ValidationExceptionFactory.generateInvalidJsonBodyException(ValidationException.java:202)
at io.vertx.ext.web.api.validation.impl.JsonTypeValidator.isValid(JsonTypeValidator.java:50)
at io.vertx.ext.web.api.validation.impl.BaseValidationHandler.validateEntireBody(BaseValidationHandler.java:296)
at io.vertx.ext.web.api.validation.impl.BaseValidationHandler.handle(BaseValidationHandler.java:82)
at io.vertx.ext.web.api.validation.impl.BaseValidationHandler.handle(BaseValidationHandler.java:21)
at io.vertx.ext.web.impl.RouteState.handleContext(RouteState.java:1038)
at io.vertx.ext.web.impl.RoutingContextImplBase.iterateNext(RoutingContextImplBase.java:101)
at io.vertx.ext.web.impl.RoutingContextWrapper.next(RoutingContextWrapper.java:176)
at io.vertx.ext.web.handler.impl.AuthHandlerImpl.lambda$authorizeUser$3(AuthHandlerImpl.java:223)
at io.vertx.ext.web.handler.impl.AuthHandlerImpl.authorize(AuthHandlerImpl.java:107)
at io.vertx.ext.web.handler.impl.AuthHandlerImpl.authorizeUser(AuthHandlerImpl.java:217)
at io.vertx.ext.web.handler.impl.AuthHandlerImpl.lambda$null$1(AuthHandlerImpl.java:161)
at io.vertx.ext.auth.jwt.impl.JWTAuthProviderImpl.authenticate(JWTAuthProviderImpl.java:150)
at io.vertx.ext.web.handler.impl.AuthHandlerImpl.lambda$handle$2(AuthHandlerImpl.java:150)
at io.vertx.ext.web.handler.impl.JWTAuthHandlerImpl.lambda$parseCredentials$0(JWTAuthHandlerImpl.java:76)
at io.vertx.ext.web.handler.impl.AuthorizationAuthHandler.parseAuthorization(AuthorizationAuthHandler.java:97)
at io.vertx.ext.web.handler.impl.JWTAuthHandlerImpl.parseCredentials(JWTAuthHandlerImpl.java:70)
at io.vertx.ext.web.handler.impl.AuthHandlerImpl.handle(AuthHandlerImpl.java:129)
at io.vertx.ext.web.handler.impl.AuthHandlerImpl.handle(AuthHandlerImpl.java:39)
at io.vertx.ext.web.impl.RouteState.handleContext(RouteState.java:1038)
at io.vertx.ext.web.impl.RoutingContextImplBase.iterateNext(RoutingContextImplBase.java:101)
at io.vertx.ext.web.impl.RoutingContextWrapper.next(RoutingContextWrapper.java:176)
at io.vertx.ext.web.handler.impl.ResponseContentTypeHandlerImpl.handle(ResponseContentTypeHandlerImpl.java:54)
at io.vertx.ext.web.handler.impl.ResponseContentTypeHandlerImpl.handle(ResponseContentTypeHandlerImpl.java:28)
at io.vertx.ext.web.impl.RouteState.handleContext(RouteState.java:1038)
at io.vertx.ext.web.impl.RoutingContextImplBase.iterateNext(RoutingContextImplBase.java:137)
at io.vertx.ext.web.impl.RoutingContextWrapper.next(RoutingContextWrapper.java:176)
at io.vertx.ext.web.handler.impl.BodyHandlerImpl$BHandler.doEnd(BodyHandlerImpl.java:296)
at io.vertx.ext.web.handler.impl.BodyHandlerImpl$BHandler.end(BodyHandlerImpl.java:276)
at io.vertx.ext.web.handler.impl.BodyHandlerImpl.lambda$handle$0(BodyHandlerImpl.java:87)
at io.vertx.core.http.impl.HttpServerRequestImpl.onEnd(HttpServerRequestImpl.java:525)
at io.vertx.core.http.impl.HttpServerRequestImpl.handleEnd(HttpServerRequestImpl.java:511)
at io.vertx.core.http.impl.Http1xServerConnection.handleEnd(Http1xServerConnection.java:176)
at io.vertx.core.http.impl.Http1xServerConnection.handleContent(Http1xServerConnection.java:163)
at io.vertx.core.http.impl.Http1xServerConnection.handleMessage(Http1xServerConnection.java:140)
at io.vertx.core.impl.ContextImpl.executeTask(ContextImpl.java:366)
at io.vertx.core.impl.EventLoopContext.execute(EventLoopContext.java:43)
at io.vertx.core.impl.ContextImpl.executeFromIO(ContextImpl.java:229)
at io.vertx.core.net.impl.VertxHandler.channelRead(VertxHandler.java:164)
at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:379)
at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:365)
at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:357)
at io.netty.channel.ChannelInboundHandlerAdapter.channelRead(ChannelInboundHandlerAdapter.java:93)
at io.netty.handler.codec.http.websocketx.extensions.WebSocketServerExtensionHandler.channelRead(WebSocketServerExtensionHandler.java:99)
at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:379)
at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:365)
at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:357)
at io.netty.handler.codec.ByteToMessageDecoder.fireChannelRead(ByteToMessageDecoder.java:324)
at io.netty.handler.codec.ByteToMessageDecoder.channelRead(ByteToMessageDecoder.java:296)
at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:379)
at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:365)
at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:357)
at io.netty.channel.DefaultChannelPipeline$HeadContext.channelRead(DefaultChannelPipeline.java:1410)
at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:379)
at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:365)
at io.netty.channel.DefaultChannelPipeline.fireChannelRead(DefaultChannelPipeline.java:919)
at io.netty.channel.nio.AbstractNioByteChannel$NioByteUnsafe.read(AbstractNioByteChannel.java:166)
at io.netty.channel.nio.NioEventLoop.processSelectedKey(NioEventLoop.java:719)
at io.netty.channel.nio.NioEventLoop.processSelectedKeysOptimized(NioEventLoop.java:655)
at io.netty.channel.nio.NioEventLoop.processSelectedKeys(NioEventLoop.java:581)
at io.netty.channel.nio.NioEventLoop.run(NioEventLoop.java:493)
at io.netty.util.concurrent.SingleThreadEventExecutor$4.run(SingleThreadEventExecutor.java:986)
at io.netty.util.internal.ThreadExecutorMap$2.run(ThreadExecutorMap.java:74)
at io.netty.util.concurrent.FastThreadLocalRunnable.run(FastThreadLocalRunnable.java:30)
at java.base/java.lang.Thread.run(Thread.java:834)
And this is how I handle it in Vertx.3
if (contextFailure instanceof ValidationException) {
ValidationException ve = (ValidationException) contextFailure;
ErrorType errorType = ve.type();
switch(errorType) {
case JSON_INVALID:
case NO_MATCH:
String parameterName = ve.parameterName().startsWith("body") ? ve.parameterName() : "body." + ve.parameterName();
String validationMessage = parameterName + " is invalid.";
context.put("message", validationMessage);
badRequest(context, validationMessage);
break;
default:
String message = "body is invalid.";
context.put("message", message);
badRequest(context, message);
break;
}
}
@javadevmtl Thanks for the quick response. I will try to take a look later this week and at least provide you with a proof-of-concept to let you have a chance to validate.
Grrr... I can't get the current master branch to compile so that I can update the unit tests and try to work toward a solution.
I guess this is still ongoing?
I just tried 4.2.4 and still no field name is provided. It just tells us that there's a string somewhere wrong on the Json. But not which property of the json.
io.vertx.ext.web.validation.BodyProcessorException: [Bad Request] Validation error for body application/json; charset=UTF-8: provided string should respect pattern ^[0-9A-Fa-f]{8}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{12}
For query parameters ParameterProcessorException is adequate with getParameterName()
@vietj Do you foresee this being fixed any time soon?
Any update on this? I can't be the only one who needs this? lol
@javadevmtl No updates... It's quite a bit more involved than I had originally expected. The new implementation to support JSONSchema (to handle OpenAPI 3.1) meant a much bigger change in the schema validation than I had expected. @pmlopes has done a great job getting the JSONSchema implementation compliant, but this work also means that there is no current way to bubble this information back up to provide the detailed exception you are looking for. Fixing this regression is going to be an extremely large amount of work. I don't know that any of us will be able to give you an estimate on if/when this might get resolved.
Yeah, I can't migrate from 3.9.X to 4 because of this. I'm I the only one who actually returns the field name as part of my error messages to the API clients?
Is there a work around?
Also can you point to where the validation happens in the OpenApi code I was curious to go look and see if I could maybe contribute. Last time I tried to look but got lost in the code.
Yes am noticing this with our product testing as well. It would be very helpful to have that information provided again in the validation exception.
Hi any news on this? I would really like to migrate from 3.9.x to 4.x
cc @pmlopes @pk-work
Hi, with Vert.x 4.4 we released a complete re-build of Vert.x OpenAPI [1]. Especially the error messages which are now generated are very helpful. They now look like this:
The value of the request / response body is invalid. Reason: Instance type number is invalid. Expected integer at #/guest/friends/0/age
Or in case a header param is missing:
The related request / response does not contain the required header parameter p1.
But the APIs have changed, so you should expect some work to use the new libraries.
[1] https://vertx.io/docs/#standards
Hi thats fine we already had to make changes to go from 3.x to 4.x Open-Api. This is why we opened this bug because the older 4.x changes removed "functionality".
Just want to make sure with 4.4 and the new error message do we have a way to get the name of the property that failed the validation? In 3.x we had ValidationException.getParameterName() is something similar available in 4.4? Or we have to parse the error message?
For now I'm not convinced that a getParameterName() is beneficial in general, because if the "path" contains cycles the name of the property isn't enough to identify the error location.
So yes, for now you would have to parse the error message. Please check also getCause(). This will give you a JsonSchemaValidationException which has more details. Maybe it's easier for you to get relevant information from JsonSchemaValidationException instead of parsing the error messages.
So far it seems we are 3 people that needed this @InfoSec812 @pantinor and myself (But it doesn't seem other people have had this issue???). I can't speak for the entire API world but for me it makes sense to be able to get the name of the problematic "parameter" as a function even if it's a json path like #/guest/friends/0/age. rather then trying to figure what the name is through these 2 texts:
The value of the request / response body is invalid. Reason: Instance type number is invalid. Expected integer at #/guest/friends/0/age.
The related request / response does not contain the required header parameter p1
I think parsing this would very brittle.
I would also hate to find out that I start migration again and find out that JsonSchemaValidationException isn't adequate.
A function that returns the JSON Path / Pointer to the problematic property would make sense in my opinion,, but not only the "name".
I need too I update my applications with new validators, and now i've problem to have my errors for consumers.
So, need, json pointers for field rejected value perhaps ?
So, with new validators, we need to update OuputUnit and JsonSchemaValidationException to do that ?
i'll try to see that on next week end
Hi @InfoSec812 @javadevmtl @pantinor @Fyro-Ing @tsegismont ,
as I mentioned above, it is not that easy to identify the "parameter" which causes the issue, at least not if you want to have a general solution. I'd also like to have a "getParameter" or "getPointer" method, but that is not that simple.
I will share the following example with you and maybe together we will find a solution.
components:
schemas:
Identifiable:
type: object
properties:
id:
type: string
required:
- id
IntegerElement:
allOf:
- "$ref": "#/components/schemas/Identifiable"
- properties:
value:
type: integer
modelType:
type: string
pattern: IntegerElement
required:
- value
- modelType
StringElement:
allOf:
- "$ref": "#/components/schemas/Identifiable"
- properties:
value:
type: string
modelType:
type: string
pattern: StringElement
required:
- value
- modelType
ListElement:
allOf:
- "$ref": "#/components/schemas/Identifiable"
- properties:
value:
type: array
items:
allOf:
- $ref: "#/components/schemas/ListElement_choice"
modelType:
type: string
pattern: ListElement
required:
- value
- modelType
ListElement_choice:
oneOf:
- "$ref": "#/components/schemas/IntegerElement"
- "$ref": "#/components/schemas/StringElement"
- "$ref": "#/components/schemas/ListElement"
List:
type: array
items:
"$ref": "#/components/schemas/ListElement_choice"
minItems: 1
[
{
"id": "integerElement1",
"value": "ABC",
"modelType": "IntegerElement"
}
]
#/items: Items did not match schema
#/items/$ref: A subschema had errors
#/items/$ref/oneOf: Instance does not match exactly one subschema (0 matches)
#/items/$ref/oneOf/0/$ref: A subschema had errors
#/items/$ref/oneOf/0/$ref/allOf: Instance does not match every subschema
#/items/$ref/oneOf/0/$ref/allOf/1/properties: Property "value" does not match schema
#/items/$ref/oneOf/0/$ref/allOf/1/properties/value/type: Instance type string is invalid. Expected integer
#/items/$ref/oneOf/1/$ref: A subschema had errors
#/items/$ref/oneOf/1/$ref/allOf: Instance does not match every subschema
#/items/$ref/oneOf/1/$ref/allOf/1/properties: Property "modelType" does not match schema
#/items/$ref/oneOf/1/$ref/allOf/1/properties/modelType/pattern: String does not match pattern
#/items/$ref/oneOf/2/$ref: A subschema had errors
#/items/$ref/oneOf/2/$ref/allOf: Instance does not match every subschema
#/items/$ref/oneOf/2/$ref/allOf/1/properties: Property "value" does not match schema
#/items/$ref/oneOf/2/$ref/allOf/1/properties/value/type: Instance type string is invalid. Expected array
#/items/$ref/oneOf/2/$ref/allOf/1/properties: Property "modelType" does not match schema
#/items/$ref/oneOf/2/$ref/allOf/1/properties/modelType/pattern: String does not match pattern
The Problem
As you can see the error is very simple, the IntegerElement requires a integer value and not a String. So in the list of errors the OutputUnit provides, this line
#/items/$ref/oneOf/0/$ref/allOf/1/properties/value/type: Instance type string is invalid. Expected integer
is obviously the correct error and if I understood you correctly you want to have a method that returns this value: /items/$ref/oneOf/0/$ref/allOf/1/properties/value/type.
Now the question to you, how can an algorithm find out that exactly this is the related error? Otherwise I can't provide it in a method.
@pk-work How does it work on vertx-3? In my opinion it should work like vertx-3 but separated into the new classes validators that where created for vertx-4.
Sample stack trace from vertx-3:
io.vertx.ext.web.api.validation.ValidationException: $.myFancyField: does not match the regex pattern ^\d+$
at io.vertx.ext.web.api.validation.ValidationException$ValidationExceptionFactory.generateInvalidJsonBodyException(ValidationException.java:202)
at io.vertx.ext.web.api.validation.impl.JsonTypeValidator.isValid(JsonTypeValidator.java:50)
at io.vertx.ext.web.api.validation.impl.BaseValidationHandler.validateEntireBody(BaseValidationHandler.java:296)
at io.vertx.ext.web.api.validation.impl.BaseValidationHandler.handle(BaseValidationHandler.java:82)
at io.vertx.ext.web.api.validation.impl.BaseValidationHandler.handle(BaseValidationHandler.java:21)
...
And we do
ValidationException ve = (ValidationException)contextFailure;
String validationMessage = ve.parameterName() + " is invalid."
So the message returned to the client in this case will print myFancyField is invalid.
We don't use nested field or really complexe Json. But if vertx-3 ve.parameterName() returns a full Json path then thats exactly what our code would print.
How does it work on vertx-3?
I don't know exactly. But this what the new JSON Schema validation engine provides. Maybe it was possible, because the old engine wasn't able to do such complex schemas ...
As I mentioned in an earlier post you can access the "cause" and get more information. Maybe ((JsonSchemaValidationException) exception.cause).location() do already the job for you in case you have very simple schemas.
Do you have example of what ((JsonSchemaValidationException) exception.cause).location() it would return?
It will return the keywordLocation like #/items/$ref/oneOf/2/$ref/allOf/1/properties/modelType/pattern
@pk-work And to be clear for my case where there's no nesting it will return $.myFancyField as is?
It will return the "keywordLocation" of the first error from OutputUnit. If there is only one error, it will work, otherwise you need to call getCause() recursively.
Here [1] is the code that I'm using in vertx-openapi to generate the text message. I would suggest you create a reproducer with your schema and then we will see if it works or not.
[1] https://github.com/eclipse-vertx/vertx-openapi/blob/d0be35dbf9df2f69d69baef0b5e5e32b333e0b66/src/main/java/io/vertx/openapi/validation/ValidatorException.java#L93
After some discussion, we believe that the best solution is to make the errors from the OutputUnit easily consumable. Then everyone can parse the necessary information from the errors.