Skip to content

Commit 28bc256

Browse files
authored
feat: pagination support for api calls (#128)
This commit adds implementation for 4 pagination types in API Calls, along with their unit tests.
1 parent b7873d5 commit 28bc256

21 files changed

Lines changed: 2123 additions & 203 deletions

src/main/java/io/apimatic/core/ApiCall.java

Lines changed: 51 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,8 @@
88
import io.apimatic.core.logger.SdkLoggerFactory;
99
import io.apimatic.core.request.async.AsyncExecutor;
1010
import io.apimatic.core.types.CoreApiException;
11+
import io.apimatic.coreinterfaces.http.Context;
1112
import io.apimatic.coreinterfaces.http.request.Request;
12-
import io.apimatic.coreinterfaces.http.request.configuration.CoreEndpointConfiguration;
1313
import io.apimatic.coreinterfaces.http.response.Response;
1414
import io.apimatic.coreinterfaces.logger.ApiLogger;
1515

@@ -27,19 +27,19 @@ public final class ApiCall<ResponseType, ExceptionType extends CoreApiException>
2727
private final GlobalConfiguration globalConfig;
2828

2929
/**
30-
* An instance of {@link Request}
30+
* An instance of {@link HttpRequest.Builder}
3131
*/
32-
private final Request request;
32+
private final HttpRequest.Builder requestBuilder;
3333

3434
/**
3535
* An instance of {@link ResponseHandler.Builder}.
3636
*/
3737
private final ResponseHandler<ResponseType, ExceptionType> responseHandler;
3838

3939
/**
40-
* An instance of {@link CoreEndpointConfiguration}.
40+
* An instance of {@link EndpointConfiguration}.
4141
*/
42-
private final CoreEndpointConfiguration endpointConfiguration;
42+
private final EndpointConfiguration endpointConfiguration;
4343

4444
/**
4545
* An instance of {@link ApiLogger} for logging.
@@ -50,17 +50,18 @@ public final class ApiCall<ResponseType, ExceptionType extends CoreApiException>
5050
/**
5151
* ApiCall constructor.
5252
* @param globalConfig the required configuration to built the ApiCall.
53-
* @param coreHttpRequest Http request for the api call.
53+
* @param endpointConfiguration The endPoint configuration.
54+
* @param requestBuilder Http request builder for the api call.
5455
* @param responseHandler the handler for the response.
55-
* @param coreEndpointConfiguration endPoint configuration.
5656
*/
57-
private ApiCall(final GlobalConfiguration globalConfig, final Request coreHttpRequest,
58-
final ResponseHandler<ResponseType, ExceptionType> responseHandler,
59-
final CoreEndpointConfiguration coreEndpointConfiguration) {
57+
private ApiCall(final GlobalConfiguration globalConfig,
58+
final EndpointConfiguration endpointConfiguration,
59+
final HttpRequest.Builder requestBuilder,
60+
final ResponseHandler<ResponseType, ExceptionType> responseHandler) {
6061
this.globalConfig = globalConfig;
61-
this.request = coreHttpRequest;
62+
this.requestBuilder = requestBuilder;
6263
this.responseHandler = responseHandler;
63-
this.endpointConfiguration = coreEndpointConfiguration;
64+
this.endpointConfiguration = endpointConfiguration;
6465
this.apiLogger = SdkLoggerFactory.getLogger(globalConfig.getLoggingConfiguration());
6566
}
6667

@@ -71,24 +72,31 @@ private ApiCall(final GlobalConfiguration globalConfig, final Request coreHttpRe
7172
* @throws ExceptionType Represents error response from the server.
7273
*/
7374
public ResponseType execute() throws IOException, ExceptionType {
75+
Request request = requestBuilder.build(globalConfig);
7476
apiLogger.logRequest(request);
75-
Response httpResponse = globalConfig.getHttpClient()
76-
.execute(request, endpointConfiguration);
77-
apiLogger.logResponse(httpResponse);
77+
Response response = globalConfig.getHttpClient().execute(request, endpointConfiguration);
78+
apiLogger.logResponse(response);
7879

79-
return responseHandler.handle(request, httpResponse, globalConfig, endpointConfiguration);
80+
Context context = globalConfig.getCompatibilityFactory()
81+
.createHttpContext(request, response);
82+
83+
return responseHandler.handle(context, endpointConfiguration, globalConfig, requestBuilder);
8084
}
8185

8286
/**
8387
* Execute the Api call asynchronously and returns the expected response in CompletableFuture.
8488
* @return the instance of {@link CompletableFuture}.
8589
*/
8690
public CompletableFuture<ResponseType> executeAsync() {
87-
return AsyncExecutor.makeHttpCallAsync(() -> request,
88-
request -> globalConfig.getHttpClient().executeAsync(request,
89-
endpointConfiguration),
90-
(httpRequest, httpResponse) -> responseHandler.handle(httpRequest, httpResponse,
91-
globalConfig, endpointConfiguration), apiLogger);
91+
return AsyncExecutor.makeHttpCallAsync(() -> requestBuilder.build(globalConfig),
92+
request -> globalConfig.getHttpClient()
93+
.executeAsync(request, endpointConfiguration),
94+
(request, response) -> {
95+
Context context = globalConfig.getCompatibilityFactory()
96+
.createHttpContext(request, response);
97+
return responseHandler.handle(context, endpointConfiguration, globalConfig,
98+
requestBuilder);
99+
}, apiLogger);
92100
}
93101

94102
/**
@@ -139,6 +147,16 @@ public Builder<ResponseType, ExceptionType> requestBuilder(
139147
return this;
140148
}
141149

150+
/**
151+
* @param builder requestBuilder {@link HttpRequest.Builder}.
152+
* @return {@link ApiCall.Builder}.
153+
*/
154+
public Builder<ResponseType, ExceptionType> requestBuilder(
155+
HttpRequest.Builder builder) {
156+
requestBuilder = builder;
157+
return this;
158+
}
159+
142160
/**
143161
* @param action responseHandler {@link Consumer}.
144162
* @return {@link ApiCall.Builder}.
@@ -161,15 +179,25 @@ public Builder<ResponseType, ExceptionType> endpointConfiguration(
161179
return this;
162180
}
163181

182+
/**
183+
* @param builder endpointConfigurationBuilder {@link EndpointConfiguration.Builder}.
184+
* @return {@link ApiCall.Builder}.
185+
*/
186+
public Builder<ResponseType, ExceptionType> endpointConfiguration(
187+
EndpointConfiguration.Builder builder) {
188+
endpointConfigurationBuilder = builder;
189+
return this;
190+
}
191+
164192
/**
165193
* build the {@link ApiCall}.
166194
* @return the instance of {@link ApiCall}.
167195
* @throws IOException Signals that an I/O exception of some sort has occurred.
168196
*/
169197
public ApiCall<ResponseType, ExceptionType> build() throws IOException {
170198
return new ApiCall<ResponseType, ExceptionType>(globalConfig,
171-
requestBuilder.build(globalConfig), responseHandlerBuilder.build(),
172-
endpointConfigurationBuilder.build());
199+
endpointConfigurationBuilder.build(), requestBuilder,
200+
responseHandlerBuilder.build());
173201
}
174202
}
175203
}

src/main/java/io/apimatic/core/ErrorCase.java

Lines changed: 2 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,13 @@
11
package io.apimatic.core;
22

3-
import java.io.StringReader;
43
import java.util.regex.Matcher;
54
import java.util.regex.Pattern;
65
import javax.json.Json;
76
import javax.json.JsonException;
87
import javax.json.JsonPointer;
9-
import javax.json.JsonReader;
108
import javax.json.JsonStructure;
119
import io.apimatic.core.types.CoreApiException;
10+
import io.apimatic.core.utilities.CoreHelper;
1211
import io.apimatic.coreinterfaces.http.Context;
1312
import io.apimatic.coreinterfaces.http.HttpHeaders;
1413
import io.apimatic.coreinterfaces.http.response.Response;
@@ -140,14 +139,7 @@ private String replaceHeadersFromTemplate(HttpHeaders headers, String reason) {
140139

141140
private String replaceBodyFromTemplate(String responseBody, String reason) {
142141
StringBuilder formatter = new StringBuilder(reason);
143-
JsonReader jsonReader = Json.createReader(new StringReader(responseBody));
144-
JsonStructure jsonStructure = null;
145-
try {
146-
jsonStructure = jsonReader.read();
147-
} catch (Exception e) {
148-
// No need to do anything here
149-
}
150-
jsonReader.close();
142+
JsonStructure jsonStructure = CoreHelper.createJsonStructure(responseBody);
151143
Matcher matcher = Pattern.compile("\\{(.*?)\\}").matcher(reason);
152144
while (matcher.find()) {
153145
String key = matcher.group(1);

src/main/java/io/apimatic/core/HttpRequest.java

Lines changed: 121 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,17 @@
33
import java.io.IOException;
44
import java.util.AbstractMap.SimpleEntry;
55
import java.util.ArrayList;
6+
import java.util.Arrays;
67
import java.util.Collections;
78
import java.util.HashMap;
89
import java.util.HashSet;
910
import java.util.List;
1011
import java.util.Map;
12+
import java.util.Map.Entry;
1113
import java.util.Set;
1214
import java.util.function.Consumer;
15+
import java.util.function.UnaryOperator;
16+
1317
import io.apimatic.core.authentication.AuthBuilder;
1418
import io.apimatic.core.exceptions.AuthValidationException;
1519
import io.apimatic.core.types.http.request.MultipartFileWrapper;
@@ -84,10 +88,12 @@ private HttpRequest(final GlobalConfiguration coreConfig, final String server,
8488
// Creating a basic request to provide it to auth instances
8589
Request request = buildBasicRequest(httpMethod, requestHeaders);
8690

87-
applyAuthentication(request, authentication);
88-
// include auth query parameters in request query params to have them in the query url
89-
if (request.getQueryParameters() != null) {
90-
queryParams.putAll(request.getQueryParameters());
91+
if (request != null) {
92+
applyAuthentication(request, authentication);
93+
// include auth query parameters in request query params to have them in the query url
94+
if (request.getQueryParameters() != null) {
95+
queryParams.putAll(request.getQueryParameters());
96+
}
9197
}
9298

9399
queryUrlBuilder = getStringBuilder(server, path, queryParams, arraySerializationFormat);
@@ -284,7 +290,7 @@ public static class Builder {
284290
/**
285291
* A map of header parameters.
286292
*/
287-
private Map<String, List<String>> headerParams = new HashMap<>();
293+
private Map<String, List<Object>> headerParams = new HashMap<>();
288294

289295
/**
290296
* A set of {@link Parameter}.
@@ -322,6 +328,78 @@ public static class Builder {
322328
*/
323329
private Parameter.Builder parameterBuilder = new Parameter.Builder();
324330

331+
/**
332+
* Update the request parameters using a setter.
333+
*
334+
* @param pointer A JSON pointer pointing to any request field.
335+
* @param setter A function that takes in an old value and returns a new value.
336+
* @return The updated instance of current request builder.
337+
*/
338+
public Builder updateByReference(String pointer, UnaryOperator<Object> setter) {
339+
if (pointer == null) {
340+
return this;
341+
}
342+
343+
String[] pointerParts = pointer.split("#");
344+
String prefix = pointerParts[0];
345+
String point = pointerParts.length > 1 ? pointerParts[1] : "";
346+
347+
switch (prefix) {
348+
case "$request.path":
349+
updateTemplateParams(setter, point);
350+
return this;
351+
case "$request.query":
352+
queryParams = CoreHelper.updateValueByPointer(queryParams, point, setter);
353+
return this;
354+
case "$request.headers":
355+
updateHeaderParams(setter, point);
356+
return this;
357+
default:
358+
return this;
359+
}
360+
}
361+
362+
@SuppressWarnings("unchecked")
363+
private void updateHeaderParams(UnaryOperator<Object> setter, String point) {
364+
Map<String, Object> simplifiedHeaders = new HashMap<>();
365+
for (Entry<String, List<Object>> entry : headerParams.entrySet()) {
366+
if (entry.getValue().size() == 1) {
367+
simplifiedHeaders.put(entry.getKey(), entry.getValue().get(0));
368+
} else {
369+
simplifiedHeaders.put(entry.getKey(), entry.getValue());
370+
}
371+
}
372+
373+
simplifiedHeaders = CoreHelper.updateValueByPointer(simplifiedHeaders, point, setter);
374+
375+
for (Map.Entry<String, Object> entry : simplifiedHeaders.entrySet()) {
376+
if (entry.getValue() instanceof List<?>) {
377+
headerParams.put(entry.getKey(), (List<Object>) entry.getValue());
378+
} else {
379+
headerParams.put(entry.getKey(), Arrays.asList(entry.getValue()));
380+
}
381+
}
382+
}
383+
384+
private void updateTemplateParams(UnaryOperator<Object> setter, String point) {
385+
Map<String, Object> simplifiedPath = new HashMap<>();
386+
for (Map.Entry<String, SimpleEntry<Object, Boolean>> entry
387+
: templateParams.entrySet()) {
388+
simplifiedPath.put(entry.getKey(), entry.getValue().getKey());
389+
}
390+
391+
simplifiedPath = CoreHelper.updateValueByPointer(simplifiedPath, point, setter);
392+
393+
for (Map.Entry<String, Object> entry : simplifiedPath.entrySet()) {
394+
// Preserve the original boolean if it exists, otherwise set default (e.g., false)
395+
Boolean originalFlag = templateParams.containsKey(entry.getKey())
396+
? templateParams.get(entry.getKey()).getValue()
397+
: false;
398+
templateParams.put(entry.getKey(),
399+
new SimpleEntry<>(entry.getValue(), originalFlag));
400+
}
401+
}
402+
325403
/**
326404
* Base uri server address.
327405
* @param server the base uri address.
@@ -372,7 +450,6 @@ public Builder queryParam(Map<String, Object> queryParameters) {
372450
return this;
373451
}
374452

375-
376453
/**
377454
* To configure the query paramater.
378455
* @param action the query parameter {@link Consumer}.
@@ -414,32 +491,17 @@ public Builder headerParam(Consumer<Parameter.Builder> action) {
414491
Parameter httpHeaderParameter = parameterBuilder.build();
415492
httpHeaderParameter.validate();
416493
String key = httpHeaderParameter.getKey();
417-
String value = getSerializedHeaderValue(httpHeaderParameter.getValue());
418494

419495
if (headerParams.containsKey(key)) {
420-
headerParams.get(key).add(value);
496+
headerParams.get(key).add(httpHeaderParameter.getValue());
421497
} else {
422-
List<String> headerValues = new ArrayList<String>();
423-
headerValues.add(value);
498+
List<Object> headerValues = new ArrayList<Object>();
499+
headerValues.add(httpHeaderParameter.getValue());
424500
headerParams.put(key, headerValues);
425501
}
426502
return this;
427503
}
428504

429-
private static String getSerializedHeaderValue(Object obj) {
430-
if (obj == null) {
431-
return null;
432-
}
433-
434-
if (CoreHelper.isTypeCombinatorStringCase(obj)
435-
|| CoreHelper.isTypeCombinatorDateTimeCase(obj)
436-
|| obj instanceof String) {
437-
return obj.toString();
438-
}
439-
440-
return CoreHelper.trySerialize(obj);
441-
}
442-
443505
/**
444506
* To configure the form parameter.
445507
* @param action the form parameter {@link Consumer}.
@@ -505,6 +567,38 @@ public Builder arraySerializationFormat(ArraySerializationFormat arraySerializat
505567
return this;
506568
}
507569

570+
private Map<String, List<String>> getHeaderParams() {
571+
Map<String, List<String>> converted = new HashMap<>();
572+
573+
for (Map.Entry<String, List<Object>> entry : headerParams.entrySet()) {
574+
String key = entry.getKey();
575+
List<Object> originalList = entry.getValue();
576+
577+
List<String> serializedList = new ArrayList<>();
578+
for (Object obj : originalList) {
579+
serializedList.add(getSerializedHeaderValue(obj));
580+
}
581+
582+
converted.put(key, serializedList);
583+
}
584+
585+
return converted;
586+
}
587+
588+
private static String getSerializedHeaderValue(Object obj) {
589+
if (obj == null) {
590+
return null;
591+
}
592+
593+
if (CoreHelper.isTypeCombinatorStringCase(obj)
594+
|| CoreHelper.isTypeCombinatorDateTimeCase(obj)
595+
|| obj instanceof String) {
596+
return obj.toString();
597+
}
598+
599+
return CoreHelper.trySerialize(obj);
600+
}
601+
508602
/**
509603
* Initialise the CoreHttpRequest.
510604
* @param coreConfig the configuration for the Http request.
@@ -515,8 +609,9 @@ public Request build(GlobalConfiguration coreConfig) throws IOException {
515609
Authentication authentication = authBuilder.build(coreConfig.getAuthentications());
516610
HttpRequest coreRequest =
517611
new HttpRequest(coreConfig, server, path, httpMethod, authentication,
518-
queryParams, templateParams, headerParams, formParams, formParamaters,
519-
body, bodySerializer, bodyParameters, arraySerializationFormat);
612+
queryParams, templateParams, getHeaderParams(), formParams,
613+
formParamaters, body, bodySerializer, bodyParameters,
614+
arraySerializationFormat);
520615
Request coreHttpRequest = coreRequest.getCoreHttpRequest();
521616

522617
if (coreConfig.getHttpCallback() != null) {

0 commit comments

Comments
 (0)