Skip to content

Commit 62bdf37

Browse files
authored
✨ add ability to easily load a webhook response (#154)
1 parent 0674aab commit 62bdf37

5 files changed

Lines changed: 189 additions & 33 deletions

File tree

README.md

Lines changed: 91 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,13 @@ Where `${mindee.sdk.version}` is the version shown here:
2525
## Loading a File and Parsing It
2626
The `MindeeClient` class is the entry point for most of the helper library features.
2727

28+
## Synchronously Parsing a File
29+
This is the easiest and fastest way to integrate into the Mindee API.
30+
31+
However, not all products are available in synchronous mode.
32+
2833
### Global Documents
29-
These classes are available in the `com.mindee.product` package:
34+
These classes are available in the `com.mindee.product` package:
3035

3136
```java
3237
import com.mindee.MindeeClient;
@@ -55,19 +60,24 @@ public class SimpleMindeeClient {
5560
System.out.println(response.getDocument().toString());
5661
}
5762
}
58-
5963
```
6064

61-
### Region-Specific Documents
65+
**Note for Region-Specific Documents:**
66+
6267
Each region will have its own package within the general `com.mindee.product` package.
6368

6469
For example USA-specific classes will be in the `com.mindee.product.us` package:
70+
```java
71+
import com.mindee.product.us.bankcheck.BankCheckV1;
72+
```
6573

74+
### Custom Documents (API Builder)
6675
```java
6776
import com.mindee.MindeeClient;
6877
import com.mindee.input.LocalInputSource;
6978
import com.mindee.parsing.common.PredictResponse;
70-
import com.mindee.product.us.bankcheck.BankCheckV1;
79+
import com.mindee.product.custom.CustomV1;
80+
import com.mindee.http.Endpoint;
7181
import java.io.File;
7282
import java.io.IOException;
7383

@@ -76,54 +86,113 @@ public class SimpleMindeeClient {
7686

7787
// Init a new client
7888
MindeeClient mindeeClient = new MindeeClient("my-api-key");
89+
90+
// Init the endpoint for the custom document
91+
Endpoint endpoint = new Endpoint("my-endpoint", "my-account");
7992

8093
// Load a file from disk
8194
LocalInputSource localInputSource = new LocalInputSource(
82-
"/path/to/the/file.ext"
95+
"src/main/resources/invoices/invoice1.pdf"
8396
);
8497
// Parse the file
85-
Document<BankCheckV1> response = mindeeClient.parse(
86-
BankCheckV1.class,
87-
localInputSource
98+
Document<CustomV1> customDocument = mindeeClient.parse(
99+
localInputSource,
100+
endpoint
88101
);
89-
// Print a summary of the parsed data
90-
System.out.println(response.getDocument().toString());
91102
}
92103
}
93104
```
94105

95-
### Custom Documents (API Builder)
106+
## Synchronously Parsing a File
107+
This allows for easier handling of bursts of documents sent.
108+
109+
Some products are only available asynchronously, check the example code
110+
directly on the Mindee platform.
111+
112+
### Enqueue and Parse a File
113+
The client library will take care of handling the polling requests for you.
114+
115+
This is the easiest way to get started.
116+
96117
```java
97118
import com.mindee.MindeeClient;
98119
import com.mindee.input.LocalInputSource;
99-
import com.mindee.parsing.common.PredictResponse;
100-
import com.mindee.product.custom.CustomV1;
101-
import com.mindee.http.Endpoint;
120+
import com.mindee.parsing.common.AsyncPredictResponse;
121+
import com.mindee.product.internationalid.InternationalIdV2;
102122
import java.io.File;
103123
import java.io.IOException;
104124

125+
public class SimpleMindeeClient {
126+
127+
public static void main(String[] args) throws IOException, InterruptedException {
128+
String apiKey = "my-api-key";
129+
String filePath = "/path/to/the/file.ext";
130+
131+
// Init a new client
132+
MindeeClient mindeeClient = new MindeeClient(apiKey);
133+
134+
// Load a file from disk
135+
LocalInputSource inputSource = new LocalInputSource(new File(filePath));
136+
137+
// Parse the file asynchronously
138+
AsyncPredictResponse<InternationalIdV2> response = mindeeClient.enqueueAndParse(
139+
InternationalIdV2.class,
140+
inputSource
141+
);
142+
143+
// Print a summary of the response
144+
System.out.println(response.toString());
145+
}
146+
}
147+
```
148+
149+
### Enqueue and Parse a Webhook Response
150+
This is an optional way of handling asynchronous APIs.
151+
152+
```java
153+
import com.mindee.MindeeClient;
154+
import com.mindee.input.LocalInputSource;
155+
import com.mindee.input.LocalResponse;
156+
import com.mindee.input.WebhookSource;
157+
import com.mindee.product.internationalid.InternationalIdV2;
158+
159+
import java.io.IOException;
160+
105161
public class SimpleMindeeClient {
106162
public static void main(String[] args) throws IOException {
107163

108164
// Init a new client
109165
MindeeClient mindeeClient = new MindeeClient("my-api-key");
110-
111-
// Init the endpoint for the custom document
112-
Endpoint endpoint = new Endpoint("my-endpoint", "my-account");
113166

114167
// Load a file from disk
115168
LocalInputSource localInputSource = new LocalInputSource(
116-
"src/main/resources/invoices/invoice1.pdf"
169+
"/path/to/the/file.ext"
117170
);
118-
// Parse the file
119-
Document<CustomV1> customDocument = mindeeClient.parse(
120-
localInputSource,
121-
endpoint
171+
// Enqueue the file
172+
String jobId = client.enqueue(InternationalIdV2.class, localInputSource)
173+
.getJob().getId();
174+
175+
// Load the JSON string sent by the Mindee webhook callback.
176+
//
177+
// Reading the callback data will vary greatly depending on which
178+
// HTTP server you are using, and is beyond the scope of this example.
179+
LocalResponse localResponse = new LocalResponse("{'json': 'data'}");
180+
181+
// You can also use a File object as the input.
182+
//LocalResponse localResponse = new LocalResponse(new File("/path/to/file.json"));
183+
184+
AsyncPredictResponse<InternationalIdV2> response = mindeeClient.loadPrediction(
185+
InternationalIdV2.class,
186+
new LocalResponse(webhookStream)
122187
);
188+
189+
// Print a summary of the parsed data
190+
System.out.println(response.getDocument().toString());
123191
}
124192
}
125193
```
126194

195+
127196
## Further Reading
128197
Complete details on the working of the library are available in the following guides:
129198

src/main/java/com/mindee/MindeeClient.java

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,14 @@
11
package com.mindee;
22

3+
import com.fasterxml.jackson.databind.JavaType;
4+
import com.fasterxml.jackson.databind.ObjectMapper;
35
import com.mindee.http.Endpoint;
46
import com.mindee.http.MindeeApi;
57
import com.mindee.http.MindeeHttpApi;
68
import com.mindee.http.RequestParameters;
79
import com.mindee.input.InputSourceUtils;
810
import com.mindee.input.LocalInputSource;
11+
import com.mindee.input.LocalResponse;
912
import com.mindee.input.PageOptions;
1013
import com.mindee.parsing.common.AsyncPredictResponse;
1114
import com.mindee.parsing.common.Inference;
@@ -487,6 +490,24 @@ private PredictResponse<CustomV1> parse(
487490
.build());
488491
}
489492

493+
/**
494+
* Load a local prediction.
495+
* Typically used when wanting to load from a webhook callback.
496+
* However, any kind of Mindee response may be loaded.
497+
*/
498+
public <T extends Inference> AsyncPredictResponse<T> loadPrediction(
499+
Class<T> type,
500+
LocalResponse localResponse
501+
) throws IOException {
502+
ObjectMapper objectMapper = new ObjectMapper();
503+
objectMapper.findAndRegisterModules();
504+
JavaType parametricType = objectMapper.getTypeFactory().constructParametricType(
505+
AsyncPredictResponse.class,
506+
type
507+
);
508+
return objectMapper.readValue(localResponse.getFile(), parametricType);
509+
}
510+
490511
private byte[] getSplitFile(
491512
LocalInputSource localInputSource,
492513
PageOptions pageOptions

src/main/java/com/mindee/http/MindeeHttpApi.java

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -125,7 +125,7 @@ public <DocT extends Inference> AsyncPredictResponse<DocT> documentQueueGet(
125125

126126
// required to register jackson date module format to deserialize
127127
mapper.findAndRegisterModules();
128-
JavaType type = mapper.getTypeFactory().constructParametricType(
128+
JavaType parametricType = mapper.getTypeFactory().constructParametricType(
129129
AsyncPredictResponse.class,
130130
documentClass
131131
);
@@ -142,10 +142,10 @@ public <DocT extends Inference> AsyncPredictResponse<DocT> documentQueueGet(
142142
HttpEntity responseEntity = response.getEntity();
143143
int statusCode = response.getStatusLine().getStatusCode();
144144
if (!is2xxStatusCode(statusCode)) {
145-
throw getHttpError(type, response);
145+
throw getHttpError(parametricType, response);
146146
}
147147
String rawResponse = readRawResponse(responseEntity);
148-
AsyncPredictResponse<DocT> mappedResponse = mapper.readValue(rawResponse, type);
148+
AsyncPredictResponse<DocT> mappedResponse = mapper.readValue(rawResponse, parametricType);
149149
mappedResponse.setRawResponse(rawResponse);
150150
if (
151151
mappedResponse.getJob() != null
@@ -179,7 +179,7 @@ public <DocT extends Inference> PredictResponse<DocT> predictPost(
179179

180180
// required to register jackson date module format to deserialize
181181
mapper.findAndRegisterModules();
182-
JavaType type = mapper.getTypeFactory().constructParametricType(
182+
JavaType parametricType = mapper.getTypeFactory().constructParametricType(
183183
PredictResponse.class,
184184
documentClass
185185
);
@@ -190,13 +190,13 @@ public <DocT extends Inference> PredictResponse<DocT> predictPost(
190190
HttpEntity responseEntity = response.getEntity();
191191
int statusCode = response.getStatusLine().getStatusCode();
192192
if (!is2xxStatusCode(statusCode)) {
193-
throw getHttpError(type, response);
193+
throw getHttpError(parametricType, response);
194194
}
195195
if (responseEntity.getContentLength() == 0) {
196196
throw new MindeeException("Empty response from server.");
197197
}
198198
String rawResponse = readRawResponse(responseEntity);
199-
PredictResponse<DocT> mappedResponse = mapper.readValue(rawResponse, type);
199+
PredictResponse<DocT> mappedResponse = mapper.readValue(rawResponse, parametricType);
200200
mappedResponse.setRawResponse(rawResponse);
201201
return mappedResponse;
202202
} catch (IOException err) {
@@ -218,7 +218,7 @@ public <DocT extends Inference> AsyncPredictResponse<DocT> predictAsyncPost(
218218

219219
// required to register jackson date module format to deserialize
220220
mapper.findAndRegisterModules();
221-
JavaType type = mapper.getTypeFactory().constructParametricType(
221+
JavaType parametricType = mapper.getTypeFactory().constructParametricType(
222222
AsyncPredictResponse.class,
223223
documentClass
224224
);
@@ -229,13 +229,13 @@ public <DocT extends Inference> AsyncPredictResponse<DocT> predictAsyncPost(
229229
HttpEntity responseEntity = response.getEntity();
230230
int statusCode = response.getStatusLine().getStatusCode();
231231
if (!is2xxStatusCode(statusCode)) {
232-
throw getHttpError(type, response);
232+
throw getHttpError(parametricType, response);
233233
}
234234
if (responseEntity.getContentLength() == 0) {
235235
throw new MindeeException("Empty response from server.");
236236
}
237237
String rawResponse = readRawResponse(responseEntity);
238-
AsyncPredictResponse<DocT> mappedResponse = mapper.readValue(rawResponse, type);
238+
AsyncPredictResponse<DocT> mappedResponse = mapper.readValue(rawResponse, parametricType);
239239
mappedResponse.setRawResponse(rawResponse);
240240
return mappedResponse;
241241
} catch (IOException err) {
@@ -244,7 +244,7 @@ public <DocT extends Inference> AsyncPredictResponse<DocT> predictAsyncPost(
244244
}
245245

246246
private <ResponseT extends ApiResponse> MindeeHttpException getHttpError(
247-
JavaType javaType,
247+
JavaType parametricType,
248248
CloseableHttpResponse response
249249
) {
250250
int statusCode = response.getStatusLine().getStatusCode();
@@ -261,7 +261,7 @@ private <ResponseT extends ApiResponse> MindeeHttpException getHttpError(
261261
return new MindeeHttpException(statusCode, message, details, errorCode);
262262
}
263263
try {
264-
ResponseT predictResponse = mapper.readValue(rawResponse, javaType);
264+
ResponseT predictResponse = mapper.readValue(rawResponse, parametricType);
265265
message = predictResponse.getApiRequest().getError().getMessage();
266266
ErrorDetails errorDetails = predictResponse.getApiRequest().getError().getDetails();
267267
if (errorDetails != null) {
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
package com.mindee.input;
2+
3+
import java.io.File;
4+
import java.io.IOException;
5+
import java.io.InputStream;
6+
import java.nio.charset.StandardCharsets;
7+
import java.nio.file.Files;
8+
import lombok.Getter;
9+
import org.apache.pdfbox.io.IOUtils;
10+
11+
/**
12+
* A Mindee response saved locally.
13+
*/
14+
@Getter
15+
public class LocalResponse {
16+
private final byte[] file;
17+
18+
/**
19+
* Load from an {@link InputStream}.
20+
*/
21+
public LocalResponse(InputStream input) throws IOException {
22+
this.file = IOUtils.toByteArray(input);
23+
}
24+
25+
/**
26+
* Load from a UTF-8 {@link String}.
27+
*/
28+
public LocalResponse(String input) {
29+
this.file = input.getBytes(StandardCharsets.UTF_8);
30+
}
31+
32+
/**
33+
* Load from a {@link File}.
34+
*/
35+
public LocalResponse(File input) throws IOException {
36+
this.file = Files.readAllBytes(input.toPath());
37+
}
38+
}

src/test/java/com/mindee/MindeeClientTest.java

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package com.mindee;
22

33
import com.mindee.http.Endpoint;
4+
import com.mindee.input.LocalResponse;
45
import com.mindee.input.LocalInputSource;
56
import com.mindee.http.MindeeApi;
67
import com.mindee.http.RequestParameters;
@@ -10,8 +11,10 @@
1011
import com.mindee.parsing.common.Document;
1112
import com.mindee.parsing.common.Job;
1213
import com.mindee.parsing.common.PredictResponse;
14+
import com.mindee.product.ProductTestHelper;
1315
import com.mindee.product.custom.CustomV1;
1416
import com.mindee.product.invoice.InvoiceV4;
17+
import com.mindee.product.internationalid.InternationalIdV2;
1518
import com.mindee.pdf.PdfOperation;
1619
import com.mindee.pdf.SplitPdf;
1720
import java.io.File;
@@ -391,4 +394,29 @@ void givenAnAsyncUrl_whenEnqueued_shouldInvokeApiCorrectly() throws IOException
391394
Assertions.assertEquals(new URL("https://fake.pdf"), requestParameters.getFileUrl());
392395
Assertions.assertEquals("someid", jobId);
393396
}
397+
398+
@Test
399+
void givenJsonInput_whenSync_shouldDeserializeCorrectly() throws IOException {
400+
File file = new File("src/test/resources/products/invoices/response_v4/complete.json");
401+
LocalResponse localResponse = new LocalResponse(file);
402+
AsyncPredictResponse<InvoiceV4> predictResponse = client.loadPrediction(InvoiceV4.class, localResponse);
403+
ProductTestHelper.assertStringEqualsFile(
404+
predictResponse.getDocumentObj().toString(),
405+
"src/test/resources/products/invoices/response_v4/summary_full.rst"
406+
);
407+
}
408+
409+
@Test
410+
void givenJsonInput_whenAsync_shouldDeserializeCorrectly() throws IOException {
411+
File file = new File("src/test/resources/products/international_id/response_v2/complete.json");
412+
LocalResponse localResponse = new LocalResponse(file);
413+
AsyncPredictResponse<InternationalIdV2> predictResponse = client.loadPrediction(
414+
InternationalIdV2.class,
415+
localResponse
416+
);
417+
ProductTestHelper.assertStringEqualsFile(
418+
predictResponse.getDocumentObj().toString(),
419+
"src/test/resources/products/international_id/response_v2/summary_full.rst"
420+
);
421+
}
394422
}

0 commit comments

Comments
 (0)