From 9e1cb091da69f2380f0a6b40f6b102dd86ffc29a Mon Sep 17 00:00:00 2001 From: Eric Deandrea Date: Thu, 11 Dec 2025 13:13:15 -0500 Subject: [PATCH] refactor: Refactor clear and task APIs to utilize request objects I decided to go back to encapsulating everything to do with a request into a request object. Doing it the other way just makes method signatures too brittle and open to breaking changes. Signed-off-by: Eric Deandrea --- .../serve/api/DoclingServeClearApi.java | 39 +++----- .../serve/api/DoclingServeTaskApi.java | 81 +++++++---------- .../serve/api/clear/request/ClearRequest.java | 33 +++++++ .../serve/api/clear/request/package-info.java | 4 + .../api/task/request/TaskResultRequest.java | 21 +++++ .../task/request/TaskStatusPollRequest.java | 42 +++++++++ .../serve/api/task/request/package-info.java | 7 ++ .../src/main/java/module-info.java | 2 + .../api/clear/request/ClearRequestTests.java | 25 ++++++ .../task/request/TaskResultRequestTests.java | 21 +++++ .../request/TaskStatusPollRequestTests.java | 39 ++++++++ .../docling/serve/client/ClearOperations.java | 25 +++--- .../serve/client/DoclingServeClient.java | 21 ++--- .../docling/serve/client/TaskOperations.java | 89 +++++++++---------- .../AbstractDoclingServeClientTests.java | 27 ++++-- 15 files changed, 324 insertions(+), 152 deletions(-) create mode 100644 docling-serve/docling-serve-api/src/main/java/ai/docling/serve/api/clear/request/ClearRequest.java create mode 100644 docling-serve/docling-serve-api/src/main/java/ai/docling/serve/api/clear/request/package-info.java create mode 100644 docling-serve/docling-serve-api/src/main/java/ai/docling/serve/api/task/request/TaskResultRequest.java create mode 100644 docling-serve/docling-serve-api/src/main/java/ai/docling/serve/api/task/request/TaskStatusPollRequest.java create mode 100644 docling-serve/docling-serve-api/src/main/java/ai/docling/serve/api/task/request/package-info.java create mode 100644 docling-serve/docling-serve-api/src/test/java/ai/docling/serve/api/clear/request/ClearRequestTests.java create mode 100644 docling-serve/docling-serve-api/src/test/java/ai/docling/serve/api/task/request/TaskResultRequestTests.java create mode 100644 docling-serve/docling-serve-api/src/test/java/ai/docling/serve/api/task/request/TaskStatusPollRequestTests.java diff --git a/docling-serve/docling-serve-api/src/main/java/ai/docling/serve/api/DoclingServeClearApi.java b/docling-serve/docling-serve-api/src/main/java/ai/docling/serve/api/DoclingServeClearApi.java index b1a85b0e..62990f2b 100644 --- a/docling-serve/docling-serve-api/src/main/java/ai/docling/serve/api/DoclingServeClearApi.java +++ b/docling-serve/docling-serve-api/src/main/java/ai/docling/serve/api/DoclingServeClearApi.java @@ -1,9 +1,6 @@ package ai.docling.serve.api; -import java.time.Duration; - -import org.jspecify.annotations.Nullable; - +import ai.docling.serve.api.clear.request.ClearRequest; import ai.docling.serve.api.clear.response.ClearResponse; /** @@ -13,15 +10,6 @@ * on specified thresholds or default configurations. */ public interface DoclingServeClearApi { - /** - * Represents the default duration used as a threshold for clearing stale results - * or data in the Docling Serve Clear API. Results older than this duration - * are considered stale and may be subject to cleanup. - * - * The value is predefined as 1 hour (3600 seconds). - */ - Duration DEFAULT_OLDER_THAN = Duration.ofSeconds(3600); - /** * Clears all registered converters associated with the API. * This method removes any previously configured or cached converters, @@ -31,22 +19,15 @@ public interface DoclingServeClearApi { ClearResponse clearConverters(); /** - * Clears stored results that are older than the specified duration threshold. - * This method is used for housekeeping to remove stale or outdated data from the system. - * - * @param olderThen the duration threshold; only results older than this duration will be cleared. - * @return a {@link ClearResponse} object containing the status of the clear operation. - */ - ClearResponse clearResults(@Nullable Duration olderThen); - - /** - * Clears stored results that are older than the default duration threshold. - * This method uses the pre-defined {@code DEFAULT_OLDER_THAN} as the threshold - * to determine which results are considered stale and should be removed. + * Clears stored results based on the specified {@link ClearRequest}. + * This method removes results that match the criteria provided in the + * request, such as results older than a specified duration. * - * @return a {@link ClearResponse} object containing the status of the clear operation. + * @param request an instance of {@link ClearRequest} containing the criteria + * for clearing stored results, including the duration threshold + * or other parameters. + * @return a {@link ClearResponse} object indicating the status of the clear + * operation, such as success or failure. */ - default ClearResponse clearResults() { - return clearResults(DEFAULT_OLDER_THAN); - } + ClearResponse clearResults(ClearRequest request); } diff --git a/docling-serve/docling-serve-api/src/main/java/ai/docling/serve/api/DoclingServeTaskApi.java b/docling-serve/docling-serve-api/src/main/java/ai/docling/serve/api/DoclingServeTaskApi.java index da25a987..6290b359 100644 --- a/docling-serve/docling-serve-api/src/main/java/ai/docling/serve/api/DoclingServeTaskApi.java +++ b/docling-serve/docling-serve-api/src/main/java/ai/docling/serve/api/DoclingServeTaskApi.java @@ -1,11 +1,9 @@ package ai.docling.serve.api; -import java.time.Duration; - -import org.jspecify.annotations.Nullable; - import ai.docling.serve.api.chunk.response.ChunkDocumentResponse; import ai.docling.serve.api.convert.response.ConvertDocumentResponse; +import ai.docling.serve.api.task.request.TaskResultRequest; +import ai.docling.serve.api.task.request.TaskStatusPollRequest; import ai.docling.serve.api.task.response.TaskStatusPollResponse; /** @@ -18,59 +16,46 @@ */ public interface DoclingServeTaskApi { /** - * The default wait time between status polling attempts for a task. - *

- * This value is used when no explicit wait time is specified in a - * {@code TaskStatusPollRequest} instance. It is set to {@link Duration#ZERO}, - * meaning there is no delay by default between consecutive polling attempts. - *

- */ - Duration DEFAULT_STATUS_POLL_WAIT_TIME = Duration.ZERO; - - /** - * Polls the status of a task asynchronously and retrieves its current state. - * Allows for configurable wait time between polling attempts. - * If the wait time is {@code }, the default wait time ({@link #DEFAULT_STATUS_POLL_WAIT_TIME}) is used. + * Polls the status of an asynchronous task based on the provided request. * - * @param taskId the unique identifier of the task whose status is being polled - * @param waitTime the duration to wait before polling the status, or null to use the default polling interval - * @return a {@link TaskStatusPollResponse} containing the current status of the task and associated metadata - */ - TaskStatusPollResponse pollTaskStatus(String taskId, @Nullable Duration waitTime); - - /** - * Polls the status of a task asynchronously using the default wait time. - * This convenience method delegates to {@link #pollTaskStatus(String, Duration)} - * with {@code DEFAULT_STATUS_POLL_WAIT_TIME} as the wait time. + * This method retrieves the current status of a task using the task identifier specified + * in the request object. It allows monitoring of a task's progress, position in the queue, + * and detailed status metadata, if available. The polling behavior can be influenced by + * optional configurations such as the wait time between polling attempts provided in the request. * - * @param taskId the unique identifier of the task whose status is being polled - * @return a {@link TaskStatusPollResponse} containing the current status of the task - * and associated metadata + * @param request the {@link TaskStatusPollRequest} containing the task identifier and optional + * wait time between polling attempts + * @return a {@link TaskStatusPollResponse} encapsulating the current status, position in the + * queue, and optional metadata for the specified task */ - default TaskStatusPollResponse pollTaskStatus(String taskId) { - return pollTaskStatus(taskId, DEFAULT_STATUS_POLL_WAIT_TIME); - } + TaskStatusPollResponse pollTaskStatus(TaskStatusPollRequest request); /** - * Converts the completed task result identified by the provided task ID into a document response. - * This method processes the task data associated with the given ID and generates a response - * encapsulating the converted document details. + * Converts the result of a completed task into a document format as specified in the request. * - * @param taskId the unique identifier of the task whose result needs to be converted into a document response - * @return a {@link ConvertDocumentResponse} containing the details of the converted document, processing metadata, - * errors (if any), and other relevant information + * This method processes the task result identified by the unique task ID provided in + * the request, performs any necessary transformation, and returns a response + * containing the converted document and related details. + * + * @param request the {@link TaskResultRequest} containing the unique task identifier + * for which the result is to be converted + * @return a {@link ConvertDocumentResponse} encapsulating the converted document, + * processing status, timings, and potential errors */ - ConvertDocumentResponse convertTaskResult(String taskId); + ConvertDocumentResponse convertTaskResult(TaskResultRequest request); /** - * Processes the results of a completed task identified by the given task ID and generates a - * response containing chunked document details. This method is used to break down the document - * associated with the task into manageable chunks, making it suitable for subsequent processing - * or analysis. + * Processes the result of a completed task and splits it into smaller chunks as per + * the provided request specifications. + * + * The method uses the unique task identifier, provided in the request, to locate the + * task result. It then analyzes the result and breaks it into chunks, which are + * included in the response along with any relevant metadata. * - * @param taskId the unique identifier of the task whose result is to be processed and chunked into - * a {@link ChunkDocumentResponse} - * @return a {@link ChunkDocumentResponse} containing the chunked document details and related metadata + * @param request the {@link TaskResultRequest} containing the unique identifier of + * the task whose result is to be processed and chunked + * @return a {@link ChunkDocumentResponse} containing the chunked result, + * associated documents, processing time, and additional relevant metadata */ - ChunkDocumentResponse chunkTaskResult(String taskId); + ChunkDocumentResponse chunkTaskResult(TaskResultRequest request); } diff --git a/docling-serve/docling-serve-api/src/main/java/ai/docling/serve/api/clear/request/ClearRequest.java b/docling-serve/docling-serve-api/src/main/java/ai/docling/serve/api/clear/request/ClearRequest.java new file mode 100644 index 00000000..f7667f26 --- /dev/null +++ b/docling-serve/docling-serve-api/src/main/java/ai/docling/serve/api/clear/request/ClearRequest.java @@ -0,0 +1,33 @@ +package ai.docling.serve.api.clear.request; + +import java.time.Duration; + +/** + * Represents a request to clear stale data via the Docling Serve Clear API. + * This class provides a mechanism to specify a threshold duration, after which data + * is considered stale and eligible for cleanup. + * + * The {@code olderThen} field specifies the duration threshold, with a default + * value of {@link #DEFAULT_OLDER_THAN}, predefined as 1 hour (3600 seconds). + * This default can be overridden using the builder or by explicitly passing a custom + * duration at runtime. + * + * Instances of this class are immutable and can be created or modified through its builder. + */ +@lombok.Builder(toBuilder = true) +@lombok.Getter +@lombok.ToString +public class ClearRequest { + /** + * Represents the default duration used as a threshold for clearing stale results + * or data in the Docling Serve Clear API. Results older than this duration + * are considered stale and may be subject to cleanup. + * + * The value is predefined as 1 hour (3600 seconds). + */ + public static final Duration DEFAULT_OLDER_THAN = Duration.ofSeconds(3600); + + @lombok.NonNull + @lombok.Builder.Default + private Duration olderThen = DEFAULT_OLDER_THAN; +} diff --git a/docling-serve/docling-serve-api/src/main/java/ai/docling/serve/api/clear/request/package-info.java b/docling-serve/docling-serve-api/src/main/java/ai/docling/serve/api/clear/request/package-info.java new file mode 100644 index 00000000..66ba63c8 --- /dev/null +++ b/docling-serve/docling-serve-api/src/main/java/ai/docling/serve/api/clear/request/package-info.java @@ -0,0 +1,4 @@ +@NullMarked +package ai.docling.serve.api.clear.request; + +import org.jspecify.annotations.NullMarked; diff --git a/docling-serve/docling-serve-api/src/main/java/ai/docling/serve/api/task/request/TaskResultRequest.java b/docling-serve/docling-serve-api/src/main/java/ai/docling/serve/api/task/request/TaskResultRequest.java new file mode 100644 index 00000000..f413c3ed --- /dev/null +++ b/docling-serve/docling-serve-api/src/main/java/ai/docling/serve/api/task/request/TaskResultRequest.java @@ -0,0 +1,21 @@ +package ai.docling.serve.api.task.request; + +/** + * Represents a request to retrieve the result of a task. + * + * This class is used to encapsulate the necessary information required to + * request the result corresponding to the task identified by its unique task ID. + * Instances of this class are immutable and can be built using the provided + * builder methods. + * + * Attributes: + * - `taskId`: The unique identifier of the task whose result is being requested. + * This field is mandatory and must not be null. + */ +@lombok.Builder(toBuilder = true) +@lombok.Getter +@lombok.ToString +public class TaskResultRequest { + @lombok.NonNull + private String taskId; +} diff --git a/docling-serve/docling-serve-api/src/main/java/ai/docling/serve/api/task/request/TaskStatusPollRequest.java b/docling-serve/docling-serve-api/src/main/java/ai/docling/serve/api/task/request/TaskStatusPollRequest.java new file mode 100644 index 00000000..c6d7156e --- /dev/null +++ b/docling-serve/docling-serve-api/src/main/java/ai/docling/serve/api/task/request/TaskStatusPollRequest.java @@ -0,0 +1,42 @@ +package ai.docling.serve.api.task.request; + +import java.time.Duration; + +/** + * Represents a request for polling the status of a task. + * + * This class encapsulates the information required to poll the status of a specific task. + * It includes the unique task identifier and an optional wait time between consecutive + * polling attempts. Instances of this class are immutable and can be built using the + * provided builder methods. + * + * Attributes: + * - `DEFAULT_STATUS_POLL_WAIT_TIME`: Specifies the default value for the wait time + * between polling attempts. By default, this is set to {@link Duration#ZERO}, indicating + * no delay between consecutive polling attempts. + * - `taskId`: The unique identifier of the task whose status is being polled. This field is + * mandatory and must not be null. + * - `waitTime`: The duration to wait between consecutive polling attempts. If not explicitly + * set, it defaults to {@link #DEFAULT_STATUS_POLL_WAIT_TIME}. + */ +@lombok.Builder(toBuilder = true) +@lombok.Getter +@lombok.ToString +public class TaskStatusPollRequest { + /** + * The default wait time between status polling attempts for a task. + *

+ * This value is used when no explicit wait time is specified in a + * {@code TaskStatusPollRequest} instance. It is set to {@link Duration#ZERO}, + * meaning there is no delay by default between consecutive polling attempts. + *

+ */ + public static final Duration DEFAULT_STATUS_POLL_WAIT_TIME = Duration.ZERO; + + @lombok.NonNull + private String taskId; + + @lombok.NonNull + @lombok.Builder.Default + private Duration waitTime = DEFAULT_STATUS_POLL_WAIT_TIME; +} diff --git a/docling-serve/docling-serve-api/src/main/java/ai/docling/serve/api/task/request/package-info.java b/docling-serve/docling-serve-api/src/main/java/ai/docling/serve/api/task/request/package-info.java new file mode 100644 index 00000000..701fb4c6 --- /dev/null +++ b/docling-serve/docling-serve-api/src/main/java/ai/docling/serve/api/task/request/package-info.java @@ -0,0 +1,7 @@ +/** + * The Docling task api + */ +@NullMarked +package ai.docling.serve.api.task.request; + +import org.jspecify.annotations.NullMarked; diff --git a/docling-serve/docling-serve-api/src/main/java/module-info.java b/docling-serve/docling-serve-api/src/main/java/module-info.java index c58f7736..eb589609 100644 --- a/docling-serve/docling-serve-api/src/main/java/module-info.java +++ b/docling-serve/docling-serve-api/src/main/java/module-info.java @@ -27,9 +27,11 @@ exports ai.docling.serve.api.convert.response; // Clear API + exports ai.docling.serve.api.clear.request; exports ai.docling.serve.api.clear.response; // Task API + exports ai.docling.serve.api.task.request; exports ai.docling.serve.api.task.response; // Serialization helpers diff --git a/docling-serve/docling-serve-api/src/test/java/ai/docling/serve/api/clear/request/ClearRequestTests.java b/docling-serve/docling-serve-api/src/test/java/ai/docling/serve/api/clear/request/ClearRequestTests.java new file mode 100644 index 00000000..91c00250 --- /dev/null +++ b/docling-serve/docling-serve-api/src/test/java/ai/docling/serve/api/clear/request/ClearRequestTests.java @@ -0,0 +1,25 @@ +package ai.docling.serve.api.clear.request; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatExceptionOfType; + +import org.assertj.core.api.InstanceOfAssertFactories; +import org.junit.jupiter.api.Test; + +class ClearRequestTests { + @Test + void nullOlderThen() { + assertThatExceptionOfType(IllegalArgumentException.class) + .isThrownBy(() -> ClearRequest.builder().olderThen(null).build()) + .withMessage("olderThen is marked non-null but is null"); + } + + @Test + void defaultOlderThen() { + assertThat(ClearRequest.builder().build()) + .isNotNull() + .extracting(ClearRequest::getOlderThen) + .asInstanceOf(InstanceOfAssertFactories.DURATION) + .isEqualByComparingTo(ClearRequest.DEFAULT_OLDER_THAN); + } +} diff --git a/docling-serve/docling-serve-api/src/test/java/ai/docling/serve/api/task/request/TaskResultRequestTests.java b/docling-serve/docling-serve-api/src/test/java/ai/docling/serve/api/task/request/TaskResultRequestTests.java new file mode 100644 index 00000000..c5fdf742 --- /dev/null +++ b/docling-serve/docling-serve-api/src/test/java/ai/docling/serve/api/task/request/TaskResultRequestTests.java @@ -0,0 +1,21 @@ +package ai.docling.serve.api.task.request; + +import static org.assertj.core.api.Assertions.assertThatExceptionOfType; + +import org.junit.jupiter.api.Test; + +class TaskResultRequestTests { + @Test + void nullTaskId() { + assertThatExceptionOfType(IllegalArgumentException.class) + .isThrownBy(() -> TaskResultRequest.builder().taskId(null).build()) + .withMessage("taskId is marked non-null but is null"); + } + + @Test + void noTaskId() { + assertThatExceptionOfType(IllegalArgumentException.class) + .isThrownBy(() -> TaskResultRequest.builder().build()) + .withMessage("taskId is marked non-null but is null"); + } +} diff --git a/docling-serve/docling-serve-api/src/test/java/ai/docling/serve/api/task/request/TaskStatusPollRequestTests.java b/docling-serve/docling-serve-api/src/test/java/ai/docling/serve/api/task/request/TaskStatusPollRequestTests.java new file mode 100644 index 00000000..ecc85e6a --- /dev/null +++ b/docling-serve/docling-serve-api/src/test/java/ai/docling/serve/api/task/request/TaskStatusPollRequestTests.java @@ -0,0 +1,39 @@ +package ai.docling.serve.api.task.request; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatExceptionOfType; + +import org.assertj.core.api.InstanceOfAssertFactories; +import org.junit.jupiter.api.Test; + +class TaskStatusPollRequestTests { + @Test + void nullTaskId() { + assertThatExceptionOfType(IllegalArgumentException.class) + .isThrownBy(() -> TaskStatusPollRequest.builder().taskId(null).build()) + .withMessage("taskId is marked non-null but is null"); + } + + @Test + void noTaskId() { + assertThatExceptionOfType(IllegalArgumentException.class) + .isThrownBy(() -> TaskStatusPollRequest.builder().build()) + .withMessage("taskId is marked non-null but is null"); + } + + @Test + void nullWaitTime() { + assertThatExceptionOfType(IllegalArgumentException.class) + .isThrownBy(() -> TaskStatusPollRequest.builder().waitTime(null).build()) + .withMessage("waitTime is marked non-null but is null"); + } + + @Test + void defaultWaitTime() { + assertThat(TaskStatusPollRequest.builder().taskId("1").build()) + .isNotNull() + .extracting(TaskStatusPollRequest::getWaitTime) + .asInstanceOf(InstanceOfAssertFactories.DURATION) + .isEqualByComparingTo(TaskStatusPollRequest.DEFAULT_STATUS_POLL_WAIT_TIME); + } +} diff --git a/docling-serve/docling-serve-client/src/main/java/ai/docling/serve/client/ClearOperations.java b/docling-serve/docling-serve-client/src/main/java/ai/docling/serve/client/ClearOperations.java index 43b2dde9..87bd1093 100644 --- a/docling-serve/docling-serve-client/src/main/java/ai/docling/serve/client/ClearOperations.java +++ b/docling-serve/docling-serve-client/src/main/java/ai/docling/serve/client/ClearOperations.java @@ -1,12 +1,9 @@ package ai.docling.serve.client; -import java.time.Duration; -import java.util.Optional; - -import org.jspecify.annotations.Nullable; - import ai.docling.serve.api.DoclingServeClearApi; +import ai.docling.serve.api.clear.request.ClearRequest; import ai.docling.serve.api.clear.response.ClearResponse; +import ai.docling.serve.api.util.ValidationUtils; /** * Base class for clear API operations. Provides functionality for managing and cleaning up @@ -27,17 +24,17 @@ public ClearResponse clearConverters() { } /** - * Clears the results stored by the service that are older than the specified duration. + * Clears stale results retained by the service, based on the specified threshold duration. + * Results older than the duration specified in the {@link ClearRequest} parameter will be removed. * - * @param olderThen the {@link Duration} indicating the age threshold. Results older than - * this duration will be cleared. - * @return a {@link ClearResponse} containing information about the outcome of the clear operation. + * @param request the {@link ClearRequest} object containing the threshold duration for clearing results; + * must not be null. + * @return a {@link ClearResponse} object representing the result of the clear operation, + * including the status of the operation. */ - public ClearResponse clearResults(@Nullable Duration olderThen) { - var olderThenSeconds = Optional.ofNullable(olderThen) - .orElse(DEFAULT_OLDER_THAN) - .toSeconds(); + public ClearResponse clearResults(ClearRequest request) { + ValidationUtils.ensureNotNull(request, "request"); - return this.httpOperations.executeGet("/v1/clear/results?older_then=%d".formatted(olderThenSeconds), ClearResponse.class); + return this.httpOperations.executeGet("/v1/clear/results?older_then=%d".formatted(request.getOlderThen().toSeconds()), ClearResponse.class); } } diff --git a/docling-serve/docling-serve-client/src/main/java/ai/docling/serve/client/DoclingServeClient.java b/docling-serve/docling-serve-client/src/main/java/ai/docling/serve/client/DoclingServeClient.java index 5a9bd7e7..e0abb1ad 100644 --- a/docling-serve/docling-serve-client/src/main/java/ai/docling/serve/client/DoclingServeClient.java +++ b/docling-serve/docling-serve-client/src/main/java/ai/docling/serve/client/DoclingServeClient.java @@ -12,12 +12,10 @@ import java.net.http.HttpResponse; import java.net.http.HttpResponse.BodyHandlers; import java.nio.ByteBuffer; -import java.time.Duration; import java.util.Objects; import java.util.Optional; import java.util.concurrent.Flow.Subscriber; -import org.jspecify.annotations.Nullable; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -30,10 +28,13 @@ import ai.docling.serve.api.chunk.request.HierarchicalChunkDocumentRequest; import ai.docling.serve.api.chunk.request.HybridChunkDocumentRequest; import ai.docling.serve.api.chunk.response.ChunkDocumentResponse; +import ai.docling.serve.api.clear.request.ClearRequest; import ai.docling.serve.api.clear.response.ClearResponse; import ai.docling.serve.api.convert.request.ConvertDocumentRequest; import ai.docling.serve.api.convert.response.ConvertDocumentResponse; import ai.docling.serve.api.health.HealthCheckResponse; +import ai.docling.serve.api.task.request.TaskResultRequest; +import ai.docling.serve.api.task.request.TaskStatusPollRequest; import ai.docling.serve.api.task.response.TaskStatusPollResponse; /** @@ -221,18 +222,18 @@ public ChunkDocumentResponse chunkSourceWithHybridChunker(HybridChunkDocumentReq } @Override - public TaskStatusPollResponse pollTaskStatus(String taskId, @Nullable Duration waitTime) { - return this.taskOps.pollTaskStatus(taskId, waitTime); + public TaskStatusPollResponse pollTaskStatus(TaskStatusPollRequest request) { + return this.taskOps.pollTaskStatus(request); } @Override - public ConvertDocumentResponse convertTaskResult(String taskId) { - return this.taskOps.convertTaskResult(taskId); + public ConvertDocumentResponse convertTaskResult(TaskResultRequest request) { + return this.taskOps.convertTaskResult(request); } @Override - public ChunkDocumentResponse chunkTaskResult(String taskId) { - return this.taskOps.chunkTaskResult(taskId); + public ChunkDocumentResponse chunkTaskResult(TaskResultRequest request) { + return this.taskOps.chunkTaskResult(request); } @Override @@ -241,8 +242,8 @@ public ClearResponse clearConverters() { } @Override - public ClearResponse clearResults(Duration olderThen) { - return this.clearOps.clearResults(olderThen); + public ClearResponse clearResults(ClearRequest request) { + return this.clearOps.clearResults(request); } private class LoggingBodyPublisher implements BodyPublisher { diff --git a/docling-serve/docling-serve-client/src/main/java/ai/docling/serve/client/TaskOperations.java b/docling-serve/docling-serve-client/src/main/java/ai/docling/serve/client/TaskOperations.java index fc39420a..d34d099c 100644 --- a/docling-serve/docling-serve-client/src/main/java/ai/docling/serve/client/TaskOperations.java +++ b/docling-serve/docling-serve-client/src/main/java/ai/docling/serve/client/TaskOperations.java @@ -1,13 +1,10 @@ package ai.docling.serve.client; -import java.time.Duration; -import java.util.Optional; - -import org.jspecify.annotations.Nullable; - import ai.docling.serve.api.DoclingServeTaskApi; import ai.docling.serve.api.chunk.response.ChunkDocumentResponse; import ai.docling.serve.api.convert.response.ConvertDocumentResponse; +import ai.docling.serve.api.task.request.TaskResultRequest; +import ai.docling.serve.api.task.request.TaskStatusPollRequest; import ai.docling.serve.api.task.response.TaskStatusPollResponse; import ai.docling.serve.api.util.ValidationUtils; @@ -25,61 +22,63 @@ final class TaskOperations implements DoclingServeTaskApi { /** * Polls the current status of a specified task. * - * This method sends a request to the server to retrieve the current status - * of the task identified by the given {@code taskId}. Optionally, a {@code waitTime} - * can be specified to indicate how long the server should wait for a status change - * before responding. + * This method sends a GET request to the task status API endpoint to retrieve + * the current status of a task. The request includes the task ID and an optional + * wait time between polling attempts. The response contains details about the + * task's status, position in the queue, and any relevant metadata. * - * @param taskId the unique identifier of the task whose status is being polled. - * Must not be blank or null. - * @param waitTime an optional {@link Duration} specifying how long the server should - * wait for a status change before responding. If null, no wait is applied. - * @return a {@link TaskStatusPollResponse} containing the current status details of the task. - * @throws IllegalArgumentException if {@code taskId} is blank or null. + * @param request an instance of {@link TaskStatusPollRequest} containing the + * unique task identifier and optional wait time for polling. + * Must not be null. + * @return a {@link TaskStatusPollResponse} containing the current status of + * the task, its position in the queue, and any associated metadata. + * @throws IllegalArgumentException if the {@code request} is null. */ - public TaskStatusPollResponse pollTaskStatus(String taskId, @Nullable Duration waitTime) { - ValidationUtils.ensureNotBlank(taskId, "taskId"); - - var waitTimeSeconds = Optional.ofNullable(waitTime) - .orElse(DEFAULT_STATUS_POLL_WAIT_TIME) - .toSeconds(); + public TaskStatusPollResponse pollTaskStatus(TaskStatusPollRequest request) { + ValidationUtils.ensureNotNull(request, "request"); - return this.httpOperations.executeGet("/v1/status/poll/%s?wait=%d".formatted(taskId, waitTimeSeconds), TaskStatusPollResponse.class); + return this.httpOperations.executeGet( + "/v1/status/poll/%s?wait=%d".formatted( + request.getTaskId(), + request.getWaitTime().toSeconds() + ), + TaskStatusPollResponse.class + ); } /** * Retrieves the result of a completed task identified by the specified task ID. * - * This method sends a GET request to the server to fetch the result of the task. - * The returned response includes details about the converted document and any - * potential errors or processing metadata associated with the task. + * This method sends a GET request to fetch the result of a task that has been processed. + * The response includes details about the converted document, processing time, status, + * and any potential errors or additional metadata related to the task. * - * @param taskId the unique identifier of the task whose result is being fetched. - * Must not be blank or null. - * @return a {@link ConvertDocumentResponse} containing the details of the converted document, - * processing time, status, and any errors encountered during processing. - * @throws IllegalArgumentException if {@code taskId} is blank or null. + * @param request an instance of {@link TaskResultRequest} containing the unique task + * identifier. Must not be null. + * @return a {@link ConvertDocumentResponse} containing details about the converted + * document, processing time, status, and any associated errors or metadata. + * @throws IllegalArgumentException if {@code request} is null. */ - public ConvertDocumentResponse convertTaskResult(String taskId) { - ValidationUtils.ensureNotBlank(taskId, "taskId"); - return this.httpOperations.executeGet("/v1/result/%s".formatted(taskId), ConvertDocumentResponse.class); + public ConvertDocumentResponse convertTaskResult(TaskResultRequest request) { + ValidationUtils.ensureNotNull(request, "request"); + return this.httpOperations.executeGet("/v1/result/%s".formatted(request.getTaskId()), ConvertDocumentResponse.class); } /** - * Retrieves the result of a completed task in chunks, identified by the specified task ID. + * Retrieves the chunked result of a completed task identified by the specified task ID. * - * This method sends a GET request to fetch the result of the task, providing the output - * in a chunked format. The response includes details about the chunks, related documents, - * processing time, and other metadata related to task completion. + * This method sends a GET request to fetch the chunked result of a task that has been processed. + * The response includes details about the chunks, associated documents, processing time, + * and any relevant metadata. * - * @param taskId the unique identifier of the task whose chunked result is being fetched. - * Must not be blank or null. - * @return a {@link ChunkDocumentResponse} containing information about the chunks, - * related documents, processing time, and any additional task metadata. - * @throws IllegalArgumentException if {@code taskId} is blank or null. + * @param request an instance of {@link TaskResultRequest} containing the unique task + * identifier. Must not be null. + * @return a {@link ChunkDocumentResponse} containing details about the chunks, + * documents, processing time, and any associated metadata. + * @throws IllegalArgumentException if {@code request} is null. */ - public ChunkDocumentResponse chunkTaskResult(String taskId) { - ValidationUtils.ensureNotBlank(taskId, "taskId"); - return this.httpOperations.executeGet("/v1/result/%s".formatted(taskId), ChunkDocumentResponse.class); + public ChunkDocumentResponse chunkTaskResult(TaskResultRequest request) { + ValidationUtils.ensureNotNull(request, "request"); + return this.httpOperations.executeGet("/v1/result/%s".formatted(request.getTaskId()), ChunkDocumentResponse.class); } } diff --git a/docling-serve/docling-serve-client/src/test/java/ai/docling/serve/client/AbstractDoclingServeClientTests.java b/docling-serve/docling-serve-client/src/test/java/ai/docling/serve/client/AbstractDoclingServeClientTests.java index 40ac1190..badeab4a 100644 --- a/docling-serve/docling-serve-client/src/test/java/ai/docling/serve/client/AbstractDoclingServeClientTests.java +++ b/docling-serve/docling-serve-client/src/test/java/ai/docling/serve/client/AbstractDoclingServeClientTests.java @@ -43,6 +43,7 @@ import ai.docling.serve.api.chunk.request.options.HybridChunkerOptions; import ai.docling.serve.api.chunk.response.Chunk; import ai.docling.serve.api.chunk.response.ChunkDocumentResponse; +import ai.docling.serve.api.clear.request.ClearRequest; import ai.docling.serve.api.clear.response.ClearResponse; import ai.docling.serve.api.convert.request.ConvertDocumentRequest; import ai.docling.serve.api.convert.request.options.ConvertDocumentOptions; @@ -52,6 +53,8 @@ import ai.docling.serve.api.convert.request.source.HttpSource; import ai.docling.serve.api.convert.response.ConvertDocumentResponse; import ai.docling.serve.api.health.HealthCheckResponse; +import ai.docling.serve.api.task.request.TaskResultRequest; +import ai.docling.serve.api.task.request.TaskStatusPollRequest; import ai.docling.serve.api.task.response.TaskStatus; import ai.docling.serve.api.task.response.TaskStatusPollResponse; import ai.docling.testcontainers.serve.DoclingServeContainer; @@ -115,7 +118,7 @@ void shouldClearConvertersSuccessfully() { @Test void shouldClearResultsSuccessfully() { - var response = getDoclingClient().clearResults(); + var response = getDoclingClient().clearResults(ClearRequest.builder().build()); assertThat(response) .isNotNull() @@ -128,15 +131,23 @@ void shouldClearResultsSuccessfully() { class TaskTests { @Test void pollInvalidTaskId() { - assertThatThrownBy(() -> getDoclingClient().pollTaskStatus("someInvalidTaskId")) + var request = TaskStatusPollRequest.builder() + .taskId("someInvalidTaskId") + .build(); + + + assertThatThrownBy(() -> getDoclingClient().pollTaskStatus(request)) .hasRootCauseInstanceOf(DoclingServeClientException.class) .hasRootCauseMessage("An error occurred: {\"detail\":\"Task not found.\"}"); } @Test void convertUrlTaskResult() throws IOException, InterruptedException { - var pollResponse = doPollForTaskCompletion(); - var result = getDoclingClient().convertTaskResult(pollResponse.getTaskId()); + var request = TaskResultRequest.builder() + .taskId(doPollForTaskCompletion().getTaskId()) + .build(); + + var result = getDoclingClient().convertTaskResult(request); ConvertTests.assertConvertHttpSource(result); } @@ -147,8 +158,12 @@ void pollConvertUrlTask() throws IOException, InterruptedException { private TaskStatusPollResponse doPollForTaskCompletion() throws IOException, InterruptedException { var response = submitTask(); + var pollRequest = TaskStatusPollRequest.builder() + .taskId(response.getTaskId()) + .build(); + var doclingClient = getDoclingClient(); - var taskPollResponse = new AtomicReference<>(doclingClient.pollTaskStatus(response.getTaskId())); + var taskPollResponse = new AtomicReference<>(doclingClient.pollTaskStatus(pollRequest)); assertThat(taskPollResponse).isNotNull(); assertThat(taskPollResponse.get()) @@ -177,7 +192,7 @@ private TaskStatusPollResponse doPollForTaskCompletion() throws IOException, Int .pollInterval(Duration.ofSeconds(5)) .logging(LoggerFactory.getLogger("org.awaitility")::info) .until(() -> { - taskPollResponse.set(doclingClient.pollTaskStatus(response.getTaskId())); + taskPollResponse.set(doclingClient.pollTaskStatus(pollRequest)); return taskPollResponse.get().getTaskStatus() == TaskStatus.SUCCESS; }); }