Skip to content

Commit 3752b2d

Browse files
authored
feat: Add validation error handling to API and client layers (#241)
- Implemented `ValidationException` and supporting classes to handle `422` validation errors returned by the server. - Updated all API methods to throw `ValidationException` when applicable. - Added support for detailed validation error responses, including error context and locations. - Enhanced test coverage for validation cases in client and hierarchical/hybrid chunking workflows. - Updated documentation and release notes to reflect validation error handling changes. Fixes #240 Signed-off-by: Eric Deandrea <[email protected]>
1 parent 6e754f0 commit 3752b2d

File tree

15 files changed

+512
-14
lines changed

15 files changed

+512
-14
lines changed

docling-serve/docling-serve-api/src/main/java/ai/docling/serve/api/DoclingServeChunkApi.java

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,9 @@ public interface DoclingServeChunkApi {
2121
/**
2222
* Converts and chunks the provided document source(s) into a processed document based on the specified options
2323
* and using a hierarchical chunker for splitting the document into smaller chunks.
24+
* @param request the request containing the document source(s) and options for hierarchical chunking
25+
* @return a {@link ChunkDocumentResponse} containing the processed chunks, optionally the converted document,
26+
* @throws ai.docling.serve.api.validation.ValidationException If request validation fails for any reason.
2427
*/
2528
ChunkDocumentResponse chunkSourceWithHierarchicalChunker(HierarchicalChunkDocumentRequest request);
2629

@@ -32,6 +35,7 @@ public interface DoclingServeChunkApi {
3235
* @param files the files to be processed and chunked using the hierarchical chunker
3336
* @return a {@link ChunkDocumentResponse} containing the processed chunks, optionally the
3437
* converted documents, and associated metadata
38+
* @throws ai.docling.serve.api.validation.ValidationException If request validation fails for any reason.
3539
*/
3640
default ChunkDocumentResponse chunkFilesWithHierarchicalChunker(Path... files) {
3741
return chunkFilesWithHierarchicalChunker(null, files);
@@ -49,6 +53,7 @@ default ChunkDocumentResponse chunkFilesWithHierarchicalChunker(Path... files) {
4953
* @param files the files to be processed and chunked using the hierarchical chunker.
5054
* @return a {@link ChunkDocumentResponse} containing the processed chunks, optionally the
5155
* converted documents, and associated metadata.
56+
* @throws ai.docling.serve.api.validation.ValidationException If request validation fails for any reason.
5257
*/
5358
default ChunkDocumentResponse chunkFilesWithHierarchicalChunker(@Nullable HierarchicalChunkDocumentRequest request, Path... files) {
5459
return chunkSourceWithHierarchicalChunker(createHierarchicalChunkRequest(request, files));
@@ -63,6 +68,7 @@ default ChunkDocumentResponse chunkFilesWithHierarchicalChunker(@Nullable Hierar
6368
* chunker configurations, and optional specifications for output targets
6469
* @return a {@link ChunkDocumentResponse} containing the processed chunks, optionally the
6570
* converted document, and other relevant metadata
71+
* @throws ai.docling.serve.api.validation.ValidationException If request validation fails for any reason.
6672
*/
6773
ChunkDocumentResponse chunkSourceWithHybridChunker(HybridChunkDocumentRequest request);
6874

@@ -74,6 +80,7 @@ default ChunkDocumentResponse chunkFilesWithHierarchicalChunker(@Nullable Hierar
7480
* @param files the files to be processed and chunked using the hybrid chunker
7581
* @return a {@link ChunkDocumentResponse} containing the processed chunks,
7682
* optionally the converted documents, and associated metadata
83+
* @throws ai.docling.serve.api.validation.ValidationException If request validation fails for any reason.
7784
*/
7885
default ChunkDocumentResponse chunkFilesWithHybridChunker(Path... files) {
7986
return chunkFilesWithHybridChunker(null, files);
@@ -91,6 +98,7 @@ default ChunkDocumentResponse chunkFilesWithHybridChunker(Path... files) {
9198
* @param files the files to be processed and chunked using the hybrid chunking strategy.
9299
* @return a {@code ChunkDocumentResponse} containing the processed chunks, optionally the
93100
* converted documents, and associated metadata.
101+
* @throws ai.docling.serve.api.validation.ValidationException If request validation fails for any reason.
94102
*/
95103
default ChunkDocumentResponse chunkFilesWithHybridChunker(@Nullable HybridChunkDocumentRequest request, Path... files) {
96104
return chunkSourceWithHybridChunker(createHybridChunkRequest(request, files));
@@ -104,6 +112,7 @@ default ChunkDocumentResponse chunkFilesWithHybridChunker(@Nullable HybridChunkD
104112
* @param request the request containing the document source(s) and options for hierarchical chunking
105113
* @return a {@link CompletionStage} that resolves to a {@link ChunkDocumentResponse}, which contains
106114
* the processed chunks, optionally the converted document, and processing metadata
115+
* @throws ai.docling.serve.api.validation.ValidationException If request validation fails for any reason.
107116
*/
108117
CompletionStage<ChunkDocumentResponse> chunkSourceWithHierarchicalChunkerAsync(HierarchicalChunkDocumentRequest request);
109118

@@ -117,6 +126,7 @@ default ChunkDocumentResponse chunkFilesWithHybridChunker(@Nullable HybridChunkD
117126
* @return a {@link CompletionStage} resolving to a {@link ChunkDocumentResponse}, which
118127
* includes the processed chunks, optionally the converted documents, and associated
119128
* metadata
129+
* @throws ai.docling.serve.api.validation.ValidationException If request validation fails for any reason.
120130
*/
121131
default CompletionStage<ChunkDocumentResponse> chunkFilesWithHierarchicalChunkerAsync(Path... files) {
122132
return chunkFilesWithHierarchicalChunkerAsync(null, files);
@@ -135,6 +145,7 @@ default CompletionStage<ChunkDocumentResponse> chunkFilesWithHierarchicalChunker
135145
* @return a {@link CompletionStage} that resolves to a {@link ChunkDocumentResponse},
136146
* which includes the processed chunks, optionally the converted documents,
137147
* and associated metadata.
148+
* @throws ai.docling.serve.api.validation.ValidationException If request validation fails for any reason.
138149
*/
139150
default CompletionStage<ChunkDocumentResponse> chunkFilesWithHierarchicalChunkerAsync(@Nullable HierarchicalChunkDocumentRequest request, Path... files) {
140151
return chunkSourceWithHierarchicalChunkerAsync(createHierarchicalChunkRequest(request, files));
@@ -149,6 +160,7 @@ default CompletionStage<ChunkDocumentResponse> chunkFilesWithHierarchicalChunker
149160
* hybrid chunking parameters, and optional specifications for output targets
150161
* @return a {@link CompletionStage} that resolves to a {@link ChunkDocumentResponse}, which includes
151162
* the processed chunks, optionally the converted document, and relevant processing metadata
163+
* @throws ai.docling.serve.api.validation.ValidationException If request validation fails for any reason.
152164
*/
153165
CompletionStage<ChunkDocumentResponse> chunkSourceWithHybridChunkerAsync(HybridChunkDocumentRequest request);
154166

@@ -159,6 +171,7 @@ default CompletionStage<ChunkDocumentResponse> chunkFilesWithHierarchicalChunker
159171
* a valid file location.
160172
* @return A CompletionStage that, when completed, holds a ChunkDocumentResponse containing
161173
* the results of the chunking operation.
174+
* @throws ai.docling.serve.api.validation.ValidationException If request validation fails for any reason.
162175
*/
163176
default CompletionStage<ChunkDocumentResponse> chunkFilesWithHybridChunkerAsync(Path... files) {
164177
return chunkFilesWithHybridChunkerAsync(null, files);
@@ -173,6 +186,7 @@ default CompletionStage<ChunkDocumentResponse> chunkFilesWithHybridChunkerAsync(
173186
* Must not be null or empty.
174187
* @return A {@code CompletionStage<ChunkDocumentResponse>} that completes with the resulting
175188
* {@code ChunkDocumentResponse} once the chunking operation is finished.
189+
* @throws ai.docling.serve.api.validation.ValidationException If request validation fails for any reason.
176190
*/
177191
default CompletionStage<ChunkDocumentResponse> chunkFilesWithHybridChunkerAsync(@Nullable HybridChunkDocumentRequest request, Path... files) {
178192
return chunkSourceWithHybridChunkerAsync(createHybridChunkRequest(request, files));

docling-serve/docling-serve-api/src/main/java/ai/docling/serve/api/DoclingServeClearApi.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ public interface DoclingServeClearApi {
3333
* or other parameters.
3434
* @return a {@link ClearResponse} object indicating the status of the clear
3535
* operation, such as success or failure.
36+
* @throws ai.docling.serve.api.validation.ValidationException If request validation fails for any reason.
3637
*/
3738
ClearResponse clearResults(ClearResultsRequest request);
3839
}

docling-serve/docling-serve-api/src/main/java/ai/docling/serve/api/DoclingServeConvertApi.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ public interface DoclingServeConvertApi {
2222
*
2323
* @param request the {@link ConvertDocumentRequest} containing the source(s), conversion options, and optional target.
2424
* @return a {@link ConvertDocumentResponse} containing the processed document data, processing details, and any errors.
25+
* @throws ai.docling.serve.api.validation.ValidationException If request validation fails for any reason.
2526
*/
2627
ConvertDocumentResponse convertSource(ConvertDocumentRequest request);
2728

@@ -32,6 +33,7 @@ public interface DoclingServeConvertApi {
3233
*
3334
* @param files an array of {@link Path} objects representing the file paths to be converted
3435
* @return a {@link ConvertDocumentResponse} containing the processed document data, metadata, and any errors
36+
* @throws ai.docling.serve.api.validation.ValidationException If request validation fails for any reason.
3537
*/
3638
default ConvertDocumentResponse convertFiles(Path... files) {
3739
return convertFiles(null, files);
@@ -45,6 +47,7 @@ default ConvertDocumentResponse convertFiles(Path... files) {
4547
* @param files an array of {@link Path} objects representing the file paths to be converted
4648
* @return a {@link ConvertDocumentResponse} containing the processed document data, any errors encountered,
4749
* and additional processing metadata
50+
* @throws ai.docling.serve.api.validation.ValidationException If request validation fails for any reason.
4851
*/
4952
default ConvertDocumentResponse convertFiles(@Nullable ConvertDocumentRequest request, Path... files) {
5053
return convertSource(createRequest(request, files));
@@ -69,6 +72,7 @@ default ConvertDocumentResponse convertFiles(@Nullable ConvertDocumentRequest re
6972
* @return a {@link CompletionStage} that completes with the {@link ConvertDocumentResponse}
7073
* when the conversion is finished, or completes exceptionally if the conversion fails
7174
* or times out.
75+
* @throws ai.docling.serve.api.validation.ValidationException If request validation fails for any reason.
7276
*/
7377
CompletionStage<ConvertDocumentResponse> convertSourceAsync(ConvertDocumentRequest request);
7478

@@ -80,6 +84,7 @@ default ConvertDocumentResponse convertFiles(@Nullable ConvertDocumentRequest re
8084
* @return a {@link CompletionStage} that completes with the {@link ConvertDocumentResponse}
8185
* when the conversion finishes, or completes exceptionally if the conversion fails
8286
* or times out
87+
* @throws ai.docling.serve.api.validation.ValidationException If request validation fails for any reason.
8388
*/
8489
default CompletionStage<ConvertDocumentResponse> convertFilesAsync(Path... files) {
8590
return convertFilesAsync(null, files);
@@ -96,6 +101,7 @@ default CompletionStage<ConvertDocumentResponse> convertFilesAsync(Path... files
96101
* @return a {@link CompletionStage} that completes with the {@link ConvertDocumentResponse}
97102
* when the conversion finishes, or completes exceptionally if the conversion fails
98103
* or times out
104+
* @throws ai.docling.serve.api.validation.ValidationException If request validation fails for any reason.
99105
*/
100106
default CompletionStage<ConvertDocumentResponse> convertFilesAsync(@Nullable ConvertDocumentRequest request, Path... files) {
101107
return convertSourceAsync(createRequest(request, files));

docling-serve/docling-serve-api/src/main/java/ai/docling/serve/api/DoclingServeTaskApi.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ public interface DoclingServeTaskApi {
2727
* wait time between polling attempts
2828
* @return a {@link TaskStatusPollResponse} encapsulating the current status, position in the
2929
* queue, and optional metadata for the specified task
30+
* @throws ai.docling.serve.api.validation.ValidationException If request validation fails for any reason.
3031
*/
3132
TaskStatusPollResponse pollTaskStatus(TaskStatusPollRequest request);
3233

@@ -41,6 +42,7 @@ public interface DoclingServeTaskApi {
4142
* for which the result is to be converted
4243
* @return a {@link ConvertDocumentResponse} encapsulating the converted document,
4344
* processing status, timings, and potential errors
45+
* @throws ai.docling.serve.api.validation.ValidationException If request validation fails for any reason.
4446
*/
4547
ConvertDocumentResponse convertTaskResult(TaskResultRequest request);
4648

@@ -56,6 +58,7 @@ public interface DoclingServeTaskApi {
5658
* the task whose result is to be processed and chunked
5759
* @return a {@link ChunkDocumentResponse} containing the chunked result,
5860
* associated documents, processing time, and additional relevant metadata
61+
* @throws ai.docling.serve.api.validation.ValidationException If request validation fails for any reason.
5962
*/
6063
ChunkDocumentResponse chunkTaskResult(TaskResultRequest request);
6164
}
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
package ai.docling.serve.api.validation;
2+
3+
import java.util.List;
4+
5+
import com.fasterxml.jackson.annotation.JsonInclude;
6+
import com.fasterxml.jackson.annotation.JsonProperty;
7+
import com.fasterxml.jackson.annotation.JsonSetter;
8+
import com.fasterxml.jackson.annotation.Nulls;
9+
10+
/**
11+
* Represents a validation error with customizable serialization and deserialization behavior.
12+
*
13+
* This class includes Jackson annotations for JSON handling and Lombok annotations for
14+
* boilerplate code generation, such as getters and builder patterns.
15+
*
16+
* It is designed to be used in scenarios where structured validation error
17+
* details are required to be captured and processed.
18+
*
19+
* An inner static Builder class is provided for constructing ValidationError instances
20+
* using the builder pattern.
21+
*/
22+
@JsonInclude(JsonInclude.Include.NON_EMPTY)
23+
@tools.jackson.databind.annotation.JsonDeserialize(builder = ValidationError.Builder.class)
24+
@lombok.extern.jackson.Jacksonized
25+
@lombok.Builder(toBuilder = true)
26+
@lombok.Getter
27+
@lombok.ToString
28+
public class ValidationError {
29+
/**
30+
* A collection of validation error details associated with the validation error.
31+
*
32+
* @param errorDetails the collection of validation error details
33+
* @return the collection of validation error details
34+
*/
35+
@JsonProperty("detail")
36+
@JsonSetter(nulls = Nulls.AS_EMPTY)
37+
@lombok.Singular
38+
private List<ValidationErrorDetail> errorDetails;
39+
40+
/**
41+
* Builder class for constructing instances of the enclosing class.
42+
*
43+
* This class is used to implement the builder pattern, enabling the creation
44+
* of objects with a more readable and flexible initialization format. The builder
45+
* follows the fluent API design by allowing method chaining.
46+
*/
47+
@tools.jackson.databind.annotation.JsonPOJOBuilder(withPrefix = "")
48+
public static class Builder { }
49+
}
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
package ai.docling.serve.api.validation;
2+
3+
import org.jspecify.annotations.Nullable;
4+
5+
import com.fasterxml.jackson.annotation.JsonInclude;
6+
import com.fasterxml.jackson.annotation.JsonProperty;
7+
8+
/**
9+
* Represents the context information associated with a validation error.
10+
*
11+
* This class provides additional details about the validation error,
12+
* such as the expected schemes relevant to the validation process.
13+
*
14+
* The class leverages Jackson annotations for JSON serialization and
15+
* deserialization, and Lombok annotations to reduce boilerplate code,
16+
* including getter methods and builder patterns.
17+
*
18+
* The builder pattern is implemented through an inner static `Builder` class
19+
* for flexible and readable object construction.
20+
*/
21+
@JsonInclude(JsonInclude.Include.NON_EMPTY)
22+
@tools.jackson.databind.annotation.JsonDeserialize(builder = ValidationErrorContext.Builder.class)
23+
@lombok.extern.jackson.Jacksonized
24+
@lombok.Builder(toBuilder = true)
25+
@lombok.Getter
26+
@lombok.ToString
27+
public class ValidationErrorContext {
28+
/**
29+
* Specifies the expected schemes relevant to a validation process.
30+
*
31+
* @param expectedSchemes the expected schemes relevant to a validation process
32+
* @return the expected schemes relevant to a validation process
33+
*/
34+
@JsonProperty("expected_schemes")
35+
@Nullable
36+
private String expectedSchemes;
37+
38+
/**
39+
* Builder class for constructing instances of the enclosing class.
40+
*
41+
* This class implements the builder pattern, providing a convenient and flexible
42+
* approach to construct objects of the enclosing class. It allows for a fluent
43+
* API style by enabling method chaining during object creation.
44+
*/
45+
@tools.jackson.databind.annotation.JsonPOJOBuilder(withPrefix = "")
46+
public static class Builder { }
47+
}
Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
package ai.docling.serve.api.validation;
2+
3+
import java.util.List;
4+
5+
import org.jspecify.annotations.Nullable;
6+
7+
import com.fasterxml.jackson.annotation.JsonInclude;
8+
import com.fasterxml.jackson.annotation.JsonProperty;
9+
import com.fasterxml.jackson.annotation.JsonSetter;
10+
import com.fasterxml.jackson.annotation.Nulls;
11+
12+
/**
13+
* Represents detailed information about a validation error.
14+
*
15+
* This class provides structured data associated with a validation error,
16+
* including the locations of the error, an error message, the type of
17+
* validation error, and the input causing the error.
18+
*/
19+
@JsonInclude(JsonInclude.Include.NON_EMPTY)
20+
@tools.jackson.databind.annotation.JsonDeserialize(builder = ValidationErrorDetail.Builder.class)
21+
@lombok.extern.jackson.Jacksonized
22+
@lombok.Builder(toBuilder = true)
23+
@lombok.Getter
24+
@lombok.ToString
25+
public class ValidationErrorDetail {
26+
/**
27+
* Represents the specific locations associated with a validation error.
28+
*
29+
* @param loc the specific locations associated with a validation error
30+
* @return the specific locations associated with a validation error
31+
*/
32+
@JsonProperty("loc")
33+
@JsonSetter(nulls = Nulls.AS_EMPTY)
34+
@lombok.Singular
35+
private List<Object> locations;
36+
37+
/**
38+
* Represents an optional validation error message.
39+
*
40+
* @param message the optional validation error message
41+
* @return the optional validation error message
42+
*/
43+
@JsonProperty("msg")
44+
@Nullable
45+
private String message;
46+
47+
/**
48+
* Represents the type of validation error.
49+
*
50+
* @param type the type of validation error
51+
* @return the type of validation error
52+
*/
53+
@JsonProperty("type")
54+
@Nullable
55+
private String type;
56+
57+
/**
58+
* Represents the input causing the validation error.
59+
*
60+
* @param input the input causing the validation error
61+
* @return the input causing the validation error
62+
*/
63+
@JsonProperty("input")
64+
@Nullable
65+
private String input;
66+
67+
/**
68+
* Represents the context information associated with a validation error.
69+
*
70+
* @param context the context information associated with a validation error
71+
* @return the context information associated with a validation error
72+
*/
73+
@JsonProperty("ctx")
74+
@lombok.NonNull
75+
@lombok.Builder.Default
76+
private ValidationErrorContext context = ValidationErrorContext.builder().build();
77+
78+
/**
79+
* Builder class for constructing instances of the enclosing class.
80+
*
81+
* This class is used to implement the builder pattern, providing a flexible
82+
* and readable mechanism for initializing instances of the enclosing class.
83+
*/
84+
@tools.jackson.databind.annotation.JsonPOJOBuilder(withPrefix = "")
85+
public static class Builder { }
86+
}

0 commit comments

Comments
 (0)