Skip to content

feat(api): introduce DocumentRequest and ProcessedDocumentResponse sealed base classes - #634

Open
edeandrea wants to merge 1 commit into
docling-project:mainfrom
edeandrea:issue-632-document-request-base-class
Open

feat(api): introduce DocumentRequest and ProcessedDocumentResponse sealed base classes#634
edeandrea wants to merge 1 commit into
docling-project:mainfrom
edeandrea:issue-632-document-request-base-class

Conversation

@edeandrea

@edeandrea edeandrea commented Aug 13, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Introduces a sealed abstract DocumentRequest base class (ai.docling.serve.api.request) that consolidates the shared sources and target fields from ConvertDocumentRequest, BatchConvertDocumentRequest, and ChunkDocumentRequest
  • Introduces a sealed abstract ProcessedDocumentResponse base class (ai.docling.serve.api.response) that unifies ConvertDocumentResponse and ChunkDocumentResponse under a common type
  • Enables polymorphism — consumers can accept a DocumentRequest or ProcessedDocumentResponse and dispatch based on concrete type (e.g., LangChain4j's DoclingDocumentParser can use ProcessedDocumentResponse as a type bound for generic builders that work with both conversion and chunking results)

Request type hierarchy

DocumentRequest (sealed abstract)
├── ConvertDocumentRequest (final)
├── BatchConvertDocumentRequest (final)
└── ChunkDocumentRequest (sealed abstract)
    ├── HierarchicalChunkDocumentRequest (final)
    └── HybridChunkDocumentRequest (final)

Response type hierarchy

ProcessedDocumentResponse (sealed abstract)
├── ConvertDocumentResponse (sealed abstract)
│   ├── InBodyConvertDocumentResponse (final)
│   ├── PreSignedUrlConvertDocumentResponse (final)
│   ├── PreSignedUrlConvertResponse (final)
│   └── ZipArchiveConvertDocumentResponse (final)
└── ChunkDocumentResponse (final)

Test plan

  • ./gradlew --no-daemon :docling-serve-api:test passes
  • New DocumentRequestTests — 7 tests covering sealed hierarchy, field inheritance, instanceof dispatch, toBuilder(), and BatchConvertDocumentRequest.getTarget() non-null contract
  • New ProcessedDocumentResponseTests — 4 tests covering sealed hierarchy, instanceof assignability for both convert and chunk responses, and sealed permits verification

Resolves #632

Copilot AI lite review requested due to automatic review settings August 13, 2026 02:04
@edeandrea
edeandrea force-pushed the issue-632-document-request-base-class branch from 040a6fc to a3792be Compare August 13, 2026 02:07
@edeandrea edeandrea changed the title feat(docling-serve-api): introduce DocumentRequest sealed base class feat(api): introduce DocumentRequest sealed base class Aug 13, 2026
@edeandrea
edeandrea requested a review from ThomasVitale August 13, 2026 02:07
@edeandrea

Copy link
Copy Markdown
Contributor Author

@ThomasVitale this is what the implementation of #632 would look like. WDYT?

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Introduces a new sealed abstract DocumentRequest base type in docling-serve-api to consolidate shared sources and target request fields across convert, batch-convert, and chunk request models, enabling polymorphic handling of these request types by consumers.

Changes:

  • Added ai.docling.serve.api.request.DocumentRequest (sealed) with shared sources and optional target.
  • Updated ConvertDocumentRequest, BatchConvertDocumentRequest, and ChunkDocumentRequest to extend DocumentRequest (with toString(callSuper = true)).
  • Added DocumentRequestTests covering the sealed hierarchy and inherited field behavior; updated module exports and “What’s New” docs accordingly.

Reviewed changes

Copilot reviewed 8 out of 8 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
docs/src/doc/docs/whats-new.md Adds release note entry for the new DocumentRequest base type and polymorphic usage.
docling-serve/docling-serve-api/src/test/java/ai/docling/serve/api/request/DocumentRequestTests.java New unit tests validating the sealed hierarchy, inheritance, and dispatch behavior.
docling-serve/docling-serve-api/src/main/java/module-info.java Exports the new ai.docling.serve.api.request package.
docling-serve/docling-serve-api/src/main/java/ai/docling/serve/api/request/package-info.java Marks the new package as @NullMarked.
docling-serve/docling-serve-api/src/main/java/ai/docling/serve/api/request/DocumentRequest.java New sealed base request type with shared sources / target.
docling-serve/docling-serve-api/src/main/java/ai/docling/serve/api/convert/request/ConvertDocumentRequest.java Refactors to extend DocumentRequest; keeps convert-specific fields.
docling-serve/docling-serve-api/src/main/java/ai/docling/serve/api/convert/request/BatchConvertDocumentRequest.java Refactors to extend DocumentRequest; enforces non-null target via overridden getter.
docling-serve/docling-serve-api/src/main/java/ai/docling/serve/api/chunk/request/ChunkDocumentRequest.java Refactors to extend DocumentRequest; keeps chunk-specific fields.
Suppressed comments (2)

docling-serve/docling-serve-api/src/test/java/ai/docling/serve/api/request/DocumentRequestTests.java:54

  • Batch requests are documented as requiring a PresignedUrlTarget or S3Target, but this test constructs a BatchConvertDocumentRequest with a ZipTarget. Please switch to a valid batch target implementation.
    DocumentRequest request = BatchConvertDocumentRequest.builder()
        .source(HTTP_SOURCE)
        .target(ZipTarget.builder().build())
        .build();

docling-serve/docling-serve-api/src/test/java/ai/docling/serve/api/request/DocumentRequestTests.java:122

  • Batch requests are documented as requiring a PresignedUrlTarget or S3Target, but this test uses ZipTarget for the batch request in the list. Use a valid batch target here (and consider formatting the List.of(...) entries one-per-line for readability).
    List<DocumentRequest> requests = List.of(
        ConvertDocumentRequest.builder().source(HTTP_SOURCE).build(), BatchConvertDocumentRequest.builder().source(HTTP_SOURCE).target(ZipTarget.builder().build())
            .build(), HierarchicalChunkDocumentRequest.builder().source(HTTP_SOURCE).build(), HybridChunkDocumentRequest.builder().source(HTTP_SOURCE).build()
    );

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Copilot AI review requested due to automatic review settings August 13, 2026 02:10
@edeandrea
edeandrea force-pushed the issue-632-document-request-base-class branch 2 times, most recently from b3c52be to f7127c0 Compare August 13, 2026 02:15
@github-actions

github-actions Bot commented Aug 13, 2026

Copy link
Copy Markdown

:java_duke: JaCoCo coverage report

Overall Project 47.83% 🔴

There is no coverage information present for the Files changed

@github-actions

github-actions Bot commented Aug 13, 2026

Copy link
Copy Markdown
TestsPassed ✅SkippedFailed
Gradle Test Results (all modules & JDKs)1836 ran1836 passed0 skipped0 failed
TestResult
No test annotations available

@github-actions

Copy link
Copy Markdown

HTML test reports are available as workflow artifacts (zipped HTML).

• Download: Artifacts for this run

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 8 out of 8 changed files in this pull request and generated no new comments.

Suppressed comments (7)

docling-serve/docling-serve-api/src/test/java/ai/docling/serve/api/request/DocumentRequestTests.java:54

  • BatchConvertDocumentRequest docs and client tests indicate the batch endpoint requires a PresignedUrlTarget or S3Target; using ZipTarget here makes the test demonstrate an invalid request shape.
  }

  @Test
  void sourcesDefaultToEmptyList() {
    DocumentRequest request = ConvertDocumentRequest.builder().build();

docling-serve/docling-serve-api/src/test/java/ai/docling/serve/api/request/DocumentRequestTests.java:18

  • This test file imports ZipTarget, but batch conversion targets should be PresignedUrlTarget or S3Target (and the updated assertions below use PresignedUrlTarget). Update the import to match the supported batch target types.
import ai.docling.serve.api.convert.request.target.ZipTarget;

docling-serve/docling-serve-api/src/test/java/ai/docling/serve/api/request/DocumentRequestTests.java:122

  • This list-based test also uses ZipTarget for a batch request, which conflicts with the documented batch target contract. Switching to PresignedUrlTarget keeps the test aligned with supported request shapes and improves readability.
    }
    else if (request instanceof HierarchicalChunkDocumentRequest) {
      return "hierarchical-chunk";
    }
    else if (request instanceof HybridChunkDocumentRequest) {

docling-serve/docling-serve-api/src/test/java/ai/docling/serve/api/request/DocumentRequestTests.java:174

  • The codebase consistently formats else if on the same line as the closing brace (e.g., Jackson2ConvertDocumentResponseDeserializer.java:38). This method’s style is inconsistent and may be reformatted by Spotless.
    docling-serve/docling-serve-api/src/main/java/ai/docling/serve/api/request/DocumentRequest.java:34
  • The Javadoc example uses switch pattern matching (e.g., case ConvertDocumentRequest r -> ...), which requires preview features on Java 17 and may not compile for typical consumers of this library. Consider using an instanceof-based example (Java 16+) or explicitly noting the Java version requirement.
 * <p>This is a {@code sealed} class — the only permitted subtypes are
 * {@link ConvertDocumentRequest}, {@link BatchConvertDocumentRequest}, and
 * {@link ChunkDocumentRequest} — enabling exhaustive pattern matching:
 *
 * <pre>{@code

docling-serve/docling-serve-api/src/main/java/ai/docling/serve/api/convert/request/BatchConvertDocumentRequest.java:67

  • getTarget() throws with the message "target is marked non-null but is null", but the non-null contract here is specific to batch requests rather than an annotation-derived constraint. Using a batch-specific message will be clearer for API consumers.
  @Override
  public Target getTarget() {
    return Objects.requireNonNull(super.getTarget(), "target is marked non-null but is null");
  }

docs/src/doc/docs/whats-new.md:28

  • This changelog entry suggests dispatch "via pattern matching". Since the project targets Java 17, it would be clearer to mention instanceof-based dispatch (and optionally note switch pattern matching as Java 21+/preview) to avoid implying consumers must enable preview features.
* **New `DocumentRequest` sealed base class** — `ConvertDocumentRequest`, `BatchConvertDocumentRequest`, and `ChunkDocumentRequest` now extend a common `DocumentRequest` abstract class in the `ai.docling.serve.api.request` package. This enables polymorphism when working with different request types — for example, accepting a `DocumentRequest` and dispatching to the correct endpoint based on the concrete type via pattern matching.

Copilot AI review requested due to automatic review settings August 13, 2026 02:18

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 8 out of 8 changed files in this pull request and generated no new comments.

Suppressed comments (3)

docling-serve/docling-serve-api/src/main/java/ai/docling/serve/api/convert/request/BatchConvertDocumentRequest.java:66

  • The null-check message in getTarget() says "marked non-null" even though the non-null contract is enforced by this override (the superclass getter is @nullable). This makes the exception harder to understand; use a message that states the batch-request contract explicitly.
    return Objects.requireNonNull(super.getTarget(), "target is marked non-null but is null");

docling-serve/docling-serve-api/src/test/java/ai/docling/serve/api/request/DocumentRequestTests.java:65

  • This test verifies instanceof dispatch, but it doesn’t assert the sealed "permits" list on DocumentRequest. Since the PR introduces a sealed base type, add an explicit check of getPermittedSubclasses() so accidental future permit changes are caught by tests.
  void allConcreteSubtypesAreDistinguishableViaInstanceOf() {
    List<DocumentRequest> requests = List.of(
        ConvertDocumentRequest.builder().source(HTTP_SOURCE).build(), BatchConvertDocumentRequest.builder().source(HTTP_SOURCE).target(ZipTarget.builder().build())
            .build(), HierarchicalChunkDocumentRequest.builder().source(HTTP_SOURCE).build(), HybridChunkDocumentRequest.builder().source(HTTP_SOURCE).build()
    );

docs/src/doc/docs/whats-new.md:28

  • This changelog entry introduces the new sealed base class, but it doesn’t mention that ConvertDocumentRequest and BatchConvertDocumentRequest are now final to participate in the sealed hierarchy. That can be a breaking change for consumers who subclassed these request models; consider calling it out here.
* **New `DocumentRequest` sealed base class** — `ConvertDocumentRequest`, `BatchConvertDocumentRequest`, and `ChunkDocumentRequest` now extend a common `DocumentRequest` abstract class in the `ai.docling.serve.api.request` package. This enables polymorphism when working with different request types — for example, accepting a `DocumentRequest` and dispatching to the correct endpoint based on the concrete type via pattern matching.

@github-actions

Copy link
Copy Markdown

HTML test reports are available as workflow artifacts (zipped HTML).

• Download: Artifacts for this run

Copilot AI review requested due to automatic review settings August 13, 2026 02:49
@edeandrea
edeandrea force-pushed the issue-632-document-request-base-class branch from 69a6506 to 426358e Compare August 13, 2026 02:50

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 9 out of 9 changed files in this pull request and generated 1 comment.

Suppressed comments (1)

docling-serve/docling-serve-api/src/test/java/ai/docling/serve/api/request/DocumentRequestTests.java:22

  • PR description says DocumentRequestTests contains 12 test cases including a direct sealed-permits verification. This file currently contains 7 @Test methods and does not verify DocumentRequest’s direct permitted subclasses, so the implementation doesn’t match the stated test plan/coverage.
/**
 * Unit tests for the {@link DocumentRequest} sealed hierarchy.
 */
class DocumentRequestTests {

Copilot AI review requested due to automatic review settings August 13, 2026 02:53

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 9 out of 9 changed files in this pull request and generated no new comments.

Suppressed comments (1)

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

  • ConvertDocumentRequest does not declare a nested Builder type (it declares ConvertDocumentRequestBuilder via Lombok @SuperBuilder), so the explicit type witness .<ConvertDocumentRequest.Builder<?, ?>> is very likely to fail compilation. Removing the explicit type parameter (and relying on var inference) avoids referencing a non-existent nested type.
    var builder = Optional.ofNullable(request).<ConvertDocumentRequest.Builder<?, ?>>map(ConvertDocumentRequest::toBuilder)
        .orElseGet(ConvertDocumentRequest::builder);

@github-actions

Copy link
Copy Markdown

HTML test reports are available as workflow artifacts (zipped HTML).

• Download: Artifacts for this run

@github-actions

Copy link
Copy Markdown

HTML test reports are available as workflow artifacts (zipped HTML).

• Download: Artifacts for this run

Copilot AI review requested due to automatic review settings August 17, 2026 17:16
@edeandrea
edeandrea force-pushed the issue-632-document-request-base-class branch from 426358e to de1c877 Compare August 17, 2026 17:16
@edeandrea edeandrea changed the title feat(api): introduce DocumentRequest sealed base class feat(api): introduce DocumentRequest and ProcessedDocumentResponse sealed base classes Aug 17, 2026
…aled base classes

Introduce sealed type hierarchies for both requests and responses in the
docling-serve-api module, enabling exhaustive pattern matching and
polymorphic handling of document processing operations.

DocumentRequest (ai.docling.serve.api.request) is an abstract sealed class
with common fields (sources, target) that permits ConvertDocumentRequest,
BatchConvertDocumentRequest, and ChunkDocumentRequest.

ProcessedDocumentResponse (ai.docling.serve.api.response) is an abstract
sealed marker class that permits ConvertDocumentResponse (itself sealed)
and ChunkDocumentResponse (final). This enables consumers to use a common
type bound when working with either conversion or chunking results.

Closes docling-project#632

Assisted-By: Claude Code <noreply@anthropic.com>
Signed-off-by: Eric Deandrea <eric.deandrea@ibm.com>
@edeandrea
edeandrea force-pushed the issue-632-document-request-base-class branch from de1c877 to 7cf8632 Compare August 17, 2026 17:17

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 14 out of 14 changed files in this pull request and generated no new comments.

Suppressed comments (2)

docling-serve/docling-serve-api/src/main/java/ai/docling/serve/api/convert/request/BatchConvertDocumentRequest.java:67

  • BatchConvertDocumentRequest.getTarget() currently throws a NullPointerException via Objects.requireNonNull(...), but this module is configured to throw IllegalArgumentException for Lombok @NonNull contract violations (see src/lombok.config). For consistency with the rest of the API (and existing tests that expect IllegalArgumentException for missing required fields), throw IllegalArgumentException when target is absent.
  @Override
  public Target getTarget() {
    return Objects.requireNonNull(super.getTarget(), "target is marked non-null but is null");
  }

docling-serve/docling-serve-api/src/test/java/ai/docling/serve/api/request/DocumentRequestTests.java:98

  • This test currently asserts that BatchConvertDocumentRequest.getTarget() throws NullPointerException when target is missing. The module’s Lombok configuration uses IllegalArgumentException for @NonNull violations (lombok.nonNull.exceptionType=IllegalArgumentException), so the assertion should match the intended contract.
    assertThatThrownBy(request::getTarget)
        .isInstanceOf(NullPointerException.class);
  }

Copilot AI review requested due to automatic review settings August 17, 2026 17:22
@github-actions

Copy link
Copy Markdown

HTML test reports are available as workflow artifacts (zipped HTML).

• Download: Artifacts for this run

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 14 out of 14 changed files in this pull request and generated no new comments.

Suppressed comments (4)

docling-serve/docling-serve-api/src/main/java/ai/docling/serve/api/convert/request/BatchConvertDocumentRequest.java:67

  • BatchConvertDocumentRequest previously enforced a non-null target via Lombok @NonNull (configured to throw IllegalArgumentException in this module), but the new override uses Objects.requireNonNull(...) which throws a NullPointerException and shifts the failure from build-time to getter-time. For consistency with the module’s Lombok @NonNull contract (see docling-serve/docling-serve-api/src/lombok.config:6) and to avoid surprising exception type changes, throw IllegalArgumentException here instead of NullPointerException.
  @Override
  public Target getTarget() {
    return Objects.requireNonNull(super.getTarget(), "target is marked non-null but is null");
  }

docling-serve/docling-serve-api/src/test/java/ai/docling/serve/api/request/DocumentRequestTests.java:97

  • This test currently asserts NullPointerException, but BatchConvertDocumentRequest#getTarget() should align with the module’s Lombok @NonNull convention (IllegalArgumentException via lombok.nonNull.exceptionType) when target is missing.
    assertThatThrownBy(request::getTarget)
        .isInstanceOf(NullPointerException.class);
  }

docling-serve/docling-serve-api/src/test/java/ai/docling/serve/api/response/ProcessedDocumentResponseTests.java:79

  • The project codebase consistently formats else if on the same line as the closing brace (e.g., Jackson2ConvertDocumentResponseDeserializer.java:40). This new test uses a different style (} then else if on the next line), which is likely to fail Spotless formatting checks.
    }
    else if (response instanceof PreSignedUrlConvertDocumentResponse) {
      return "pre-signed-url";
    }
    else if (response instanceof PreSignedUrlConvertResponse) {
      return "pre-signed-url-response";

docling-serve/docling-serve-api/src/test/java/ai/docling/serve/api/request/DocumentRequestTests.java:126

  • The project codebase consistently formats else if on the same line as the closing brace (e.g., Jackson2ConvertDocumentResponseDeserializer.java:40). This new test uses a different style (} then else if on the next line), which is likely to fail Spotless formatting checks.
    }
    else if (request instanceof BatchConvertDocumentRequest) {
      return "batch";
    }
    else if (request instanceof HierarchicalChunkDocumentRequest) {

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Enhance ConvertDocumentRequest hierarchy

2 participants