Conversation
618755c to
ce1b74f
Compare
:java_duke: JaCoCo coverage report
|
|
||||||||||||||
|
HTML test reports are available as workflow artifacts (zipped HTML). • Download: Artifacts for this run |
ce1b74f to
7e70f96
Compare
edeandrea
left a comment
There was a problem hiding this comment.
Thanks for the PR, @Ashfaqbs. Falling back to DoclingServeClientException (status and body kept) when a 422 isn't a validation error is the right behavior. I tried it on both Jackson backends: an HTML page, [1, 2], an empty body and null all end up as a DoclingServeClientException with status 422 and the original body, while a real validation body still throws ValidationException.
A few things I'd like to see before this merges (details inline):
- Narrow the catch to
JsonReadException. This relies on #711, which came out of my review of this PR. See the inline comment. - Move the tests into
AbstractDoclingServeClientTestsso both Jackson backends run them, and add a case for the narrowed catch (inline). - (non-blocking) Return
OptionalfromparseValidationErrorinstead of@Nullable(inline). - Docs.
docs/src/doc/docs/docling-serve/serve-api.md("Validation errors") says a422from docling-serve makes the API throwValidationException. With this change that is only true when the body can be read as a validation error. Otherwise it is aDoclingServeClientExceptioncarrying the status code and body. Could you add a sentence there, and a bug-fix bullet under the current version indocs/src/doc/docs/whats-new.mdnext to the entry from #711?
One thing that is not needed here: a JSON 422 body with no detail (for example {"error": "..."}) still parses into an empty ValidationError and gives a ValidationException with a blank message. That is pre-existing and separate from what this PR fixes, so I opened #710 for it.
|
HTML test reports are available as workflow artifacts (zipped HTML). • Download: Artifacts for this run |
7e70f96 to
9a9707f
Compare
|
Thanks for the thorough review, @edeandrea. Rebased onto main (picked up #711 and #714) and addressed all four points:
Since #714 landed first and already added the no-details filter inline, I merged that filter into
|
|
HTML test reports are available as workflow artifacts (zipped HTML). • Download: Artifacts for this run |
There was a problem hiding this comment.
Thanks for the quick turnaround, @Ashfaqbs, and for folding #714's filter into parseValidationError instead of keeping two checks. All four points from the last round are in: the narrowed JsonReadException catch, the Optional return, the tests in AbstractDoclingServeClientTests (with a working mixin hook for the propagation case), and the docs. I ran the new tests on both Jackson backends and checked that they can fail: widening the catch, making it never fire, and dropping the empty-details filter each turn them red. I've resolved the three earlier threads.
What is left is small, and none of it blocks the merge:
getResponsestyle (inline).isPresent()followed byget()should be a fluentOptionalchain, and the condition of the inline conditional needs parentheses. One suggestion covers both.nullbodies (inline). Nothing guardsOptional.ofNullable, and{"detail":null}is worth a case.- The PR description still describes the first version of this change: it mentions
DoclingServeClientErrorResponseTests, "no container needed", and "if the 422 body cannot be parsed". Could you update it to match what the PR does now? The catch namesJsonReadException(added by #711), the tests are inUnprocessableEntityResponseTestsand run through Testcontainers, and the no-details filter from #714 now lives inparseValidationError.
Once these are in, this looks good to me.
| var validationError = statusCode == 422 ? parseValidationError(body.toString()) : Optional.<ValidationError>empty(); | ||
|
|
||
| if (validationError.isPresent()) { | ||
| var errorText = validationError.get() | ||
| .getErrorDetails() | ||
| .stream() | ||
| .map(ValidationErrorDetail::getMessage) | ||
| .filter(Objects::nonNull) | ||
| .collect(Collectors.joining("\n")); | ||
|
|
||
| throw new ValidationException( | ||
| validationError.get(), "An error occurred while making %s request to %s:\n%s".formatted(request.method(), request.uri(), errorText) | ||
| ); | ||
| } | ||
|
|
||
| throw new DoclingServeClientException("An error occurred: %s".formatted(body.toString()), statusCode, body.toString()); |
There was a problem hiding this comment.
Two small style items in this block, both from the project's coding standards. The suggestion below fixes both:
validationError.isPresent()followed byvalidationError.get()(twice) is theisPresent/getpattern. It reads better as a fluent chain that builds the exception, sogetResponsethrows the result ofmap(...).orElseGet(...). That also letsbody.toString()be computed once, asresponseBody, instead of three times.- The condition of the inline conditional gets parentheses:
(statusCode == 422) ? ... : ....
This is the shape from my first review, before it was folded together with #714's change, so it is easy to lose. I tried the suggestion on top of your branch (nothing is pushed): it compiles, spotlessCheck passes, and the UnprocessableEntityResponseTests pass on both Jackson backends.
| var validationError = statusCode == 422 ? parseValidationError(body.toString()) : Optional.<ValidationError>empty(); | |
| if (validationError.isPresent()) { | |
| var errorText = validationError.get() | |
| .getErrorDetails() | |
| .stream() | |
| .map(ValidationErrorDetail::getMessage) | |
| .filter(Objects::nonNull) | |
| .collect(Collectors.joining("\n")); | |
| throw new ValidationException( | |
| validationError.get(), "An error occurred while making %s request to %s:\n%s".formatted(request.method(), request.uri(), errorText) | |
| ); | |
| } | |
| throw new DoclingServeClientException("An error occurred: %s".formatted(body.toString()), statusCode, body.toString()); | |
| var responseBody = body.toString(); | |
| var validationError = (statusCode == 422) ? parseValidationError(responseBody) : Optional.<ValidationError>empty(); | |
| throw validationError | |
| .<RuntimeException>map(error -> new ValidationException( | |
| error, "An error occurred while making %s request to %s:\n%s".formatted( | |
| request.method(), request.uri(), error.getErrorDetails() | |
| .stream() | |
| .map(ValidationErrorDetail::getMessage) | |
| .filter(Objects::nonNull) | |
| .collect(Collectors.joining("\n"))))) | |
| .orElseGet(() -> new DoclingServeClientException("An error occurred: %s".formatted(responseBody), statusCode, responseBody)); |
| @@ -573,7 +584,7 @@ void resetStubs() { | |||
| "{\"detail\":[]}" | |||
There was a problem hiding this comment.
The tests guard the narrowed catch and the empty-details filter well. One gap: nothing guards Optional.ofNullable in parseValidationError. When I replaced it with Optional.of, all the tests stayed green, so a JSON null body could start throwing an NPE unnoticed. Adding "null" here turns that red on both backends.
{"detail":null} is worth a case too. It used to surface as a JsonReadException on Jackson 2 ("errorDetails cannot be null", from the builder). With this PR it falls back to a DoclingServeClientException on both backends, and a test would keep it that way. I ran both bodies against your branch, and they pass on both Jackson backends.
| "{\"detail\":[]}" | |
| "{\"detail\":[]}", | |
| "null", | |
| "{\"detail\":null}" |
6d377fd to
b8e0a71
Compare
|
HTML test reports are available as workflow artifacts (zipped HTML). • Download: Artifacts for this run |
A 422 whose body cannot be parsed as a validation error (for example an HTML page from a gateway) surfaced as a raw Jackson parse exception and lost the status code and response body. Fall back to DoclingServeClientException, as for any other 4xx/5xx. Rebased onto main after docling-project#711 and docling-project#714: - Narrow the fallback catch to the new JsonReadException instead of a broad RuntimeException, so a failure that is not a parse failure (a broken custom deserializer) still propagates. - parseValidationError now returns Optional<ValidationError> instead of a @nullable, folded together with docling-project#714's no-details filter. - Move the standalone tests into AbstractDoclingServeClientTests' UnprocessableEntityResponseTests, so both Jackson backends run them, and add a case guarding the narrowed catch via a mixin-based failing deserializer for ValidationError. - Update serve-api.md and whats-new.md. Signed-off-by: Ashfaqbs <105435085+Ashfaqbs@users.noreply.github.com>
b8e0a71 to
2e7612b
Compare
|
HTML test reports are available as workflow artifacts (zipped HTML). • Download: Artifacts for this run |
What
DoclingServeClient.getResponseparses every 422 body as aValidationError. If the body is not one (for example an HTML error page from a gateway or proxy in front of docling-serve), the parse failure escapes as a raw Jackson exception, and the status code and response body are lost.Change
If the 422 body cannot be parsed as a validation error, fall back to
DoclingServeClientExceptioncarrying the status code and body, the same as any other 4xx/5xx response. Real validation errors still throwValidationException.Tests
New
DoclingServeClientErrorResponseTests(WireMock, no container needed):ValidationExceptionDoclingServeClientExceptionwith status 422 and the bodyBefore the change the second test fails with
tools.jackson.core.exc.StreamReadException; after it, both pass.spotlessCheckreports no violations in the files touched here.Signed off per DCO.