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 62990f2..c230650 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,6 +1,7 @@ package ai.docling.serve.api; -import ai.docling.serve.api.clear.request.ClearRequest; +import ai.docling.serve.api.clear.request.ClearConvertersRequest; +import ai.docling.serve.api.clear.request.ClearResultsRequest; import ai.docling.serve.api.clear.response.ClearResponse; /** @@ -11,23 +12,27 @@ */ public interface DoclingServeClearApi { /** - * Clears all registered converters associated with the API. - * This method removes any previously configured or cached converters, - * effectively resetting the converter state to an uninitialized state. - * After invoking this method, no converters will be available until new ones are added or configured. + * Clears all currently configured converters within the Docling Serve API. + * This operation removes any registered converters, effectively resetting + * the system to a state without active converter configurations. + * + * @param request an instance of {@link ClearConvertersRequest} containing + * the authentication details required to authorize this operation. + * @return a {@link ClearResponse} object indicating the status of the clear + * operation, such as success or failure. */ - ClearResponse clearConverters(); + ClearResponse clearConverters(ClearConvertersRequest request); /** - * Clears stored results based on the specified {@link ClearRequest}. + * Clears stored results based on the specified {@link ClearResultsRequest}. * This method removes results that match the criteria provided in the * request, such as results older than a specified duration. * - * @param request an instance of {@link ClearRequest} containing the criteria + * @param request an instance of {@link ClearResultsRequest} 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. */ - ClearResponse clearResults(ClearRequest request); + ClearResponse clearResults(ClearResultsRequest request); } diff --git a/docling-serve/docling-serve-api/src/main/java/ai/docling/serve/api/auth/AuthenticatedRequest.java b/docling-serve/docling-serve-api/src/main/java/ai/docling/serve/api/auth/AuthenticatedRequest.java new file mode 100644 index 0000000..6928d29 --- /dev/null +++ b/docling-serve/docling-serve-api/src/main/java/ai/docling/serve/api/auth/AuthenticatedRequest.java @@ -0,0 +1,28 @@ +package ai.docling.serve.api.auth; + +/** + * Represents a request that requires authentication for secure access to resources. + * + * Implementations of this interface define a contract for providing authentication + * details associated with the request. This is used to ensure authorized interaction + * with APIs or protected services. + * + * Methods: + * - {@code getAuthentication()}: Retrieves the {@link Authentication} details + * required to authorize the request. + */ +public interface AuthenticatedRequest { + /** + * Retrieves the authentication details associated with the request. + * + * This method provides the {@link Authentication} object required to authorize + * interaction with secure resources or APIs. Implementations should ensure + * the returned object contains the necessary credentials for successful + * authentication. + * + * @return an {@link Authentication} instance containing the authentication + * details required for the request, or {@code null} if authentication + * is not applicable. + */ + Authentication getAuthentication(); +} diff --git a/docling-serve/docling-serve-api/src/main/java/ai/docling/serve/api/auth/Authentication.java b/docling-serve/docling-serve-api/src/main/java/ai/docling/serve/api/auth/Authentication.java new file mode 100644 index 0000000..4dc4c17 --- /dev/null +++ b/docling-serve/docling-serve-api/src/main/java/ai/docling/serve/api/auth/Authentication.java @@ -0,0 +1,31 @@ +package ai.docling.serve.api.auth; + +import org.jspecify.annotations.Nullable; + +/** + * Represents authentication details required for secure API usage. + * + * The {@code Authentication} class encapsulates the necessary credentials + * for accessing restricted resources, such as an API key. It is designed + * to be immutable and thread-safe. + * + * This class uses Lombok annotations to provide a builder pattern, generate + * a getter for its fields, and produce a string representation. + * + * Features: + * - {@code apiKey}: Optional API key to authorize requests. + */ +@lombok.Builder(toBuilder = true) +@lombok.Getter +@lombok.ToString +public class Authentication { + /** + * Represents an optional API key used to authenticate requests to a secure service. + * + * This field is nullable, indicating that the API key may not always be provided + * or required, depending on the specific use case or configuration of the client. + * When present, the API key is used to authorize access to restricted resources. + */ + @Nullable + private String apiKey; +} diff --git a/docling-serve/docling-serve-api/src/main/java/ai/docling/serve/api/auth/package-info.java b/docling-serve/docling-serve-api/src/main/java/ai/docling/serve/api/auth/package-info.java new file mode 100644 index 0000000..a4c33c0 --- /dev/null +++ b/docling-serve/docling-serve-api/src/main/java/ai/docling/serve/api/auth/package-info.java @@ -0,0 +1,4 @@ +@NullMarked +package ai.docling.serve.api.auth; + +import org.jspecify.annotations.NullMarked; diff --git a/docling-serve/docling-serve-api/src/main/java/ai/docling/serve/api/chunk/request/HierarchicalChunkDocumentRequest.java b/docling-serve/docling-serve-api/src/main/java/ai/docling/serve/api/chunk/request/HierarchicalChunkDocumentRequest.java index 159be6f..a3f99f0 100644 --- a/docling-serve/docling-serve-api/src/main/java/ai/docling/serve/api/chunk/request/HierarchicalChunkDocumentRequest.java +++ b/docling-serve/docling-serve-api/src/main/java/ai/docling/serve/api/chunk/request/HierarchicalChunkDocumentRequest.java @@ -4,11 +4,14 @@ import org.jspecify.annotations.Nullable; +import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.annotation.JsonSetter; import com.fasterxml.jackson.annotation.Nulls; +import ai.docling.serve.api.auth.AuthenticatedRequest; +import ai.docling.serve.api.auth.Authentication; import ai.docling.serve.api.chunk.request.options.HierarchicalChunkerOptions; import ai.docling.serve.api.convert.request.options.ConvertDocumentOptions; import ai.docling.serve.api.convert.request.source.Source; @@ -24,7 +27,7 @@ @lombok.Builder(toBuilder = true) @lombok.Getter @lombok.ToString -public class HierarchicalChunkDocumentRequest { +public class HierarchicalChunkDocumentRequest implements AuthenticatedRequest { @JsonProperty("sources") @JsonSetter(nulls = Nulls.AS_EMPTY) @@ -48,6 +51,11 @@ public class HierarchicalChunkDocumentRequest { @lombok.Builder.Default private HierarchicalChunkerOptions chunkingOptions = HierarchicalChunkerOptions.builder().build(); + @JsonIgnore + @lombok.NonNull + @lombok.Builder.Default + private Authentication authentication = Authentication.builder().build(); + @tools.jackson.databind.annotation.JsonPOJOBuilder(withPrefix = "") public static class Builder { } diff --git a/docling-serve/docling-serve-api/src/main/java/ai/docling/serve/api/chunk/request/HybridChunkDocumentRequest.java b/docling-serve/docling-serve-api/src/main/java/ai/docling/serve/api/chunk/request/HybridChunkDocumentRequest.java index 6e76b8d..d55b626 100644 --- a/docling-serve/docling-serve-api/src/main/java/ai/docling/serve/api/chunk/request/HybridChunkDocumentRequest.java +++ b/docling-serve/docling-serve-api/src/main/java/ai/docling/serve/api/chunk/request/HybridChunkDocumentRequest.java @@ -4,11 +4,14 @@ import org.jspecify.annotations.Nullable; +import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.annotation.JsonSetter; import com.fasterxml.jackson.annotation.Nulls; +import ai.docling.serve.api.auth.AuthenticatedRequest; +import ai.docling.serve.api.auth.Authentication; import ai.docling.serve.api.chunk.request.options.HybridChunkerOptions; import ai.docling.serve.api.convert.request.options.ConvertDocumentOptions; import ai.docling.serve.api.convert.request.source.Source; @@ -24,7 +27,7 @@ @lombok.Builder(toBuilder = true) @lombok.Getter @lombok.ToString -public class HybridChunkDocumentRequest { +public class HybridChunkDocumentRequest implements AuthenticatedRequest { @JsonProperty("sources") @JsonSetter(nulls = Nulls.AS_EMPTY) @@ -48,6 +51,11 @@ public class HybridChunkDocumentRequest { @lombok.Builder.Default private HybridChunkerOptions chunkingOptions = HybridChunkerOptions.builder().build(); + @JsonIgnore + @lombok.NonNull + @lombok.Builder.Default + private Authentication authentication = Authentication.builder().build(); + @tools.jackson.databind.annotation.JsonPOJOBuilder(withPrefix = "") public static class Builder { } diff --git a/docling-serve/docling-serve-api/src/main/java/ai/docling/serve/api/clear/request/ClearConvertersRequest.java b/docling-serve/docling-serve-api/src/main/java/ai/docling/serve/api/clear/request/ClearConvertersRequest.java new file mode 100644 index 0000000..a9e9a6e --- /dev/null +++ b/docling-serve/docling-serve-api/src/main/java/ai/docling/serve/api/clear/request/ClearConvertersRequest.java @@ -0,0 +1,30 @@ +package ai.docling.serve.api.clear.request; + +import com.fasterxml.jackson.annotation.JsonIgnore; + +import ai.docling.serve.api.auth.AuthenticatedRequest; +import ai.docling.serve.api.auth.Authentication; + +/** + * Represents a request to clear or reset configured converters using the Docling Serve API. + * + * This class provides a mechanism to manage converter configurations through explicit + * reset operations. It includes authentication information required to authorize the request. + * + * Features: + * - {@code authentication}: Provides the authentication details needed to validate + * and authorize the reset operation. A default authentication instance is used + * when none is explicitly provided. + * + * This class is immutable and can be constructed or modified using a builder + * that is generated via Lombok annotations. + */ +@lombok.Builder(toBuilder = true) +@lombok.Getter +@lombok.ToString +public class ClearConvertersRequest implements AuthenticatedRequest { + @JsonIgnore + @lombok.NonNull + @lombok.Builder.Default + private Authentication authentication = Authentication.builder().build(); +} 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/ClearResultsRequest.java similarity index 76% rename from docling-serve/docling-serve-api/src/main/java/ai/docling/serve/api/clear/request/ClearRequest.java rename to docling-serve/docling-serve-api/src/main/java/ai/docling/serve/api/clear/request/ClearResultsRequest.java index f7667f2..881dfd9 100644 --- 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/ClearResultsRequest.java @@ -2,6 +2,11 @@ import java.time.Duration; +import com.fasterxml.jackson.annotation.JsonIgnore; + +import ai.docling.serve.api.auth.AuthenticatedRequest; +import ai.docling.serve.api.auth.Authentication; + /** * 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 @@ -17,7 +22,7 @@ @lombok.Builder(toBuilder = true) @lombok.Getter @lombok.ToString -public class ClearRequest { +public class ClearResultsRequest implements AuthenticatedRequest { /** * 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 @@ -30,4 +35,9 @@ public class ClearRequest { @lombok.NonNull @lombok.Builder.Default private Duration olderThen = DEFAULT_OLDER_THAN; + + @JsonIgnore + @lombok.NonNull + @lombok.Builder.Default + private Authentication authentication = Authentication.builder().build(); } diff --git a/docling-serve/docling-serve-api/src/main/java/ai/docling/serve/api/convert/request/ConvertDocumentRequest.java b/docling-serve/docling-serve-api/src/main/java/ai/docling/serve/api/convert/request/ConvertDocumentRequest.java index bcfea23..e41ab91 100644 --- a/docling-serve/docling-serve-api/src/main/java/ai/docling/serve/api/convert/request/ConvertDocumentRequest.java +++ b/docling-serve/docling-serve-api/src/main/java/ai/docling/serve/api/convert/request/ConvertDocumentRequest.java @@ -4,11 +4,14 @@ import org.jspecify.annotations.Nullable; +import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.annotation.JsonSetter; import com.fasterxml.jackson.annotation.Nulls; +import ai.docling.serve.api.auth.AuthenticatedRequest; +import ai.docling.serve.api.auth.Authentication; import ai.docling.serve.api.convert.request.options.ConvertDocumentOptions; import ai.docling.serve.api.convert.request.source.Source; import ai.docling.serve.api.convert.request.target.Target; @@ -27,7 +30,7 @@ @lombok.Builder(toBuilder = true) @lombok.Getter @lombok.ToString -public class ConvertDocumentRequest { +public class ConvertDocumentRequest implements AuthenticatedRequest { @JsonProperty("sources") @JsonSetter(nulls = Nulls.AS_EMPTY) @lombok.Singular @@ -42,6 +45,11 @@ public class ConvertDocumentRequest { @Nullable private Target target; + @JsonIgnore + @lombok.NonNull + @lombok.Builder.Default + private Authentication authentication = Authentication.builder().build(); + @tools.jackson.databind.annotation.JsonPOJOBuilder(withPrefix = "") public static class Builder { } } 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 index f413c3e..414fa21 100644 --- 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 @@ -1,5 +1,10 @@ package ai.docling.serve.api.task.request; +import com.fasterxml.jackson.annotation.JsonIgnore; + +import ai.docling.serve.api.auth.AuthenticatedRequest; +import ai.docling.serve.api.auth.Authentication; + /** * Represents a request to retrieve the result of a task. * @@ -15,7 +20,12 @@ @lombok.Builder(toBuilder = true) @lombok.Getter @lombok.ToString -public class TaskResultRequest { +public class TaskResultRequest implements AuthenticatedRequest { @lombok.NonNull private String taskId; + + @JsonIgnore + @lombok.NonNull + @lombok.Builder.Default + private Authentication authentication = Authentication.builder().build(); } 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 index c6d7156..134ecf7 100644 --- 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 @@ -2,6 +2,11 @@ import java.time.Duration; +import com.fasterxml.jackson.annotation.JsonIgnore; + +import ai.docling.serve.api.auth.AuthenticatedRequest; +import ai.docling.serve.api.auth.Authentication; + /** * Represents a request for polling the status of a task. * @@ -22,7 +27,7 @@ @lombok.Builder(toBuilder = true) @lombok.Getter @lombok.ToString -public class TaskStatusPollRequest { +public class TaskStatusPollRequest implements AuthenticatedRequest { /** * The default wait time between status polling attempts for a task. *
@@ -39,4 +44,9 @@ public class TaskStatusPollRequest {
@lombok.NonNull
@lombok.Builder.Default
private Duration waitTime = DEFAULT_STATUS_POLL_WAIT_TIME;
+
+ @JsonIgnore
+ @lombok.NonNull
+ @lombok.Builder.Default
+ private Authentication authentication = Authentication.builder().build();
}
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 eb58960..455e531 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
@@ -14,6 +14,9 @@
exports ai.docling.serve.api.health;
exports ai.docling.serve.api.util;
+ // Auth
+ exports ai.docling.serve.api.auth;
+
// Chunking API
exports ai.docling.serve.api.chunk.request;
exports ai.docling.serve.api.chunk.request.options;
diff --git a/docling-serve/docling-serve-api/src/test/java/ai/docling/serve/api/auth/AuthenticationTests.java b/docling-serve/docling-serve-api/src/test/java/ai/docling/serve/api/auth/AuthenticationTests.java
new file mode 100644
index 0000000..98e6b00
--- /dev/null
+++ b/docling-serve/docling-serve-api/src/test/java/ai/docling/serve/api/auth/AuthenticationTests.java
@@ -0,0 +1,23 @@
+package ai.docling.serve.api.auth;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+import org.junit.jupiter.api.Test;
+
+class AuthenticationTests {
+ @Test
+ void nothingSpecified() {
+ assertThat(Authentication.builder().build())
+ .isNotNull()
+ .extracting(Authentication::getApiKey)
+ .isNull();
+ }
+
+ @Test
+ void keySpecified() {
+ assertThat(Authentication.builder().apiKey("key").build())
+ .isNotNull()
+ .extracting(Authentication::getApiKey)
+ .isEqualTo("key");
+ }
+}
diff --git a/docling-serve/docling-serve-api/src/test/java/ai/docling/serve/api/chunk/request/HierarchicalChunkDocumentRequestTests.java b/docling-serve/docling-serve-api/src/test/java/ai/docling/serve/api/chunk/request/HierarchicalChunkDocumentRequestTests.java
index bdadb09..af45343 100644
--- a/docling-serve/docling-serve-api/src/test/java/ai/docling/serve/api/chunk/request/HierarchicalChunkDocumentRequestTests.java
+++ b/docling-serve/docling-serve-api/src/test/java/ai/docling/serve/api/chunk/request/HierarchicalChunkDocumentRequestTests.java
@@ -10,6 +10,7 @@
import org.junit.jupiter.api.Test;
+import ai.docling.serve.api.auth.Authentication;
import ai.docling.serve.api.chunk.request.options.HierarchicalChunkerOptions;
import ai.docling.serve.api.convert.request.options.ConvertDocumentOptions;
import ai.docling.serve.api.convert.request.source.FileSource;
@@ -40,6 +41,10 @@ void buildWithHttpSourcesAsList() {
.build();
assertThat(request.getSources()).hasSize(1);
assertThat(request.getSources().get(0)).isInstanceOf(HttpSource.class);
+ assertThat(request.getAuthentication())
+ .isNotNull()
+ .extracting(Authentication::getApiKey)
+ .isNull();
}
@Test
@@ -54,6 +59,11 @@ void buildWithHttpSourcesAsVarargs() {
assertThat(request.getSources())
.hasSize(2)
.allSatisfy(source -> assertThat(source).isInstanceOf(HttpSource.class));
+
+ assertThat(request.getAuthentication())
+ .isNotNull()
+ .extracting(Authentication::getApiKey)
+ .isNull();
}
@Test
@@ -65,6 +75,11 @@ void buildWithFileSourcesAsList() {
assertThat(request.getSources())
.hasSize(1)
.allSatisfy(source -> assertThat(source).isInstanceOf(FileSource.class));
+
+ assertThat(request.getAuthentication())
+ .isNotNull()
+ .extracting(Authentication::getApiKey)
+ .isNull();
}
@Test
@@ -80,6 +95,11 @@ void buildWithFileSourcesAsVarargs() {
assertThat(request.getSources())
.hasSize(2)
.allSatisfy(source -> assertThat(source).isInstanceOf(FileSource.class));
+
+ assertThat(request.getAuthentication())
+ .isNotNull()
+ .extracting(Authentication::getApiKey)
+ .isNull();
}
@Test
@@ -105,6 +125,11 @@ void hierarchicalChunkDocumentRequestIsImmutable() {
.asInstanceOf(type(FileSource.class))
.extracting(FileSource::getFilename, FileSource::getBase64String)
.containsExactly("test.txt", "dGVzdCBjb250ZW50");
+
+ assertThat(request.getAuthentication())
+ .isNotNull()
+ .extracting(Authentication::getApiKey)
+ .isNull();
}
@Test
@@ -115,6 +140,10 @@ void buildWithIncludeConvertedDoc() {
.build();
assertThat(request.isIncludeConvertedDoc()).isTrue();
+ assertThat(request.getAuthentication())
+ .isNotNull()
+ .extracting(Authentication::getApiKey)
+ .isNull();
}
@Test
@@ -135,6 +164,11 @@ void buildWithChunkingOptions() {
assertThat(options.isUseMarkdownTables()).isTrue();
assertThat(options.isIncludeRawText()).isFalse();
});
+
+ assertThat(request.getAuthentication())
+ .isNotNull()
+ .extracting(Authentication::getApiKey)
+ .isNull();
}
@Test
@@ -157,6 +191,10 @@ void buildWithAllFields() {
assertThat(request.getTarget()).isNotNull();
assertThat(request.isIncludeConvertedDoc()).isTrue();
assertThat(request.getChunkingOptions()).isNotNull();
+ assertThat(request.getAuthentication())
+ .isNotNull()
+ .extracting(Authentication::getApiKey)
+ .isNull();
}
@Test
@@ -169,5 +207,21 @@ void buildWithDefaultOptions() {
assertThat(request.isIncludeConvertedDoc()).isFalse();
assertThat(request.getChunkingOptions()).isNotNull();
assertThat(request.getTarget()).isNull();
+ assertThat(request.getAuthentication())
+ .isNotNull()
+ .extracting(Authentication::getApiKey)
+ .isNull();
+ }
+
+ @Test
+ void buildWithAuth() {
+ var request = HierarchicalChunkDocumentRequest.builder()
+ .authentication(Authentication.builder().apiKey("key").build())
+ .build();
+
+ assertThat(request.getAuthentication())
+ .isNotNull()
+ .extracting(Authentication::getApiKey)
+ .isEqualTo("key");
}
}
diff --git a/docling-serve/docling-serve-api/src/test/java/ai/docling/serve/api/chunk/request/HybridChunkDocumentRequestTests.java b/docling-serve/docling-serve-api/src/test/java/ai/docling/serve/api/chunk/request/HybridChunkDocumentRequestTests.java
index c39e739..392af4c 100644
--- a/docling-serve/docling-serve-api/src/test/java/ai/docling/serve/api/chunk/request/HybridChunkDocumentRequestTests.java
+++ b/docling-serve/docling-serve-api/src/test/java/ai/docling/serve/api/chunk/request/HybridChunkDocumentRequestTests.java
@@ -10,6 +10,7 @@
import org.junit.jupiter.api.Test;
+import ai.docling.serve.api.auth.Authentication;
import ai.docling.serve.api.chunk.request.options.HybridChunkerOptions;
import ai.docling.serve.api.convert.request.options.ConvertDocumentOptions;
import ai.docling.serve.api.convert.request.source.FileSource;
@@ -40,6 +41,10 @@ void buildWithHttpSourcesAsList() {
.build();
assertThat(request.getSources()).hasSize(1);
assertThat(request.getSources().get(0)).isInstanceOf(HttpSource.class);
+ assertThat(request.getAuthentication())
+ .isNotNull()
+ .extracting(Authentication::getApiKey)
+ .isNull();
}
@Test
@@ -54,6 +59,11 @@ void buildWithHttpSourcesAsVarargs() {
assertThat(request.getSources())
.hasSize(2)
.allSatisfy(source -> assertThat(source).isInstanceOf(HttpSource.class));
+
+ assertThat(request.getAuthentication())
+ .isNotNull()
+ .extracting(Authentication::getApiKey)
+ .isNull();
}
@Test
@@ -65,6 +75,11 @@ void buildWithFileSourcesAsList() {
assertThat(request.getSources())
.hasSize(1)
.allSatisfy(source -> assertThat(source).isInstanceOf(FileSource.class));
+
+ assertThat(request.getAuthentication())
+ .isNotNull()
+ .extracting(Authentication::getApiKey)
+ .isNull();
}
@Test
@@ -80,6 +95,11 @@ void buildWithFileSourcesAsVarargs() {
assertThat(request.getSources())
.hasSize(2)
.allSatisfy(source -> assertThat(source).isInstanceOf(FileSource.class));
+
+ assertThat(request.getAuthentication())
+ .isNotNull()
+ .extracting(Authentication::getApiKey)
+ .isNull();
}
@Test
@@ -105,6 +125,11 @@ void hybridChunkDocumentRequestIsImmutable() {
.asInstanceOf(type(FileSource.class))
.extracting(FileSource::getFilename, FileSource::getBase64String)
.containsExactly("test.txt", "dGVzdCBjb250ZW50");
+
+ assertThat(request.getAuthentication())
+ .isNotNull()
+ .extracting(Authentication::getApiKey)
+ .isNull();
}
@Test
@@ -115,6 +140,10 @@ void buildWithIncludeConvertedDoc() {
.build();
assertThat(request.isIncludeConvertedDoc()).isTrue();
+ assertThat(request.getAuthentication())
+ .isNotNull()
+ .extracting(Authentication::getApiKey)
+ .isNull();
}
@Test
@@ -141,6 +170,11 @@ void buildWithChunkingOptions() {
assertThat(options.isIncludeRawText()).isFalse();
assertThat(options.getMergePeers()).isTrue();
});
+
+ assertThat(request.getAuthentication())
+ .isNotNull()
+ .extracting(Authentication::getApiKey)
+ .isNull();
}
@Test
@@ -162,6 +196,10 @@ void buildWithAllFields() {
assertThat(request.getTarget()).isNotNull();
assertThat(request.isIncludeConvertedDoc()).isTrue();
assertThat(request.getChunkingOptions()).isNotNull();
+ assertThat(request.getAuthentication())
+ .isNotNull()
+ .extracting(Authentication::getApiKey)
+ .isNull();
}
@Test
@@ -174,5 +212,21 @@ void buildWithDefaultOptions() {
assertThat(request.isIncludeConvertedDoc()).isFalse();
assertThat(request.getChunkingOptions()).isNotNull();
assertThat(request.getTarget()).isNull();
+ assertThat(request.getAuthentication())
+ .isNotNull()
+ .extracting(Authentication::getApiKey)
+ .isNull();
+ }
+
+ @Test
+ void buildWithAuth() {
+ var request = HybridChunkDocumentRequest.builder()
+ .authentication(Authentication.builder().apiKey("key").build())
+ .build();
+
+ assertThat(request.getAuthentication())
+ .isNotNull()
+ .extracting(Authentication::getApiKey)
+ .isEqualTo("key");
}
}
diff --git a/docling-serve/docling-serve-api/src/test/java/ai/docling/serve/api/clear/request/ClearConvertersRequestTests.java b/docling-serve/docling-serve-api/src/test/java/ai/docling/serve/api/clear/request/ClearConvertersRequestTests.java
new file mode 100644
index 0000000..9d0039e
--- /dev/null
+++ b/docling-serve/docling-serve-api/src/test/java/ai/docling/serve/api/clear/request/ClearConvertersRequestTests.java
@@ -0,0 +1,29 @@
+package ai.docling.serve.api.clear.request;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+import org.junit.jupiter.api.Test;
+
+import ai.docling.serve.api.auth.Authentication;
+
+class ClearConvertersRequestTests {
+ @Test
+ void noAuthSpecified() {
+ assertThat(ClearConvertersRequest.builder().build())
+ .isNotNull()
+ .extracting(ClearConvertersRequest::getAuthentication)
+ .isNotNull()
+ .extracting(Authentication::getApiKey)
+ .isNull();
+ }
+
+ @Test
+ void authSpecified() {
+ assertThat(ClearConvertersRequest.builder().authentication(Authentication.builder().apiKey("key").build()).build())
+ .isNotNull()
+ .extracting(ClearConvertersRequest::getAuthentication)
+ .isNotNull()
+ .extracting(Authentication::getApiKey)
+ .isEqualTo("key");
+ }
+}
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
deleted file mode 100644
index 91c0025..0000000
--- a/docling-serve/docling-serve-api/src/test/java/ai/docling/serve/api/clear/request/ClearRequestTests.java
+++ /dev/null
@@ -1,25 +0,0 @@
-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/clear/request/ClearResultsRequestTests.java b/docling-serve/docling-serve-api/src/test/java/ai/docling/serve/api/clear/request/ClearResultsRequestTests.java
new file mode 100644
index 0000000..bef64d7
--- /dev/null
+++ b/docling-serve/docling-serve-api/src/test/java/ai/docling/serve/api/clear/request/ClearResultsRequestTests.java
@@ -0,0 +1,46 @@
+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;
+
+import ai.docling.serve.api.auth.Authentication;
+
+class ClearResultsRequestTests {
+ @Test
+ void nullOlderThen() {
+ assertThatExceptionOfType(IllegalArgumentException.class)
+ .isThrownBy(() -> ClearResultsRequest.builder().olderThen(null).build())
+ .withMessage("olderThen is marked non-null but is null");
+ }
+
+ @Test
+ void defaultOlderThen() {
+ var request = ClearResultsRequest.builder().build();
+
+ assertThat(request)
+ .isNotNull()
+ .extracting(ClearResultsRequest::getOlderThen)
+ .asInstanceOf(InstanceOfAssertFactories.DURATION)
+ .isEqualByComparingTo(ClearResultsRequest.DEFAULT_OLDER_THAN);
+
+ assertThat(request.getAuthentication())
+ .isNotNull()
+ .extracting(Authentication::getApiKey)
+ .isNull();
+ }
+
+ @Test
+ void buildWithAuth() {
+ var request = ClearResultsRequest.builder()
+ .authentication(Authentication.builder().apiKey("key").build())
+ .build();
+
+ assertThat(request.getAuthentication())
+ .isNotNull()
+ .extracting(Authentication::getApiKey)
+ .isEqualTo("key");
+ }
+}
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
index c5fdf74..bbb6799 100644
--- 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
@@ -1,9 +1,12 @@
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.junit.jupiter.api.Test;
+import ai.docling.serve.api.auth.Authentication;
+
class TaskResultRequestTests {
@Test
void nullTaskId() {
@@ -18,4 +21,28 @@ void noTaskId() {
.isThrownBy(() -> TaskResultRequest.builder().build())
.withMessage("taskId is marked non-null but is null");
}
+
+ @Test
+ void buildWithoutAuth() {
+ var request = TaskResultRequest.builder().taskId("1").build();
+
+ assertThat(request.getTaskId()).isEqualTo("1");
+ assertThat(request.getAuthentication())
+ .isNotNull()
+ .extracting(Authentication::getApiKey)
+ .isNull();
+ }
+
+ @Test
+ void buildWithAuth() {
+ var request = TaskResultRequest.builder()
+ .taskId("1")
+ .authentication(Authentication.builder().apiKey("key").build())
+ .build();
+
+ assertThat(request.getAuthentication())
+ .isNotNull()
+ .extracting(Authentication::getApiKey)
+ .isEqualTo("key");
+ }
}
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
index ecc85e6..a8e10b7 100644
--- 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
@@ -6,6 +6,8 @@
import org.assertj.core.api.InstanceOfAssertFactories;
import org.junit.jupiter.api.Test;
+import ai.docling.serve.api.auth.Authentication;
+
class TaskStatusPollRequestTests {
@Test
void nullTaskId() {
@@ -30,10 +32,40 @@ void nullWaitTime() {
@Test
void defaultWaitTime() {
- assertThat(TaskStatusPollRequest.builder().taskId("1").build())
+ var request = TaskStatusPollRequest.builder().taskId("1").build();
+ assertThat(request)
.isNotNull()
.extracting(TaskStatusPollRequest::getWaitTime)
.asInstanceOf(InstanceOfAssertFactories.DURATION)
.isEqualByComparingTo(TaskStatusPollRequest.DEFAULT_STATUS_POLL_WAIT_TIME);
+
+ assertThat(request.getAuthentication())
+ .isNotNull()
+ .extracting(Authentication::getApiKey)
+ .isNull();
+ }
+
+ @Test
+ void buildWithoutAuth() {
+ var request = TaskStatusPollRequest.builder().taskId("1").build();
+
+ assertThat(request.getTaskId()).isEqualTo("1");
+ assertThat(request.getAuthentication())
+ .isNotNull()
+ .extracting(Authentication::getApiKey)
+ .isNull();
+ }
+
+ @Test
+ void buildWithAuth() {
+ var request = TaskStatusPollRequest.builder()
+ .taskId("1")
+ .authentication(Authentication.builder().apiKey("key").build())
+ .build();
+
+ assertThat(request.getAuthentication())
+ .isNotNull()
+ .extracting(Authentication::getApiKey)
+ .isEqualTo("key");
}
}
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
deleted file mode 100644
index 87bd109..0000000
--- a/docling-serve/docling-serve-client/src/main/java/ai/docling/serve/client/ClearOperations.java
+++ /dev/null
@@ -1,40 +0,0 @@
-package ai.docling.serve.client;
-
-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
- * converters and stale data retained by the service.
- */
-final class ClearOperations implements DoclingServeClearApi {
- private final HttpOperations httpOperations;
-
- ClearOperations(HttpOperations httpOperations) {
- this.httpOperations = httpOperations;
- }
-
- /**
- * Clears all registered converters associated with the API.
- */
- public ClearResponse clearConverters() {
- return this.httpOperations.executeGet("/v1/clear/converters", ClearResponse.class);
- }
-
- /**
- * 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 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(ClearRequest request) {
- ValidationUtils.ensureNotNull(request, "request");
-
- 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 e0abb1a..ede14e6 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,10 +12,13 @@
import java.net.http.HttpResponse;
import java.net.http.HttpResponse.BodyHandlers;
import java.nio.ByteBuffer;
+import java.util.List;
+import java.util.Map;
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;
@@ -25,10 +28,13 @@
import ai.docling.serve.api.DoclingServeConvertApi;
import ai.docling.serve.api.DoclingServeHealthApi;
import ai.docling.serve.api.DoclingServeTaskApi;
+import ai.docling.serve.api.auth.AuthenticatedRequest;
+import ai.docling.serve.api.auth.Authentication;
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.request.ClearConvertersRequest;
+import ai.docling.serve.api.clear.request.ClearResultsRequest;
import ai.docling.serve.api.clear.response.ClearResponse;
import ai.docling.serve.api.convert.request.ConvertDocumentRequest;
import ai.docling.serve.api.convert.response.ConvertDocumentResponse;
@@ -36,6 +42,12 @@
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.client.operations.ChunkOperations;
+import ai.docling.serve.client.operations.ClearOperations;
+import ai.docling.serve.client.operations.ConvertOperations;
+import ai.docling.serve.client.operations.HealthOperations;
+import ai.docling.serve.client.operations.HttpOperations;
+import ai.docling.serve.client.operations.TaskOperations;
/**
* Abstract class representing a client for interacting with the Docling API.
@@ -116,14 +128,32 @@ protected void logRequest(HttpRequest request) {
stringBuilder.append("\nā REQUEST: %s %s\n".formatted(request.method(), request.uri()));
stringBuilder.append(" HEADERS:\n");
- request.headers().map().forEach((key, values) ->
- stringBuilder.append(" %s: %s\n".formatted(key, String.join(", ", values)))
+ // Need to mask sensitive headers
+ request.headers()
+ .map()
+ .entrySet()
+ .stream()
+ .map(this::maskSensitiveHeaderValues)
+ .forEach(entry -> stringBuilder.append(" %s: %s\n".formatted(entry.getKey(), String.join(", ", entry.getValue())))
);
LOG.info(stringBuilder.toString());
}
}
+ private boolean isSensitiveHeader(String headerName) {
+ return API_KEY_HEADER_NAME.equalsIgnoreCase(headerName);
+ }
+
+ private Map.Entry