From ad04fb31b65f604b76d1c84b6c03d67fecf81d3c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?ianar=C3=A9=20s=C3=A9vi?= Date: Tue, 12 Mar 2024 18:23:45 +0100 Subject: [PATCH 1/3] :sparkles: add ability to easily load a webhook response --- README.md | 112 ++++++++++++++---- src/main/java/com/mindee/MindeeClient.java | 19 +++ .../java/com/mindee/http/MindeeHttpApi.java | 22 ++-- .../java/com/mindee/input/WebhookSource.java | 38 ++++++ .../java/com/mindee/MindeeClientTest.java | 13 ++ 5 files changed, 171 insertions(+), 33 deletions(-) create mode 100644 src/main/java/com/mindee/input/WebhookSource.java diff --git a/README.md b/README.md index a811d7627..5e900bffd 100644 --- a/README.md +++ b/README.md @@ -25,8 +25,13 @@ Where `${mindee.sdk.version}` is the version shown here: ## Loading a File and Parsing It The `MindeeClient` class is the entry point for most of the helper library features. +## Synchronously Parsing a File +This is the easiest and fastest way to integrate into the Mindee API. + +However, not all products are available in synchronous mode. + ### Global Documents -These classes are available in the `com.mindee.product` package: +These classes are available in the `com.mindee.product` package: ```java import com.mindee.MindeeClient; @@ -55,19 +60,24 @@ public class SimpleMindeeClient { System.out.println(response.getDocument().toString()); } } - ``` -### Region-Specific Documents +**Note for Region-Specific Documents:** + Each region will have its own package within the general `com.mindee.product` package. For example USA-specific classes will be in the `com.mindee.product.us` package: +```java +import com.mindee.product.us.bankcheck.BankCheckV1; +``` +### Custom Documents (API Builder) ```java import com.mindee.MindeeClient; import com.mindee.input.LocalInputSource; import com.mindee.parsing.common.PredictResponse; -import com.mindee.product.us.bankcheck.BankCheckV1; +import com.mindee.product.custom.CustomV1; +import com.mindee.http.Endpoint; import java.io.File; import java.io.IOException; @@ -76,54 +86,112 @@ public class SimpleMindeeClient { // Init a new client MindeeClient mindeeClient = new MindeeClient("my-api-key"); + + // Init the endpoint for the custom document + Endpoint endpoint = new Endpoint("my-endpoint", "my-account"); // Load a file from disk LocalInputSource localInputSource = new LocalInputSource( - "/path/to/the/file.ext" + "src/main/resources/invoices/invoice1.pdf" ); // Parse the file - Document response = mindeeClient.parse( - BankCheckV1.class, - localInputSource + Document customDocument = mindeeClient.parse( + localInputSource, + endpoint ); - // Print a summary of the parsed data - System.out.println(response.getDocument().toString()); } } ``` -### Custom Documents (API Builder) +## Synchronously Parsing a File +This allows for easier handling of bursts of documents sent. + +Some products are only available asynchronously, check the example code +directly on the Mindee platform. + +### Enqueue and Parse a File +The client library will take care of handling the polling requests for you. + +This is the easiest way to get started. + ```java import com.mindee.MindeeClient; import com.mindee.input.LocalInputSource; -import com.mindee.parsing.common.PredictResponse; -import com.mindee.product.custom.CustomV1; -import com.mindee.http.Endpoint; +import com.mindee.parsing.common.AsyncPredictResponse; +import com.mindee.product.internationalid.InternationalIdV2; import java.io.File; import java.io.IOException; +public class SimpleMindeeClient { + + public static void main(String[] args) throws IOException, InterruptedException { + String apiKey = "my-api-key"; + String filePath = "/path/to/the/file.ext"; + + // Init a new client + MindeeClient mindeeClient = new MindeeClient(apiKey); + + // Load a file from disk + LocalInputSource inputSource = new LocalInputSource(new File(filePath)); + + // Parse the file asynchronously + AsyncPredictResponse response = mindeeClient.enqueueAndParse( + InternationalIdV2.class, + inputSource + ); + + // Print a summary of the response + System.out.println(response.toString()); + } +} +``` + +### Enqueue and Parse a Webhook Response +This is an optional way of handling asynchronous APIs. + +```java +import com.mindee.MindeeClient; +import com.mindee.input.LocalInputSource; +import com.mindee.input.WebhookSource; +import com.mindee.product.internationalid.InternationalIdV2; + +import java.io.IOException; + public class SimpleMindeeClient { public static void main(String[] args) throws IOException { // Init a new client MindeeClient mindeeClient = new MindeeClient("my-api-key"); - - // Init the endpoint for the custom document - Endpoint endpoint = new Endpoint("my-endpoint", "my-account"); // Load a file from disk LocalInputSource localInputSource = new LocalInputSource( - "src/main/resources/invoices/invoice1.pdf" + "/path/to/the/file.ext" ); - // Parse the file - Document customDocument = mindeeClient.parse( - localInputSource, - endpoint + // Enqueue the file + String jobId = client.enqueue(InternationalIdV2.class, localInputSource) + .getJob().getId(); + + // Load the JSON string sent by the Mindee webhook callback. + // + // Reading the callback data will vary greatly depending on which + // HTTP server you are using, and is beyond the scope of this example. + WebhookSource webhookSource = new WebhookSource("{'json': 'data'}"); + + // You can also use a File object as the input. + //WebhookSource webhookSource = new WebhookSource(new File("/path/to/file.json")); + + AsyncPredictResponse response = mindeeClient.loadPrediction( + InternationalIdV2.class, + new WebhookSource(webhookStream) ); + + // Print a summary of the parsed data + System.out.println(response.getDocument().toString()); } } ``` + ## Further Reading Complete details on the working of the library are available in the following guides: diff --git a/src/main/java/com/mindee/MindeeClient.java b/src/main/java/com/mindee/MindeeClient.java index 780990d64..e5ba8513c 100644 --- a/src/main/java/com/mindee/MindeeClient.java +++ b/src/main/java/com/mindee/MindeeClient.java @@ -1,5 +1,7 @@ package com.mindee; +import com.fasterxml.jackson.databind.JavaType; +import com.fasterxml.jackson.databind.ObjectMapper; import com.mindee.http.Endpoint; import com.mindee.http.MindeeApi; import com.mindee.http.MindeeHttpApi; @@ -7,6 +9,7 @@ import com.mindee.input.InputSourceUtils; import com.mindee.input.LocalInputSource; import com.mindee.input.PageOptions; +import com.mindee.input.WebhookSource; import com.mindee.parsing.common.AsyncPredictResponse; import com.mindee.parsing.common.Inference; import com.mindee.parsing.common.PredictResponse; @@ -487,6 +490,22 @@ private PredictResponse parse( .build()); } + /** + * Load a prediction from a webhook response. + */ + public AsyncPredictResponse loadPrediction( + Class type, + WebhookSource webhookSource + ) throws IOException { + ObjectMapper objectMapper = new ObjectMapper(); + objectMapper.findAndRegisterModules(); + JavaType parametricType = objectMapper.getTypeFactory().constructParametricType( + AsyncPredictResponse.class, + type + ); + return objectMapper.readValue(webhookSource.getFile(), parametricType); + } + private byte[] getSplitFile( LocalInputSource localInputSource, PageOptions pageOptions diff --git a/src/main/java/com/mindee/http/MindeeHttpApi.java b/src/main/java/com/mindee/http/MindeeHttpApi.java index a0a1f936a..c338379e9 100644 --- a/src/main/java/com/mindee/http/MindeeHttpApi.java +++ b/src/main/java/com/mindee/http/MindeeHttpApi.java @@ -125,7 +125,7 @@ public AsyncPredictResponse documentQueueGet( // required to register jackson date module format to deserialize mapper.findAndRegisterModules(); - JavaType type = mapper.getTypeFactory().constructParametricType( + JavaType parametricType = mapper.getTypeFactory().constructParametricType( AsyncPredictResponse.class, documentClass ); @@ -142,10 +142,10 @@ public AsyncPredictResponse documentQueueGet( HttpEntity responseEntity = response.getEntity(); int statusCode = response.getStatusLine().getStatusCode(); if (!is2xxStatusCode(statusCode)) { - throw getHttpError(type, response); + throw getHttpError(parametricType, response); } String rawResponse = readRawResponse(responseEntity); - AsyncPredictResponse mappedResponse = mapper.readValue(rawResponse, type); + AsyncPredictResponse mappedResponse = mapper.readValue(rawResponse, parametricType); mappedResponse.setRawResponse(rawResponse); if ( mappedResponse.getJob() != null @@ -179,7 +179,7 @@ public PredictResponse predictPost( // required to register jackson date module format to deserialize mapper.findAndRegisterModules(); - JavaType type = mapper.getTypeFactory().constructParametricType( + JavaType parametricType = mapper.getTypeFactory().constructParametricType( PredictResponse.class, documentClass ); @@ -190,13 +190,13 @@ public PredictResponse predictPost( HttpEntity responseEntity = response.getEntity(); int statusCode = response.getStatusLine().getStatusCode(); if (!is2xxStatusCode(statusCode)) { - throw getHttpError(type, response); + throw getHttpError(parametricType, response); } if (responseEntity.getContentLength() == 0) { throw new MindeeException("Empty response from server."); } String rawResponse = readRawResponse(responseEntity); - PredictResponse mappedResponse = mapper.readValue(rawResponse, type); + PredictResponse mappedResponse = mapper.readValue(rawResponse, parametricType); mappedResponse.setRawResponse(rawResponse); return mappedResponse; } catch (IOException err) { @@ -218,7 +218,7 @@ public AsyncPredictResponse predictAsyncPost( // required to register jackson date module format to deserialize mapper.findAndRegisterModules(); - JavaType type = mapper.getTypeFactory().constructParametricType( + JavaType parametricType = mapper.getTypeFactory().constructParametricType( AsyncPredictResponse.class, documentClass ); @@ -229,13 +229,13 @@ public AsyncPredictResponse predictAsyncPost( HttpEntity responseEntity = response.getEntity(); int statusCode = response.getStatusLine().getStatusCode(); if (!is2xxStatusCode(statusCode)) { - throw getHttpError(type, response); + throw getHttpError(parametricType, response); } if (responseEntity.getContentLength() == 0) { throw new MindeeException("Empty response from server."); } String rawResponse = readRawResponse(responseEntity); - AsyncPredictResponse mappedResponse = mapper.readValue(rawResponse, type); + AsyncPredictResponse mappedResponse = mapper.readValue(rawResponse, parametricType); mappedResponse.setRawResponse(rawResponse); return mappedResponse; } catch (IOException err) { @@ -244,7 +244,7 @@ public AsyncPredictResponse predictAsyncPost( } private MindeeHttpException getHttpError( - JavaType javaType, + JavaType parametricType, CloseableHttpResponse response ) { int statusCode = response.getStatusLine().getStatusCode(); @@ -261,7 +261,7 @@ private MindeeHttpException getHttpError( return new MindeeHttpException(statusCode, message, details, errorCode); } try { - ResponseT predictResponse = mapper.readValue(rawResponse, javaType); + ResponseT predictResponse = mapper.readValue(rawResponse, parametricType); message = predictResponse.getApiRequest().getError().getMessage(); ErrorDetails errorDetails = predictResponse.getApiRequest().getError().getDetails(); if (errorDetails != null) { diff --git a/src/main/java/com/mindee/input/WebhookSource.java b/src/main/java/com/mindee/input/WebhookSource.java new file mode 100644 index 000000000..a00bfca46 --- /dev/null +++ b/src/main/java/com/mindee/input/WebhookSource.java @@ -0,0 +1,38 @@ +package com.mindee.input; + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import lombok.Getter; +import org.apache.pdfbox.io.IOUtils; + +/** + * A source JSON coming from a Mindee webhook callback. + */ +@Getter +public class WebhookSource { + private final byte[] file; + + /** + * Load from an {@link InputStream}. + */ + public WebhookSource(InputStream input) throws IOException { + this.file = IOUtils.toByteArray(input); + } + + /** + * Load from a UTF-8 {@link String}. + */ + public WebhookSource(String input) { + this.file = input.getBytes(StandardCharsets.UTF_8); + } + + /** + * Load from a {@link File}. + */ + public WebhookSource(File input) throws IOException { + this.file = Files.readAllBytes(input.toPath()); + } +} diff --git a/src/test/java/com/mindee/MindeeClientTest.java b/src/test/java/com/mindee/MindeeClientTest.java index 0d9f84c74..7814d9c69 100644 --- a/src/test/java/com/mindee/MindeeClientTest.java +++ b/src/test/java/com/mindee/MindeeClientTest.java @@ -1,6 +1,7 @@ package com.mindee; import com.mindee.http.Endpoint; +import com.mindee.input.WebhookSource; import com.mindee.input.LocalInputSource; import com.mindee.http.MindeeApi; import com.mindee.http.RequestParameters; @@ -10,6 +11,7 @@ import com.mindee.parsing.common.Document; import com.mindee.parsing.common.Job; import com.mindee.parsing.common.PredictResponse; +import com.mindee.product.ProductTestHelper; import com.mindee.product.custom.CustomV1; import com.mindee.product.invoice.InvoiceV4; import com.mindee.pdf.PdfOperation; @@ -391,4 +393,15 @@ void givenAnAsyncUrl_whenEnqueued_shouldInvokeApiCorrectly() throws IOException Assertions.assertEquals(new URL("https://fake.pdf"), requestParameters.getFileUrl()); Assertions.assertEquals("someid", jobId); } + + @Test + void givenJsonInput_whenLoaded_shouldDeserializeCorrectly() throws IOException { + File file = new File("src/test/resources/products/invoices/response_v4/complete.json"); + WebhookSource webhookSource = new WebhookSource(file); + AsyncPredictResponse predictResponse = client.loadPrediction(InvoiceV4.class, webhookSource); + ProductTestHelper.assertStringEqualsFile( + predictResponse.getDocumentObj().toString(), + "src/test/resources/products/invoices/response_v4/summary_full.rst" + ); + } } From fe7e9f5df3f25195716757d736445b883427e97d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?ianar=C3=A9=20s=C3=A9vi?= Date: Wed, 13 Mar 2024 15:35:27 +0100 Subject: [PATCH 2/3] review of naming --- README.md | 11 ++++++----- src/main/java/com/mindee/MindeeClient.java | 10 ++++++---- .../input/{WebhookSource.java => LocalResponse.java} | 10 +++++----- src/test/java/com/mindee/MindeeClientTest.java | 6 +++--- 4 files changed, 20 insertions(+), 17 deletions(-) rename src/main/java/com/mindee/input/{WebhookSource.java => LocalResponse.java} (70%) diff --git a/README.md b/README.md index 5e900bffd..260ebc588 100644 --- a/README.md +++ b/README.md @@ -152,6 +152,7 @@ This is an optional way of handling asynchronous APIs. ```java import com.mindee.MindeeClient; import com.mindee.input.LocalInputSource; +import com.mindee.input.LocalResponse; import com.mindee.input.WebhookSource; import com.mindee.product.internationalid.InternationalIdV2; @@ -170,19 +171,19 @@ public class SimpleMindeeClient { // Enqueue the file String jobId = client.enqueue(InternationalIdV2.class, localInputSource) .getJob().getId(); - + // Load the JSON string sent by the Mindee webhook callback. // // Reading the callback data will vary greatly depending on which // HTTP server you are using, and is beyond the scope of this example. - WebhookSource webhookSource = new WebhookSource("{'json': 'data'}"); - + LocalResponse localResponse = new LocalResponse("{'json': 'data'}"); + // You can also use a File object as the input. - //WebhookSource webhookSource = new WebhookSource(new File("/path/to/file.json")); + //LocalResponse localResponse = new LocalResponse(new File("/path/to/file.json")); AsyncPredictResponse response = mindeeClient.loadPrediction( InternationalIdV2.class, - new WebhookSource(webhookStream) + new LocalResponse(webhookStream) ); // Print a summary of the parsed data diff --git a/src/main/java/com/mindee/MindeeClient.java b/src/main/java/com/mindee/MindeeClient.java index e5ba8513c..214d88e9c 100644 --- a/src/main/java/com/mindee/MindeeClient.java +++ b/src/main/java/com/mindee/MindeeClient.java @@ -8,8 +8,8 @@ import com.mindee.http.RequestParameters; import com.mindee.input.InputSourceUtils; import com.mindee.input.LocalInputSource; +import com.mindee.input.LocalResponse; import com.mindee.input.PageOptions; -import com.mindee.input.WebhookSource; import com.mindee.parsing.common.AsyncPredictResponse; import com.mindee.parsing.common.Inference; import com.mindee.parsing.common.PredictResponse; @@ -491,11 +491,13 @@ private PredictResponse parse( } /** - * Load a prediction from a webhook response. + * Load a local prediction. + * Typically used when wanting to load from a webhook callback. + * However, any kind of Mindee response may be loaded. */ public AsyncPredictResponse loadPrediction( Class type, - WebhookSource webhookSource + LocalResponse localResponse ) throws IOException { ObjectMapper objectMapper = new ObjectMapper(); objectMapper.findAndRegisterModules(); @@ -503,7 +505,7 @@ public AsyncPredictResponse loadPrediction( AsyncPredictResponse.class, type ); - return objectMapper.readValue(webhookSource.getFile(), parametricType); + return objectMapper.readValue(localResponse.getFile(), parametricType); } private byte[] getSplitFile( diff --git a/src/main/java/com/mindee/input/WebhookSource.java b/src/main/java/com/mindee/input/LocalResponse.java similarity index 70% rename from src/main/java/com/mindee/input/WebhookSource.java rename to src/main/java/com/mindee/input/LocalResponse.java index a00bfca46..4d85bb201 100644 --- a/src/main/java/com/mindee/input/WebhookSource.java +++ b/src/main/java/com/mindee/input/LocalResponse.java @@ -9,30 +9,30 @@ import org.apache.pdfbox.io.IOUtils; /** - * A source JSON coming from a Mindee webhook callback. + * A Mindee response saved locally. */ @Getter -public class WebhookSource { +public class LocalResponse { private final byte[] file; /** * Load from an {@link InputStream}. */ - public WebhookSource(InputStream input) throws IOException { + public LocalResponse(InputStream input) throws IOException { this.file = IOUtils.toByteArray(input); } /** * Load from a UTF-8 {@link String}. */ - public WebhookSource(String input) { + public LocalResponse(String input) { this.file = input.getBytes(StandardCharsets.UTF_8); } /** * Load from a {@link File}. */ - public WebhookSource(File input) throws IOException { + public LocalResponse(File input) throws IOException { this.file = Files.readAllBytes(input.toPath()); } } diff --git a/src/test/java/com/mindee/MindeeClientTest.java b/src/test/java/com/mindee/MindeeClientTest.java index 7814d9c69..19a14b13e 100644 --- a/src/test/java/com/mindee/MindeeClientTest.java +++ b/src/test/java/com/mindee/MindeeClientTest.java @@ -1,7 +1,7 @@ package com.mindee; import com.mindee.http.Endpoint; -import com.mindee.input.WebhookSource; +import com.mindee.input.LocalResponse; import com.mindee.input.LocalInputSource; import com.mindee.http.MindeeApi; import com.mindee.http.RequestParameters; @@ -397,8 +397,8 @@ void givenAnAsyncUrl_whenEnqueued_shouldInvokeApiCorrectly() throws IOException @Test void givenJsonInput_whenLoaded_shouldDeserializeCorrectly() throws IOException { File file = new File("src/test/resources/products/invoices/response_v4/complete.json"); - WebhookSource webhookSource = new WebhookSource(file); - AsyncPredictResponse predictResponse = client.loadPrediction(InvoiceV4.class, webhookSource); + LocalResponse localResponse = new LocalResponse(file); + AsyncPredictResponse predictResponse = client.loadPrediction(InvoiceV4.class, localResponse); ProductTestHelper.assertStringEqualsFile( predictResponse.getDocumentObj().toString(), "src/test/resources/products/invoices/response_v4/summary_full.rst" From 614f0d9375d2c2969fc9f0ba4ee866b9aaae7ce2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?ianar=C3=A9=20s=C3=A9vi?= Date: Wed, 13 Mar 2024 15:47:15 +0100 Subject: [PATCH 3/3] add async test --- src/test/java/com/mindee/MindeeClientTest.java | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/src/test/java/com/mindee/MindeeClientTest.java b/src/test/java/com/mindee/MindeeClientTest.java index 19a14b13e..0452dc324 100644 --- a/src/test/java/com/mindee/MindeeClientTest.java +++ b/src/test/java/com/mindee/MindeeClientTest.java @@ -14,6 +14,7 @@ import com.mindee.product.ProductTestHelper; import com.mindee.product.custom.CustomV1; import com.mindee.product.invoice.InvoiceV4; +import com.mindee.product.internationalid.InternationalIdV2; import com.mindee.pdf.PdfOperation; import com.mindee.pdf.SplitPdf; import java.io.File; @@ -395,7 +396,7 @@ void givenAnAsyncUrl_whenEnqueued_shouldInvokeApiCorrectly() throws IOException } @Test - void givenJsonInput_whenLoaded_shouldDeserializeCorrectly() throws IOException { + void givenJsonInput_whenSync_shouldDeserializeCorrectly() throws IOException { File file = new File("src/test/resources/products/invoices/response_v4/complete.json"); LocalResponse localResponse = new LocalResponse(file); AsyncPredictResponse predictResponse = client.loadPrediction(InvoiceV4.class, localResponse); @@ -404,4 +405,18 @@ void givenJsonInput_whenLoaded_shouldDeserializeCorrectly() throws IOException { "src/test/resources/products/invoices/response_v4/summary_full.rst" ); } + + @Test + void givenJsonInput_whenAsync_shouldDeserializeCorrectly() throws IOException { + File file = new File("src/test/resources/products/international_id/response_v2/complete.json"); + LocalResponse localResponse = new LocalResponse(file); + AsyncPredictResponse predictResponse = client.loadPrediction( + InternationalIdV2.class, + localResponse + ); + ProductTestHelper.assertStringEqualsFile( + predictResponse.getDocumentObj().toString(), + "src/test/resources/products/international_id/response_v2/summary_full.rst" + ); + } }