From dff8b27a36a45e0b7853417dc8901b31a371ba55 Mon Sep 17 00:00:00 2001 From: Asad Ali <14asadali@gmail.com> Date: Sat, 15 Mar 2025 21:13:13 +0500 Subject: [PATCH 01/20] adds link and cursor pagination handling --- src/main/java/io/apimatic/core/ApiCall.java | 68 +++-- src/main/java/io/apimatic/core/ErrorCase.java | 12 +- .../io/apimatic/core/ResponseHandler.java | 240 ++++++++++-------- .../http/request/EndpointConfiguration.java | 63 ++++- .../apimatic/core/utilities/CoreHelper.java | 19 ++ .../apimatic/core/ResponseHandlerTest.java | 234 +++++++---------- .../request/EndpointConfigurationTest.java | 2 +- 7 files changed, 337 insertions(+), 301 deletions(-) diff --git a/src/main/java/io/apimatic/core/ApiCall.java b/src/main/java/io/apimatic/core/ApiCall.java index fb9ae229..f87b7f8d 100644 --- a/src/main/java/io/apimatic/core/ApiCall.java +++ b/src/main/java/io/apimatic/core/ApiCall.java @@ -9,14 +9,14 @@ import io.apimatic.core.request.async.AsyncExecutor; import io.apimatic.core.types.CoreApiException; import io.apimatic.coreinterfaces.http.request.Request; -import io.apimatic.coreinterfaces.http.request.configuration.CoreEndpointConfiguration; import io.apimatic.coreinterfaces.http.response.Response; import io.apimatic.coreinterfaces.logger.ApiLogger; /** - * An API call, or API request, is a message sent to a server asking an API to provide a service or - * information. - * @param resource from server. + * An API call, or API request, is a message sent to a server asking an API to + * provide a service or information. + * + * @param resource from server. * @param in case of a problem or the connection was aborted. */ public final class ApiCall { @@ -37,63 +37,64 @@ public final class ApiCall private final ResponseHandler responseHandler; /** - * An instance of {@link CoreEndpointConfiguration}. + * An instance of {@link EndpointConfiguration}. */ - private final CoreEndpointConfiguration endpointConfiguration; + private final EndpointConfiguration endpointConfiguration; /** * An instance of {@link ApiLogger} for logging. */ private final ApiLogger apiLogger; - /** * ApiCall constructor. - * @param globalConfig the required configuration to built the ApiCall. - * @param coreHttpRequest Http request for the api call. - * @param responseHandler the handler for the response. - * @param coreEndpointConfiguration endPoint configuration. + * + * @param coreHttpRequest Http request for the api call. + * @param responseHandler the handler for the response. + * @param endpointConfiguration endPoint configuration. */ - private ApiCall(final GlobalConfiguration globalConfig, final Request coreHttpRequest, - final ResponseHandler responseHandler, - final CoreEndpointConfiguration coreEndpointConfiguration) { - this.globalConfig = globalConfig; + private ApiCall(final Request coreHttpRequest, final ResponseHandler responseHandler, + final EndpointConfiguration endpointConfiguration) { this.request = coreHttpRequest; this.responseHandler = responseHandler; - this.endpointConfiguration = coreEndpointConfiguration; + this.endpointConfiguration = endpointConfiguration; + this.globalConfig = endpointConfiguration.getGlobalConfiguration(); this.apiLogger = SdkLoggerFactory.getLogger(globalConfig.getLoggingConfiguration()); } /** * Execute the ApiCall and returns the expected response. + * * @return instance of ResponseType. - * @throws IOException Signals that an I/O exception of some sort has occurred. + * @throws IOException Signals that an I/O exception of some sort has + * occurred. * @throws ExceptionType Represents error response from the server. */ public ResponseType execute() throws IOException, ExceptionType { apiLogger.logRequest(request); - Response httpResponse = globalConfig.getHttpClient() - .execute(request, endpointConfiguration); + Response httpResponse = globalConfig.getHttpClient().execute(request, endpointConfiguration); apiLogger.logResponse(httpResponse); - return responseHandler.handle(request, httpResponse, globalConfig, endpointConfiguration); + return responseHandler.handle(request, httpResponse, endpointConfiguration); } /** - * Execute the Api call asynchronously and returns the expected response in CompletableFuture. + * Execute the Api call asynchronously and returns the expected response in + * CompletableFuture. + * * @return the instance of {@link CompletableFuture}. */ public CompletableFuture executeAsync() { return AsyncExecutor.makeHttpCallAsync(() -> request, - request -> globalConfig.getHttpClient().executeAsync(request, - endpointConfiguration), - (httpRequest, httpResponse) -> responseHandler.handle(httpRequest, httpResponse, - globalConfig, endpointConfiguration), apiLogger); + request -> globalConfig.getHttpClient().executeAsync(request, endpointConfiguration), + (httpRequest, httpResponse) -> responseHandler.handle(httpRequest, httpResponse, endpointConfiguration), + apiLogger); } /** * Builder class for the {@link ApiCall} class. - * @param resource from server. + * + * @param resource from server. * @param Represents error response from the server. */ public static class Builder { @@ -110,14 +111,12 @@ public static class Builder responseHandlerBuilder = - new ResponseHandler.Builder(); + private ResponseHandler.Builder responseHandlerBuilder = new ResponseHandler.Builder(); /** * An instance of {@link EndpointConfiguration.Builder}. */ - private EndpointConfiguration.Builder endpointConfigurationBuilder = - new EndpointConfiguration.Builder(); + private EndpointConfiguration.Builder endpointConfigurationBuilder = new EndpointConfiguration.Builder(); /** * @param globalConfig the configuration of Http Request. @@ -132,8 +131,7 @@ public Builder globalConfig(GlobalConfiguration glo * @param action requestBuilder {@link Consumer}. * @return {@link ApiCall.Builder}. */ - public Builder requestBuilder( - Consumer action) { + public Builder requestBuilder(Consumer action) { requestBuilder = new HttpRequest.Builder(); action.accept(requestBuilder); return this; @@ -163,13 +161,13 @@ public Builder endpointConfiguration( /** * build the {@link ApiCall}. + * * @return the instance of {@link ApiCall}. * @throws IOException Signals that an I/O exception of some sort has occurred. */ public ApiCall build() throws IOException { - return new ApiCall(globalConfig, - requestBuilder.build(globalConfig), responseHandlerBuilder.build(), - endpointConfigurationBuilder.build()); + return new ApiCall(requestBuilder.build(globalConfig), + responseHandlerBuilder.build(), endpointConfigurationBuilder.build(globalConfig, requestBuilder)); } } } diff --git a/src/main/java/io/apimatic/core/ErrorCase.java b/src/main/java/io/apimatic/core/ErrorCase.java index f5f254b1..ad514cfe 100644 --- a/src/main/java/io/apimatic/core/ErrorCase.java +++ b/src/main/java/io/apimatic/core/ErrorCase.java @@ -1,14 +1,13 @@ package io.apimatic.core; -import java.io.StringReader; import java.util.regex.Matcher; import java.util.regex.Pattern; import javax.json.Json; import javax.json.JsonException; import javax.json.JsonPointer; -import javax.json.JsonReader; import javax.json.JsonStructure; import io.apimatic.core.types.CoreApiException; +import io.apimatic.core.utilities.CoreHelper; import io.apimatic.coreinterfaces.http.Context; import io.apimatic.coreinterfaces.http.HttpHeaders; import io.apimatic.coreinterfaces.http.response.Response; @@ -140,14 +139,7 @@ private String replaceHeadersFromTemplate(HttpHeaders headers, String reason) { private String replaceBodyFromTemplate(String responseBody, String reason) { StringBuilder formatter = new StringBuilder(reason); - JsonReader jsonReader = Json.createReader(new StringReader(responseBody)); - JsonStructure jsonStructure = null; - try { - jsonStructure = jsonReader.read(); - } catch (Exception e) { - // No need to do anything here - } - jsonReader.close(); + JsonStructure jsonStructure = CoreHelper.createJsonStructure(responseBody); Matcher matcher = Pattern.compile("\\{(.*?)\\}").matcher(reason); while (matcher.find()) { String key = matcher.group(1); diff --git a/src/main/java/io/apimatic/core/ResponseHandler.java b/src/main/java/io/apimatic/core/ResponseHandler.java index 1462585a..459ca14a 100644 --- a/src/main/java/io/apimatic/core/ResponseHandler.java +++ b/src/main/java/io/apimatic/core/ResponseHandler.java @@ -5,20 +5,26 @@ import java.util.Map; import java.util.regex.Matcher; import java.util.regex.Pattern; + +import io.apimatic.core.configurations.http.request.EndpointConfiguration; import io.apimatic.core.types.CoreApiException; +import io.apimatic.core.types.pagination.CursorPaginated; +import io.apimatic.core.types.pagination.LinkPaginated; +import io.apimatic.core.types.pagination.PaginationDeserializer; import io.apimatic.core.utilities.CoreHelper; import io.apimatic.coreinterfaces.compatibility.CompatibilityFactory; import io.apimatic.coreinterfaces.http.Context; import io.apimatic.coreinterfaces.http.request.Request; import io.apimatic.coreinterfaces.http.request.ResponseClassType; -import io.apimatic.coreinterfaces.http.request.configuration.CoreEndpointConfiguration; import io.apimatic.coreinterfaces.http.response.Response; import io.apimatic.coreinterfaces.type.functional.ContextInitializer; import io.apimatic.coreinterfaces.type.functional.Deserializer; /** - * Handler that encapsulates the process of generating a response object from a Response. - * @param The response to process. + * Handler that encapsulates the process of generating a response object from a + * Response. + * + * @param The response to process. * @param in case of a problem. */ public final class ResponseHandler { @@ -50,12 +56,13 @@ public final class ResponseHandler deserializer; + private final Deserializer deserializer; /** - * An instance of Intermediate {@link Deserializer}. + * A functional interface to wrap in pagination types + * {@link PaginationDeserializer}. */ - private final Deserializer intermediateDeserializer; + private final PaginationDeserializer paginationDeserializer; /** * An instance of {@link ResponseClassType}. @@ -78,27 +85,24 @@ public final class ResponseHandler> localErrorCases, - final Map> globalErrorCases, - final Deserializer deserializer, - final Deserializer intermediateDeserializer, - final ResponseClassType responseClassType, - final ContextInitializer contextInitializer, - final boolean isNullify404Enabled, - final boolean isNullableResponseType) { + final Map> globalErrorCases, final Deserializer deserializer, + final Deserializer intermediateDeserializer, final PaginationDeserializer paginationDeserializer, + final ResponseClassType responseClassType, final ContextInitializer contextInitializer, + final boolean isNullify404Enabled, final boolean isNullableResponseType) { this.localErrorCases = localErrorCases; this.globalErrorCases = globalErrorCases; - this.deserializer = deserializer; - this.intermediateDeserializer = intermediateDeserializer; + this.deserializer = deserializer == null ? intermediateDeserializer : deserializer; + this.paginationDeserializer = paginationDeserializer; this.responseClassType = responseClassType; this.contextInitializer = contextInitializer; this.isNullify404Enabled = isNullify404Enabled; @@ -106,25 +110,25 @@ private ResponseHandler(final Map> localErrorCa } /** - * Processes an HttpResponse and returns some value corresponding to that response. - * @param httpRequest Request which is made for endpoint. - * @param httpResponse Response which is received after execution. - * @param globalConfiguration the global configuration to store the request global information. - * @param endpointConfiguration the endpoint level configuration. + * Processes an HttpResponse and returns some value corresponding to that + * response. + * + * @param httpRequest Request which is made for endpoint. + * @param httpResponse Response which is received after execution. + * @param endpointConfiguration All endPoint level configuration. * @return An object of type ResponseType. - * @throws IOException Signals that an I/O exception of some sort has occurred. + * @throws IOException Signals that an I/O exception of some sort has + * occurred. * @throws ExceptionType Represents error response from the server. */ - @SuppressWarnings("unchecked") - public ResponseType handle(Request httpRequest, Response httpResponse, - GlobalConfiguration globalConfiguration, - CoreEndpointConfiguration endpointConfiguration) throws IOException, ExceptionType { + public ResponseType handle(Request httpRequest, Response httpResponse, EndpointConfiguration config) + throws IOException, ExceptionType { - Context httpContext = globalConfiguration.getCompatibilityFactory() - .createHttpContext(httpRequest, httpResponse); + Context httpContext = config.getGlobalConfiguration().getCompatibilityFactory().createHttpContext(httpRequest, + httpResponse); // invoke the callback after response if its not null - if (globalConfiguration.getHttpCallback() != null) { - globalConfiguration.getHttpCallback().onAfterResponse(httpContext); + if (config.getGlobalConfiguration().getHttpCallback() != null) { + config.getGlobalConfiguration().getHttpCallback().onAfterResponse(httpContext); } if (isNullify404Enabled) { @@ -138,76 +142,64 @@ public ResponseType handle(Request httpRequest, Response httpResponse, // handle errors defined at the API level validateResponse(httpContext); - ResponseType result = applyDeserializer(deserializer, httpResponse); - - result = applyContextInitializer(contextInitializer, httpContext, result); - - if (endpointConfiguration.hasBinaryResponse()) { - result = (ResponseType) httpResponse.getRawBody(); - } + Object result = convertResponse(httpResponse, config); - if (responseClassType != null) { - return createResponseClassType(httpResponse, globalConfiguration, - endpointConfiguration.hasBinaryResponse()); + if (responseClassType == ResponseClassType.API_RESPONSE + || responseClassType == ResponseClassType.DYNAMIC_API_RESPONSE) { + return createApiResponse(httpResponse, config.getGlobalConfiguration().getCompatibilityFactory(), result); } - return result; + return applyContextInitializer(httpContext, result); } - - private ResponseType applyContextInitializer( - ContextInitializer contextInitializer, Context httpContext, - ResponseType result) throws IOException { + @SuppressWarnings("unchecked") + private ResponseType applyContextInitializer(Context httpContext, Object result) throws IOException { if (contextInitializer != null && deserializer != null) { - result = contextInitializer.apply(httpContext, result); + result = contextInitializer.apply(httpContext, (ResponseType) result); } - return result; + return (ResponseType) result; } - private T applyDeserializer(Deserializer deserializer, Response httpResponse) - throws IOException { - if (this.isNullableResponseType && CoreHelper.isNullOrWhiteSpace(httpResponse.getBody())) { + private Object convertResponse(Response response, EndpointConfiguration config) throws IOException { + if (responseClassType == ResponseClassType.DYNAMIC_RESPONSE + || responseClassType == ResponseClassType.DYNAMIC_API_RESPONSE) { + return createDynamicResponse(response, config.getGlobalConfiguration().getCompatibilityFactory()); + } + + if (config.hasBinaryResponse()) { + return response.getRawBody(); + } + + if (isNullableResponseType && CoreHelper.isNullOrWhiteSpace(response.getBody())) { return null; } - T result = null; if (deserializer != null) { - // extract result from the http response - result = deserializer.apply(httpResponse.getBody()); + return deserializer.apply(response.getBody()); } - return result; - } - @SuppressWarnings("unchecked") - private ResponseType createResponseClassType(Response httpResponse, - GlobalConfiguration coreConfig, boolean hasBinaryResponse) throws IOException { - CompatibilityFactory compatibilityFactory = coreConfig.getCompatibilityFactory(); - switch (responseClassType) { - case API_RESPONSE: - return (ResponseType) compatibilityFactory.createApiResponse( - httpResponse.getStatusCode(), httpResponse.getHeaders(), - hasBinaryResponse ? httpResponse.getRawBody() - : applyDeserializer(intermediateDeserializer, httpResponse)); - case DYNAMIC_RESPONSE: - return createDynamicResponse(httpResponse, compatibilityFactory); - case DYNAMIC_API_RESPONSE: - return (ResponseType) compatibilityFactory.createApiResponse( - httpResponse.getStatusCode(), httpResponse.getHeaders(), - createDynamicResponse(httpResponse, compatibilityFactory)); - default: - return null; + if (paginationDeserializer != null) { + return paginationDeserializer.apply(response, config); } + + return null; + } + + private Object createDynamicResponse(Response httpResponse, CompatibilityFactory compatibilityFactory) { + return compatibilityFactory.createDynamicResponse(httpResponse); } @SuppressWarnings("unchecked") - private ResponseType createDynamicResponse(Response httpResponse, - CompatibilityFactory compatibilityFactory) { - return (ResponseType) compatibilityFactory.createDynamicResponse(httpResponse); + private ResponseType createApiResponse(Response httpResponse, CompatibilityFactory compatibilityFactory, + Object innerValue) { + return (ResponseType) compatibilityFactory.createApiResponse(httpResponse.getStatusCode(), + httpResponse.getHeaders(), innerValue); } /** - * Validate the response and check that response contains the error code and throw the - * corresponding exception + * Validate the response and check that response contains the error code and + * throw the corresponding exception + * * @param httpContext * @throws ExceptionType */ @@ -216,7 +208,6 @@ private void validateResponse(Context httpContext) throws ExceptionType { int statusCode = response.getStatusCode(); String errorCode = String.valueOf(statusCode); - throwConfiguredException(localErrorCases, errorCode, httpContext); throwConfiguredException(globalErrorCases, errorCode, httpContext); @@ -225,8 +216,8 @@ private void validateResponse(Context httpContext) throws ExceptionType { } } - private void throwConfiguredException(Map> errorCases, - String errorCode, Context httpContext) throws ExceptionType { + private void throwConfiguredException(Map> errorCases, String errorCode, + Context httpContext) throws ExceptionType { String defaultErrorCode = ""; Matcher match = Pattern.compile("^[(4|5)[0-9]]{3}").matcher(errorCode); if (match.find()) { @@ -263,6 +254,12 @@ public static class Builder intermediateDeserializer; + /** + * A functional interface to wrap in pagination types + * {@link PaginationDeserializer}. + */ + private PaginationDeserializer paginationDeserializer; + /** * An instance of {@link ResponseClassType}. */ @@ -279,14 +276,16 @@ public static class Builder localErrorCase(String statusCode, @@ -301,6 +300,7 @@ public Builder localErrorCase(String statusCode, /** * Setter for the globalErrorCases. + * * @param globalErrorCases the global error cases for endpoints. * @return {@link ResponseHandler.Builder}. */ @@ -312,42 +312,74 @@ public Builder globalErrorCase( /** * Setter for the deserializer. + * * @param deserializer to deserialize the server response. * @return {@link ResponseHandler.Builder}. */ - public Builder deserializer( - Deserializer deserializer) { + public Builder deserializer(Deserializer deserializer) { this.deserializer = deserializer; return this; } - /** * Setter for the deserializer. - * @param intermediateDeserializer to deserialize the api response. + * + * @param intermediateDeserializer to deserialize the api response. * @param the intermediate type of api response. * @return {@link ResponseHandler.Builder}. */ - public Builder - apiResponseDeserializer( - Deserializer intermediateDeserializer) { + public Builder apiResponseDeserializer( + Deserializer intermediateDeserializer) { this.intermediateDeserializer = intermediateDeserializer; return this; } + /** + * Setter for the deserializer to be used in link based pagination wrapper. + * + * @param deserializer to deserialize the server response. + * @param the type wrapped by linked pagination. + * @return {@link ResponseHandler.Builder}. + */ + @SuppressWarnings("unchecked") + public Builder linkPaginatedDeserializer( + Deserializer deserializer, LinkPaginated.Configuration config) { + this.paginationDeserializer = (res, c) -> new LinkPaginated( + deserializer.apply(res.getBody()), config, c.getGlobalConfiguration(), res, + (Builder, ExceptionType>) this); + return this; + } + + /** + * Setter for the deserializer to be used in cursor based pagination wrapper. + * + * @param deserializer to deserialize the server response. + * @param the type wrapped by cursor pagination. + * @return {@link ResponseHandler.Builder}. + */ + @SuppressWarnings("unchecked") + public Builder cursorPaginatedDeserializer( + Deserializer deserializer, CursorPaginated.Configuration config) { + this.paginationDeserializer = (res, c) -> new CursorPaginated( + deserializer.apply(res.getBody()), config, c, res, + (Builder, ExceptionType>) this); + return this; + } + /** * Setter for the responseClassType. + * * @param responseClassType specify the response class type for result. * @return {@link ResponseHandler.Builder}. */ - public Builder responseClassType( - ResponseClassType responseClassType) { + public Builder responseClassType(ResponseClassType responseClassType) { this.responseClassType = responseClassType; return this; } /** * Setter for the {@link ContextInitializer}. + * * @param contextInitializer the context initializer in response models. * @return {@link ResponseHandler.Builder}. */ @@ -359,6 +391,7 @@ public Builder contextInitializer( /** * Setter for the nullify404. + * * @param isNullify404Enabled in case of 404 error return null or not. * @return {@link ResponseHandler.Builder}. */ @@ -369,23 +402,24 @@ public Builder nullify404(boolean isNullify404Enabl /** * Setter for the isNullableResponseType. + * * @param isNullableResponseType in case of nullable response type. * @return {@link ResponseHandler.Builder}. */ - public Builder nullableResponseType( - boolean isNullableResponseType) { + public Builder nullableResponseType(boolean isNullableResponseType) { this.isNullableResponseType = isNullableResponseType; return this; } /** * build the ResponseHandler. + * * @return the instance of {@link ResponseHandler}. */ public ResponseHandler build() { - return new ResponseHandler(localErrorCases, - globalErrorCases, deserializer, intermediateDeserializer, responseClassType, - contextInitializer, isNullify404Enabled, isNullableResponseType); + return new ResponseHandler(localErrorCases, globalErrorCases, deserializer, + intermediateDeserializer, paginationDeserializer, responseClassType, contextInitializer, + isNullify404Enabled, isNullableResponseType); } } } diff --git a/src/main/java/io/apimatic/core/configurations/http/request/EndpointConfiguration.java b/src/main/java/io/apimatic/core/configurations/http/request/EndpointConfiguration.java index a7859332..6bc50a1f 100644 --- a/src/main/java/io/apimatic/core/configurations/http/request/EndpointConfiguration.java +++ b/src/main/java/io/apimatic/core/configurations/http/request/EndpointConfiguration.java @@ -1,5 +1,7 @@ package io.apimatic.core.configurations.http.request; +import io.apimatic.core.GlobalConfiguration; +import io.apimatic.core.HttpRequest; import io.apimatic.coreinterfaces.http.request.ArraySerializationFormat; import io.apimatic.coreinterfaces.http.request.configuration.CoreEndpointConfiguration; import io.apimatic.coreinterfaces.http.request.configuration.RetryOption; @@ -25,19 +27,34 @@ public class EndpointConfiguration implements CoreEndpointConfiguration { private final ArraySerializationFormat arraySerializationFormat; /** - * @param hasBinary A boolean variable for binary response. - * @param retryOption Retry options enumeration for HTTP request. - * @param arraySerializationFormat Enumeration for all ArraySerialization formats. + * GlobalConfiguration applicable along with this EndpointConfiguration. + */ + private final GlobalConfiguration globalConfig; + + /** + * Mutable request builder for HTTP request re initialization. + */ + private final HttpRequest.Builder requestBuilder; + + /** + * @param hasBinary A boolean variable for binary response. + * @param retryOption Retry options enumeration for HTTP request. + * @param arraySerializationFormat Enumeration for all ArraySerialization + * formats. */ public EndpointConfiguration(final boolean hasBinary, final RetryOption retryOption, - final ArraySerializationFormat arraySerializationFormat) { + final ArraySerializationFormat arraySerializationFormat, final GlobalConfiguration globalConfig, + final HttpRequest.Builder requestBuilder) { this.hasBinaryResponse = hasBinary; this.retryOption = retryOption; this.arraySerializationFormat = arraySerializationFormat; + this.globalConfig = globalConfig; + this.requestBuilder = requestBuilder; } /** * Retry options enumeration for HTTP request + * * @return the option for the retries {@link RetryOption}. */ public RetryOption getRetryOption() { @@ -46,6 +63,7 @@ public RetryOption getRetryOption() { /** * Endpoint response has the binary response or not. + * * @return the response is binary or not. */ public boolean hasBinaryResponse() { @@ -54,12 +72,31 @@ public boolean hasBinaryResponse() { /** * Enumeration for all ArraySerialization formats + * * @return the array serialization format. */ public ArraySerializationFormat getArraySerializationFormat() { return arraySerializationFormat; } + /** + * GlobalConfiguration applicable along with this EndpointConfiguration. + * + * @return the global configuration. + */ + public GlobalConfiguration getGlobalConfiguration() { + return globalConfig; + } + + /** + * Mutable request builder for HTTP request re initialization. + * + * @return the request builder instance. + */ + public HttpRequest.Builder getRequestBuilder() { + return requestBuilder; + } + public static class Builder { /** * A boolean variable for binary response. @@ -74,11 +111,11 @@ public static class Builder { /** * Enumeration for all ArraySerialization formats. */ - private ArraySerializationFormat arraySerializationFormat = - ArraySerializationFormat.INDEXED; + private ArraySerializationFormat arraySerializationFormat = ArraySerializationFormat.INDEXED; /** * Setter for the binary response. + * * @param hasBinary end point may have binary response. * @return {@link EndpointConfiguration.Builder}. */ @@ -89,6 +126,7 @@ public Builder hasBinaryResponse(boolean hasBinary) { /** * Setter for the {@link RetryOption}. + * * @param retryOption Retry options enumeration for HTTP request. * @return {@link EndpointConfiguration.Builder}. */ @@ -99,7 +137,9 @@ public Builder retryOption(RetryOption retryOption) { /** * Setter for the arraySerializationFormat. - * @param arraySerializationFormat Enumeration for all ArraySerialization formats. + * + * @param arraySerializationFormat Enumeration for all ArraySerialization + * formats. * @return {@link EndpointConfiguration.Builder}. */ public Builder arraySerializationFormat(ArraySerializationFormat arraySerializationFormat) { @@ -109,11 +149,14 @@ public Builder arraySerializationFormat(ArraySerializationFormat arraySerializat /** * Initialise the {@link EndpointConfiguration}. + * + * @param globalConfig + * @param requestBuilder * @return the {@link EndpointConfiguration} instance. */ - public EndpointConfiguration build() { - return new EndpointConfiguration(hasBinaryResponse, retryOption, - arraySerializationFormat); + public EndpointConfiguration build(GlobalConfiguration globalConfig, HttpRequest.Builder requestBuilder) { + return new EndpointConfiguration(hasBinaryResponse, retryOption, arraySerializationFormat, globalConfig, + requestBuilder); } } } diff --git a/src/main/java/io/apimatic/core/utilities/CoreHelper.java b/src/main/java/io/apimatic/core/utilities/CoreHelper.java index d608e20a..87dd47fd 100644 --- a/src/main/java/io/apimatic/core/utilities/CoreHelper.java +++ b/src/main/java/io/apimatic/core/utilities/CoreHelper.java @@ -28,6 +28,10 @@ import java.util.regex.Matcher; import java.util.regex.Pattern; import java.util.stream.Collectors; + +import javax.json.Json; +import javax.json.JsonReader; +import javax.json.JsonStructure; import javax.xml.bind.JAXBContext; import javax.xml.bind.JAXBElement; import javax.xml.bind.JAXBException; @@ -1564,4 +1568,19 @@ public static String getQueryParametersFromUrl(String queryUrl) { int queryStringIndex = queryUrl.indexOf('?'); return queryStringIndex != -1 ? queryUrl.substring(queryStringIndex + 1) : ""; } + + public static JsonStructure createJsonStructure(String json) { + JsonReader jsonReader = Json.createReader(new StringReader(json)); + JsonStructure jsonStructure = null; + + try { + jsonStructure = jsonReader.read(); + } catch (Exception e) { + // No need to do anything here + } + + jsonReader.close(); + + return jsonStructure; + } } diff --git a/src/test/java/apimatic/core/ResponseHandlerTest.java b/src/test/java/apimatic/core/ResponseHandlerTest.java index 751508c7..06f71c53 100644 --- a/src/test/java/apimatic/core/ResponseHandlerTest.java +++ b/src/test/java/apimatic/core/ResponseHandlerTest.java @@ -24,6 +24,7 @@ import io.apimatic.core.ApiCall; import io.apimatic.core.ErrorCase; import io.apimatic.core.ResponseHandler; +import io.apimatic.core.configurations.http.request.EndpointConfiguration; import io.apimatic.core.types.CoreApiException; import io.apimatic.core.utilities.CoreHelper; import io.apimatic.coreinterfaces.http.Callback; @@ -32,7 +33,6 @@ import io.apimatic.coreinterfaces.http.HttpHeaders; import io.apimatic.coreinterfaces.http.Method; import io.apimatic.coreinterfaces.http.request.ResponseClassType; -import io.apimatic.coreinterfaces.http.request.configuration.CoreEndpointConfiguration; import io.apimatic.coreinterfaces.http.response.ApiResponseType; import io.apimatic.coreinterfaces.http.response.DynamicType; import io.apimatic.coreinterfaces.http.response.Response; @@ -104,10 +104,10 @@ public class ResponseHandlerTest extends MockCoreConfig { private HttpClient client; /** - * Mock of {@link CoreEndpointConfiguration}. + * Mock of {@link EndpointConfiguration}. */ @Mock - private CoreEndpointConfiguration endpointSetting; + private EndpointConfiguration endpointSetting; /** * Mock of {@link ResponseHandler}. @@ -157,26 +157,30 @@ public class ResponseHandlerTest extends MockCoreConfig { @Before public void setup() throws IOException { setExpectations(); + prepareMockEndpointConfiguration(); + } + + private void prepareMockEndpointConfiguration() { + // stubs + when(endpointSetting.getGlobalConfiguration()).thenReturn(getMockGlobalConfig()); } @Test public void testDeserializerMethod() throws IOException, CoreApiException { - ResponseHandler coreResponseHandler = - new ResponseHandler.Builder() - .deserializer(string -> new String(string)).build(); + ResponseHandler coreResponseHandler = new ResponseHandler.Builder() + .deserializer(string -> new String(string)).build(); // stub when(coreHttpResponse.getStatusCode()).thenReturn(SUCCESS_CODE); when(coreHttpResponse.getBody()).thenReturn("bodyValue"); // verify - assertEquals(coreResponseHandler.handle(getCoreHttpRequest(), coreHttpResponse, - getMockGlobalConfig(), endpointSetting), "bodyValue"); + assertEquals(coreResponseHandler.handle(getCoreHttpRequest(), coreHttpResponse, endpointSetting), "bodyValue"); } @Test public void testDeserializerMethodUsingRawBody() throws IOException, CoreApiException { - ResponseHandler coreResponseHandler = - new ResponseHandler.Builder().build(); + ResponseHandler coreResponseHandler = new ResponseHandler.Builder() + .build(); String responseString = "bodyValue"; InputStream inputStream = new ByteArrayInputStream(responseString.getBytes()); @@ -187,19 +191,14 @@ public void testDeserializerMethodUsingRawBody() throws IOException, CoreApiExce when(coreHttpResponse.getRawBody()).thenReturn(inputStream); // verify - assertEquals(coreResponseHandler.handle(getCoreHttpRequest(), coreHttpResponse, - getMockGlobalConfig(), endpointSetting), inputStream); + assertEquals(coreResponseHandler.handle(getCoreHttpRequest(), coreHttpResponse, endpointSetting), inputStream); } - @Test public void testDeserializerMethodUsingContext() throws IOException, CoreApiException { - ResponseHandler coreResponseHandler = - new ResponseHandler.Builder() - .deserializer(string -> CoreHelper.deserialize(string, TestModel.class)) - .contextInitializer((context, response) -> response.toBuilder() - .httpContext(context).build()) - .build(); + ResponseHandler coreResponseHandler = new ResponseHandler.Builder() + .deserializer(string -> CoreHelper.deserialize(string, TestModel.class)) + .contextInitializer((context, response) -> response.toBuilder().httpContext(context).build()).build(); TestModel model = new TestModel.Builder("ali", "DEV").httpContext(context).build(); // stub @@ -207,46 +206,40 @@ public void testDeserializerMethodUsingContext() throws IOException, CoreApiExce when(coreHttpResponse.getBody()).thenReturn("{\"name\" : \"ali\", \"field\" : \"DEV\"}"); // verify - assertEquals(coreResponseHandler.handle(getCoreHttpRequest(), coreHttpResponse, - getMockGlobalConfig(), endpointSetting).getContext(), model.getContext()); + assertEquals(coreResponseHandler.handle(getCoreHttpRequest(), coreHttpResponse, endpointSetting).getContext(), + model.getContext()); } @Test public void testContextWithoutDeserializer() throws IOException, CoreApiException { - ResponseHandler coreResponseHandler = - new ResponseHandler.Builder().contextInitializer( - (context, response) -> response.toBuilder().httpContext(context).build()) - .build(); + ResponseHandler coreResponseHandler = new ResponseHandler.Builder() + .contextInitializer((context, response) -> response.toBuilder().httpContext(context).build()).build(); // stub when(coreHttpResponse.getStatusCode()).thenReturn(SUCCESS_CODE); when(coreHttpResponse.getBody()).thenReturn("{\"name\" : \"ali\", \"field\" : \"DEV\"}"); // verify - assertNull(coreResponseHandler.handle(getCoreHttpRequest(), coreHttpResponse, - getMockGlobalConfig(), endpointSetting)); + assertNull(coreResponseHandler.handle(getCoreHttpRequest(), coreHttpResponse, endpointSetting)); } @Test public void testDynamicResponseTypeMethod() throws IOException, CoreApiException { - ResponseHandler coreResponseHandler = - new ResponseHandler.Builder() - .responseClassType(ResponseClassType.DYNAMIC_RESPONSE).build(); + ResponseHandler coreResponseHandler = new ResponseHandler.Builder() + .responseClassType(ResponseClassType.DYNAMIC_RESPONSE).build(); // stub when(coreHttpResponse.getStatusCode()).thenReturn(SUCCESS_CODE); when(coreHttpResponse.getBody()).thenReturn("bodyValue"); // verify - assertEquals(coreResponseHandler.handle(getCoreHttpRequest(), coreHttpResponse, - getMockGlobalConfig(), endpointSetting), dynamicType); + assertEquals(coreResponseHandler.handle(getCoreHttpRequest(), coreHttpResponse, endpointSetting), dynamicType); } @Test public void testApiResponseTypeMethod() throws IOException, CoreApiException { - ResponseHandler, CoreApiException> coreResponseHandler = - new ResponseHandler.Builder, CoreApiException>() - .responseClassType(ResponseClassType.API_RESPONSE) - .apiResponseDeserializer(response -> new String(response)).build(); + ResponseHandler, CoreApiException> coreResponseHandler = new ResponseHandler.Builder, CoreApiException>() + .responseClassType(ResponseClassType.API_RESPONSE) + .apiResponseDeserializer(response -> new String(response)).build(); // stub when(coreHttpResponse.getStatusCode()).thenReturn(CREATED_SUCCESS_CODE); when(coreHttpResponse.getHeaders()).thenReturn(getHttpHeaders()); @@ -254,66 +247,55 @@ public void testApiResponseTypeMethod() throws IOException, CoreApiException { when(stringApiResponseType.getResult()).thenReturn("bodyValue"); // verify - assertEquals( - coreResponseHandler.handle(getCoreHttpRequest(), coreHttpResponse, - getMockGlobalConfig(), endpointSetting).getResult(), + assertEquals(coreResponseHandler.handle(getCoreHttpRequest(), coreHttpResponse, endpointSetting).getResult(), stringApiResponseType.getResult()); } @Test public void testDynamicApiResponseTypeMethod() throws IOException, CoreApiException { - ResponseHandler, CoreApiException> coreResponseHandler = - new ResponseHandler.Builder, CoreApiException>() - .responseClassType(ResponseClassType.DYNAMIC_API_RESPONSE).build(); + ResponseHandler, CoreApiException> coreResponseHandler = new ResponseHandler.Builder, CoreApiException>() + .responseClassType(ResponseClassType.DYNAMIC_API_RESPONSE).build(); // stub when(coreHttpResponse.getStatusCode()).thenReturn(CREATED_SUCCESS_CODE); when(coreHttpResponse.getHeaders()).thenReturn(getHttpHeaders()); when(dynamicApiResponseType.getResult()).thenReturn(dynamicType); // verify - assertEquals( - coreResponseHandler.handle(getCoreHttpRequest(), coreHttpResponse, - getMockGlobalConfig(), endpointSetting).getResult(), + assertEquals(coreResponseHandler.handle(getCoreHttpRequest(), coreHttpResponse, endpointSetting).getResult(), dynamicApiResponseType.getResult()); } @Test public void testDefaultTypeMethod() throws IOException, CoreApiException { - ResponseHandler coreResponseHandler = - new ResponseHandler.Builder().build(); + ResponseHandler coreResponseHandler = new ResponseHandler.Builder() + .build(); // stub when(coreHttpResponse.getStatusCode()).thenReturn(CREATED_SUCCESS_CODE); // verify - assertNull(coreResponseHandler.handle(getCoreHttpRequest(), coreHttpResponse, - getMockGlobalConfig(), endpointSetting)); + assertNull(coreResponseHandler.handle(getCoreHttpRequest(), coreHttpResponse, endpointSetting)); } @Test public void testNullify404() throws IOException, CoreApiException { - ResponseHandler coreResponseHandler = - new ResponseHandler.Builder().nullify404(true).build(); + ResponseHandler coreResponseHandler = new ResponseHandler.Builder() + .nullify404(true).build(); // stub when(coreHttpResponse.getStatusCode()).thenReturn(NOT_FOUND_STATUS_CODE); - when(getCompatibilityFactory().createHttpContext(getCoreHttpRequest(), coreHttpResponse)) - .thenReturn(context); - + when(getCompatibilityFactory().createHttpContext(getCoreHttpRequest(), coreHttpResponse)).thenReturn(context); // verify - assertNull(coreResponseHandler.handle(getCoreHttpRequest(), coreHttpResponse, - getMockGlobalConfig(), endpointSetting)); + assertNull(coreResponseHandler.handle(getCoreHttpRequest(), coreHttpResponse, endpointSetting)); } @Test public void testNullify404False() throws IOException, CoreApiException { - ResponseHandler coreResponseHandler = - new ResponseHandler.Builder().nullify404(false) - .globalErrorCase(getGlobalErrorCases()).build(); + ResponseHandler coreResponseHandler = new ResponseHandler.Builder() + .nullify404(false).globalErrorCase(getGlobalErrorCases()).build(); // stub when(coreHttpResponse.getStatusCode()).thenReturn(NOT_FOUND_STATUS_CODE); CoreApiException apiException = assertThrows(CoreApiException.class, () -> { - coreResponseHandler.handle(getCoreHttpRequest(), coreHttpResponse, - getMockGlobalConfig(), endpointSetting); + coreResponseHandler.handle(getCoreHttpRequest(), coreHttpResponse, endpointSetting); }); String expectedMessage = "Not found"; @@ -324,76 +306,65 @@ public void testNullify404False() throws IOException, CoreApiException { @Test public void testNullableResponseType() throws IOException, CoreApiException { - ResponseHandler coreResponseHandler = - new ResponseHandler.Builder() - .nullableResponseType(true).build(); + ResponseHandler coreResponseHandler = new ResponseHandler.Builder() + .nullableResponseType(true).build(); // stub when(coreHttpResponse.getStatusCode()).thenReturn(SUCCESS_CODE); when(coreHttpResponse.getBody()).thenReturn(null); - when(getCompatibilityFactory().createHttpContext(getCoreHttpRequest(), coreHttpResponse)) - .thenReturn(context); + when(getCompatibilityFactory().createHttpContext(getCoreHttpRequest(), coreHttpResponse)).thenReturn(context); // verify - assertNull(coreResponseHandler.handle(getCoreHttpRequest(), coreHttpResponse, - getMockGlobalConfig(), endpointSetting)); + assertNull(coreResponseHandler.handle(getCoreHttpRequest(), coreHttpResponse, endpointSetting)); when(coreHttpResponse.getBody()).thenReturn(""); // verify - assertNull(coreResponseHandler.handle(getCoreHttpRequest(), coreHttpResponse, - getMockGlobalConfig(), endpointSetting)); + assertNull(coreResponseHandler.handle(getCoreHttpRequest(), coreHttpResponse, endpointSetting)); when(coreHttpResponse.getBody()).thenReturn(" "); // verify - assertNull(coreResponseHandler.handle(getCoreHttpRequest(), coreHttpResponse, - getMockGlobalConfig(), endpointSetting)); + assertNull(coreResponseHandler.handle(getCoreHttpRequest(), coreHttpResponse, endpointSetting)); } @Test public void testNullableResponseTypeFalse() throws IOException, CoreApiException { - ResponseHandler coreResponseHandler = - new ResponseHandler.Builder().nullableResponseType(false) - .deserializer(response -> Integer.parseInt(response)) - .globalErrorCase(getGlobalErrorCases()).build(); + ResponseHandler coreResponseHandler = new ResponseHandler.Builder() + .nullableResponseType(false).deserializer(response -> Integer.parseInt(response)) + .globalErrorCase(getGlobalErrorCases()).build(); // stub when(coreHttpResponse.getStatusCode()).thenReturn(SUCCESS_CODE); String body = "50"; when(coreHttpResponse.getBody()).thenReturn(body); // verify - assertEquals(Integer.valueOf(body), coreResponseHandler.handle(getCoreHttpRequest(), - coreHttpResponse, getMockGlobalConfig(), endpointSetting)); + assertEquals(Integer.valueOf(body), + coreResponseHandler.handle(getCoreHttpRequest(), coreHttpResponse, endpointSetting)); when(coreHttpResponse.getBody()).thenReturn(""); assertThrows(NumberFormatException.class, () -> { - coreResponseHandler.handle(getCoreHttpRequest(), coreHttpResponse, - getMockGlobalConfig(), endpointSetting); + coreResponseHandler.handle(getCoreHttpRequest(), coreHttpResponse, endpointSetting); }); when(coreHttpResponse.getBody()).thenReturn(" "); assertThrows(NumberFormatException.class, () -> { - coreResponseHandler.handle(getCoreHttpRequest(), coreHttpResponse, - getMockGlobalConfig(), endpointSetting); + coreResponseHandler.handle(getCoreHttpRequest(), coreHttpResponse, endpointSetting); }); } @Test public void testLocalException() throws IOException, CoreApiException { - ResponseHandler coreResponseHandler = - new ResponseHandler.Builder() - .localErrorCase("403", - ErrorCase.setReason("Forbidden", - (reason, context) -> new CoreApiException(reason, context))) - .build(); + ResponseHandler coreResponseHandler = new ResponseHandler.Builder() + .localErrorCase("403", + ErrorCase.setReason("Forbidden", (reason, context) -> new CoreApiException(reason, context))) + .build(); // stub when(coreHttpResponse.getStatusCode()).thenReturn(FORBIDDEN_STATUS_CODE); CoreApiException apiException = assertThrows(CoreApiException.class, () -> { - coreResponseHandler.handle(getCoreHttpRequest(), coreHttpResponse, - getMockGlobalConfig(), endpointSetting); + coreResponseHandler.handle(getCoreHttpRequest(), coreHttpResponse, endpointSetting); }); String expectedMessage = "Forbidden"; @@ -404,19 +375,16 @@ public void testLocalException() throws IOException, CoreApiException { @Test public void testDefaultException() throws IOException, CoreApiException { - ResponseHandler coreResponseHandler = - new ResponseHandler.Builder() - .localErrorCase("403", - ErrorCase.setReason("Forbidden", - (reason, context) -> new CoreApiException(reason, context))) - .globalErrorCase(getGlobalErrorCases()).build(); + ResponseHandler coreResponseHandler = new ResponseHandler.Builder() + .localErrorCase("403", + ErrorCase.setReason("Forbidden", (reason, context) -> new CoreApiException(reason, context))) + .globalErrorCase(getGlobalErrorCases()).build(); // stub when(coreHttpResponse.getStatusCode()).thenReturn(INFORMATION_CODE); CoreApiException apiException = assertThrows(CoreApiException.class, () -> { - coreResponseHandler.handle(getCoreHttpRequest(), coreHttpResponse, - getMockGlobalConfig(), endpointSetting); + coreResponseHandler.handle(getCoreHttpRequest(), coreHttpResponse, endpointSetting); }); String expectedMessage = "Invalid response."; @@ -428,21 +396,16 @@ public void testDefaultException() throws IOException, CoreApiException { @Test public void testDefaultException1() throws IOException, CoreApiException { // when - ResponseHandler coreResponseHandler = - new ResponseHandler.Builder() - .localErrorCase("403", - ErrorCase.setReason("Forbidden", - (reason, context) -> new CoreApiException(reason, context))) - .globalErrorCase(getGlobalErrorCases()).build(); - + ResponseHandler coreResponseHandler = new ResponseHandler.Builder() + .localErrorCase("403", + ErrorCase.setReason("Forbidden", (reason, context) -> new CoreApiException(reason, context))) + .globalErrorCase(getGlobalErrorCases()).build(); // stub when(coreHttpResponse.getStatusCode()).thenReturn(RECEIVED_STATUS_CODE); - CoreApiException apiException = assertThrows(CoreApiException.class, () -> { - coreResponseHandler.handle(getCoreHttpRequest(), coreHttpResponse, - getMockGlobalConfig(), endpointSetting); + coreResponseHandler.handle(getCoreHttpRequest(), coreHttpResponse, endpointSetting); }); String expectedMessage = "Invalid response."; @@ -461,24 +424,19 @@ public void testDefaultException1() throws IOException, CoreApiException { @Test public void testGlobalException() throws IOException, CoreApiException { - ResponseHandler coreResponseHandler = - new ResponseHandler.Builder() - .globalErrorCase(getGlobalErrorCases()).build(); - - String exceptionResponse = - "{\"ServerMessage\" : \"Internal server error\" , \"ServerCode\" : 500 }"; - InputStream exceptionResponseStream = - new ByteArrayInputStream(exceptionResponse.getBytes()); + ResponseHandler coreResponseHandler = new ResponseHandler.Builder() + .globalErrorCase(getGlobalErrorCases()).build(); + + String exceptionResponse = "{\"ServerMessage\" : \"Internal server error\" , \"ServerCode\" : 500 }"; + InputStream exceptionResponseStream = new ByteArrayInputStream(exceptionResponse.getBytes()); // stub when(coreHttpResponse.getStatusCode()).thenReturn(BAD_REQUEST_CODE); when(coreHttpResponse.getRawBody()).thenReturn(exceptionResponseStream); when(endpointSetting.hasBinaryResponse()).thenReturn(true); - GlobalTestException apiException = assertThrows(GlobalTestException.class, () -> { - coreResponseHandler.handle(getCoreHttpRequest(), coreHttpResponse, - getMockGlobalConfig(), endpointSetting); + coreResponseHandler.handle(getCoreHttpRequest(), coreHttpResponse, endpointSetting); }); String expectedMessage = "Bad Request"; @@ -490,7 +448,6 @@ public void testGlobalException() throws IOException, CoreApiException { int expectedServerCode = INTERNAL_SERVER_ERROR; int actualSeverCode = apiException.getServerCode(); - assertEquals(actualMessage, expectedMessage); assertEquals(actualSeverMessage, expectedServerMessage); assertEquals(actualSeverCode, expectedServerCode); @@ -499,20 +456,19 @@ public void testGlobalException() throws IOException, CoreApiException { private Map> getGlobalErrorCases() { Map> globalErrorCase = new HashMap<>(); - globalErrorCase.put("400", ErrorCase.setReason("Bad Request", - (reason, context) -> new GlobalTestException(reason, context))); + globalErrorCase.put("400", + ErrorCase.setReason("Bad Request", (reason, context) -> new GlobalTestException(reason, context))); - globalErrorCase.put("404", ErrorCase.setReason("Not found", - (reason, context) -> new CoreApiException(reason, context))); + globalErrorCase.put("404", + ErrorCase.setReason("Not found", (reason, context) -> new CoreApiException(reason, context))); - globalErrorCase.put(ErrorCase.DEFAULT, ErrorCase.setReason("Invalid response.", - (reason, context) -> new CoreApiException(reason, context))); + globalErrorCase.put(ErrorCase.DEFAULT, + ErrorCase.setReason("Invalid response.", (reason, context) -> new CoreApiException(reason, context))); return globalErrorCase; } - private void prepareCoreConfigStub() throws IOException { when(getMockGlobalConfig().getBaseUri()).thenReturn(test -> getBaseUri(test)); when(getMockGlobalConfig().getCompatibilityFactory()).thenReturn(getCompatibilityFactory()); @@ -528,24 +484,18 @@ private void setExpectations() throws IOException { private void prepareCompatibilityStub() { when(getCompatibilityFactory().createHttpHeaders(anyMap())).thenReturn(getHttpHeaders()); - when(getCompatibilityFactory().createHttpRequest(any(Method.class), - any(StringBuilder.class), any(HttpHeaders.class), anyMap(), any(Object.class))) - .thenReturn(getCoreHttpRequest()); - when(getCompatibilityFactory().createHttpRequest(any(Method.class), - any(StringBuilder.class), any(HttpHeaders.class), anyMap(), anyList())) - .thenReturn(getCoreHttpRequest()); - when(getCompatibilityFactory().createHttpContext(getCoreHttpRequest(), coreHttpResponse)) - .thenReturn(context); - - when(getCompatibilityFactory().createDynamicResponse(coreHttpResponse)) - .thenReturn(dynamicType); + when(getCompatibilityFactory().createHttpRequest(any(Method.class), any(StringBuilder.class), + any(HttpHeaders.class), anyMap(), any(Object.class))).thenReturn(getCoreHttpRequest()); + when(getCompatibilityFactory().createHttpRequest(any(Method.class), any(StringBuilder.class), + any(HttpHeaders.class), anyMap(), anyList())).thenReturn(getCoreHttpRequest()); + when(getCompatibilityFactory().createHttpContext(getCoreHttpRequest(), coreHttpResponse)).thenReturn(context); - when(getCompatibilityFactory().createApiResponse(any(int.class), any(HttpHeaders.class), - any(String.class))).thenReturn(stringApiResponseType); + when(getCompatibilityFactory().createDynamicResponse(coreHttpResponse)).thenReturn(dynamicType); + + when(getCompatibilityFactory().createApiResponse(any(int.class), any(HttpHeaders.class), any(String.class))) + .thenReturn(stringApiResponseType); when(getCompatibilityFactory().createApiResponse(any(int.class), any(HttpHeaders.class), any(DynamicType.class))).thenReturn(dynamicApiResponseType); } - } - diff --git a/src/test/java/apimatic/core/configurations/http/request/EndpointConfigurationTest.java b/src/test/java/apimatic/core/configurations/http/request/EndpointConfigurationTest.java index b06ec6aa..98c5e04a 100644 --- a/src/test/java/apimatic/core/configurations/http/request/EndpointConfigurationTest.java +++ b/src/test/java/apimatic/core/configurations/http/request/EndpointConfigurationTest.java @@ -12,7 +12,7 @@ public class EndpointConfigurationTest { public void testEndpointConfiguration() { EndpointConfiguration configuration = new EndpointConfiguration(false, RetryOption.DEFAULT, - ArraySerializationFormat.INDEXED); + ArraySerializationFormat.INDEXED, null, null); assertEquals(configuration.getRetryOption(), RetryOption.DEFAULT); assertEquals(configuration.hasBinaryResponse(), false); } From e1bc563b7ff3d3c3c4957fdf61e9adaab898f629 Mon Sep 17 00:00:00 2001 From: Asad Ali <14asadali@gmail.com> Date: Sat, 15 Mar 2025 21:14:09 +0500 Subject: [PATCH 02/20] adds new classes --- .../types/pagination/CursorPaginated.java | 85 +++++++++++++++ .../core/types/pagination/LinkPaginated.java | 100 ++++++++++++++++++ .../types/pagination/OffsetPaginated.java | 8 ++ .../pagination/PaginationDeserializer.java | 24 +++++ 4 files changed, 217 insertions(+) create mode 100644 src/main/java/io/apimatic/core/types/pagination/CursorPaginated.java create mode 100644 src/main/java/io/apimatic/core/types/pagination/LinkPaginated.java create mode 100644 src/main/java/io/apimatic/core/types/pagination/OffsetPaginated.java create mode 100644 src/main/java/io/apimatic/core/types/pagination/PaginationDeserializer.java diff --git a/src/main/java/io/apimatic/core/types/pagination/CursorPaginated.java b/src/main/java/io/apimatic/core/types/pagination/CursorPaginated.java new file mode 100644 index 00000000..e61f832b --- /dev/null +++ b/src/main/java/io/apimatic/core/types/pagination/CursorPaginated.java @@ -0,0 +1,85 @@ +package io.apimatic.core.types.pagination; + +import java.io.IOException; + +import javax.json.Json; +import javax.json.JsonValue; +import javax.json.JsonPointer; +import javax.json.JsonStructure; + +import io.apimatic.core.ApiCall; +import io.apimatic.core.GlobalConfiguration; +import io.apimatic.core.HttpRequest; +import io.apimatic.core.ResponseHandler.Builder; +import io.apimatic.core.configurations.http.request.EndpointConfiguration; +import io.apimatic.core.types.CoreApiException; +import io.apimatic.core.utilities.CoreHelper; +import io.apimatic.coreinterfaces.http.response.Response; + +public class CursorPaginated { + + /** + * Private store for encapsulated object's value. + */ + private T value; + + private Configuration configuration; + + private GlobalConfiguration globalConfig; + + private Response response; + + private Builder, ExceptionType> responseBuilder; + + private HttpRequest.Builder requestBuilder; + + public CursorPaginated(final T value, final Configuration configuration, final EndpointConfiguration endPointConfig, + final Response response, final Builder, ExceptionType> responseBuilder) { + this.value = value; + this.configuration = configuration; + this.globalConfig = endPointConfig.getGlobalConfiguration(); + this.response = response; + this.responseBuilder = responseBuilder; + this.requestBuilder = endPointConfig.getRequestBuilder(); + } + + /** + * Converts this CursorPaginated into string format. + * + * @return String representation of this class. + */ + @Override + public String toString() { + return "" + value; + } + + public T value() { + return value; + } + + public CursorPaginated next() throws ExceptionType, IOException { + + JsonPointer nextCursorPointerRes = Json.createPointer(configuration.nextPointerResponse); + JsonStructure resStructure = CoreHelper.createJsonStructure(response.getBody()); + + if (resStructure != null && nextCursorPointerRes.containsValue(resStructure)) { + JsonValue value = nextCursorPointerRes.getValue(resStructure); + + return new ApiCall.Builder, ExceptionType>().globalConfig(globalConfig) + .requestBuilder(req -> req = requestBuilder) + .responseHandler(res -> res = responseBuilder).build().execute(); + } + + return null; + } + + public static class Configuration { + private String nextPointerResponse; + private String nextPointerRequest; + + public Configuration(String nextPointerResponse, String nextPointerRequest) { + this.nextPointerResponse = nextPointerResponse; + this.nextPointerRequest = nextPointerRequest; + } + } +} diff --git a/src/main/java/io/apimatic/core/types/pagination/LinkPaginated.java b/src/main/java/io/apimatic/core/types/pagination/LinkPaginated.java new file mode 100644 index 00000000..69558b3f --- /dev/null +++ b/src/main/java/io/apimatic/core/types/pagination/LinkPaginated.java @@ -0,0 +1,100 @@ +package io.apimatic.core.types.pagination; + +import java.io.IOException; + +import javax.json.Json; +import javax.json.JsonPointer; +import javax.json.JsonStructure; + +import io.apimatic.core.ApiCall; +import io.apimatic.core.GlobalConfiguration; +import io.apimatic.core.ResponseHandler.Builder; +import io.apimatic.core.types.CoreApiException; +import io.apimatic.core.utilities.CoreHelper; +import io.apimatic.coreinterfaces.http.Method; +import io.apimatic.coreinterfaces.http.response.Response; + +public class LinkPaginated { + + /** + * Private store for encapsulated object's value. + */ + private T value; + + private Configuration configuration; + + private GlobalConfiguration globalConfig; + + private Builder, ExceptionType> responseBuilder; + + private JsonStructure jsonStructure; + + public LinkPaginated(final T value, final Configuration configuration, + final GlobalConfiguration globalConfig, final Response response, + final Builder, ExceptionType> responseBuilder) { + this.value = value; + this.configuration = configuration; + this.globalConfig = globalConfig; + this.responseBuilder = responseBuilder; + this.jsonStructure = CoreHelper.createJsonStructure(response.getBody()); + } + + /** + * Converts this LinkPaginated into string format. + * + * @return String representation of this class. + */ + @Override + public String toString() { + return "" + value; + } + + public T value() { + return value; + } + + public LinkPaginated first() throws ExceptionType, IOException { + return executeForPointer(configuration.firstPointer); + } + + public LinkPaginated last() throws ExceptionType, IOException { + return executeForPointer(configuration.lastPointer); + } + + public LinkPaginated previous() throws ExceptionType, IOException { + return executeForPointer(configuration.prevPointer); + } + + public LinkPaginated next() throws ExceptionType, IOException { + return executeForPointer(configuration.nextPointer); + } + + private LinkPaginated executeForPointer(String pointer) throws ExceptionType, IOException { + + JsonPointer jsonPointer = Json.createPointer(pointer); + + if (jsonStructure != null && jsonPointer.containsValue(jsonStructure)) { + String pathValue = jsonPointer.getValue(jsonStructure).toString(); + + return new ApiCall.Builder, ExceptionType>().globalConfig(globalConfig) + .requestBuilder(requestBuilder -> requestBuilder.path(pathValue).httpMethod(Method.GET)) + .responseHandler(res -> res = responseBuilder).build().execute(); + } + + return null; + } + + public static class Configuration { + private String firstPointer; + private String lastPointer; + private String prevPointer; + private String nextPointer; + + public Configuration(String firstPointer, String lastPointer, String prevPointer, String nextPointer) { + this.firstPointer = firstPointer; + this.lastPointer = lastPointer; + this.prevPointer = prevPointer; + this.nextPointer = nextPointer; + } + } +} diff --git a/src/main/java/io/apimatic/core/types/pagination/OffsetPaginated.java b/src/main/java/io/apimatic/core/types/pagination/OffsetPaginated.java new file mode 100644 index 00000000..437e6dfb --- /dev/null +++ b/src/main/java/io/apimatic/core/types/pagination/OffsetPaginated.java @@ -0,0 +1,8 @@ +package io.apimatic.core.types.pagination; + +public class OffsetPaginated { + + public T next() { + return null; + }; +} diff --git a/src/main/java/io/apimatic/core/types/pagination/PaginationDeserializer.java b/src/main/java/io/apimatic/core/types/pagination/PaginationDeserializer.java new file mode 100644 index 00000000..4331bbea --- /dev/null +++ b/src/main/java/io/apimatic/core/types/pagination/PaginationDeserializer.java @@ -0,0 +1,24 @@ +package io.apimatic.core.types.pagination; + +import java.io.IOException; + +import io.apimatic.core.configurations.http.request.EndpointConfiguration; +import io.apimatic.coreinterfaces.http.response.Response; + +/** + * Functional Interface to apply the deserializer function. + * + * @param The type of the response to deserialize into. + */ +@FunctionalInterface +public interface PaginationDeserializer { + /** + * Apply the deserialization function and returns the ResponseType response. + * + * @param response The response of current API Call. + * @param config The EndPoint configuration for paginated API Calls. + * @return The deserialized data. + * @throws IOException Exception to be thrown while applying the function. + */ + T apply(Response response, EndpointConfiguration config) throws IOException; +} \ No newline at end of file From 332afe691ae83a02d076a072248a42f68de9f436 Mon Sep 17 00:00:00 2001 From: Asad Ali <14asadali@gmail.com> Date: Sun, 16 Mar 2025 14:54:31 +0500 Subject: [PATCH 03/20] adds handling for cursor and offset based pagination --- src/main/java/io/apimatic/core/ApiCall.java | 19 ++++ .../io/apimatic/core/ResponseHandler.java | 27 ++++- .../types/pagination/CursorPaginated.java | 39 +++---- .../core/types/pagination/LinkPaginated.java | 60 +++++----- .../types/pagination/OffsetPaginated.java | 105 +++++++++++++++++- .../apimatic/core/utilities/CoreHelper.java | 41 +++++++ 6 files changed, 233 insertions(+), 58 deletions(-) diff --git a/src/main/java/io/apimatic/core/ApiCall.java b/src/main/java/io/apimatic/core/ApiCall.java index f87b7f8d..dd3c1897 100644 --- a/src/main/java/io/apimatic/core/ApiCall.java +++ b/src/main/java/io/apimatic/core/ApiCall.java @@ -137,6 +137,15 @@ public Builder requestBuilder(Consumer requestBuilder(HttpRequest.Builder requestBuilder) { + this.requestBuilder = requestBuilder; + return this; + } + /** * @param action responseHandler {@link Consumer}. * @return {@link ApiCall.Builder}. @@ -148,6 +157,16 @@ public Builder responseHandler( return this; } + /** + * @param responseHandlerBuilder response handler builder instance. + * @return {@link ApiCall.Builder}. + */ + public Builder responseHandler( + ResponseHandler.Builder responseHandlerBuilder) { + this.responseHandlerBuilder = responseHandlerBuilder; + return this; + } + /** * @param action endpointConfiguration {@link Consumer}. * @return {@link ApiCall.Builder}. diff --git a/src/main/java/io/apimatic/core/ResponseHandler.java b/src/main/java/io/apimatic/core/ResponseHandler.java index 459ca14a..71c885b1 100644 --- a/src/main/java/io/apimatic/core/ResponseHandler.java +++ b/src/main/java/io/apimatic/core/ResponseHandler.java @@ -10,6 +10,7 @@ import io.apimatic.core.types.CoreApiException; import io.apimatic.core.types.pagination.CursorPaginated; import io.apimatic.core.types.pagination.LinkPaginated; +import io.apimatic.core.types.pagination.OffsetPaginated; import io.apimatic.core.types.pagination.PaginationDeserializer; import io.apimatic.core.utilities.CoreHelper; import io.apimatic.coreinterfaces.compatibility.CompatibilityFactory; @@ -344,9 +345,9 @@ public Builder apiRespon @SuppressWarnings("unchecked") public Builder linkPaginatedDeserializer( Deserializer deserializer, LinkPaginated.Configuration config) { - this.paginationDeserializer = (res, c) -> new LinkPaginated( - deserializer.apply(res.getBody()), config, c.getGlobalConfiguration(), res, - (Builder, ExceptionType>) this); + this.paginationDeserializer = (res, c) -> new LinkPaginated( + deserializer.apply(res.getBody()), config, c, res, + (Builder, CoreApiException>) this); return this; } @@ -360,9 +361,25 @@ public Builder linkPaginatedDeserialize @SuppressWarnings("unchecked") public Builder cursorPaginatedDeserializer( Deserializer deserializer, CursorPaginated.Configuration config) { - this.paginationDeserializer = (res, c) -> new CursorPaginated( + this.paginationDeserializer = (res, c) -> new CursorPaginated( + deserializer.apply(res.getBody()), config, c, res, + (Builder, CoreApiException>) this); + return this; + } + + /** + * Setter for the deserializer to be used in offset based pagination wrapper. + * + * @param deserializer to deserialize the server response. + * @param the type wrapped by offset pagination. + * @return {@link ResponseHandler.Builder}. + */ + @SuppressWarnings("unchecked") + public Builder offsetPaginatedDeserializer( + Deserializer deserializer, OffsetPaginated.Configuration config) { + this.paginationDeserializer = (res, c) -> new OffsetPaginated( deserializer.apply(res.getBody()), config, c, res, - (Builder, ExceptionType>) this); + (Builder, CoreApiException>) this); return this; } diff --git a/src/main/java/io/apimatic/core/types/pagination/CursorPaginated.java b/src/main/java/io/apimatic/core/types/pagination/CursorPaginated.java index e61f832b..df49f736 100644 --- a/src/main/java/io/apimatic/core/types/pagination/CursorPaginated.java +++ b/src/main/java/io/apimatic/core/types/pagination/CursorPaginated.java @@ -2,11 +2,6 @@ import java.io.IOException; -import javax.json.Json; -import javax.json.JsonValue; -import javax.json.JsonPointer; -import javax.json.JsonStructure; - import io.apimatic.core.ApiCall; import io.apimatic.core.GlobalConfiguration; import io.apimatic.core.HttpRequest; @@ -16,7 +11,7 @@ import io.apimatic.core.utilities.CoreHelper; import io.apimatic.coreinterfaces.http.response.Response; -public class CursorPaginated { +public class CursorPaginated { /** * Private store for encapsulated object's value. @@ -29,12 +24,12 @@ public class CursorPaginated { private Response response; - private Builder, ExceptionType> responseBuilder; + private Builder, CoreApiException> responseBuilder; private HttpRequest.Builder requestBuilder; public CursorPaginated(final T value, final Configuration configuration, final EndpointConfiguration endPointConfig, - final Response response, final Builder, ExceptionType> responseBuilder) { + final Response response, final Builder, CoreApiException> responseBuilder) { this.value = value; this.configuration = configuration; this.globalConfig = endPointConfig.getGlobalConfiguration(); @@ -57,17 +52,19 @@ public T value() { return value; } - public CursorPaginated next() throws ExceptionType, IOException { - - JsonPointer nextCursorPointerRes = Json.createPointer(configuration.nextPointerResponse); - JsonStructure resStructure = CoreHelper.createJsonStructure(response.getBody()); - - if (resStructure != null && nextCursorPointerRes.containsValue(resStructure)) { - JsonValue value = nextCursorPointerRes.getValue(resStructure); + public CursorPaginated next() { + String cursorValue = CoreHelper.getValueFromJson(configuration.nextPointerResponse, response.getBody()); + + if (cursorValue == null) { + return null; + } - return new ApiCall.Builder, ExceptionType>().globalConfig(globalConfig) - .requestBuilder(req -> req = requestBuilder) - .responseHandler(res -> res = responseBuilder).build().execute(); + try { + return new ApiCall.Builder, CoreApiException>().globalConfig(globalConfig) + .requestBuilder(requestBuilder.queryParam(q -> q.key(configuration.cursorQueryParamName).value(cursorValue))) + .responseHandler(responseBuilder).build().execute(); + } catch (IOException | CoreApiException e) { + // Ignore exceptions } return null; @@ -75,11 +72,11 @@ public CursorPaginated next() throws ExceptionType, IOExceptio public static class Configuration { private String nextPointerResponse; - private String nextPointerRequest; + private String cursorQueryParamName; - public Configuration(String nextPointerResponse, String nextPointerRequest) { + public Configuration(String nextPointerResponse, String cursorQueryParamName) { this.nextPointerResponse = nextPointerResponse; - this.nextPointerRequest = nextPointerRequest; + this.cursorQueryParamName = cursorQueryParamName; } } } diff --git a/src/main/java/io/apimatic/core/types/pagination/LinkPaginated.java b/src/main/java/io/apimatic/core/types/pagination/LinkPaginated.java index 69558b3f..ef3d67ef 100644 --- a/src/main/java/io/apimatic/core/types/pagination/LinkPaginated.java +++ b/src/main/java/io/apimatic/core/types/pagination/LinkPaginated.java @@ -2,19 +2,16 @@ import java.io.IOException; -import javax.json.Json; -import javax.json.JsonPointer; -import javax.json.JsonStructure; - import io.apimatic.core.ApiCall; import io.apimatic.core.GlobalConfiguration; +import io.apimatic.core.HttpRequest; import io.apimatic.core.ResponseHandler.Builder; +import io.apimatic.core.configurations.http.request.EndpointConfiguration; import io.apimatic.core.types.CoreApiException; import io.apimatic.core.utilities.CoreHelper; -import io.apimatic.coreinterfaces.http.Method; import io.apimatic.coreinterfaces.http.response.Response; -public class LinkPaginated { +public class LinkPaginated { /** * Private store for encapsulated object's value. @@ -24,19 +21,21 @@ public class LinkPaginated { private Configuration configuration; private GlobalConfiguration globalConfig; + + private Response response; - private Builder, ExceptionType> responseBuilder; + private Builder, CoreApiException> responseBuilder; - private JsonStructure jsonStructure; + private HttpRequest.Builder requestBuilder; - public LinkPaginated(final T value, final Configuration configuration, - final GlobalConfiguration globalConfig, final Response response, - final Builder, ExceptionType> responseBuilder) { + public LinkPaginated(final T value, final Configuration configuration, final EndpointConfiguration endPointConfig, + final Response response, final Builder, CoreApiException> builder) { this.value = value; this.configuration = configuration; - this.globalConfig = globalConfig; - this.responseBuilder = responseBuilder; - this.jsonStructure = CoreHelper.createJsonStructure(response.getBody()); + this.globalConfig = endPointConfig.getGlobalConfiguration(); + this.response = response; + this.responseBuilder = builder; + this.requestBuilder = endPointConfig.getRequestBuilder(); } /** @@ -53,34 +52,37 @@ public T value() { return value; } - public LinkPaginated first() throws ExceptionType, IOException { + public LinkPaginated first() { return executeForPointer(configuration.firstPointer); } - public LinkPaginated last() throws ExceptionType, IOException { + public LinkPaginated last() { return executeForPointer(configuration.lastPointer); } - public LinkPaginated previous() throws ExceptionType, IOException { + public LinkPaginated previous() { return executeForPointer(configuration.prevPointer); } - public LinkPaginated next() throws ExceptionType, IOException { + public LinkPaginated next() { return executeForPointer(configuration.nextPointer); } - - private LinkPaginated executeForPointer(String pointer) throws ExceptionType, IOException { - - JsonPointer jsonPointer = Json.createPointer(pointer); + + private LinkPaginated executeForPointer(String pointer) { + String linkValue = CoreHelper.getValueFromJson(pointer, response.getBody()); - if (jsonStructure != null && jsonPointer.containsValue(jsonStructure)) { - String pathValue = jsonPointer.getValue(jsonStructure).toString(); - - return new ApiCall.Builder, ExceptionType>().globalConfig(globalConfig) - .requestBuilder(requestBuilder -> requestBuilder.path(pathValue).httpMethod(Method.GET)) - .responseHandler(res -> res = responseBuilder).build().execute(); + if (linkValue == null) { + return null; } - + + try { + return new ApiCall.Builder, CoreApiException>().globalConfig(globalConfig) + .requestBuilder(requestBuilder.queryParam(CoreHelper.getQueryParameters(linkValue))) + .responseHandler(responseBuilder).build().execute(); + } catch (IOException | CoreApiException e) { + // Ignore exceptions + } + return null; } diff --git a/src/main/java/io/apimatic/core/types/pagination/OffsetPaginated.java b/src/main/java/io/apimatic/core/types/pagination/OffsetPaginated.java index 437e6dfb..a2588e02 100644 --- a/src/main/java/io/apimatic/core/types/pagination/OffsetPaginated.java +++ b/src/main/java/io/apimatic/core/types/pagination/OffsetPaginated.java @@ -1,8 +1,107 @@ package io.apimatic.core.types.pagination; +import java.io.IOException; +import java.util.List; + +import com.fasterxml.jackson.core.type.TypeReference; + +import io.apimatic.core.ApiCall; +import io.apimatic.core.GlobalConfiguration; +import io.apimatic.core.HttpRequest; +import io.apimatic.core.ResponseHandler.Builder; +import io.apimatic.core.configurations.http.request.EndpointConfiguration; +import io.apimatic.core.types.CoreApiException; +import io.apimatic.core.utilities.CoreHelper; +import io.apimatic.coreinterfaces.http.request.Request; +import io.apimatic.coreinterfaces.http.response.Response; + public class OffsetPaginated { - public T next() { - return null; - }; + /** + * Private store for encapsulated object's value. + */ + private T value; + + private Configuration configuration; + + private GlobalConfiguration globalConfig; + + private Response response; + + private Builder, CoreApiException> responseBuilder; + + private HttpRequest.Builder requestBuilder; + + public OffsetPaginated(final T value, final Configuration configuration, final EndpointConfiguration endPointConfig, + final Response response, final Builder, CoreApiException> responseBuilder) { + this.value = value; + this.configuration = configuration; + this.globalConfig = endPointConfig.getGlobalConfiguration(); + this.response = response; + this.responseBuilder = responseBuilder; + this.requestBuilder = endPointConfig.getRequestBuilder(); + } + + /** + * Converts this CursorPaginated into string format. + * + * @return String representation of this class. + */ + @Override + public String toString() { + return "" + value; + } + + public T value() { + return value; + } + + public OffsetPaginated next() { + try { + return new ApiCall.Builder, CoreApiException>().globalConfig(globalConfig) + .requestBuilder(configuration.getNextPageRequest(requestBuilder, globalConfig, response.getBody())) + .responseHandler(responseBuilder).build().execute(); + } catch (IOException | CoreApiException e) { + // Ignore exceptions + } + + return null; + } + + public static class Configuration { + private String pageParamName; + private String offsetParamName; + private String resultPointer; + + public Configuration(String pageParamName, String offsetParamName, String resultPointer) { + this.pageParamName = pageParamName; + this.offsetParamName = offsetParamName; + this.resultPointer = resultPointer; + } + + private HttpRequest.Builder getNextPageRequest(HttpRequest.Builder builder, GlobalConfiguration config, + String response) throws IOException { + if (pageParamName == null && offsetParamName == null) { + return builder; + } + + Request req = builder.build(config); + + if (pageParamName != null) { + Integer newPageValue = Integer.parseInt((String) req.getQueryParameters().get(pageParamName)) + 1; + return builder.queryParam(q -> q.key(pageParamName).value(newPageValue)); + } + + if (offsetParamName != null) { + String resultArray = CoreHelper.getValueFromJson(resultPointer, response); + List result = CoreHelper.deserialize(resultArray, new TypeReference>() { + }); + Integer nextOffsetValue = Integer.parseInt("" + req.getQueryParameters().get(offsetParamName)) + + (result == null ? 0 : result.size()); + return builder.queryParam(q -> q.key(offsetParamName).value(nextOffsetValue)); + } + + return builder; + } + } } diff --git a/src/main/java/io/apimatic/core/utilities/CoreHelper.java b/src/main/java/io/apimatic/core/utilities/CoreHelper.java index 87dd47fd..c01fe864 100644 --- a/src/main/java/io/apimatic/core/utilities/CoreHelper.java +++ b/src/main/java/io/apimatic/core/utilities/CoreHelper.java @@ -11,6 +11,8 @@ import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.math.BigDecimal; +import java.net.URLDecoder; +import java.nio.charset.StandardCharsets; import java.net.URLEncoder; import java.util.AbstractMap.SimpleEntry; import java.util.ArrayList; @@ -18,6 +20,7 @@ import java.util.Base64; import java.util.Collection; import java.util.Collections; +import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.LinkedHashMap; @@ -30,8 +33,12 @@ import java.util.stream.Collectors; import javax.json.Json; +import javax.json.JsonPointer; import javax.json.JsonReader; +import javax.json.JsonString; import javax.json.JsonStructure; +import javax.json.JsonValue; +import javax.json.JsonValue.ValueType; import javax.xml.bind.JAXBContext; import javax.xml.bind.JAXBElement; import javax.xml.bind.JAXBException; @@ -1569,6 +1576,15 @@ public static String getQueryParametersFromUrl(String queryUrl) { return queryStringIndex != -1 ? queryUrl.substring(queryStringIndex + 1) : ""; } + public static Map getQueryParameters(String queryUrl) { + return Arrays.stream(getQueryParametersFromUrl(queryUrl).split("&")) + .map(param -> param.split("=")) + .collect(Collectors.toMap( + pair -> pair[0], + pair -> pair[1] + )); + } + public static JsonStructure createJsonStructure(String json) { JsonReader jsonReader = Json.createReader(new StringReader(json)); JsonStructure jsonStructure = null; @@ -1583,4 +1599,29 @@ public static JsonStructure createJsonStructure(String json) { return jsonStructure; } + + public static String getValueFromJson(String pointer, String json) { + if (pointer == null) { + return null; + } + + JsonStructure jsonStructure = CoreHelper.createJsonStructure(json); + JsonPointer jsonPointer = Json.createPointer(pointer); + + if (jsonStructure == null || !jsonPointer.containsValue(jsonStructure)) { + return null; + } + + JsonValue value = jsonPointer.getValue(jsonStructure); + + if (value == JsonValue.NULL) { // Explicitly check for JsonValue.NULL + return null; + } + + if (value instanceof JsonString) { // Extract raw string value + return ((JsonString) value).getString(); + } + + return value.toString(); // Only convert toString() if it's not null + } } From 0b8ba679cbe5e490972da9f02672b5daec9885e0 Mon Sep 17 00:00:00 2001 From: Asad Ali <14asadali@gmail.com> Date: Tue, 18 Mar 2025 12:20:43 +0500 Subject: [PATCH 04/20] converted paginator types to iterator --- src/main/java/io/apimatic/core/ApiCall.java | 19 ++-- .../io/apimatic/core/ResponseHandler.java | 15 +-- .../types/pagination/CursorPaginated.java | 89 ++++++++------- .../core/types/pagination/LinkPaginated.java | 104 +++++++----------- .../types/pagination/OffsetPaginated.java | 79 +++++++------ .../core/types/pagination/PaginatedData.java | 98 +++++++++++++++++ .../apimatic/core/utilities/CoreHelper.java | 6 +- 7 files changed, 234 insertions(+), 176 deletions(-) create mode 100644 src/main/java/io/apimatic/core/types/pagination/PaginatedData.java diff --git a/src/main/java/io/apimatic/core/ApiCall.java b/src/main/java/io/apimatic/core/ApiCall.java index dd3c1897..d5da862f 100644 --- a/src/main/java/io/apimatic/core/ApiCall.java +++ b/src/main/java/io/apimatic/core/ApiCall.java @@ -158,23 +158,26 @@ public Builder responseHandler( } /** - * @param responseHandlerBuilder response handler builder instance. + * @param action endpointConfiguration {@link Consumer}. * @return {@link ApiCall.Builder}. */ - public Builder responseHandler( - ResponseHandler.Builder responseHandlerBuilder) { - this.responseHandlerBuilder = responseHandlerBuilder; + public Builder endpointConfiguration( + Consumer action) { + endpointConfigurationBuilder = new EndpointConfiguration.Builder(); + action.accept(endpointConfigurationBuilder); return this; } /** - * @param action endpointConfiguration {@link Consumer}. + * @param endpointConfiguration end point configuration instance. * @return {@link ApiCall.Builder}. */ public Builder endpointConfiguration( - Consumer action) { - endpointConfigurationBuilder = new EndpointConfiguration.Builder(); - action.accept(endpointConfigurationBuilder); + EndpointConfiguration endpointConfiguration) { + endpointConfigurationBuilder = new EndpointConfiguration.Builder() + .arraySerializationFormat(endpointConfiguration.getArraySerializationFormat()) + .hasBinaryResponse(endpointConfiguration.hasBinaryResponse()) + .retryOption(endpointConfiguration.getRetryOption()); return this; } diff --git a/src/main/java/io/apimatic/core/ResponseHandler.java b/src/main/java/io/apimatic/core/ResponseHandler.java index 71c885b1..6961adb4 100644 --- a/src/main/java/io/apimatic/core/ResponseHandler.java +++ b/src/main/java/io/apimatic/core/ResponseHandler.java @@ -342,12 +342,9 @@ public Builder apiRespon * @param the type wrapped by linked pagination. * @return {@link ResponseHandler.Builder}. */ - @SuppressWarnings("unchecked") public Builder linkPaginatedDeserializer( Deserializer deserializer, LinkPaginated.Configuration config) { - this.paginationDeserializer = (res, c) -> new LinkPaginated( - deserializer.apply(res.getBody()), config, c, res, - (Builder, CoreApiException>) this); + this.paginationDeserializer = (res, ec) -> LinkPaginated.Create(deserializer, config, ec, res); return this; } @@ -358,12 +355,9 @@ public Builder linkPaginatedDeserialize * @param the type wrapped by cursor pagination. * @return {@link ResponseHandler.Builder}. */ - @SuppressWarnings("unchecked") public Builder cursorPaginatedDeserializer( Deserializer deserializer, CursorPaginated.Configuration config) { - this.paginationDeserializer = (res, c) -> new CursorPaginated( - deserializer.apply(res.getBody()), config, c, res, - (Builder, CoreApiException>) this); + this.paginationDeserializer = (res, ec) -> CursorPaginated.Create(deserializer, config, ec, res); return this; } @@ -374,12 +368,9 @@ public Builder cursorPaginatedDeseriali * @param the type wrapped by offset pagination. * @return {@link ResponseHandler.Builder}. */ - @SuppressWarnings("unchecked") public Builder offsetPaginatedDeserializer( Deserializer deserializer, OffsetPaginated.Configuration config) { - this.paginationDeserializer = (res, c) -> new OffsetPaginated( - deserializer.apply(res.getBody()), config, c, res, - (Builder, CoreApiException>) this); + this.paginationDeserializer = (res, ec) -> OffsetPaginated.Create(deserializer, config, ec, res); return this; } diff --git a/src/main/java/io/apimatic/core/types/pagination/CursorPaginated.java b/src/main/java/io/apimatic/core/types/pagination/CursorPaginated.java index df49f736..3be5c199 100644 --- a/src/main/java/io/apimatic/core/types/pagination/CursorPaginated.java +++ b/src/main/java/io/apimatic/core/types/pagination/CursorPaginated.java @@ -1,69 +1,57 @@ package io.apimatic.core.types.pagination; import java.io.IOException; +import java.util.Collections; import io.apimatic.core.ApiCall; -import io.apimatic.core.GlobalConfiguration; -import io.apimatic.core.HttpRequest; -import io.apimatic.core.ResponseHandler.Builder; +import io.apimatic.core.ErrorCase; import io.apimatic.core.configurations.http.request.EndpointConfiguration; import io.apimatic.core.types.CoreApiException; import io.apimatic.core.utilities.CoreHelper; import io.apimatic.coreinterfaces.http.response.Response; +import io.apimatic.coreinterfaces.type.functional.Deserializer; -public class CursorPaginated { - - /** - * Private store for encapsulated object's value. - */ - private T value; +public class CursorPaginated extends PaginatedData { + private Deserializer deserializer; private Configuration configuration; - private GlobalConfiguration globalConfig; - - private Response response; - - private Builder, CoreApiException> responseBuilder; - - private HttpRequest.Builder requestBuilder; - - public CursorPaginated(final T value, final Configuration configuration, final EndpointConfiguration endPointConfig, - final Response response, final Builder, CoreApiException> responseBuilder) { - this.value = value; + private CursorPaginated(final Deserializer deserializer, final Configuration configuration, + final EndpointConfiguration endPointConfig, final Response response) throws IOException { + super(deserializer.apply(response.getBody()), response, endPointConfig, configuration.resultPointer); + this.deserializer = deserializer; this.configuration = configuration; - this.globalConfig = endPointConfig.getGlobalConfiguration(); - this.response = response; - this.responseBuilder = responseBuilder; - this.requestBuilder = endPointConfig.getRequestBuilder(); } - /** - * Converts this CursorPaginated into string format. - * - * @return String representation of this class. - */ - @Override - public String toString() { - return "" + value; + public static CursorPaginated Create(Deserializer deserializer, Configuration configuration, + EndpointConfiguration endPointConfig, Response response) throws IOException { + return new CursorPaginated(deserializer, configuration, endPointConfig, response); } - public T value() { - return value; - } + @Override + protected PaginatedData fetchData() { + String cursorValue = CoreHelper.getValueFromJson(configuration.nextCursor, + getLastResponse().getBody()); + EndpointConfiguration endpointConfig = getLastEndpointConfiguration(); - public CursorPaginated next() { - String cursorValue = CoreHelper.getValueFromJson(configuration.nextPointerResponse, response.getBody()); - if (cursorValue == null) { return null; } try { - return new ApiCall.Builder, CoreApiException>().globalConfig(globalConfig) - .requestBuilder(requestBuilder.queryParam(q -> q.key(configuration.cursorQueryParamName).value(cursorValue))) - .responseHandler(responseBuilder).build().execute(); - } catch (IOException | CoreApiException e) { + return new ApiCall.Builder, CoreApiException>().endpointConfiguration(endpointConfig) + .globalConfig(endpointConfig.getGlobalConfiguration()) + .requestBuilder( + endpointConfig + .getRequestBuilder().queryParam(q -> q.key(configuration.cursorQueryParamName) + .value(cursorValue))) + .responseHandler(res -> res + .globalErrorCase(Collections.singletonMap(ErrorCase.DEFAULT, + ErrorCase.setReason(null, + (reason, context) -> new CoreApiException(reason, context)))) + .cursorPaginatedDeserializer(deserializer, configuration)) + .build().execute(); + } catch (Exception e) { // Ignore exceptions } @@ -71,12 +59,23 @@ public CursorPaginated next() { } public static class Configuration { - private String nextPointerResponse; + private String nextCursor; private String cursorQueryParamName; + private String resultPointer; + + public Configuration nextCursor(String nextCursor) { + this.nextCursor = nextCursor; + return this; + } - public Configuration(String nextPointerResponse, String cursorQueryParamName) { - this.nextPointerResponse = nextPointerResponse; + public Configuration cursorQueryParamName(String cursorQueryParamName) { this.cursorQueryParamName = cursorQueryParamName; + return this; + } + + public Configuration resultPointer(String resultPointer) { + this.resultPointer = resultPointer; + return this; } } } diff --git a/src/main/java/io/apimatic/core/types/pagination/LinkPaginated.java b/src/main/java/io/apimatic/core/types/pagination/LinkPaginated.java index ef3d67ef..0651e8ac 100644 --- a/src/main/java/io/apimatic/core/types/pagination/LinkPaginated.java +++ b/src/main/java/io/apimatic/core/types/pagination/LinkPaginated.java @@ -1,102 +1,74 @@ package io.apimatic.core.types.pagination; import java.io.IOException; +import java.util.Collections; import io.apimatic.core.ApiCall; -import io.apimatic.core.GlobalConfiguration; -import io.apimatic.core.HttpRequest; -import io.apimatic.core.ResponseHandler.Builder; +import io.apimatic.core.ErrorCase; import io.apimatic.core.configurations.http.request.EndpointConfiguration; import io.apimatic.core.types.CoreApiException; import io.apimatic.core.utilities.CoreHelper; import io.apimatic.coreinterfaces.http.response.Response; +import io.apimatic.coreinterfaces.type.functional.Deserializer; -public class LinkPaginated { - - /** - * Private store for encapsulated object's value. - */ - private T value; +public class LinkPaginated extends PaginatedData { + private Deserializer deserializer; private Configuration configuration; - private GlobalConfiguration globalConfig; - - private Response response; - - private Builder, CoreApiException> responseBuilder; - - private HttpRequest.Builder requestBuilder; - - public LinkPaginated(final T value, final Configuration configuration, final EndpointConfiguration endPointConfig, - final Response response, final Builder, CoreApiException> builder) { - this.value = value; + private LinkPaginated(final Deserializer deserializer, final Configuration configuration, + final EndpointConfiguration endPointConfig, final Response response) throws IOException { + super(deserializer.apply(response.getBody()), response, endPointConfig, configuration.resultPointer); + this.deserializer = deserializer; this.configuration = configuration; - this.globalConfig = endPointConfig.getGlobalConfiguration(); - this.response = response; - this.responseBuilder = builder; - this.requestBuilder = endPointConfig.getRequestBuilder(); - } - - /** - * Converts this LinkPaginated into string format. - * - * @return String representation of this class. - */ - @Override - public String toString() { - return "" + value; - } - - public T value() { - return value; - } - - public LinkPaginated first() { - return executeForPointer(configuration.firstPointer); - } - - public LinkPaginated last() { - return executeForPointer(configuration.lastPointer); } - public LinkPaginated previous() { - return executeForPointer(configuration.prevPointer); + public static LinkPaginated Create(Deserializer deserializer, Configuration configuration, + EndpointConfiguration endPointConfig, Response response) throws IOException { + return new LinkPaginated(deserializer, configuration, endPointConfig, response); } - public LinkPaginated next() { - return executeForPointer(configuration.nextPointer); - } + @Override + protected PaginatedData fetchData() { + String linkValue = CoreHelper.getValueFromJson(configuration.nextPointer, getLastResponse().getBody()); + EndpointConfiguration endpointConfig = getLastEndpointConfiguration(); - private LinkPaginated executeForPointer(String pointer) { - String linkValue = CoreHelper.getValueFromJson(pointer, response.getBody()); - if (linkValue == null) { return null; } try { - return new ApiCall.Builder, CoreApiException>().globalConfig(globalConfig) - .requestBuilder(requestBuilder.queryParam(CoreHelper.getQueryParameters(linkValue))) - .responseHandler(responseBuilder).build().execute(); - } catch (IOException | CoreApiException e) { + return new ApiCall.Builder, CoreApiException>() + .endpointConfiguration(endpointConfig) + .globalConfig( + endpointConfig.getGlobalConfiguration()) + .requestBuilder(endpointConfig.getRequestBuilder() + .queryParam(CoreHelper.getQueryParameters(linkValue))) + .responseHandler(res -> res + .globalErrorCase(Collections.singletonMap(ErrorCase.DEFAULT, + ErrorCase.setReason(null, + (reason, context) -> new CoreApiException(reason, context)))) + .linkPaginatedDeserializer(deserializer, configuration)) + .build().execute(); + } catch (Exception e) { // Ignore exceptions } - + return null; } public static class Configuration { - private String firstPointer; - private String lastPointer; - private String prevPointer; private String nextPointer; + private String resultPointer; - public Configuration(String firstPointer, String lastPointer, String prevPointer, String nextPointer) { - this.firstPointer = firstPointer; - this.lastPointer = lastPointer; - this.prevPointer = prevPointer; + public Configuration nextPointer(String nextPointer) { this.nextPointer = nextPointer; + return this; + } + + public Configuration resultPointer(String resultPointer) { + this.resultPointer = resultPointer; + return this; } } } diff --git a/src/main/java/io/apimatic/core/types/pagination/OffsetPaginated.java b/src/main/java/io/apimatic/core/types/pagination/OffsetPaginated.java index a2588e02..803e1151 100644 --- a/src/main/java/io/apimatic/core/types/pagination/OffsetPaginated.java +++ b/src/main/java/io/apimatic/core/types/pagination/OffsetPaginated.java @@ -1,67 +1,57 @@ package io.apimatic.core.types.pagination; import java.io.IOException; +import java.util.Collections; import java.util.List; import com.fasterxml.jackson.core.type.TypeReference; import io.apimatic.core.ApiCall; +import io.apimatic.core.ErrorCase; import io.apimatic.core.GlobalConfiguration; import io.apimatic.core.HttpRequest; -import io.apimatic.core.ResponseHandler.Builder; import io.apimatic.core.configurations.http.request.EndpointConfiguration; import io.apimatic.core.types.CoreApiException; import io.apimatic.core.utilities.CoreHelper; import io.apimatic.coreinterfaces.http.request.Request; import io.apimatic.coreinterfaces.http.response.Response; +import io.apimatic.coreinterfaces.type.functional.Deserializer; -public class OffsetPaginated { - - /** - * Private store for encapsulated object's value. - */ - private T value; +public class OffsetPaginated extends PaginatedData { + private Deserializer deserializer; private Configuration configuration; - private GlobalConfiguration globalConfig; - - private Response response; - - private Builder, CoreApiException> responseBuilder; - - private HttpRequest.Builder requestBuilder; - - public OffsetPaginated(final T value, final Configuration configuration, final EndpointConfiguration endPointConfig, - final Response response, final Builder, CoreApiException> responseBuilder) { - this.value = value; + private OffsetPaginated(final Deserializer deserializer, final Configuration configuration, + final EndpointConfiguration endPointConfig, final Response response) throws IOException { + super(deserializer.apply(response.getBody()), response, endPointConfig, configuration.resultPointer); + this.deserializer = deserializer; this.configuration = configuration; - this.globalConfig = endPointConfig.getGlobalConfiguration(); - this.response = response; - this.responseBuilder = responseBuilder; - this.requestBuilder = endPointConfig.getRequestBuilder(); } - /** - * Converts this CursorPaginated into string format. - * - * @return String representation of this class. - */ - @Override - public String toString() { - return "" + value; + public static OffsetPaginated Create(Deserializer deserializer, Configuration configuration, + EndpointConfiguration endPointConfig, Response response) throws IOException { + return new OffsetPaginated(deserializer, configuration, endPointConfig, response); } - public T value() { - return value; - } - - public OffsetPaginated next() { + @Override + protected PaginatedData fetchData() { + EndpointConfiguration endpointConfig = getLastEndpointConfiguration(); try { - return new ApiCall.Builder, CoreApiException>().globalConfig(globalConfig) - .requestBuilder(configuration.getNextPageRequest(requestBuilder, globalConfig, response.getBody())) - .responseHandler(responseBuilder).build().execute(); - } catch (IOException | CoreApiException e) { + return new ApiCall.Builder, CoreApiException>().endpointConfiguration(endpointConfig) + .globalConfig(endpointConfig.getGlobalConfiguration()) + .requestBuilder( + configuration + .getNextPageRequest( + endpointConfig.getRequestBuilder(), endpointConfig.getGlobalConfiguration(), + getLastResponse().getBody())) + .responseHandler(res -> res + .globalErrorCase(Collections.singletonMap(ErrorCase.DEFAULT, + ErrorCase.setReason(null, + (reason, context) -> new CoreApiException(reason, context)))) + .offsetPaginatedDeserializer(deserializer, configuration)) + .build().execute(); + } catch (Exception e) { // Ignore exceptions } @@ -73,10 +63,19 @@ public static class Configuration { private String offsetParamName; private String resultPointer; - public Configuration(String pageParamName, String offsetParamName, String resultPointer) { + public Configuration pageParamName(String pageParamName) { this.pageParamName = pageParamName; + return this; + } + + public Configuration offsetParamName(String offsetParamName) { this.offsetParamName = offsetParamName; + return this; + } + + public Configuration resultPointer(String resultPointer) { this.resultPointer = resultPointer; + return this; } private HttpRequest.Builder getNextPageRequest(HttpRequest.Builder builder, GlobalConfiguration config, diff --git a/src/main/java/io/apimatic/core/types/pagination/PaginatedData.java b/src/main/java/io/apimatic/core/types/pagination/PaginatedData.java new file mode 100644 index 00000000..d6a4b4e0 --- /dev/null +++ b/src/main/java/io/apimatic/core/types/pagination/PaginatedData.java @@ -0,0 +1,98 @@ +package io.apimatic.core.types.pagination; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; +import java.util.NoSuchElementException; + +import com.fasterxml.jackson.core.type.TypeReference; + +import io.apimatic.core.configurations.http.request.EndpointConfiguration; +import io.apimatic.core.utilities.CoreHelper; +import io.apimatic.coreinterfaces.http.response.Response; + +public abstract class PaginatedData implements Iterator { + + private int currentIndex = 0; + + private List data = new ArrayList(); + + private List responses = new ArrayList(); + + private List endPointConfigs = new ArrayList(); + + private String resultPointer; + + public PaginatedData(final T value, final Response response, final EndpointConfiguration config, + final String resultPointer) { + data.add(value); + endPointConfigs.add(config); + responses.add(response); + this.resultPointer = resultPointer; + } + + public static List ExtractData(Iterator iterator, Class clazz) throws IOException { + List items = new ArrayList(); + + if (!(iterator instanceof PaginatedData)) { + return items; + } + + PaginatedData paginatedData = (PaginatedData) iterator; + for (Response res : paginatedData.responses) { + String resultArray = CoreHelper.getValueFromJson(paginatedData.resultPointer, res.getBody()); + List result = CoreHelper.deserialize(resultArray, new TypeReference>() {}); + for (InnerType inner : result) { + items.add(CoreHelper.deserialize(CoreHelper.serialize(inner), clazz)); + } + } + + return items; + } + + protected EndpointConfiguration getLastEndpointConfiguration() { + return endPointConfigs.get(currentIndex - 1); + } + + protected Response getLastResponse() { + return responses.get(currentIndex - 1); + } + + @Override + public String toString() { + return "PaginatedData [currentIndex=" + currentIndex + ", data=" + data + "]"; + } + + @Override + public boolean hasNext() { + if (currentIndex < data.size()) { + return true; + } + + PaginatedData data = fetchData(); + if (data != null) { + updateUsing(data); + return true; + } + + return false; + } + + @Override + public T next() { + if (hasNext()) { + return data.get(currentIndex++); + } + + throw new NoSuchElementException("No more data available."); + } + + private void updateUsing(PaginatedData existing) { + this.data.add(existing.data.get(existing.currentIndex)); + this.responses.add(existing.responses.get(existing.currentIndex)); + this.endPointConfigs.add(existing.endPointConfigs.get(existing.currentIndex)); + } + + protected abstract PaginatedData fetchData(); +} diff --git a/src/main/java/io/apimatic/core/utilities/CoreHelper.java b/src/main/java/io/apimatic/core/utilities/CoreHelper.java index c01fe864..59479106 100644 --- a/src/main/java/io/apimatic/core/utilities/CoreHelper.java +++ b/src/main/java/io/apimatic/core/utilities/CoreHelper.java @@ -11,8 +11,6 @@ import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.math.BigDecimal; -import java.net.URLDecoder; -import java.nio.charset.StandardCharsets; import java.net.URLEncoder; import java.util.AbstractMap.SimpleEntry; import java.util.ArrayList; @@ -20,7 +18,6 @@ import java.util.Base64; import java.util.Collection; import java.util.Collections; -import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.LinkedHashMap; @@ -38,7 +35,6 @@ import javax.json.JsonString; import javax.json.JsonStructure; import javax.json.JsonValue; -import javax.json.JsonValue.ValueType; import javax.xml.bind.JAXBContext; import javax.xml.bind.JAXBElement; import javax.xml.bind.JAXBException; @@ -1601,7 +1597,7 @@ public static JsonStructure createJsonStructure(String json) { } public static String getValueFromJson(String pointer, String json) { - if (pointer == null) { + if (pointer == null || json == null) { return null; } From afb9ab28fd42bc35af86551fe319bfd27fbf93fa Mon Sep 17 00:00:00 2001 From: Asad Ali <14asadali@gmail.com> Date: Tue, 18 Mar 2025 12:24:50 +0500 Subject: [PATCH 05/20] adds common pagination class --- .../java/io/apimatic/core/types/pagination/PaginatedData.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/io/apimatic/core/types/pagination/PaginatedData.java b/src/main/java/io/apimatic/core/types/pagination/PaginatedData.java index d6a4b4e0..0825435f 100644 --- a/src/main/java/io/apimatic/core/types/pagination/PaginatedData.java +++ b/src/main/java/io/apimatic/core/types/pagination/PaginatedData.java @@ -32,7 +32,7 @@ public PaginatedData(final T value, final Response response, final EndpointConfi this.resultPointer = resultPointer; } - public static List ExtractData(Iterator iterator, Class clazz) throws IOException { + public static List extract(Iterator iterator, Class clazz) throws IOException { List items = new ArrayList(); if (!(iterator instanceof PaginatedData)) { From 9a4a469a2c0fe9ffc9d99d8f76f38e9897a4dafa Mon Sep 17 00:00:00 2001 From: Asad Ali <14asadali@gmail.com> Date: Wed, 19 Mar 2025 10:03:02 +0500 Subject: [PATCH 06/20] updated implementation to wrap inner class --- .../io/apimatic/core/ResponseHandler.java | 7 +- .../types/pagination/CursorPaginated.java | 15 +-- .../core/types/pagination/LinkPaginated.java | 18 ++-- .../types/pagination/OffsetPaginated.java | 97 ++++++++++--------- .../core/types/pagination/PaginatedData.java | 65 ++++--------- 5 files changed, 89 insertions(+), 113 deletions(-) diff --git a/src/main/java/io/apimatic/core/ResponseHandler.java b/src/main/java/io/apimatic/core/ResponseHandler.java index 6961adb4..b85301db 100644 --- a/src/main/java/io/apimatic/core/ResponseHandler.java +++ b/src/main/java/io/apimatic/core/ResponseHandler.java @@ -2,6 +2,7 @@ import java.io.IOException; import java.util.HashMap; +import java.util.List; import java.util.Map; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -343,7 +344,7 @@ public Builder apiRespon * @return {@link ResponseHandler.Builder}. */ public Builder linkPaginatedDeserializer( - Deserializer deserializer, LinkPaginated.Configuration config) { + Deserializer> deserializer, LinkPaginated.Configuration config) { this.paginationDeserializer = (res, ec) -> LinkPaginated.Create(deserializer, config, ec, res); return this; } @@ -356,7 +357,7 @@ public Builder linkPaginatedDeserialize * @return {@link ResponseHandler.Builder}. */ public Builder cursorPaginatedDeserializer( - Deserializer deserializer, CursorPaginated.Configuration config) { + Deserializer> deserializer, CursorPaginated.Configuration config) { this.paginationDeserializer = (res, ec) -> CursorPaginated.Create(deserializer, config, ec, res); return this; } @@ -369,7 +370,7 @@ public Builder cursorPaginatedDeseriali * @return {@link ResponseHandler.Builder}. */ public Builder offsetPaginatedDeserializer( - Deserializer deserializer, OffsetPaginated.Configuration config) { + Deserializer> deserializer, OffsetPaginated.Configuration config) { this.paginationDeserializer = (res, ec) -> OffsetPaginated.Create(deserializer, config, ec, res); return this; } diff --git a/src/main/java/io/apimatic/core/types/pagination/CursorPaginated.java b/src/main/java/io/apimatic/core/types/pagination/CursorPaginated.java index 3be5c199..af08181d 100644 --- a/src/main/java/io/apimatic/core/types/pagination/CursorPaginated.java +++ b/src/main/java/io/apimatic/core/types/pagination/CursorPaginated.java @@ -2,6 +2,7 @@ import java.io.IOException; import java.util.Collections; +import java.util.List; import io.apimatic.core.ApiCall; import io.apimatic.core.ErrorCase; @@ -12,18 +13,18 @@ import io.apimatic.coreinterfaces.type.functional.Deserializer; public class CursorPaginated extends PaginatedData { - private Deserializer deserializer; + private Deserializer> deserializer; private Configuration configuration; - private CursorPaginated(final Deserializer deserializer, final Configuration configuration, + private CursorPaginated(final Deserializer> deserializer, final Configuration configuration, final EndpointConfiguration endPointConfig, final Response response) throws IOException { - super(deserializer.apply(response.getBody()), response, endPointConfig, configuration.resultPointer); + super(deserializer.apply(response.getBody()), response, endPointConfig); this.deserializer = deserializer; this.configuration = configuration; } - public static CursorPaginated Create(Deserializer deserializer, Configuration configuration, + public static CursorPaginated Create(Deserializer> deserializer, Configuration configuration, EndpointConfiguration endPointConfig, Response response) throws IOException { return new CursorPaginated(deserializer, configuration, endPointConfig, response); } @@ -61,7 +62,6 @@ protected PaginatedData fetchData() { public static class Configuration { private String nextCursor; private String cursorQueryParamName; - private String resultPointer; public Configuration nextCursor(String nextCursor) { this.nextCursor = nextCursor; @@ -72,10 +72,5 @@ public Configuration cursorQueryParamName(String cursorQueryParamName) { this.cursorQueryParamName = cursorQueryParamName; return this; } - - public Configuration resultPointer(String resultPointer) { - this.resultPointer = resultPointer; - return this; - } } } diff --git a/src/main/java/io/apimatic/core/types/pagination/LinkPaginated.java b/src/main/java/io/apimatic/core/types/pagination/LinkPaginated.java index 0651e8ac..3f8a088c 100644 --- a/src/main/java/io/apimatic/core/types/pagination/LinkPaginated.java +++ b/src/main/java/io/apimatic/core/types/pagination/LinkPaginated.java @@ -2,6 +2,7 @@ import java.io.IOException; import java.util.Collections; +import java.util.List; import io.apimatic.core.ApiCall; import io.apimatic.core.ErrorCase; @@ -12,18 +13,18 @@ import io.apimatic.coreinterfaces.type.functional.Deserializer; public class LinkPaginated extends PaginatedData { - private Deserializer deserializer; + private Deserializer> deserializer; private Configuration configuration; - private LinkPaginated(final Deserializer deserializer, final Configuration configuration, + private LinkPaginated(final Deserializer> deserializer, final Configuration configuration, final EndpointConfiguration endPointConfig, final Response response) throws IOException { - super(deserializer.apply(response.getBody()), response, endPointConfig, configuration.resultPointer); + super(deserializer.apply(response.getBody()), response, endPointConfig); this.deserializer = deserializer; this.configuration = configuration; } - public static LinkPaginated Create(Deserializer deserializer, Configuration configuration, + public static LinkPaginated Create(Deserializer> deserializer, Configuration configuration, EndpointConfiguration endPointConfig, Response response) throws IOException { return new LinkPaginated(deserializer, configuration, endPointConfig, response); } @@ -31,12 +32,13 @@ public static LinkPaginated Create(Deserializer deserializer, Configur @Override protected PaginatedData fetchData() { String linkValue = CoreHelper.getValueFromJson(configuration.nextPointer, getLastResponse().getBody()); - EndpointConfiguration endpointConfig = getLastEndpointConfiguration(); if (linkValue == null) { return null; } + EndpointConfiguration endpointConfig = getLastEndpointConfiguration(); + try { return new ApiCall.Builder, CoreApiException>() .endpointConfiguration(endpointConfig) @@ -59,16 +61,10 @@ protected PaginatedData fetchData() { public static class Configuration { private String nextPointer; - private String resultPointer; public Configuration nextPointer(String nextPointer) { this.nextPointer = nextPointer; return this; } - - public Configuration resultPointer(String resultPointer) { - this.resultPointer = resultPointer; - return this; - } } } diff --git a/src/main/java/io/apimatic/core/types/pagination/OffsetPaginated.java b/src/main/java/io/apimatic/core/types/pagination/OffsetPaginated.java index 803e1151..a50d934c 100644 --- a/src/main/java/io/apimatic/core/types/pagination/OffsetPaginated.java +++ b/src/main/java/io/apimatic/core/types/pagination/OffsetPaginated.java @@ -3,48 +3,50 @@ import java.io.IOException; import java.util.Collections; import java.util.List; - -import com.fasterxml.jackson.core.type.TypeReference; +import java.util.Map; import io.apimatic.core.ApiCall; import io.apimatic.core.ErrorCase; -import io.apimatic.core.GlobalConfiguration; import io.apimatic.core.HttpRequest; import io.apimatic.core.configurations.http.request.EndpointConfiguration; import io.apimatic.core.types.CoreApiException; -import io.apimatic.core.utilities.CoreHelper; -import io.apimatic.coreinterfaces.http.request.Request; import io.apimatic.coreinterfaces.http.response.Response; import io.apimatic.coreinterfaces.type.functional.Deserializer; public class OffsetPaginated extends PaginatedData { - private Deserializer deserializer; + private Deserializer> deserializer; private Configuration configuration; - private OffsetPaginated(final Deserializer deserializer, final Configuration configuration, + private boolean isRequestBuilderUpdated; + + private OffsetPaginated(final Deserializer> deserializer, final Configuration configuration, final EndpointConfiguration endPointConfig, final Response response) throws IOException { - super(deserializer.apply(response.getBody()), response, endPointConfig, configuration.resultPointer); + super(deserializer.apply(response.getBody()), response, endPointConfig); this.deserializer = deserializer; this.configuration = configuration; + isRequestBuilderUpdated = updateRequestBuilderForNextCall(endPointConfig, getDataSize()); } - public static OffsetPaginated Create(Deserializer deserializer, Configuration configuration, + public static OffsetPaginated Create(Deserializer> deserializer, Configuration configuration, EndpointConfiguration endPointConfig, Response response) throws IOException { return new OffsetPaginated(deserializer, configuration, endPointConfig, response); } @Override protected PaginatedData fetchData() { + if (!isRequestBuilderUpdated) { + return null; + } + EndpointConfiguration endpointConfig = getLastEndpointConfiguration(); try { - return new ApiCall.Builder, CoreApiException>().endpointConfiguration(endpointConfig) + return new ApiCall.Builder, CoreApiException>() + .endpointConfiguration( + endpointConfig) .globalConfig(endpointConfig.getGlobalConfiguration()) .requestBuilder( - configuration - .getNextPageRequest( - endpointConfig.getRequestBuilder(), endpointConfig.getGlobalConfiguration(), - getLastResponse().getBody())) + endpointConfig.getRequestBuilder()) .responseHandler(res -> res .globalErrorCase(Collections.singletonMap(ErrorCase.DEFAULT, ErrorCase.setReason(null, @@ -58,10 +60,45 @@ protected PaginatedData fetchData() { return null; } + @Override + protected void updateData(PaginatedData newData) { + super.updateData(newData); + isRequestBuilderUpdated = ((OffsetPaginated) newData).isRequestBuilderUpdated; + } + + private boolean updateRequestBuilderForNextCall(EndpointConfiguration lastEndpointConfig, int lastDataSize) { + if (configuration.pageParamName == null && configuration.offsetParamName == null) { + return false; + } + + boolean isUpdated = false; + + try { + HttpRequest.Builder lastRequest = lastEndpointConfig.getRequestBuilder(); + Map reqQuery = lastRequest.build(lastEndpointConfig.getGlobalConfiguration()) + .getQueryParameters(); + + if (configuration.pageParamName != null && reqQuery.containsKey(configuration.pageParamName)) { + Integer newPageValue = Integer.parseInt((String) reqQuery.get(configuration.pageParamName)) + 1; + lastRequest.queryParam(q -> q.key(configuration.pageParamName).value(newPageValue)); + isUpdated = true; + } + + if (configuration.offsetParamName != null && reqQuery.containsKey(configuration.offsetParamName)) { + Integer nextOffsetValue = Integer.parseInt("" + reqQuery.get(configuration.offsetParamName)) + + lastDataSize; + lastRequest.queryParam(q -> q.key(configuration.offsetParamName).value(nextOffsetValue)); + isUpdated = true; + } + } catch (Exception e) { + } + + return isUpdated; + } + public static class Configuration { private String pageParamName; private String offsetParamName; - private String resultPointer; public Configuration pageParamName(String pageParamName) { this.pageParamName = pageParamName; @@ -72,35 +109,5 @@ public Configuration offsetParamName(String offsetParamName) { this.offsetParamName = offsetParamName; return this; } - - public Configuration resultPointer(String resultPointer) { - this.resultPointer = resultPointer; - return this; - } - - private HttpRequest.Builder getNextPageRequest(HttpRequest.Builder builder, GlobalConfiguration config, - String response) throws IOException { - if (pageParamName == null && offsetParamName == null) { - return builder; - } - - Request req = builder.build(config); - - if (pageParamName != null) { - Integer newPageValue = Integer.parseInt((String) req.getQueryParameters().get(pageParamName)) + 1; - return builder.queryParam(q -> q.key(pageParamName).value(newPageValue)); - } - - if (offsetParamName != null) { - String resultArray = CoreHelper.getValueFromJson(resultPointer, response); - List result = CoreHelper.deserialize(resultArray, new TypeReference>() { - }); - Integer nextOffsetValue = Integer.parseInt("" + req.getQueryParameters().get(offsetParamName)) - + (result == null ? 0 : result.size()); - return builder.queryParam(q -> q.key(offsetParamName).value(nextOffsetValue)); - } - - return builder; - } } } diff --git a/src/main/java/io/apimatic/core/types/pagination/PaginatedData.java b/src/main/java/io/apimatic/core/types/pagination/PaginatedData.java index 0825435f..de4f65ba 100644 --- a/src/main/java/io/apimatic/core/types/pagination/PaginatedData.java +++ b/src/main/java/io/apimatic/core/types/pagination/PaginatedData.java @@ -1,15 +1,11 @@ package io.apimatic.core.types.pagination; -import java.io.IOException; import java.util.ArrayList; import java.util.Iterator; import java.util.List; import java.util.NoSuchElementException; -import com.fasterxml.jackson.core.type.TypeReference; - import io.apimatic.core.configurations.http.request.EndpointConfiguration; -import io.apimatic.core.utilities.CoreHelper; import io.apimatic.coreinterfaces.http.response.Response; public abstract class PaginatedData implements Iterator { @@ -18,45 +14,26 @@ public abstract class PaginatedData implements Iterator { private List data = new ArrayList(); - private List responses = new ArrayList(); - - private List endPointConfigs = new ArrayList(); + private Response lastResponse; - private String resultPointer; - - public PaginatedData(final T value, final Response response, final EndpointConfiguration config, - final String resultPointer) { - data.add(value); - endPointConfigs.add(config); - responses.add(response); - this.resultPointer = resultPointer; - } - - public static List extract(Iterator iterator, Class clazz) throws IOException { - List items = new ArrayList(); - - if (!(iterator instanceof PaginatedData)) { - return items; - } + private EndpointConfiguration lastEndpointConfig; - PaginatedData paginatedData = (PaginatedData) iterator; - for (Response res : paginatedData.responses) { - String resultArray = CoreHelper.getValueFromJson(paginatedData.resultPointer, res.getBody()); - List result = CoreHelper.deserialize(resultArray, new TypeReference>() {}); - for (InnerType inner : result) { - items.add(CoreHelper.deserialize(CoreHelper.serialize(inner), clazz)); - } - } - - return items; + public PaginatedData(final List values, final Response response, final EndpointConfiguration config) { + data.addAll(values); + lastResponse = response; + lastEndpointConfig = config; } protected EndpointConfiguration getLastEndpointConfiguration() { - return endPointConfigs.get(currentIndex - 1); + return lastEndpointConfig; } protected Response getLastResponse() { - return responses.get(currentIndex - 1); + return lastResponse; + } + + protected int getDataSize() { + return data.size(); } @Override @@ -70,12 +47,12 @@ public boolean hasNext() { return true; } - PaginatedData data = fetchData(); - if (data != null) { - updateUsing(data); - return true; + PaginatedData newData = fetchData(); + if (newData != null) { + updateData(newData); + return currentIndex < data.size(); } - + return false; } @@ -88,10 +65,10 @@ public T next() { throw new NoSuchElementException("No more data available."); } - private void updateUsing(PaginatedData existing) { - this.data.add(existing.data.get(existing.currentIndex)); - this.responses.add(existing.responses.get(existing.currentIndex)); - this.endPointConfigs.add(existing.endPointConfigs.get(existing.currentIndex)); + protected void updateData(PaginatedData newData) { + this.data.addAll(newData.data); + this.lastResponse = newData.lastResponse; + this.lastEndpointConfig = newData.lastEndpointConfig; } protected abstract PaginatedData fetchData(); From 0db5cea4262417237fd886688b8616122045befe Mon Sep 17 00:00:00 2001 From: Asad Ali <14asadali@gmail.com> Date: Mon, 14 Apr 2025 15:33:22 +0500 Subject: [PATCH 07/20] feat: updated design to return the paginated iterable --- .../io/apimatic/core/ResponseHandler.java | 41 ++----- .../types/pagination/CursorPaginated.java | 76 ------------ .../types/pagination/CursorPagination.java | 38 ++++++ .../core/types/pagination/LinkPaginated.java | 70 ----------- .../core/types/pagination/LinkPagination.java | 32 +++++ .../types/pagination/OffsetPaginated.java | 113 ------------------ .../types/pagination/OffsetPagination.java | 43 +++++++ .../core/types/pagination/PagePagination.java | 43 +++++++ .../core/types/pagination/PaginatedData.java | 78 +++++++++--- .../types/pagination/PaginatedIterable.java | 18 +++ .../pagination/PaginationDataManager.java | 9 ++ .../apimatic/core/utilities/CoreHelper.java | 28 ++++- 12 files changed, 278 insertions(+), 311 deletions(-) delete mode 100644 src/main/java/io/apimatic/core/types/pagination/CursorPaginated.java create mode 100644 src/main/java/io/apimatic/core/types/pagination/CursorPagination.java delete mode 100644 src/main/java/io/apimatic/core/types/pagination/LinkPaginated.java create mode 100644 src/main/java/io/apimatic/core/types/pagination/LinkPagination.java delete mode 100644 src/main/java/io/apimatic/core/types/pagination/OffsetPaginated.java create mode 100644 src/main/java/io/apimatic/core/types/pagination/OffsetPagination.java create mode 100644 src/main/java/io/apimatic/core/types/pagination/PagePagination.java create mode 100644 src/main/java/io/apimatic/core/types/pagination/PaginatedIterable.java create mode 100644 src/main/java/io/apimatic/core/types/pagination/PaginationDataManager.java diff --git a/src/main/java/io/apimatic/core/ResponseHandler.java b/src/main/java/io/apimatic/core/ResponseHandler.java index b85301db..ee59197a 100644 --- a/src/main/java/io/apimatic/core/ResponseHandler.java +++ b/src/main/java/io/apimatic/core/ResponseHandler.java @@ -9,10 +9,9 @@ import io.apimatic.core.configurations.http.request.EndpointConfiguration; import io.apimatic.core.types.CoreApiException; -import io.apimatic.core.types.pagination.CursorPaginated; -import io.apimatic.core.types.pagination.LinkPaginated; -import io.apimatic.core.types.pagination.OffsetPaginated; +import io.apimatic.core.types.pagination.PaginatedData; import io.apimatic.core.types.pagination.PaginationDeserializer; +import io.apimatic.core.types.pagination.PaginationDataManager; import io.apimatic.core.utilities.CoreHelper; import io.apimatic.coreinterfaces.compatibility.CompatibilityFactory; import io.apimatic.coreinterfaces.http.Context; @@ -337,41 +336,15 @@ public Builder apiRespon } /** - * Setter for the deserializer to be used in link based pagination wrapper. + * Setter for the deserializer to be used in pagination wrapper. * * @param deserializer to deserialize the server response. - * @param the type wrapped by linked pagination. + * @param the type wrapped by PaginatedIterable. * @return {@link ResponseHandler.Builder}. */ - public Builder linkPaginatedDeserializer( - Deserializer> deserializer, LinkPaginated.Configuration config) { - this.paginationDeserializer = (res, ec) -> LinkPaginated.Create(deserializer, config, ec, res); - return this; - } - - /** - * Setter for the deserializer to be used in cursor based pagination wrapper. - * - * @param deserializer to deserialize the server response. - * @param the type wrapped by cursor pagination. - * @return {@link ResponseHandler.Builder}. - */ - public Builder cursorPaginatedDeserializer( - Deserializer> deserializer, CursorPaginated.Configuration config) { - this.paginationDeserializer = (res, ec) -> CursorPaginated.Create(deserializer, config, ec, res); - return this; - } - - /** - * Setter for the deserializer to be used in offset based pagination wrapper. - * - * @param deserializer to deserialize the server response. - * @param the type wrapped by offset pagination. - * @return {@link ResponseHandler.Builder}. - */ - public Builder offsetPaginatedDeserializer( - Deserializer> deserializer, OffsetPaginated.Configuration config) { - this.paginationDeserializer = (res, ec) -> OffsetPaginated.Create(deserializer, config, ec, res); + public Builder paginatedDeserializer( + Deserializer> deserializer, final PaginationDataManager... dataManagers) { + this.paginationDeserializer = (res, ec) -> PaginatedData.Create(deserializer, ec, res, dataManagers); return this; } diff --git a/src/main/java/io/apimatic/core/types/pagination/CursorPaginated.java b/src/main/java/io/apimatic/core/types/pagination/CursorPaginated.java deleted file mode 100644 index af08181d..00000000 --- a/src/main/java/io/apimatic/core/types/pagination/CursorPaginated.java +++ /dev/null @@ -1,76 +0,0 @@ -package io.apimatic.core.types.pagination; - -import java.io.IOException; -import java.util.Collections; -import java.util.List; - -import io.apimatic.core.ApiCall; -import io.apimatic.core.ErrorCase; -import io.apimatic.core.configurations.http.request.EndpointConfiguration; -import io.apimatic.core.types.CoreApiException; -import io.apimatic.core.utilities.CoreHelper; -import io.apimatic.coreinterfaces.http.response.Response; -import io.apimatic.coreinterfaces.type.functional.Deserializer; - -public class CursorPaginated extends PaginatedData { - private Deserializer> deserializer; - - private Configuration configuration; - - private CursorPaginated(final Deserializer> deserializer, final Configuration configuration, - final EndpointConfiguration endPointConfig, final Response response) throws IOException { - super(deserializer.apply(response.getBody()), response, endPointConfig); - this.deserializer = deserializer; - this.configuration = configuration; - } - - public static CursorPaginated Create(Deserializer> deserializer, Configuration configuration, - EndpointConfiguration endPointConfig, Response response) throws IOException { - return new CursorPaginated(deserializer, configuration, endPointConfig, response); - } - - @Override - protected PaginatedData fetchData() { - String cursorValue = CoreHelper.getValueFromJson(configuration.nextCursor, - getLastResponse().getBody()); - EndpointConfiguration endpointConfig = getLastEndpointConfiguration(); - - if (cursorValue == null) { - return null; - } - - try { - return new ApiCall.Builder, CoreApiException>().endpointConfiguration(endpointConfig) - .globalConfig(endpointConfig.getGlobalConfiguration()) - .requestBuilder( - endpointConfig - .getRequestBuilder().queryParam(q -> q.key(configuration.cursorQueryParamName) - .value(cursorValue))) - .responseHandler(res -> res - .globalErrorCase(Collections.singletonMap(ErrorCase.DEFAULT, - ErrorCase.setReason(null, - (reason, context) -> new CoreApiException(reason, context)))) - .cursorPaginatedDeserializer(deserializer, configuration)) - .build().execute(); - } catch (Exception e) { - // Ignore exceptions - } - - return null; - } - - public static class Configuration { - private String nextCursor; - private String cursorQueryParamName; - - public Configuration nextCursor(String nextCursor) { - this.nextCursor = nextCursor; - return this; - } - - public Configuration cursorQueryParamName(String cursorQueryParamName) { - this.cursorQueryParamName = cursorQueryParamName; - return this; - } - } -} diff --git a/src/main/java/io/apimatic/core/types/pagination/CursorPagination.java b/src/main/java/io/apimatic/core/types/pagination/CursorPagination.java new file mode 100644 index 00000000..13d71266 --- /dev/null +++ b/src/main/java/io/apimatic/core/types/pagination/CursorPagination.java @@ -0,0 +1,38 @@ +package io.apimatic.core.types.pagination; + +import io.apimatic.core.HttpRequest.Builder; +import io.apimatic.core.utilities.CoreHelper; + +public class CursorPagination implements PaginationDataManager { + private String output; + private String input; + private String cursorValue; + + public CursorPagination output(String output) { + this.output = output; + return this; + } + + public CursorPagination input(String input) { + this.input = input; + return this; + } + + @Override + public boolean isValid(PaginatedData paginatedData) { + String responseBody = paginatedData.getLastResponse().getBody(); + cursorValue = CoreHelper.getValueFromJson(output, responseBody); + + if (cursorValue == null) { + return false; + } + + return true; + } + + @Override + public Builder getNextRequestBuilder(PaginatedData paginatedData) { + Builder lastRequestBuilder = paginatedData.getLastEndpointConfiguration().getRequestBuilder(); + return lastRequestBuilder.queryParam(q -> q.key(input).value(cursorValue)); + } +} diff --git a/src/main/java/io/apimatic/core/types/pagination/LinkPaginated.java b/src/main/java/io/apimatic/core/types/pagination/LinkPaginated.java deleted file mode 100644 index 3f8a088c..00000000 --- a/src/main/java/io/apimatic/core/types/pagination/LinkPaginated.java +++ /dev/null @@ -1,70 +0,0 @@ -package io.apimatic.core.types.pagination; - -import java.io.IOException; -import java.util.Collections; -import java.util.List; - -import io.apimatic.core.ApiCall; -import io.apimatic.core.ErrorCase; -import io.apimatic.core.configurations.http.request.EndpointConfiguration; -import io.apimatic.core.types.CoreApiException; -import io.apimatic.core.utilities.CoreHelper; -import io.apimatic.coreinterfaces.http.response.Response; -import io.apimatic.coreinterfaces.type.functional.Deserializer; - -public class LinkPaginated extends PaginatedData { - private Deserializer> deserializer; - - private Configuration configuration; - - private LinkPaginated(final Deserializer> deserializer, final Configuration configuration, - final EndpointConfiguration endPointConfig, final Response response) throws IOException { - super(deserializer.apply(response.getBody()), response, endPointConfig); - this.deserializer = deserializer; - this.configuration = configuration; - } - - public static LinkPaginated Create(Deserializer> deserializer, Configuration configuration, - EndpointConfiguration endPointConfig, Response response) throws IOException { - return new LinkPaginated(deserializer, configuration, endPointConfig, response); - } - - @Override - protected PaginatedData fetchData() { - String linkValue = CoreHelper.getValueFromJson(configuration.nextPointer, getLastResponse().getBody()); - - if (linkValue == null) { - return null; - } - - EndpointConfiguration endpointConfig = getLastEndpointConfiguration(); - - try { - return new ApiCall.Builder, CoreApiException>() - .endpointConfiguration(endpointConfig) - .globalConfig( - endpointConfig.getGlobalConfiguration()) - .requestBuilder(endpointConfig.getRequestBuilder() - .queryParam(CoreHelper.getQueryParameters(linkValue))) - .responseHandler(res -> res - .globalErrorCase(Collections.singletonMap(ErrorCase.DEFAULT, - ErrorCase.setReason(null, - (reason, context) -> new CoreApiException(reason, context)))) - .linkPaginatedDeserializer(deserializer, configuration)) - .build().execute(); - } catch (Exception e) { - // Ignore exceptions - } - - return null; - } - - public static class Configuration { - private String nextPointer; - - public Configuration nextPointer(String nextPointer) { - this.nextPointer = nextPointer; - return this; - } - } -} diff --git a/src/main/java/io/apimatic/core/types/pagination/LinkPagination.java b/src/main/java/io/apimatic/core/types/pagination/LinkPagination.java new file mode 100644 index 00000000..dc2c07e8 --- /dev/null +++ b/src/main/java/io/apimatic/core/types/pagination/LinkPagination.java @@ -0,0 +1,32 @@ +package io.apimatic.core.types.pagination; + +import io.apimatic.core.HttpRequest.Builder; +import io.apimatic.core.utilities.CoreHelper; + +public class LinkPagination implements PaginationDataManager { + private String next; + private String linkValue; + + public LinkPagination next(String next) { + this.next = next; + return this; + } + + @Override + public boolean isValid(PaginatedData paginatedData) { + String responseBody = paginatedData.getLastResponse().getBody(); + linkValue = CoreHelper.getValueFromJson(next, responseBody); + + if (linkValue == null) { + return false; + } + + return true; + } + + @Override + public Builder getNextRequestBuilder(PaginatedData paginatedData) { + Builder lastRequestBuilder = paginatedData.getLastEndpointConfiguration().getRequestBuilder(); + return lastRequestBuilder.queryParam(CoreHelper.getQueryParameters(linkValue)); + } +} diff --git a/src/main/java/io/apimatic/core/types/pagination/OffsetPaginated.java b/src/main/java/io/apimatic/core/types/pagination/OffsetPaginated.java deleted file mode 100644 index a50d934c..00000000 --- a/src/main/java/io/apimatic/core/types/pagination/OffsetPaginated.java +++ /dev/null @@ -1,113 +0,0 @@ -package io.apimatic.core.types.pagination; - -import java.io.IOException; -import java.util.Collections; -import java.util.List; -import java.util.Map; - -import io.apimatic.core.ApiCall; -import io.apimatic.core.ErrorCase; -import io.apimatic.core.HttpRequest; -import io.apimatic.core.configurations.http.request.EndpointConfiguration; -import io.apimatic.core.types.CoreApiException; -import io.apimatic.coreinterfaces.http.response.Response; -import io.apimatic.coreinterfaces.type.functional.Deserializer; - -public class OffsetPaginated extends PaginatedData { - private Deserializer> deserializer; - - private Configuration configuration; - - private boolean isRequestBuilderUpdated; - - private OffsetPaginated(final Deserializer> deserializer, final Configuration configuration, - final EndpointConfiguration endPointConfig, final Response response) throws IOException { - super(deserializer.apply(response.getBody()), response, endPointConfig); - this.deserializer = deserializer; - this.configuration = configuration; - isRequestBuilderUpdated = updateRequestBuilderForNextCall(endPointConfig, getDataSize()); - } - - public static OffsetPaginated Create(Deserializer> deserializer, Configuration configuration, - EndpointConfiguration endPointConfig, Response response) throws IOException { - return new OffsetPaginated(deserializer, configuration, endPointConfig, response); - } - - @Override - protected PaginatedData fetchData() { - if (!isRequestBuilderUpdated) { - return null; - } - - EndpointConfiguration endpointConfig = getLastEndpointConfiguration(); - try { - return new ApiCall.Builder, CoreApiException>() - .endpointConfiguration( - endpointConfig) - .globalConfig(endpointConfig.getGlobalConfiguration()) - .requestBuilder( - endpointConfig.getRequestBuilder()) - .responseHandler(res -> res - .globalErrorCase(Collections.singletonMap(ErrorCase.DEFAULT, - ErrorCase.setReason(null, - (reason, context) -> new CoreApiException(reason, context)))) - .offsetPaginatedDeserializer(deserializer, configuration)) - .build().execute(); - } catch (Exception e) { - // Ignore exceptions - } - - return null; - } - - @Override - protected void updateData(PaginatedData newData) { - super.updateData(newData); - isRequestBuilderUpdated = ((OffsetPaginated) newData).isRequestBuilderUpdated; - } - - private boolean updateRequestBuilderForNextCall(EndpointConfiguration lastEndpointConfig, int lastDataSize) { - if (configuration.pageParamName == null && configuration.offsetParamName == null) { - return false; - } - - boolean isUpdated = false; - - try { - HttpRequest.Builder lastRequest = lastEndpointConfig.getRequestBuilder(); - Map reqQuery = lastRequest.build(lastEndpointConfig.getGlobalConfiguration()) - .getQueryParameters(); - - if (configuration.pageParamName != null && reqQuery.containsKey(configuration.pageParamName)) { - Integer newPageValue = Integer.parseInt((String) reqQuery.get(configuration.pageParamName)) + 1; - lastRequest.queryParam(q -> q.key(configuration.pageParamName).value(newPageValue)); - isUpdated = true; - } - - if (configuration.offsetParamName != null && reqQuery.containsKey(configuration.offsetParamName)) { - Integer nextOffsetValue = Integer.parseInt("" + reqQuery.get(configuration.offsetParamName)) - + lastDataSize; - lastRequest.queryParam(q -> q.key(configuration.offsetParamName).value(nextOffsetValue)); - isUpdated = true; - } - } catch (Exception e) { - } - - return isUpdated; - } - - public static class Configuration { - private String pageParamName; - private String offsetParamName; - - public Configuration pageParamName(String pageParamName) { - this.pageParamName = pageParamName; - return this; - } - - public Configuration offsetParamName(String offsetParamName) { - this.offsetParamName = offsetParamName; - return this; - } - } -} diff --git a/src/main/java/io/apimatic/core/types/pagination/OffsetPagination.java b/src/main/java/io/apimatic/core/types/pagination/OffsetPagination.java new file mode 100644 index 00000000..1ae686dc --- /dev/null +++ b/src/main/java/io/apimatic/core/types/pagination/OffsetPagination.java @@ -0,0 +1,43 @@ +package io.apimatic.core.types.pagination; + +import java.util.Map; + +import io.apimatic.core.HttpRequest.Builder; + +public class OffsetPagination implements PaginationDataManager { + private String input; + private Builder nextReqBuilder; + + public OffsetPagination input(String input) { + this.input = input; + return this; + } + + @Override + public boolean isValid(PaginatedData paginatedData) { + if (input == null) { + return false; + } + + try { + Builder lastRequest = paginatedData.getLastEndpointConfiguration().getRequestBuilder(); + Map reqQuery = lastRequest + .build(paginatedData.getLastEndpointConfiguration().getGlobalConfiguration()).getQueryParameters(); + + if (input != null && reqQuery.containsKey(input)) { + Integer nextOffsetValue = Integer.parseInt("" + reqQuery.get(input)) + paginatedData.getLastDataSize(); + nextReqBuilder = lastRequest.queryParam(q -> q.key(input).value(nextOffsetValue)); + return true; + } + + } catch (Exception e) { + } + + return false; + } + + @Override + public Builder getNextRequestBuilder(PaginatedData paginatedData) { + return nextReqBuilder; + } +} diff --git a/src/main/java/io/apimatic/core/types/pagination/PagePagination.java b/src/main/java/io/apimatic/core/types/pagination/PagePagination.java new file mode 100644 index 00000000..3f7f6fda --- /dev/null +++ b/src/main/java/io/apimatic/core/types/pagination/PagePagination.java @@ -0,0 +1,43 @@ +package io.apimatic.core.types.pagination; + +import java.util.Map; + +import io.apimatic.core.HttpRequest.Builder; + +public class PagePagination implements PaginationDataManager { + private String input; + private Builder nextReqBuilder; + + public PagePagination input(String input) { + this.input = input; + return this; + } + + @Override + public boolean isValid(PaginatedData paginatedData) { + if (input == null) { + return false; + } + + try { + Builder lastRequest = paginatedData.getLastEndpointConfiguration().getRequestBuilder(); + Map reqQuery = lastRequest.build(paginatedData.getLastEndpointConfiguration().getGlobalConfiguration()) + .getQueryParameters(); + + if (input != null && reqQuery.containsKey(input)) { + Integer newPageValue = Integer.parseInt((String) reqQuery.get(input)) + 1; + nextReqBuilder = lastRequest.queryParam(q -> q.key(input).value(newPageValue)); + return true; + } + + } catch (Exception e) { + } + + return false; + } + + @Override + public Builder getNextRequestBuilder(PaginatedData paginatedData) { + return nextReqBuilder; + } +} diff --git a/src/main/java/io/apimatic/core/types/pagination/PaginatedData.java b/src/main/java/io/apimatic/core/types/pagination/PaginatedData.java index de4f65ba..925d0e54 100644 --- a/src/main/java/io/apimatic/core/types/pagination/PaginatedData.java +++ b/src/main/java/io/apimatic/core/types/pagination/PaginatedData.java @@ -1,39 +1,62 @@ package io.apimatic.core.types.pagination; +import java.io.IOException; import java.util.ArrayList; +import java.util.Collections; import java.util.Iterator; import java.util.List; import java.util.NoSuchElementException; +import io.apimatic.core.ApiCall; +import io.apimatic.core.ErrorCase; import io.apimatic.core.configurations.http.request.EndpointConfiguration; +import io.apimatic.core.types.CoreApiException; import io.apimatic.coreinterfaces.http.response.Response; +import io.apimatic.coreinterfaces.type.functional.Deserializer; -public abstract class PaginatedData implements Iterator { +public class PaginatedData implements Iterator { private int currentIndex = 0; private List data = new ArrayList(); - + private int lastDataSize; private Response lastResponse; - private EndpointConfiguration lastEndpointConfig; + Deserializer> deserializer; + + private PaginationDataManager[] paginationDataManagers; - public PaginatedData(final List values, final Response response, final EndpointConfiguration config) { - data.addAll(values); + private PaginatedData(final Deserializer> deserializer, final Response response, + final EndpointConfiguration config, final PaginationDataManager... dataManagers) throws IOException { + data.addAll(deserializer.apply(response.getBody())); + lastDataSize = data.size(); lastResponse = response; lastEndpointConfig = config; + this.deserializer = deserializer; + paginationDataManagers = dataManagers; } - protected EndpointConfiguration getLastEndpointConfiguration() { + public static PaginatedIterable Create(Deserializer> deserializer, + EndpointConfiguration endPointConfig, Response response, final PaginationDataManager... dataManagers) + throws IOException { + return new PaginatedIterable(new PaginatedData(deserializer, response, endPointConfig, dataManagers)); + } + + public EndpointConfiguration getLastEndpointConfiguration() { return lastEndpointConfig; } - protected Response getLastResponse() { + public Response getLastResponse() { return lastResponse; } - protected int getDataSize() { - return data.size(); + public int getLastDataSize() { + return lastDataSize; + } + + public Iterator reset() { + currentIndex = 0; + return this; } @Override @@ -47,12 +70,12 @@ public boolean hasNext() { return true; } - PaginatedData newData = fetchData(); + PaginatedIterable newData = fetchData(); if (newData != null) { - updateData(newData); + updateData((PaginatedData) newData.iterator()); return currentIndex < data.size(); } - + return false; } @@ -65,11 +88,38 @@ public T next() { throw new NoSuchElementException("No more data available."); } - protected void updateData(PaginatedData newData) { + private void updateData(PaginatedData newData) { this.data.addAll(newData.data); + this.lastDataSize = newData.lastDataSize; this.lastResponse = newData.lastResponse; this.lastEndpointConfig = newData.lastEndpointConfig; } - protected abstract PaginatedData fetchData(); + protected PaginatedIterable fetchData() { + for (PaginationDataManager manager : paginationDataManagers) { + + if (!manager.isValid(this)) { + continue; + } + + EndpointConfiguration endpointConfig = getLastEndpointConfiguration(); + + try { + return new ApiCall.Builder, CoreApiException>() + .endpointConfiguration(endpointConfig) + .globalConfig(endpointConfig.getGlobalConfiguration()) + .requestBuilder(manager.getNextRequestBuilder(this)) + .responseHandler(res -> res + .globalErrorCase(Collections.singletonMap(ErrorCase.DEFAULT, + ErrorCase.setReason(null, + (reason, context) -> new CoreApiException(reason, context)))) + .paginatedDeserializer(deserializer, paginationDataManagers)) + .build().execute(); + } catch (Exception e) { + continue; + } + } + + return null; + } } diff --git a/src/main/java/io/apimatic/core/types/pagination/PaginatedIterable.java b/src/main/java/io/apimatic/core/types/pagination/PaginatedIterable.java new file mode 100644 index 00000000..6f448389 --- /dev/null +++ b/src/main/java/io/apimatic/core/types/pagination/PaginatedIterable.java @@ -0,0 +1,18 @@ +package io.apimatic.core.types.pagination; + +import java.util.Iterator; + +public class PaginatedIterable implements Iterable { + + private PaginatedData iterator; + + public PaginatedIterable(PaginatedData iterator) { + this.iterator = iterator; + } + + @Override + public Iterator iterator() { + return iterator.reset(); + } + +} diff --git a/src/main/java/io/apimatic/core/types/pagination/PaginationDataManager.java b/src/main/java/io/apimatic/core/types/pagination/PaginationDataManager.java new file mode 100644 index 00000000..de601c60 --- /dev/null +++ b/src/main/java/io/apimatic/core/types/pagination/PaginationDataManager.java @@ -0,0 +1,9 @@ +package io.apimatic.core.types.pagination; + +import io.apimatic.core.HttpRequest.Builder; + +public interface PaginationDataManager { + + public abstract boolean isValid(PaginatedData paginatedData); + public abstract Builder getNextRequestBuilder(PaginatedData paginatedData); +} diff --git a/src/main/java/io/apimatic/core/utilities/CoreHelper.java b/src/main/java/io/apimatic/core/utilities/CoreHelper.java index 59479106..f2998b10 100644 --- a/src/main/java/io/apimatic/core/utilities/CoreHelper.java +++ b/src/main/java/io/apimatic/core/utilities/CoreHelper.java @@ -11,6 +11,7 @@ import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.math.BigDecimal; +import java.net.URLDecoder; import java.net.URLEncoder; import java.util.AbstractMap.SimpleEntry; import java.util.ArrayList; @@ -1146,6 +1147,20 @@ public static String tryUrlEncode(String value, boolean spaceAsPercentEncoded) { } } + /** + * Tries URL decoding using UTF-8. + * + * @param value The value to decode. + * @return Decoded value. + */ + public static String tryUrlDecode(String value) { + try { + return URLDecoder.decode(value, "UTF-8"); + } catch (UnsupportedEncodingException e) { + return null; + } + } + /** * Responsible to encode into base64 the username and password * @@ -1576,8 +1591,8 @@ public static Map getQueryParameters(String queryUrl) { return Arrays.stream(getQueryParametersFromUrl(queryUrl).split("&")) .map(param -> param.split("=")) .collect(Collectors.toMap( - pair -> pair[0], - pair -> pair[1] + pair -> tryUrlDecode(pair[0]), + pair -> tryUrlDecode(pair[1]) )); } @@ -1603,8 +1618,13 @@ public static String getValueFromJson(String pointer, String json) { JsonStructure jsonStructure = CoreHelper.createJsonStructure(json); JsonPointer jsonPointer = Json.createPointer(pointer); - - if (jsonStructure == null || !jsonPointer.containsValue(jsonStructure)) { + boolean containsValue = false; + try { + containsValue = jsonPointer.containsValue(jsonStructure); + } catch (Exception e) { + } + + if (jsonStructure == null || !containsValue) { return null; } From 6f7a8fab3fc6bab065be8e60aa00f79670359a22 Mon Sep 17 00:00:00 2001 From: Asad Ali <14asadali@gmail.com> Date: Mon, 14 Apr 2025 18:13:29 +0500 Subject: [PATCH 08/20] use constructors --- .../core/types/pagination/CursorPagination.java | 11 +++-------- .../core/types/pagination/LinkPagination.java | 7 +++---- .../core/types/pagination/OffsetPagination.java | 7 +++---- .../core/types/pagination/PagePagination.java | 7 +++---- .../core/types/pagination/PaginatedData.java | 15 +++++---------- .../core/types/pagination/PaginatedIterable.java | 6 +++--- 6 files changed, 20 insertions(+), 33 deletions(-) diff --git a/src/main/java/io/apimatic/core/types/pagination/CursorPagination.java b/src/main/java/io/apimatic/core/types/pagination/CursorPagination.java index 13d71266..d64af3fc 100644 --- a/src/main/java/io/apimatic/core/types/pagination/CursorPagination.java +++ b/src/main/java/io/apimatic/core/types/pagination/CursorPagination.java @@ -8,19 +8,14 @@ public class CursorPagination implements PaginationDataManager { private String input; private String cursorValue; - public CursorPagination output(String output) { + public CursorPagination(String output, String input) { this.output = output; - return this; - } - - public CursorPagination input(String input) { this.input = input; - return this; } @Override public boolean isValid(PaginatedData paginatedData) { - String responseBody = paginatedData.getLastResponse().getBody(); + String responseBody = paginatedData.getLastResponse(); cursorValue = CoreHelper.getValueFromJson(output, responseBody); if (cursorValue == null) { @@ -32,7 +27,7 @@ public boolean isValid(PaginatedData paginatedData) { @Override public Builder getNextRequestBuilder(PaginatedData paginatedData) { - Builder lastRequestBuilder = paginatedData.getLastEndpointConfiguration().getRequestBuilder(); + Builder lastRequestBuilder = paginatedData.getLastEndpointConfig().getRequestBuilder(); return lastRequestBuilder.queryParam(q -> q.key(input).value(cursorValue)); } } diff --git a/src/main/java/io/apimatic/core/types/pagination/LinkPagination.java b/src/main/java/io/apimatic/core/types/pagination/LinkPagination.java index dc2c07e8..a00f3953 100644 --- a/src/main/java/io/apimatic/core/types/pagination/LinkPagination.java +++ b/src/main/java/io/apimatic/core/types/pagination/LinkPagination.java @@ -7,14 +7,13 @@ public class LinkPagination implements PaginationDataManager { private String next; private String linkValue; - public LinkPagination next(String next) { + public LinkPagination(String next) { this.next = next; - return this; } @Override public boolean isValid(PaginatedData paginatedData) { - String responseBody = paginatedData.getLastResponse().getBody(); + String responseBody = paginatedData.getLastResponse(); linkValue = CoreHelper.getValueFromJson(next, responseBody); if (linkValue == null) { @@ -26,7 +25,7 @@ public boolean isValid(PaginatedData paginatedData) { @Override public Builder getNextRequestBuilder(PaginatedData paginatedData) { - Builder lastRequestBuilder = paginatedData.getLastEndpointConfiguration().getRequestBuilder(); + Builder lastRequestBuilder = paginatedData.getLastEndpointConfig().getRequestBuilder(); return lastRequestBuilder.queryParam(CoreHelper.getQueryParameters(linkValue)); } } diff --git a/src/main/java/io/apimatic/core/types/pagination/OffsetPagination.java b/src/main/java/io/apimatic/core/types/pagination/OffsetPagination.java index 1ae686dc..5ba926f3 100644 --- a/src/main/java/io/apimatic/core/types/pagination/OffsetPagination.java +++ b/src/main/java/io/apimatic/core/types/pagination/OffsetPagination.java @@ -8,9 +8,8 @@ public class OffsetPagination implements PaginationDataManager { private String input; private Builder nextReqBuilder; - public OffsetPagination input(String input) { + public OffsetPagination(String input) { this.input = input; - return this; } @Override @@ -20,9 +19,9 @@ public boolean isValid(PaginatedData paginatedData) { } try { - Builder lastRequest = paginatedData.getLastEndpointConfiguration().getRequestBuilder(); + Builder lastRequest = paginatedData.getLastEndpointConfig().getRequestBuilder(); Map reqQuery = lastRequest - .build(paginatedData.getLastEndpointConfiguration().getGlobalConfiguration()).getQueryParameters(); + .build(paginatedData.getLastEndpointConfig().getGlobalConfiguration()).getQueryParameters(); if (input != null && reqQuery.containsKey(input)) { Integer nextOffsetValue = Integer.parseInt("" + reqQuery.get(input)) + paginatedData.getLastDataSize(); diff --git a/src/main/java/io/apimatic/core/types/pagination/PagePagination.java b/src/main/java/io/apimatic/core/types/pagination/PagePagination.java index 3f7f6fda..b85521e5 100644 --- a/src/main/java/io/apimatic/core/types/pagination/PagePagination.java +++ b/src/main/java/io/apimatic/core/types/pagination/PagePagination.java @@ -8,9 +8,8 @@ public class PagePagination implements PaginationDataManager { private String input; private Builder nextReqBuilder; - public PagePagination input(String input) { + public PagePagination(String input) { this.input = input; - return this; } @Override @@ -20,8 +19,8 @@ public boolean isValid(PaginatedData paginatedData) { } try { - Builder lastRequest = paginatedData.getLastEndpointConfiguration().getRequestBuilder(); - Map reqQuery = lastRequest.build(paginatedData.getLastEndpointConfiguration().getGlobalConfiguration()) + Builder lastRequest = paginatedData.getLastEndpointConfig().getRequestBuilder(); + Map reqQuery = lastRequest.build(paginatedData.getLastEndpointConfig().getGlobalConfiguration()) .getQueryParameters(); if (input != null && reqQuery.containsKey(input)) { diff --git a/src/main/java/io/apimatic/core/types/pagination/PaginatedData.java b/src/main/java/io/apimatic/core/types/pagination/PaginatedData.java index 925d0e54..3aeb6db5 100644 --- a/src/main/java/io/apimatic/core/types/pagination/PaginatedData.java +++ b/src/main/java/io/apimatic/core/types/pagination/PaginatedData.java @@ -42,12 +42,12 @@ public static PaginatedIterable Create(Deserializer> deserializer return new PaginatedIterable(new PaginatedData(deserializer, response, endPointConfig, dataManagers)); } - public EndpointConfiguration getLastEndpointConfiguration() { + public EndpointConfiguration getLastEndpointConfig() { return lastEndpointConfig; } - public Response getLastResponse() { - return lastResponse; + public String getLastResponse() { + return lastResponse.getBody(); } public int getLastDataSize() { @@ -59,11 +59,6 @@ public Iterator reset() { return this; } - @Override - public String toString() { - return "PaginatedData [currentIndex=" + currentIndex + ", data=" + data + "]"; - } - @Override public boolean hasNext() { if (currentIndex < data.size()) { @@ -95,14 +90,14 @@ private void updateData(PaginatedData newData) { this.lastEndpointConfig = newData.lastEndpointConfig; } - protected PaginatedIterable fetchData() { + private PaginatedIterable fetchData() { for (PaginationDataManager manager : paginationDataManagers) { if (!manager.isValid(this)) { continue; } - EndpointConfiguration endpointConfig = getLastEndpointConfiguration(); + EndpointConfiguration endpointConfig = getLastEndpointConfig(); try { return new ApiCall.Builder, CoreApiException>() diff --git a/src/main/java/io/apimatic/core/types/pagination/PaginatedIterable.java b/src/main/java/io/apimatic/core/types/pagination/PaginatedIterable.java index 6f448389..345c9247 100644 --- a/src/main/java/io/apimatic/core/types/pagination/PaginatedIterable.java +++ b/src/main/java/io/apimatic/core/types/pagination/PaginatedIterable.java @@ -4,15 +4,15 @@ public class PaginatedIterable implements Iterable { - private PaginatedData iterator; + private PaginatedData paginatedData; public PaginatedIterable(PaginatedData iterator) { - this.iterator = iterator; + this.paginatedData = iterator; } @Override public Iterator iterator() { - return iterator.reset(); + return paginatedData.reset(); } } From 8d23cbae24d380d7e83741b672410de35f12368a Mon Sep 17 00:00:00 2001 From: Asad Ali <14asadali@gmail.com> Date: Tue, 15 Apr 2025 17:27:24 +0500 Subject: [PATCH 09/20] updated design --- .../io/apimatic/core/ResponseHandler.java | 21 ++- .../types/pagination/CursorPagination.java | 4 +- .../core/types/pagination/LinkPagination.java | 4 +- .../types/pagination/OffsetPagination.java | 4 +- .../core/types/pagination/PagePagination.java | 4 +- .../core/types/pagination/PaginatedData.java | 143 +++++++++++++----- .../types/pagination/PaginatedIterable.java | 18 --- .../pagination/PaginationDataManager.java | 5 +- .../pagination/PaginationDeserializer.java | 4 +- 9 files changed, 127 insertions(+), 80 deletions(-) delete mode 100644 src/main/java/io/apimatic/core/types/pagination/PaginatedIterable.java diff --git a/src/main/java/io/apimatic/core/ResponseHandler.java b/src/main/java/io/apimatic/core/ResponseHandler.java index ee59197a..b30e8694 100644 --- a/src/main/java/io/apimatic/core/ResponseHandler.java +++ b/src/main/java/io/apimatic/core/ResponseHandler.java @@ -4,6 +4,7 @@ import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.function.Function; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -63,7 +64,7 @@ public final class ResponseHandler paginationDeserializer; + private final PaginationDeserializer paginationDeserializer; /** * An instance of {@link ResponseClassType}. @@ -97,7 +98,7 @@ public final class ResponseHandler> localErrorCases, final Map> globalErrorCases, final Deserializer deserializer, - final Deserializer intermediateDeserializer, final PaginationDeserializer paginationDeserializer, + final Deserializer intermediateDeserializer, final PaginationDeserializer paginationDeserializer, final ResponseClassType responseClassType, final ContextInitializer contextInitializer, final boolean isNullify404Enabled, final boolean isNullableResponseType) { this.localErrorCases = localErrorCases; @@ -259,7 +260,7 @@ public static class Builder paginationDeserializer; + private PaginationDeserializer paginationDeserializer; /** * An instance of {@link ResponseClassType}. @@ -338,13 +339,17 @@ public Builder apiRespon /** * Setter for the deserializer to be used in pagination wrapper. * - * @param deserializer to deserialize the server response. - * @param the type wrapped by PaginatedIterable. + * @param converter to deserialize the server response. + * @param the type of the outer page. + * @param the type wrapped by PaginatedIterable. * @return {@link ResponseHandler.Builder}. */ - public Builder paginatedDeserializer( - Deserializer> deserializer, final PaginationDataManager... dataManagers) { - this.paginationDeserializer = (res, ec) -> PaginatedData.Create(deserializer, ec, res, dataManagers); + public Builder paginatedDeserializer(Class pageClass, + Function> converter, Function, ?> returnTypeGetter, + PaginationDataManager... dataManagers) { + this.paginationDeserializer = (res, ec) -> + new PaginatedData(pageClass, converter, res, ec, dataManagers) + .convert(returnTypeGetter); return this; } diff --git a/src/main/java/io/apimatic/core/types/pagination/CursorPagination.java b/src/main/java/io/apimatic/core/types/pagination/CursorPagination.java index d64af3fc..b22c6aa8 100644 --- a/src/main/java/io/apimatic/core/types/pagination/CursorPagination.java +++ b/src/main/java/io/apimatic/core/types/pagination/CursorPagination.java @@ -14,7 +14,7 @@ public CursorPagination(String output, String input) { } @Override - public boolean isValid(PaginatedData paginatedData) { + public boolean isValid(PaginatedData paginatedData) { String responseBody = paginatedData.getLastResponse(); cursorValue = CoreHelper.getValueFromJson(output, responseBody); @@ -26,7 +26,7 @@ public boolean isValid(PaginatedData paginatedData) { } @Override - public Builder getNextRequestBuilder(PaginatedData paginatedData) { + public Builder getNextRequestBuilder(PaginatedData paginatedData) { Builder lastRequestBuilder = paginatedData.getLastEndpointConfig().getRequestBuilder(); return lastRequestBuilder.queryParam(q -> q.key(input).value(cursorValue)); } diff --git a/src/main/java/io/apimatic/core/types/pagination/LinkPagination.java b/src/main/java/io/apimatic/core/types/pagination/LinkPagination.java index a00f3953..6f69c25b 100644 --- a/src/main/java/io/apimatic/core/types/pagination/LinkPagination.java +++ b/src/main/java/io/apimatic/core/types/pagination/LinkPagination.java @@ -12,7 +12,7 @@ public LinkPagination(String next) { } @Override - public boolean isValid(PaginatedData paginatedData) { + public boolean isValid(PaginatedData paginatedData) { String responseBody = paginatedData.getLastResponse(); linkValue = CoreHelper.getValueFromJson(next, responseBody); @@ -24,7 +24,7 @@ public boolean isValid(PaginatedData paginatedData) { } @Override - public Builder getNextRequestBuilder(PaginatedData paginatedData) { + public Builder getNextRequestBuilder(PaginatedData paginatedData) { Builder lastRequestBuilder = paginatedData.getLastEndpointConfig().getRequestBuilder(); return lastRequestBuilder.queryParam(CoreHelper.getQueryParameters(linkValue)); } diff --git a/src/main/java/io/apimatic/core/types/pagination/OffsetPagination.java b/src/main/java/io/apimatic/core/types/pagination/OffsetPagination.java index 5ba926f3..56a904b0 100644 --- a/src/main/java/io/apimatic/core/types/pagination/OffsetPagination.java +++ b/src/main/java/io/apimatic/core/types/pagination/OffsetPagination.java @@ -13,7 +13,7 @@ public OffsetPagination(String input) { } @Override - public boolean isValid(PaginatedData paginatedData) { + public boolean isValid(PaginatedData paginatedData) { if (input == null) { return false; } @@ -36,7 +36,7 @@ public boolean isValid(PaginatedData paginatedData) { } @Override - public Builder getNextRequestBuilder(PaginatedData paginatedData) { + public Builder getNextRequestBuilder(PaginatedData paginatedData) { return nextReqBuilder; } } diff --git a/src/main/java/io/apimatic/core/types/pagination/PagePagination.java b/src/main/java/io/apimatic/core/types/pagination/PagePagination.java index b85521e5..1dd2ce64 100644 --- a/src/main/java/io/apimatic/core/types/pagination/PagePagination.java +++ b/src/main/java/io/apimatic/core/types/pagination/PagePagination.java @@ -13,7 +13,7 @@ public PagePagination(String input) { } @Override - public boolean isValid(PaginatedData paginatedData) { + public boolean isValid(PaginatedData paginatedData) { if (input == null) { return false; } @@ -36,7 +36,7 @@ public boolean isValid(PaginatedData paginatedData) { } @Override - public Builder getNextRequestBuilder(PaginatedData paginatedData) { + public Builder getNextRequestBuilder(PaginatedData paginatedData) { return nextReqBuilder; } } diff --git a/src/main/java/io/apimatic/core/types/pagination/PaginatedData.java b/src/main/java/io/apimatic/core/types/pagination/PaginatedData.java index 3aeb6db5..7af8778a 100644 --- a/src/main/java/io/apimatic/core/types/pagination/PaginatedData.java +++ b/src/main/java/io/apimatic/core/types/pagination/PaginatedData.java @@ -6,40 +6,62 @@ import java.util.Iterator; import java.util.List; import java.util.NoSuchElementException; +import java.util.function.Function; import io.apimatic.core.ApiCall; import io.apimatic.core.ErrorCase; import io.apimatic.core.configurations.http.request.EndpointConfiguration; import io.apimatic.core.types.CoreApiException; +import io.apimatic.core.utilities.CoreHelper; import io.apimatic.coreinterfaces.http.response.Response; -import io.apimatic.coreinterfaces.type.functional.Deserializer; -public class PaginatedData implements Iterator { +public class PaginatedData implements Iterator { private int currentIndex = 0; private List data = new ArrayList(); + private List

pages = new ArrayList

(); private int lastDataSize; private Response lastResponse; private EndpointConfiguration lastEndpointConfig; - Deserializer> deserializer; - - private PaginationDataManager[] paginationDataManagers; - - private PaginatedData(final Deserializer> deserializer, final Response response, - final EndpointConfiguration config, final PaginationDataManager... dataManagers) throws IOException { - data.addAll(deserializer.apply(response.getBody())); - lastDataSize = data.size(); - lastResponse = response; - lastEndpointConfig = config; - this.deserializer = deserializer; - paginationDataManagers = dataManagers; + + private Class

pageClass; + private Function> converter; + private PaginationDataManager[] dataManagers; + + public PaginatedData(PaginatedData paginatedData) { + this.pageClass = paginatedData.pageClass; + this.converter = paginatedData.converter; + this.dataManagers = paginatedData.dataManagers; + + this.lastDataSize = paginatedData.lastDataSize; + this.lastResponse = paginatedData.lastResponse; + this.lastEndpointConfig = paginatedData.lastEndpointConfig; + + this.data.addAll(paginatedData.data); + this.pages.addAll(paginatedData.pages); } - public static PaginatedIterable Create(Deserializer> deserializer, - EndpointConfiguration endPointConfig, Response response, final PaginationDataManager... dataManagers) - throws IOException { - return new PaginatedIterable(new PaginatedData(deserializer, response, endPointConfig, dataManagers)); + public PaginatedData(Class

pageClass, Function> converter, Response response, + EndpointConfiguration config, PaginationDataManager... dataManagers) throws IOException { + this.pageClass = pageClass; + this.converter = converter; + this.dataManagers = dataManagers; + + updateUsing(response, config); + } + + private void updateUsing(Response response, EndpointConfiguration endpointConfig) throws IOException { + String responseBody = response.getBody(); + P page = CoreHelper.deserialize(responseBody, pageClass); + List newData = converter.apply(page); + + this.lastDataSize = newData.size(); + this.lastResponse = response; + this.lastEndpointConfig = endpointConfig; + + this.data.addAll(newData); + this.pages.add(page); } public EndpointConfiguration getLastEndpointConfig() { @@ -54,9 +76,10 @@ public int getLastDataSize() { return lastDataSize; } - public Iterator reset() { - currentIndex = 0; - return this; + public PaginatedData reset() { + if (currentIndex == 0) + return this; + return new PaginatedData(this); } @Override @@ -65,13 +88,9 @@ public boolean hasNext() { return true; } - PaginatedIterable newData = fetchData(); - if (newData != null) { - updateData((PaginatedData) newData.iterator()); - return currentIndex < data.size(); - } + fetchMoreData(); - return false; + return currentIndex < data.size(); } @Override @@ -83,16 +102,54 @@ public T next() { throw new NoSuchElementException("No more data available."); } - private void updateData(PaginatedData newData) { - this.data.addAll(newData.data); - this.lastDataSize = newData.lastDataSize; - this.lastResponse = newData.lastResponse; - this.lastEndpointConfig = newData.lastEndpointConfig; + public Iterator iterator() { + return reset(); + } + + public Iterable

pages() { + PaginatedData data = reset(); + return new Iterable

() { + @Override + public Iterator

iterator() { + return new Iterator

() { + private int currentIndex = 0; + + @Override + public boolean hasNext() { + if (currentIndex < data.pages.size()) { + return true; + } + + while (data.hasNext()) { + if (currentIndex < data.pages.size()) { + return true; + } + data.next(); + } + + return false; + } + + @Override + public P next() { + if (data.hasNext()) { + return data.pages.get(currentIndex++); + } + + throw new NoSuchElementException("No more data available."); + } + }; + } + }; + } + + public Object convert(Function, ?> returnTypeGetter) { + return returnTypeGetter.apply(this); } - private PaginatedIterable fetchData() { - for (PaginationDataManager manager : paginationDataManagers) { - + private void fetchMoreData() { + for (PaginationDataManager manager : dataManagers) { + if (!manager.isValid(this)) { continue; } @@ -100,21 +157,23 @@ private PaginatedIterable fetchData() { EndpointConfiguration endpointConfig = getLastEndpointConfig(); try { - return new ApiCall.Builder, CoreApiException>() - .endpointConfiguration(endpointConfig) - .globalConfig(endpointConfig.getGlobalConfiguration()) - .requestBuilder(manager.getNextRequestBuilder(this)) + PaginatedData result = new ApiCall.Builder, CoreApiException>() + .endpointConfiguration(endpointConfig).globalConfig( + endpointConfig.getGlobalConfiguration()) + .requestBuilder( + manager.getNextRequestBuilder(this)) .responseHandler(res -> res .globalErrorCase(Collections.singletonMap(ErrorCase.DEFAULT, ErrorCase.setReason(null, (reason, context) -> new CoreApiException(reason, context)))) - .paginatedDeserializer(deserializer, paginationDataManagers)) + .paginatedDeserializer(pageClass, converter, r -> r, dataManagers)) .build().execute(); + + updateUsing(result.lastResponse, result.lastEndpointConfig); + return; } catch (Exception e) { continue; } } - - return null; } } diff --git a/src/main/java/io/apimatic/core/types/pagination/PaginatedIterable.java b/src/main/java/io/apimatic/core/types/pagination/PaginatedIterable.java deleted file mode 100644 index 345c9247..00000000 --- a/src/main/java/io/apimatic/core/types/pagination/PaginatedIterable.java +++ /dev/null @@ -1,18 +0,0 @@ -package io.apimatic.core.types.pagination; - -import java.util.Iterator; - -public class PaginatedIterable implements Iterable { - - private PaginatedData paginatedData; - - public PaginatedIterable(PaginatedData iterator) { - this.paginatedData = iterator; - } - - @Override - public Iterator iterator() { - return paginatedData.reset(); - } - -} diff --git a/src/main/java/io/apimatic/core/types/pagination/PaginationDataManager.java b/src/main/java/io/apimatic/core/types/pagination/PaginationDataManager.java index de601c60..def8cbbe 100644 --- a/src/main/java/io/apimatic/core/types/pagination/PaginationDataManager.java +++ b/src/main/java/io/apimatic/core/types/pagination/PaginationDataManager.java @@ -4,6 +4,7 @@ public interface PaginationDataManager { - public abstract boolean isValid(PaginatedData paginatedData); - public abstract Builder getNextRequestBuilder(PaginatedData paginatedData); + public abstract boolean isValid(PaginatedData paginatedData); + + public abstract Builder getNextRequestBuilder(PaginatedData paginatedData); } diff --git a/src/main/java/io/apimatic/core/types/pagination/PaginationDeserializer.java b/src/main/java/io/apimatic/core/types/pagination/PaginationDeserializer.java index 4331bbea..299b1c87 100644 --- a/src/main/java/io/apimatic/core/types/pagination/PaginationDeserializer.java +++ b/src/main/java/io/apimatic/core/types/pagination/PaginationDeserializer.java @@ -11,7 +11,7 @@ * @param The type of the response to deserialize into. */ @FunctionalInterface -public interface PaginationDeserializer { +public interface PaginationDeserializer { /** * Apply the deserialization function and returns the ResponseType response. * @@ -20,5 +20,5 @@ public interface PaginationDeserializer { * @return The deserialized data. * @throws IOException Exception to be thrown while applying the function. */ - T apply(Response response, EndpointConfiguration config) throws IOException; + Object apply(Response response, EndpointConfiguration config) throws IOException; } \ No newline at end of file From e066884db8c8a4553589528e2c259e42f030976a Mon Sep 17 00:00:00 2001 From: Asad Ali <14asadali@gmail.com> Date: Thu, 17 Apr 2025 14:15:10 +0500 Subject: [PATCH 10/20] Adds end to end test --- .../core/types/pagination/PaginatedData.java | 1 + src/test/java/apimatic/core/EndToEndTest.java | 86 +++++++++++++++++-- 2 files changed, 78 insertions(+), 9 deletions(-) diff --git a/src/main/java/io/apimatic/core/types/pagination/PaginatedData.java b/src/main/java/io/apimatic/core/types/pagination/PaginatedData.java index 7af8778a..a2aa6d4a 100644 --- a/src/main/java/io/apimatic/core/types/pagination/PaginatedData.java +++ b/src/main/java/io/apimatic/core/types/pagination/PaginatedData.java @@ -166,6 +166,7 @@ private void fetchMoreData() { .globalErrorCase(Collections.singletonMap(ErrorCase.DEFAULT, ErrorCase.setReason(null, (reason, context) -> new CoreApiException(reason, context)))) + .nullify404(false) .paginatedDeserializer(pageClass, converter, r -> r, dataManagers)) .build().execute(); diff --git a/src/test/java/apimatic/core/EndToEndTest.java b/src/test/java/apimatic/core/EndToEndTest.java index 58c7271f..51c6e068 100644 --- a/src/test/java/apimatic/core/EndToEndTest.java +++ b/src/test/java/apimatic/core/EndToEndTest.java @@ -9,8 +9,10 @@ import static org.mockito.Mockito.when; import java.io.IOException; +import java.util.Arrays; import java.util.Collections; import java.util.HashMap; +import java.util.List; import java.util.Map; import org.junit.Before; @@ -22,10 +24,13 @@ import apimatic.core.exceptions.GlobalTestException; import apimatic.core.mocks.MockCoreConfig; +import apimatic.core.type.pagination.RecordPage; import io.apimatic.core.ApiCall; import io.apimatic.core.ErrorCase; import io.apimatic.core.GlobalConfiguration; import io.apimatic.core.types.CoreApiException; +import io.apimatic.core.types.pagination.LinkPagination; +import io.apimatic.core.types.pagination.PaginatedData; import io.apimatic.core.utilities.CoreHelper; import io.apimatic.coreinterfaces.http.Callback; import io.apimatic.coreinterfaces.http.Context; @@ -155,6 +160,38 @@ public void testEndToEndSyncCall() throws IOException, CoreApiException { String actual = getApiCall().execute(); assertEquals(actual, expected); } + + @Test + public void testPaginationData() throws IOException, CoreApiException { + PaginatedData paginatedData = getPaginatedApiCall().execute(); + + int index = 0; + List expectedData = Arrays.asList("apple", "mango", "orange", "potato", "carrot", "tomato"); + while (paginatedData.hasNext()) { + String d = paginatedData.next(); + assertEquals(expectedData.get(index), d); + index++; + } + + RecordPage expectedPage1 = new RecordPage(); + expectedPage1.data = Arrays.asList("apple", "mango", "orange"); + expectedPage1.pageInfo = "fruits"; + expectedPage1.nextLink = "https://localhost:3000/path?page=2"; + + RecordPage expectedPage2 = new RecordPage(); + expectedPage2.data = Arrays.asList("potato", "carrot", "tomato"); + expectedPage2.pageInfo = "vegitables"; + expectedPage2.nextLink = null; + + int pageNum = 0; + List expectedPages = Arrays.asList(expectedPage1, expectedPage2); + for (RecordPage p : paginatedData.pages()) { + assertEquals(expectedPages.get(pageNum).data, p.data); + assertEquals(expectedPages.get(pageNum).pageInfo, p.pageInfo); + assertEquals(expectedPages.get(pageNum).nextLink, p.nextLink); + pageNum++; + } + } /** @@ -398,7 +435,7 @@ public void testGlobalErrorTemplate5XX() throws IOException, CoreApiException { private ApiCall getApiCall() throws IOException { when(response.getBody()).thenReturn("\"Turtle\""); - return new ApiCall.Builder().globalConfig(getGlobalConfig()) + return new ApiCall.Builder().globalConfig(getGlobalConfig(callback)) .requestBuilder(requestBuilder -> requestBuilder.server("https://localhost:3000") .path("/v2/bank-accounts") .queryParam(param -> param.key("cursor").value("cursor").isRequired(false)) @@ -417,11 +454,45 @@ private ApiCall getApiCall() throws IOException { .build(); } + private ApiCall, CoreApiException> getPaginatedApiCall() throws IOException { + when(response.getBody()).thenReturn("{\"data\":[\"apple\",\"mango\",\"orange\"],\"page_info\":\"fruits\"," + + "\"next_link\":\"https://localhost:3000/path?page=2\"}"); + Callback callback = new Callback() { + private int callNumber = 1; + @Override + public void onBeforeRequest(Request request) { + if (callNumber > 1) { + when(response.getBody()).thenReturn("{\"data\":[\"potato\",\"carrot\",\"tomato\"],\"page_info\":\"vegitables\"}"); + } + callNumber++; + } + @Override + public void onAfterResponse(Context context) { + // TODO Auto-generated method stub + } + }; + return new ApiCall.Builder, CoreApiException>().globalConfig(getGlobalConfig(callback)) + .requestBuilder(requestBuilder -> requestBuilder.server("https://localhost:3000") + .path("/path") + .queryParam(param -> param.key("cursor").value("cursor").isRequired(false)) + .formParam(param -> param.key("limit").value("limit").isRequired(false)) + .headerParam(param -> param.key("accept").value("application/json")) + .httpMethod(Method.GET)) + .responseHandler(responseHandler -> responseHandler + .paginatedDeserializer(RecordPage.class, t -> t.data, r -> r, new LinkPagination("/next_link")) + .nullify404(false) + .globalErrorCase(Collections.emptyMap())) + .endpointConfiguration( + param -> param.arraySerializationFormat(ArraySerializationFormat.INDEXED) + .hasBinaryResponse(false).retryOption(RetryOption.DEFAULT)) + .build(); + } + private ApiCall getApiCallLocalErrorTemplate(String responseString, int statusCode) throws IOException { when(response.getBody()).thenReturn(responseString); when(response.getStatusCode()).thenReturn(statusCode); - return new ApiCall.Builder().globalConfig(getGlobalConfig()) + return new ApiCall.Builder().globalConfig(getGlobalConfig(callback)) .requestBuilder(requestBuilder -> requestBuilder.server("https://localhost:3000") .path("/v2/bank-accounts") .queryParam(param -> param.key("cursor").value("cursor").isRequired(false)) @@ -451,7 +522,7 @@ private ApiCall getApiCallGlobalErrorTemplate(String r int statusCode) throws IOException { when(response.getBody()).thenReturn(responseString); when(response.getStatusCode()).thenReturn(statusCode); - return new ApiCall.Builder().globalConfig(getGlobalConfig()) + return new ApiCall.Builder().globalConfig(getGlobalConfig(callback)) .requestBuilder(requestBuilder -> requestBuilder.server("https://localhost:3000") .path("/v2/bank-accounts") .queryParam(param -> param.key("cursor").value("cursor").isRequired(false)) @@ -478,7 +549,7 @@ private ApiCall getApiCallGlobalErrorTemplateWithHeade when(response.getHeaders()).thenReturn(getHttpHeaders()); when(getHttpHeaders().has("content-type")).thenReturn(true); when(getHttpHeaders().value("content-type")).thenReturn("application/json"); - return new ApiCall.Builder().globalConfig(getGlobalConfig()) + return new ApiCall.Builder().globalConfig(getGlobalConfig(callback)) .requestBuilder(requestBuilder -> requestBuilder.server("https://localhost:3000") .path("/v2/bank-accounts") .queryParam(param -> param.key("cursor").value("cursor").isRequired(false)) @@ -497,7 +568,7 @@ private ApiCall getApiCallGlobalErrorTemplateWithHeade .build(); } - private GlobalConfiguration getGlobalConfig() { + private GlobalConfiguration getGlobalConfig(Callback callback) { String userAgent = "APIMATIC 3.0"; GlobalConfiguration globalConfig = new GlobalConfiguration.Builder() .authentication(Collections.emptyMap()) @@ -517,12 +588,9 @@ private void prepareStub() throws IOException { when(httpClient.execute(any(Request.class), any(CoreEndpointConfiguration.class))) .thenReturn(response); when(getCompatibilityFactory().createHttpHeaders(anyMap())).thenReturn(getHttpHeaders()); - when(getCompatibilityFactory().createHttpRequest(any(Method.class), - any(StringBuilder.class), any(HttpHeaders.class), anyMap(), anyList())) - .thenReturn(coreHttpRequest); when(getCompatibilityFactory().createHttpRequest(any(Method.class), nullable(StringBuilder.class), nullable(HttpHeaders.class), anyMap(), anyList())) - .thenReturn(coreHttpRequest); + .thenReturn(coreHttpRequest); when(getCompatibilityFactory().createHttpContext(coreHttpRequest, response)) .thenReturn(context); when(context.getResponse()).thenReturn(response); From d1c6e2f0d3c5c104b8e264ef591fb9b64b471792 Mon Sep 17 00:00:00 2001 From: Asad Ali <14asadali@gmail.com> Date: Thu, 17 Apr 2025 14:16:14 +0500 Subject: [PATCH 11/20] add record page --- .../core/type/pagination/RecordPage.java | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 src/test/java/apimatic/core/type/pagination/RecordPage.java diff --git a/src/test/java/apimatic/core/type/pagination/RecordPage.java b/src/test/java/apimatic/core/type/pagination/RecordPage.java new file mode 100644 index 00000000..7b259d8c --- /dev/null +++ b/src/test/java/apimatic/core/type/pagination/RecordPage.java @@ -0,0 +1,17 @@ +package apimatic.core.type.pagination; + +import java.util.List; + +import com.fasterxml.jackson.annotation.JsonSetter; + +public class RecordPage { + @JsonSetter("data") + public List data; + + @JsonSetter("page_info") + public String pageInfo; + + @JsonSetter("next_link") + public String nextLink; + +} From bab73f3cd1f34b199ccb3eb79586d022b834d453 Mon Sep 17 00:00:00 2001 From: Asad Ali <14asadali@gmail.com> Date: Fri, 18 Apr 2025 17:56:21 +0500 Subject: [PATCH 12/20] updated each pagination impl and added tests for page and offset pagination types --- .../java/io/apimatic/core/HttpRequest.java | 127 +++++++++--- .../types/pagination/CursorPagination.java | 21 +- .../core/types/pagination/LinkPagination.java | 15 +- .../types/pagination/OffsetPagination.java | 27 +-- .../core/types/pagination/PagePagination.java | 31 ++- .../core/types/pagination/PaginatedData.java | 34 ++-- .../pagination/PaginationDataManager.java | 2 +- .../apimatic/core/utilities/CoreHelper.java | 90 ++++++++- src/test/java/apimatic/core/EndToEndTest.java | 42 +++- .../apimatic/core/mocks/HttpHeadersMock.java | 11 ++ .../type/pagination/CursorPaginationTest.java | 42 ++++ .../type/pagination/LinkPaginationTest.java | 42 ++++ .../type/pagination/OffsetPaginationTest.java | 182 ++++++++++++++++++ .../type/pagination/PagePaginationTest.java | 174 +++++++++++++++++ 14 files changed, 740 insertions(+), 100 deletions(-) create mode 100644 src/test/java/apimatic/core/type/pagination/CursorPaginationTest.java create mode 100644 src/test/java/apimatic/core/type/pagination/LinkPaginationTest.java create mode 100644 src/test/java/apimatic/core/type/pagination/OffsetPaginationTest.java create mode 100644 src/test/java/apimatic/core/type/pagination/PagePaginationTest.java diff --git a/src/main/java/io/apimatic/core/HttpRequest.java b/src/main/java/io/apimatic/core/HttpRequest.java index dc54a6e9..eb0fc9f4 100644 --- a/src/main/java/io/apimatic/core/HttpRequest.java +++ b/src/main/java/io/apimatic/core/HttpRequest.java @@ -3,13 +3,17 @@ import java.io.IOException; import java.util.AbstractMap.SimpleEntry; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; +import java.util.Map.Entry; import java.util.Set; import java.util.function.Consumer; +import java.util.function.Function; + import io.apimatic.core.authentication.AuthBuilder; import io.apimatic.core.exceptions.AuthValidationException; import io.apimatic.core.types.http.request.MultipartFileWrapper; @@ -84,10 +88,12 @@ private HttpRequest(final GlobalConfiguration coreConfig, final String server, // Creating a basic request to provide it to auth instances Request request = buildBasicRequest(httpMethod, requestHeaders); - applyAuthentication(request, authentication); - // include auth query parameters in request query params to have them in the query url - if (request.getQueryParameters() != null) { - queryParams.putAll(request.getQueryParameters()); + if (request != null) { + applyAuthentication(request, authentication); + // include auth query parameters in request query params to have them in the query url + if (request.getQueryParameters() != null) { + queryParams.putAll(request.getQueryParameters()); + } } queryUrlBuilder = getStringBuilder(server, path, queryParams, arraySerializationFormat); @@ -284,7 +290,7 @@ public static class Builder { /** * A map of header parameters. */ - private Map> headerParams = new HashMap<>(); + private Map> headerParams = new HashMap<>(); /** * A set of {@link Parameter}. @@ -322,6 +328,62 @@ public static class Builder { */ private Parameter.Builder parameterBuilder = new Parameter.Builder(); + @SuppressWarnings("unchecked") + public Builder updateByReference(String pointer, Function setter) { + if (pointer == null) { + return this; + } + + String[] pointerParts = pointer.split("#"); + String prefix = pointerParts[0]; + String point = pointerParts.length > 1 ? pointerParts[1] : ""; + + switch (prefix) { + case "$request.path": + Map simplifiedPath = new HashMap<>(); + for (Map.Entry> entry : templateParams.entrySet()) { + simplifiedPath.put(entry.getKey(), entry.getValue().getKey()); + } + + simplifiedPath = CoreHelper.updateValueByPointer(simplifiedPath, point, setter); + + for (Map.Entry entry : simplifiedPath.entrySet()) { + // Preserve the original boolean if it exists, otherwise set default (e.g., false) + Boolean originalFlag = templateParams.containsKey(entry.getKey()) + ? templateParams.get(entry.getKey()).getValue() + : false; + templateParams.put(entry.getKey(), new SimpleEntry<>(entry.getValue(), originalFlag)); + } + return this; + case "$request.query": + queryParams = CoreHelper.updateValueByPointer(queryParams, point, setter); + return this; + case "$request.headers": + Map simplifiedHeaders = new HashMap<>(); + for (Entry> entry : headerParams.entrySet()) { + if (entry.getValue().size() == 1) { + simplifiedHeaders.put(entry.getKey(), entry.getValue().get(0)); + } else { + simplifiedHeaders.put(entry.getKey(), entry.getValue()); + } + } + + simplifiedHeaders = CoreHelper.updateValueByPointer(simplifiedHeaders, point, setter); + + for (Map.Entry entry : simplifiedHeaders.entrySet()) { + if (entry.getValue() instanceof List) { + headerParams.put(entry.getKey(), (List)entry.getValue()); + } else { + headerParams.put(entry.getKey(), Arrays.asList(entry.getValue())); + } + } + + return this; + } + + return this; + } + /** * Base uri server address. * @param server the base uri address. @@ -414,32 +476,17 @@ public Builder headerParam(Consumer action) { Parameter httpHeaderParameter = parameterBuilder.build(); httpHeaderParameter.validate(); String key = httpHeaderParameter.getKey(); - String value = getSerializedHeaderValue(httpHeaderParameter.getValue()); if (headerParams.containsKey(key)) { - headerParams.get(key).add(value); + headerParams.get(key).add(httpHeaderParameter.getValue()); } else { - List headerValues = new ArrayList(); - headerValues.add(value); + List headerValues = new ArrayList(); + headerValues.add(httpHeaderParameter.getValue()); headerParams.put(key, headerValues); } return this; } - private static String getSerializedHeaderValue(Object obj) { - if (obj == null) { - return null; - } - - if (CoreHelper.isTypeCombinatorStringCase(obj) - || CoreHelper.isTypeCombinatorDateTimeCase(obj) - || obj instanceof String) { - return obj.toString(); - } - - return CoreHelper.trySerialize(obj); - } - /** * To configure the form parameter. * @param action the form parameter {@link Consumer}. @@ -504,6 +551,38 @@ public Builder arraySerializationFormat(ArraySerializationFormat arraySerializat this.arraySerializationFormat = arraySerializationFormat; return this; } + + private Map> getHeaderParams() { + Map> converted = new HashMap<>(); + + for (Map.Entry> entry : headerParams.entrySet()) { + String key = entry.getKey(); + List originalList = entry.getValue(); + + List serializedList = new ArrayList<>(); + for (Object obj : originalList) { + serializedList.add(getSerializedHeaderValue(obj)); + } + + converted.put(key, serializedList); + } + + return converted; + } + + private static String getSerializedHeaderValue(Object obj) { + if (obj == null) { + return null; + } + + if (CoreHelper.isTypeCombinatorStringCase(obj) + || CoreHelper.isTypeCombinatorDateTimeCase(obj) + || obj instanceof String) { + return obj.toString(); + } + + return CoreHelper.trySerialize(obj); + } /** * Initialise the CoreHttpRequest. @@ -515,7 +594,7 @@ public Request build(GlobalConfiguration coreConfig) throws IOException { Authentication authentication = authBuilder.build(coreConfig.getAuthentications()); HttpRequest coreRequest = new HttpRequest(coreConfig, server, path, httpMethod, authentication, - queryParams, templateParams, headerParams, formParams, formParamaters, + queryParams, templateParams, getHeaderParams(), formParams, formParamaters, body, bodySerializer, bodyParameters, arraySerializationFormat); Request coreHttpRequest = coreRequest.getCoreHttpRequest(); diff --git a/src/main/java/io/apimatic/core/types/pagination/CursorPagination.java b/src/main/java/io/apimatic/core/types/pagination/CursorPagination.java index b22c6aa8..805c6089 100644 --- a/src/main/java/io/apimatic/core/types/pagination/CursorPagination.java +++ b/src/main/java/io/apimatic/core/types/pagination/CursorPagination.java @@ -6,7 +6,7 @@ public class CursorPagination implements PaginationDataManager { private String output; private String input; - private String cursorValue; + private Builder nextReqBuilder; public CursorPagination(String output, String input) { this.output = output; @@ -15,19 +15,26 @@ public CursorPagination(String output, String input) { @Override public boolean isValid(PaginatedData paginatedData) { - String responseBody = paginatedData.getLastResponse(); - cursorValue = CoreHelper.getValueFromJson(output, responseBody); + nextReqBuilder = paginatedData.getLastRequestBuilder(); + + String cursorValue = CoreHelper.resolveResponsePointer(output, paginatedData.getLastResponseBody(), + paginatedData.getLastResponseHeaders()); if (cursorValue == null) { return false; } - return true; + final boolean[] isUpdated = { false }; + nextReqBuilder.updateByReference(input, old -> { + isUpdated[0] = true; + return cursorValue; + }); + + return isUpdated[0]; } @Override - public Builder getNextRequestBuilder(PaginatedData paginatedData) { - Builder lastRequestBuilder = paginatedData.getLastEndpointConfig().getRequestBuilder(); - return lastRequestBuilder.queryParam(q -> q.key(input).value(cursorValue)); + public Builder getNextRequestBuilder() { + return nextReqBuilder; } } diff --git a/src/main/java/io/apimatic/core/types/pagination/LinkPagination.java b/src/main/java/io/apimatic/core/types/pagination/LinkPagination.java index 6f69c25b..e55d74e8 100644 --- a/src/main/java/io/apimatic/core/types/pagination/LinkPagination.java +++ b/src/main/java/io/apimatic/core/types/pagination/LinkPagination.java @@ -5,7 +5,7 @@ public class LinkPagination implements PaginationDataManager { private String next; - private String linkValue; + private Builder nextReqBuilder; public LinkPagination(String next) { this.next = next; @@ -13,19 +13,22 @@ public LinkPagination(String next) { @Override public boolean isValid(PaginatedData paginatedData) { - String responseBody = paginatedData.getLastResponse(); - linkValue = CoreHelper.getValueFromJson(next, responseBody); + nextReqBuilder = paginatedData.getLastRequestBuilder(); + + String linkValue = CoreHelper.resolveResponsePointer(next, paginatedData.getLastResponseBody(), + paginatedData.getLastResponseHeaders()); if (linkValue == null) { return false; } + nextReqBuilder.queryParam(CoreHelper.getQueryParameters(linkValue)); + return true; } @Override - public Builder getNextRequestBuilder(PaginatedData paginatedData) { - Builder lastRequestBuilder = paginatedData.getLastEndpointConfig().getRequestBuilder(); - return lastRequestBuilder.queryParam(CoreHelper.getQueryParameters(linkValue)); + public Builder getNextRequestBuilder() { + return nextReqBuilder; } } diff --git a/src/main/java/io/apimatic/core/types/pagination/OffsetPagination.java b/src/main/java/io/apimatic/core/types/pagination/OffsetPagination.java index 56a904b0..1ff9d550 100644 --- a/src/main/java/io/apimatic/core/types/pagination/OffsetPagination.java +++ b/src/main/java/io/apimatic/core/types/pagination/OffsetPagination.java @@ -1,7 +1,5 @@ package io.apimatic.core.types.pagination; -import java.util.Map; - import io.apimatic.core.HttpRequest.Builder; public class OffsetPagination implements PaginationDataManager { @@ -14,29 +12,24 @@ public OffsetPagination(String input) { @Override public boolean isValid(PaginatedData paginatedData) { + nextReqBuilder = paginatedData.getLastRequestBuilder(); + if (input == null) { return false; } - try { - Builder lastRequest = paginatedData.getLastEndpointConfig().getRequestBuilder(); - Map reqQuery = lastRequest - .build(paginatedData.getLastEndpointConfig().getGlobalConfiguration()).getQueryParameters(); - - if (input != null && reqQuery.containsKey(input)) { - Integer nextOffsetValue = Integer.parseInt("" + reqQuery.get(input)) + paginatedData.getLastDataSize(); - nextReqBuilder = lastRequest.queryParam(q -> q.key(input).value(nextOffsetValue)); - return true; - } - - } catch (Exception e) { - } + final boolean[] isUpdated = { false }; + nextReqBuilder.updateByReference(input, old -> { + int newValue = Integer.parseInt("" + old) + paginatedData.getLastDataSize(); + isUpdated[0] = true; + return newValue; + }); - return false; + return isUpdated[0]; } @Override - public Builder getNextRequestBuilder(PaginatedData paginatedData) { + public Builder getNextRequestBuilder() { return nextReqBuilder; } } diff --git a/src/main/java/io/apimatic/core/types/pagination/PagePagination.java b/src/main/java/io/apimatic/core/types/pagination/PagePagination.java index 1dd2ce64..4a23a16d 100644 --- a/src/main/java/io/apimatic/core/types/pagination/PagePagination.java +++ b/src/main/java/io/apimatic/core/types/pagination/PagePagination.java @@ -1,10 +1,8 @@ package io.apimatic.core.types.pagination; -import java.util.Map; - import io.apimatic.core.HttpRequest.Builder; -public class PagePagination implements PaginationDataManager { +public class PagePagination implements PaginationDataManager { private String input; private Builder nextReqBuilder; @@ -14,29 +12,24 @@ public PagePagination(String input) { @Override public boolean isValid(PaginatedData paginatedData) { + nextReqBuilder = paginatedData.getLastRequestBuilder(); + if (input == null) { return false; } - try { - Builder lastRequest = paginatedData.getLastEndpointConfig().getRequestBuilder(); - Map reqQuery = lastRequest.build(paginatedData.getLastEndpointConfig().getGlobalConfiguration()) - .getQueryParameters(); - - if (input != null && reqQuery.containsKey(input)) { - Integer newPageValue = Integer.parseInt((String) reqQuery.get(input)) + 1; - nextReqBuilder = lastRequest.queryParam(q -> q.key(input).value(newPageValue)); - return true; - } - - } catch (Exception e) { - } - - return false; + final boolean[] isUpdated = { false }; + nextReqBuilder.updateByReference(input, old -> { + int newValue = Integer.parseInt("" + old) + 1; + isUpdated[0] = true; + return newValue; + }); + + return isUpdated[0]; } @Override - public Builder getNextRequestBuilder(PaginatedData paginatedData) { + public Builder getNextRequestBuilder() { return nextReqBuilder; } } diff --git a/src/main/java/io/apimatic/core/types/pagination/PaginatedData.java b/src/main/java/io/apimatic/core/types/pagination/PaginatedData.java index a2aa6d4a..eb5272e3 100644 --- a/src/main/java/io/apimatic/core/types/pagination/PaginatedData.java +++ b/src/main/java/io/apimatic/core/types/pagination/PaginatedData.java @@ -10,6 +10,7 @@ import io.apimatic.core.ApiCall; import io.apimatic.core.ErrorCase; +import io.apimatic.core.HttpRequest; import io.apimatic.core.configurations.http.request.EndpointConfiguration; import io.apimatic.core.types.CoreApiException; import io.apimatic.core.utilities.CoreHelper; @@ -24,7 +25,7 @@ public class PaginatedData implements Iterator { private int lastDataSize; private Response lastResponse; private EndpointConfiguration lastEndpointConfig; - + private Class

pageClass; private Function> converter; private PaginationDataManager[] dataManagers; @@ -37,7 +38,7 @@ public PaginatedData(PaginatedData paginatedData) { this.lastDataSize = paginatedData.lastDataSize; this.lastResponse = paginatedData.lastResponse; this.lastEndpointConfig = paginatedData.lastEndpointConfig; - + this.data.addAll(paginatedData.data); this.pages.addAll(paginatedData.pages); } @@ -55,23 +56,27 @@ private void updateUsing(Response response, EndpointConfiguration endpointConfig String responseBody = response.getBody(); P page = CoreHelper.deserialize(responseBody, pageClass); List newData = converter.apply(page); - + this.lastDataSize = newData.size(); this.lastResponse = response; this.lastEndpointConfig = endpointConfig; - + this.data.addAll(newData); this.pages.add(page); } - public EndpointConfiguration getLastEndpointConfig() { - return lastEndpointConfig; + public HttpRequest.Builder getLastRequestBuilder() { + return lastEndpointConfig.getRequestBuilder(); } - public String getLastResponse() { + public String getLastResponseBody() { return lastResponse.getBody(); } + public String getLastResponseHeaders() { + return CoreHelper.trySerialize(lastResponse.getHeaders().asSimpleMap()); + } + public int getLastDataSize() { return lastDataSize; } @@ -113,13 +118,13 @@ public Iterable

pages() { public Iterator

iterator() { return new Iterator

() { private int currentIndex = 0; - + @Override public boolean hasNext() { if (currentIndex < data.pages.size()) { return true; } - + while (data.hasNext()) { if (currentIndex < data.pages.size()) { return true; @@ -154,20 +159,17 @@ private void fetchMoreData() { continue; } - EndpointConfiguration endpointConfig = getLastEndpointConfig(); - try { PaginatedData result = new ApiCall.Builder, CoreApiException>() - .endpointConfiguration(endpointConfig).globalConfig( - endpointConfig.getGlobalConfiguration()) + .endpointConfiguration(lastEndpointConfig).globalConfig( + lastEndpointConfig.getGlobalConfiguration()) .requestBuilder( - manager.getNextRequestBuilder(this)) + manager.getNextRequestBuilder()) .responseHandler(res -> res .globalErrorCase(Collections.singletonMap(ErrorCase.DEFAULT, ErrorCase.setReason(null, (reason, context) -> new CoreApiException(reason, context)))) - .nullify404(false) - .paginatedDeserializer(pageClass, converter, r -> r, dataManagers)) + .nullify404(false).paginatedDeserializer(pageClass, converter, r -> r, dataManagers)) .build().execute(); updateUsing(result.lastResponse, result.lastEndpointConfig); diff --git a/src/main/java/io/apimatic/core/types/pagination/PaginationDataManager.java b/src/main/java/io/apimatic/core/types/pagination/PaginationDataManager.java index def8cbbe..a3957d28 100644 --- a/src/main/java/io/apimatic/core/types/pagination/PaginationDataManager.java +++ b/src/main/java/io/apimatic/core/types/pagination/PaginationDataManager.java @@ -6,5 +6,5 @@ public interface PaginationDataManager { public abstract boolean isValid(PaginatedData paginatedData); - public abstract Builder getNextRequestBuilder(PaginatedData paginatedData); + public abstract Builder getNextRequestBuilder(); } diff --git a/src/main/java/io/apimatic/core/utilities/CoreHelper.java b/src/main/java/io/apimatic/core/utilities/CoreHelper.java index f2998b10..cde8a84b 100644 --- a/src/main/java/io/apimatic/core/utilities/CoreHelper.java +++ b/src/main/java/io/apimatic/core/utilities/CoreHelper.java @@ -26,16 +26,19 @@ import java.util.Map; import java.util.Set; import java.util.UUID; +import java.util.function.Function; import java.util.regex.Matcher; import java.util.regex.Pattern; import java.util.stream.Collectors; import javax.json.Json; +import javax.json.JsonNumber; import javax.json.JsonPointer; import javax.json.JsonReader; import javax.json.JsonString; import javax.json.JsonStructure; import javax.json.JsonValue; +import javax.json.JsonWriter; import javax.xml.bind.JAXBContext; import javax.xml.bind.JAXBElement; import javax.xml.bind.JAXBException; @@ -1157,7 +1160,7 @@ public static String tryUrlDecode(String value) { try { return URLDecoder.decode(value, "UTF-8"); } catch (UnsupportedEncodingException e) { - return null; + return value; } } @@ -1611,7 +1614,26 @@ public static JsonStructure createJsonStructure(String json) { return jsonStructure; } - public static String getValueFromJson(String pointer, String json) { + public static String resolveResponsePointer(String pointer, String jsonBody, String jsonHeaders) { + if (pointer == null) { + return null; + } + + String[] pointerParts = pointer.split("#"); + String prefix = pointerParts[0]; + String point = pointerParts.length > 1 ? pointerParts[1] : ""; + + switch (prefix) { + case "$response.body": + return CoreHelper.getValueFromJson(point, jsonBody); + case "$response.headers": + return CoreHelper.getValueFromJson(point, jsonHeaders); + } + + return null; + } + + private static String getValueFromJson(String pointer, String json) { if (pointer == null || json == null) { return null; } @@ -1640,4 +1662,68 @@ public static String getValueFromJson(String pointer, String json) { return value.toString(); // Only convert toString() if it's not null } + + public static T updateValueByPointer(T value, String pointer, Function updater) { + if (value == null || pointer == "" || updater == null) return value; + + try { + // Step 1: Convert input object to JSON string + String json = serialize(value); + + // Step 2: Parse JSON into JsonStructure + JsonStructure structure = createJsonStructure(json); + if (structure == null) return value; + + JsonPointer jsonPointer = Json.createPointer(pointer); + + if (!jsonPointer.containsValue(structure)) { + return value; // don't change if pointer doesn't exist + } + + JsonValue oldJsonValue = jsonPointer.getValue(structure); + Object oldValue = toObject(oldJsonValue); + Object newValueRaw = updater.apply(oldValue); + if (newValueRaw == null) return value; + + JsonValue newJsonValue = toJsonValue(newValueRaw); + if (newJsonValue == null) return value; + + JsonStructure updated = jsonPointer.replace(structure, newJsonValue); + + // Step 3: Convert updated structure back to JSON string + StringWriter writer = new StringWriter(); + try (JsonWriter jsonWriter = Json.createWriter(writer)) { + jsonWriter.write(updated); + } + + String updatedJson = writer.toString(); + + // Step 4: Deserialize back to original type + return deserialize(updatedJson, new TypeReference() {}); + + } catch (Exception e) { + return value; // fallback on error + } + } + + private static Object toObject(JsonValue value) { + switch (value.getValueType()) { + case STRING: return ((JsonString) value).getString(); + case NUMBER: return ((JsonNumber) value).numberValue(); + case TRUE: return true; + case FALSE: return false; + case NULL: return null; + default: return value.toString(); // fallback for objects/arrays + } + } + + private static JsonValue toJsonValue(Object obj) { + if (obj == null) return null; + if (obj instanceof String) return Json.createValue((String) obj); + if (obj instanceof Integer) return Json.createValue((Integer) obj); + if (obj instanceof Long) return Json.createValue((Long) obj); + if (obj instanceof Double) return Json.createValue((Double) obj); + if (obj instanceof Boolean) return ((Boolean) obj) ? JsonValue.TRUE : JsonValue.FALSE; + return null; // unsupported types (could be extended) + } } diff --git a/src/test/java/apimatic/core/EndToEndTest.java b/src/test/java/apimatic/core/EndToEndTest.java index 51c6e068..792c05b4 100644 --- a/src/test/java/apimatic/core/EndToEndTest.java +++ b/src/test/java/apimatic/core/EndToEndTest.java @@ -14,6 +14,7 @@ import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.NoSuchElementException; import org.junit.Before; import org.junit.Rule; @@ -29,8 +30,11 @@ import io.apimatic.core.ErrorCase; import io.apimatic.core.GlobalConfiguration; import io.apimatic.core.types.CoreApiException; +import io.apimatic.core.types.pagination.CursorPagination; import io.apimatic.core.types.pagination.LinkPagination; +import io.apimatic.core.types.pagination.OffsetPagination; import io.apimatic.core.types.pagination.PaginatedData; +import io.apimatic.core.types.pagination.PaginationDataManager; import io.apimatic.core.utilities.CoreHelper; import io.apimatic.coreinterfaces.http.Callback; import io.apimatic.coreinterfaces.http.Context; @@ -162,9 +166,26 @@ public void testEndToEndSyncCall() throws IOException, CoreApiException { } @Test - public void testPaginationData() throws IOException, CoreApiException { - PaginatedData paginatedData = getPaginatedApiCall().execute(); - + public void testLinkPaginationData() throws IOException, CoreApiException { + verifyData(getPaginatedApiCall(new LinkPagination("$response.body#/next_link")).execute()); + } + + @Test + public void testCursorPaginationData() throws IOException, CoreApiException { + verifyData(getPaginatedApiCall(new CursorPagination("$response.body#/page_info", "$request.path#/cursor")).execute()); + } + + @Test + public void testOffsetPaginationData() throws IOException, CoreApiException { + verifyData(getPaginatedApiCall(new OffsetPagination("$request.headers#/offset")).execute()); + } + + @Test + public void testPagePaginationData() throws IOException, CoreApiException { + verifyData(getPaginatedApiCall(new OffsetPagination("$request.query#/page")).execute()); + } + + private void verifyData(PaginatedData paginatedData) { int index = 0; List expectedData = Arrays.asList("apple", "mango", "orange", "potato", "carrot", "tomato"); while (paginatedData.hasNext()) { @@ -193,7 +214,6 @@ public void testPaginationData() throws IOException, CoreApiException { } } - /** * Test the local error template. * @throws IOException Signals that an I/O exception of some sort has occurred. @@ -454,9 +474,10 @@ private ApiCall getApiCall() throws IOException { .build(); } - private ApiCall, CoreApiException> getPaginatedApiCall() throws IOException { + private ApiCall, CoreApiException> getPaginatedApiCall(PaginationDataManager... pagination) throws IOException { when(response.getBody()).thenReturn("{\"data\":[\"apple\",\"mango\",\"orange\"],\"page_info\":\"fruits\"," + "\"next_link\":\"https://localhost:3000/path?page=2\"}"); + when(response.getHeaders()).thenReturn(getHttpHeaders()); Callback callback = new Callback() { private int callNumber = 1; @Override @@ -464,6 +485,9 @@ public void onBeforeRequest(Request request) { if (callNumber > 1) { when(response.getBody()).thenReturn("{\"data\":[\"potato\",\"carrot\",\"tomato\"],\"page_info\":\"vegitables\"}"); } + if (callNumber > 2) { + throw new NoSuchElementException("No more data available."); + } callNumber++; } @Override @@ -473,13 +497,15 @@ public void onAfterResponse(Context context) { }; return new ApiCall.Builder, CoreApiException>().globalConfig(getGlobalConfig(callback)) .requestBuilder(requestBuilder -> requestBuilder.server("https://localhost:3000") - .path("/path") - .queryParam(param -> param.key("cursor").value("cursor").isRequired(false)) + .path("/path/{cursor}") + .templateParam(param -> param.key("cursor").value("cursor").isRequired(false)) + .headerParam(param -> param.key("offset").value(1).isRequired(false)) + .queryParam(param -> param.key("page").value(1).isRequired(false)) .formParam(param -> param.key("limit").value("limit").isRequired(false)) .headerParam(param -> param.key("accept").value("application/json")) .httpMethod(Method.GET)) .responseHandler(responseHandler -> responseHandler - .paginatedDeserializer(RecordPage.class, t -> t.data, r -> r, new LinkPagination("/next_link")) + .paginatedDeserializer(RecordPage.class, t -> t.data, r -> r, pagination) .nullify404(false) .globalErrorCase(Collections.emptyMap())) .endpointConfiguration( diff --git a/src/test/java/apimatic/core/mocks/HttpHeadersMock.java b/src/test/java/apimatic/core/mocks/HttpHeadersMock.java index 8f4a772d..54b83585 100644 --- a/src/test/java/apimatic/core/mocks/HttpHeadersMock.java +++ b/src/test/java/apimatic/core/mocks/HttpHeadersMock.java @@ -1,5 +1,11 @@ package apimatic.core.mocks; +import static org.mockito.Mockito.when; + +import java.io.IOException; +import java.util.HashMap; + +import org.junit.Before; import org.junit.Rule; import org.mockito.Mock; import org.mockito.junit.MockitoJUnit; @@ -20,6 +26,11 @@ public class HttpHeadersMock { @Mock private HttpHeaders httpHeaders; + @Before + public void setup() throws IOException { + when(httpHeaders.asSimpleMap()).thenReturn(new HashMap()); + } + /** * @return {@link HttpHeaders}. */ diff --git a/src/test/java/apimatic/core/type/pagination/CursorPaginationTest.java b/src/test/java/apimatic/core/type/pagination/CursorPaginationTest.java new file mode 100644 index 00000000..cd7139f6 --- /dev/null +++ b/src/test/java/apimatic/core/type/pagination/CursorPaginationTest.java @@ -0,0 +1,42 @@ +package apimatic.core.type.pagination; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import org.junit.Rule; +import org.junit.Test; +import org.mockito.junit.MockitoJUnit; +import org.mockito.junit.MockitoRule; + +import io.apimatic.core.HttpRequest; +import io.apimatic.core.types.pagination.CursorPagination; +import io.apimatic.core.types.pagination.PaginatedData; + +public class CursorPaginationTest { + @Rule + public MockitoRule initRule = MockitoJUnit.rule().silent(); + + @Test + public void testIsValid_withValidCursor_returnsTrue() { + PaginatedData paginatedData = mock(PaginatedData.class); + + when(paginatedData.getLastRequestBuilder()) + .thenReturn(new HttpRequest.Builder().queryParam(q -> q.key("cursor").value("abc"))); + when(paginatedData.getLastResponseBody()).thenReturn("{\"next_cursor\": \"xyz123\"}"); + when(paginatedData.getLastResponseHeaders()).thenReturn("{}"); + + CursorPagination cursor = new CursorPagination("$response.body#/next_cursor", "$request.query#/cursor"); + + assertTrue(cursor.isValid(paginatedData)); + assertNotNull(cursor.getNextRequestBuilder()); + + cursor.getNextRequestBuilder().updateByReference("$request.query#/cursor", v -> { + assertEquals("xyz123", v); + return v; + }); + } + +} diff --git a/src/test/java/apimatic/core/type/pagination/LinkPaginationTest.java b/src/test/java/apimatic/core/type/pagination/LinkPaginationTest.java new file mode 100644 index 00000000..cec9e475 --- /dev/null +++ b/src/test/java/apimatic/core/type/pagination/LinkPaginationTest.java @@ -0,0 +1,42 @@ +package apimatic.core.type.pagination; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import org.junit.Rule; +import org.junit.Test; +import org.mockito.junit.MockitoJUnit; +import org.mockito.junit.MockitoRule; + +import io.apimatic.core.HttpRequest; +import io.apimatic.core.types.pagination.LinkPagination; +import io.apimatic.core.types.pagination.PaginatedData; + +public class LinkPaginationTest { + @Rule + public MockitoRule initRule = MockitoJUnit.rule().silent(); + + @Test + public void testIsValid_withValidLink_returnsTrue() { + PaginatedData paginatedData = mock(PaginatedData.class); + + when(paginatedData.getLastRequestBuilder()).thenReturn(new HttpRequest.Builder()); + when(paginatedData.getLastResponseBody()).thenReturn("{\"next\": \"https://api.example.com?page=2\"}"); + when(paginatedData.getLastResponseHeaders()).thenReturn("{\"accept\": \"application/text\"}"); + + LinkPagination link = new LinkPagination("$response.body#/next"); + + assertTrue(link.isValid(paginatedData)); + assertNotNull(link.getNextRequestBuilder()); + link.getNextRequestBuilder().updateByReference("$request.query#/page", v -> + { + assertEquals("2", v); + // Assert and return original value without updating + return v; + }); + } + +} diff --git a/src/test/java/apimatic/core/type/pagination/OffsetPaginationTest.java b/src/test/java/apimatic/core/type/pagination/OffsetPaginationTest.java new file mode 100644 index 00000000..d0e21378 --- /dev/null +++ b/src/test/java/apimatic/core/type/pagination/OffsetPaginationTest.java @@ -0,0 +1,182 @@ +package apimatic.core.type.pagination; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import java.util.HashMap; + +import org.junit.Rule; +import org.junit.Test; +import org.mockito.junit.MockitoJUnit; +import org.mockito.junit.MockitoRule; + +import io.apimatic.core.HttpRequest; +import io.apimatic.core.types.pagination.OffsetPagination; +import io.apimatic.core.types.pagination.PaginatedData; + +public class OffsetPaginationTest { + @Rule + public MockitoRule initRule = MockitoJUnit.rule().silent(); + + @Test + public void testWithValidOffsetHeader_returnsTrue() { + PaginatedData paginatedData = mock(PaginatedData.class); + + when(paginatedData.getLastRequestBuilder()) + .thenReturn(new HttpRequest.Builder().headerParam(h -> h.key("offset").value(3))); + when(paginatedData.getLastDataSize()).thenReturn(100); + + OffsetPagination offset = new OffsetPagination("$request.headers#/offset"); + + assertTrue(offset.isValid(paginatedData)); + assertNotNull(offset.getNextRequestBuilder()); + + offset.getNextRequestBuilder().updateByReference("$request.headers#/offset", v -> { + assertEquals(103, v); + return v; + }); + } + + @Test + public void testWithValidOffsetTemplate_returnsTrue() { + PaginatedData paginatedData = mock(PaginatedData.class); + + when(paginatedData.getLastRequestBuilder()) + .thenReturn(new HttpRequest.Builder().templateParam(t -> t.key("offset").value(3))); + when(paginatedData.getLastDataSize()).thenReturn(100); + + OffsetPagination offset = new OffsetPagination("$request.path#/offset"); + + assertTrue(offset.isValid(paginatedData)); + assertNotNull(offset.getNextRequestBuilder()); + + offset.getNextRequestBuilder().updateByReference("$request.path#/offset", v -> { + assertEquals(103, v); + return v; + }); + } + + @Test + public void testWithValidOffset_returnsTrue() { + PaginatedData paginatedData = mock(PaginatedData.class); + + when(paginatedData.getLastRequestBuilder()) + .thenReturn(new HttpRequest.Builder().queryParam(q -> q.key("offset").value(3))); + when(paginatedData.getLastDataSize()).thenReturn(100); + + OffsetPagination offset = new OffsetPagination("$request.query#/offset"); + + assertTrue(offset.isValid(paginatedData)); + assertNotNull(offset.getNextRequestBuilder()); + + offset.getNextRequestBuilder().updateByReference("$request.query#/offset", v -> { + assertEquals(103, v); + return v; + }); + } + + @Test + public void testWithValidOffsetAsInnerField_returnsTrue() { + PaginatedData paginatedData = mock(PaginatedData.class); + + when(paginatedData.getLastRequestBuilder()).thenReturn( + new HttpRequest.Builder().queryParam(q -> q.key("offset").value(new HashMap() { + private static final long serialVersionUID = 1L; + { + put("val", "1"); + } + }))); + when(paginatedData.getLastDataSize()).thenReturn(100); + + OffsetPagination offset = new OffsetPagination("$request.query#/offset/val"); + + assertTrue(offset.isValid(paginatedData)); + assertNotNull(offset.getNextRequestBuilder()); + + offset.getNextRequestBuilder().updateByReference("$request.query#/offset/val", v -> { + assertEquals(101, v); + return v; + }); + } + + @Test + public void testWithValidStringOffset_returnsTrue() { + PaginatedData paginatedData = mock(PaginatedData.class); + + when(paginatedData.getLastRequestBuilder()) + .thenReturn(new HttpRequest.Builder().queryParam(q -> q.key("offset").value("5"))); + when(paginatedData.getLastDataSize()).thenReturn(100); + + OffsetPagination offset = new OffsetPagination("$request.query#/offset"); + + assertTrue(offset.isValid(paginatedData)); + assertNotNull(offset.getNextRequestBuilder()); + + offset.getNextRequestBuilder().updateByReference("$request.query#/offset", v -> { + assertEquals(105, v); + return v; + }); + } + + @Test + public void testWithInValidStringOffset_returnsFalse() { + PaginatedData paginatedData = mock(PaginatedData.class); + + when(paginatedData.getLastRequestBuilder()) + .thenReturn(new HttpRequest.Builder().queryParam(q -> q.key("offset").value("5a"))); + when(paginatedData.getLastDataSize()).thenReturn(100); + + OffsetPagination offset = new OffsetPagination("$request.query#/offset"); + + assertFalse(offset.isValid(paginatedData)); + assertNotNull(offset.getNextRequestBuilder()); + + offset.getNextRequestBuilder().updateByReference("$request.query#/offset", v -> { + assertEquals("5a", v); + return v; + }); + } + + @Test + public void testWithMissingOffset_returnsFalse() { + PaginatedData paginatedData = mock(PaginatedData.class); + + when(paginatedData.getLastRequestBuilder()).thenReturn(new HttpRequest.Builder()); + when(paginatedData.getLastDataSize()).thenReturn(100); + + OffsetPagination offset = new OffsetPagination("$request.query#/offset"); + + assertFalse(offset.isValid(paginatedData)); + assertNotNull(offset.getNextRequestBuilder()); + + offset.getNextRequestBuilder().updateByReference("$request.query#/offset", v -> { + fail(); + return v; + }); + } + + @Test + public void testWithNullOffset_returnsFalse() { + PaginatedData paginatedData = mock(PaginatedData.class); + + when(paginatedData.getLastRequestBuilder()) + .thenReturn(new HttpRequest.Builder().queryParam(q -> q.key("offset").value(5))); + when(paginatedData.getLastDataSize()).thenReturn(100); + + OffsetPagination offset = new OffsetPagination(null); + + assertFalse(offset.isValid(paginatedData)); + assertNotNull(offset.getNextRequestBuilder()); + + offset.getNextRequestBuilder().updateByReference("$request.query#/offset", v -> { + assertEquals(5, v); + return v; + }); + } + +} diff --git a/src/test/java/apimatic/core/type/pagination/PagePaginationTest.java b/src/test/java/apimatic/core/type/pagination/PagePaginationTest.java new file mode 100644 index 00000000..9c201e00 --- /dev/null +++ b/src/test/java/apimatic/core/type/pagination/PagePaginationTest.java @@ -0,0 +1,174 @@ +package apimatic.core.type.pagination; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import java.util.HashMap; + +import org.junit.Rule; +import org.junit.Test; +import org.mockito.junit.MockitoJUnit; +import org.mockito.junit.MockitoRule; + +import io.apimatic.core.HttpRequest; +import io.apimatic.core.types.pagination.PagePagination; +import io.apimatic.core.types.pagination.PaginatedData; + +public class PagePaginationTest { + @Rule + public MockitoRule initRule = MockitoJUnit.rule().silent(); + + @Test + public void testWithValidPageHeader_returnsTrue() { + PaginatedData paginatedData = mock(PaginatedData.class); + + when(paginatedData.getLastRequestBuilder()) + .thenReturn(new HttpRequest.Builder().headerParam(h -> h.key("page").value(3))); + + PagePagination page = new PagePagination("$request.headers#/page"); + + assertTrue(page.isValid(paginatedData)); + assertNotNull(page.getNextRequestBuilder()); + + page.getNextRequestBuilder().updateByReference("$request.headers#/page", v -> { + assertEquals(4, v); + return v; + }); + } + + @Test + public void testWithValidPageTemplate_returnsTrue() { + PaginatedData paginatedData = mock(PaginatedData.class); + + when(paginatedData.getLastRequestBuilder()) + .thenReturn(new HttpRequest.Builder().templateParam(t -> t.key("page").value(3))); + + PagePagination page = new PagePagination("$request.path#/page"); + + assertTrue(page.isValid(paginatedData)); + assertNotNull(page.getNextRequestBuilder()); + + page.getNextRequestBuilder().updateByReference("$request.path#/page", v -> { + assertEquals(4, v); + return v; + }); + } + + @Test + public void testWithValidPage_returnsTrue() { + PaginatedData paginatedData = mock(PaginatedData.class); + + when(paginatedData.getLastRequestBuilder()) + .thenReturn(new HttpRequest.Builder().queryParam(q -> q.key("page").value(3))); + + PagePagination page = new PagePagination("$request.query#/page"); + + assertTrue(page.isValid(paginatedData)); + assertNotNull(page.getNextRequestBuilder()); + + page.getNextRequestBuilder().updateByReference("$request.query#/page", v -> { + assertEquals(4, v); + return v; + }); + } + + @Test + public void testWithValidPageAsInnerField_returnsTrue() { + PaginatedData paginatedData = mock(PaginatedData.class); + + when(paginatedData.getLastRequestBuilder()).thenReturn( + new HttpRequest.Builder().queryParam(q -> q.key("page").value(new HashMap() { + private static final long serialVersionUID = 1L; + { + put("val", "1"); + } + }))); + + PagePagination page = new PagePagination("$request.query#/page/val"); + + assertTrue(page.isValid(paginatedData)); + assertNotNull(page.getNextRequestBuilder()); + + page.getNextRequestBuilder().updateByReference("$request.query#/page/val", v -> { + assertEquals(2, v); + return v; + }); + } + + @Test + public void testWithValidStringPage_returnsTrue() { + PaginatedData paginatedData = mock(PaginatedData.class); + + when(paginatedData.getLastRequestBuilder()) + .thenReturn(new HttpRequest.Builder().queryParam(q -> q.key("page").value("5"))); + + PagePagination page = new PagePagination("$request.query#/page"); + + assertTrue(page.isValid(paginatedData)); + assertNotNull(page.getNextRequestBuilder()); + + page.getNextRequestBuilder().updateByReference("$request.query#/page", v -> { + assertEquals(6, v); + return v; + }); + } + + @Test + public void testWithInValidStringPage_returnsFalse() { + PaginatedData paginatedData = mock(PaginatedData.class); + + when(paginatedData.getLastRequestBuilder()) + .thenReturn(new HttpRequest.Builder().queryParam(q -> q.key("page").value("5a"))); + + PagePagination page = new PagePagination("$request.query#/page"); + + assertFalse(page.isValid(paginatedData)); + assertNotNull(page.getNextRequestBuilder()); + + page.getNextRequestBuilder().updateByReference("$request.query#/page", v -> { + assertEquals("5a", v); + return v; + }); + } + + @Test + public void testWithMissingPage_returnsFalse() { + PaginatedData paginatedData = mock(PaginatedData.class); + + when(paginatedData.getLastRequestBuilder()).thenReturn(new HttpRequest.Builder()); + + PagePagination page = new PagePagination("$request.query#/page"); + + assertFalse(page.isValid(paginatedData)); + assertNotNull(page.getNextRequestBuilder()); + + page.getNextRequestBuilder().updateByReference("$request.query#/page", v -> { + fail(); + return v; + }); + } + + @Test + public void testWithNullPage_returnsFalse() { + PaginatedData paginatedData = mock(PaginatedData.class); + + when(paginatedData.getLastRequestBuilder()) + .thenReturn(new HttpRequest.Builder().queryParam(q -> q.key("page").value(5))); + + PagePagination page = new PagePagination(null); + + assertFalse(page.isValid(paginatedData)); + assertNotNull(page.getNextRequestBuilder()); + + page.getNextRequestBuilder().updateByReference("$request.query#/page", v -> { + assertEquals(5, v); + return v; + }); + } + +} From 84764d13f704b4031b92c5c59f37e7e59fa8a74c Mon Sep 17 00:00:00 2001 From: Asad Ali <14asadali@gmail.com> Date: Fri, 18 Apr 2025 17:59:23 +0500 Subject: [PATCH 13/20] refactor request builder --- .../java/io/apimatic/core/HttpRequest.java | 71 ++++++++++--------- 1 file changed, 39 insertions(+), 32 deletions(-) diff --git a/src/main/java/io/apimatic/core/HttpRequest.java b/src/main/java/io/apimatic/core/HttpRequest.java index eb0fc9f4..8ad13aef 100644 --- a/src/main/java/io/apimatic/core/HttpRequest.java +++ b/src/main/java/io/apimatic/core/HttpRequest.java @@ -328,7 +328,6 @@ public static class Builder { */ private Parameter.Builder parameterBuilder = new Parameter.Builder(); - @SuppressWarnings("unchecked") public Builder updateByReference(String pointer, Function setter) { if (pointer == null) { return this; @@ -340,48 +339,56 @@ public Builder updateByReference(String pointer, Function setter switch (prefix) { case "$request.path": - Map simplifiedPath = new HashMap<>(); - for (Map.Entry> entry : templateParams.entrySet()) { - simplifiedPath.put(entry.getKey(), entry.getValue().getKey()); - } - - simplifiedPath = CoreHelper.updateValueByPointer(simplifiedPath, point, setter); - - for (Map.Entry entry : simplifiedPath.entrySet()) { - // Preserve the original boolean if it exists, otherwise set default (e.g., false) - Boolean originalFlag = templateParams.containsKey(entry.getKey()) - ? templateParams.get(entry.getKey()).getValue() - : false; - templateParams.put(entry.getKey(), new SimpleEntry<>(entry.getValue(), originalFlag)); - } + updateTemplateParams(setter, point); return this; case "$request.query": queryParams = CoreHelper.updateValueByPointer(queryParams, point, setter); return this; case "$request.headers": - Map simplifiedHeaders = new HashMap<>(); - for (Entry> entry : headerParams.entrySet()) { - if (entry.getValue().size() == 1) { - simplifiedHeaders.put(entry.getKey(), entry.getValue().get(0)); - } else { - simplifiedHeaders.put(entry.getKey(), entry.getValue()); - } + updateHeaderParams(setter, point); + return this; + } + + return this; + } + + @SuppressWarnings("unchecked") + private void updateHeaderParams(Function setter, String point) { + Map simplifiedHeaders = new HashMap<>(); + for (Entry> entry : headerParams.entrySet()) { + if (entry.getValue().size() == 1) { + simplifiedHeaders.put(entry.getKey(), entry.getValue().get(0)); + } else { + simplifiedHeaders.put(entry.getKey(), entry.getValue()); } + } - simplifiedHeaders = CoreHelper.updateValueByPointer(simplifiedHeaders, point, setter); + simplifiedHeaders = CoreHelper.updateValueByPointer(simplifiedHeaders, point, setter); - for (Map.Entry entry : simplifiedHeaders.entrySet()) { - if (entry.getValue() instanceof List) { - headerParams.put(entry.getKey(), (List)entry.getValue()); - } else { - headerParams.put(entry.getKey(), Arrays.asList(entry.getValue())); - } + for (Map.Entry entry : simplifiedHeaders.entrySet()) { + if (entry.getValue() instanceof List) { + headerParams.put(entry.getKey(), (List)entry.getValue()); + } else { + headerParams.put(entry.getKey(), Arrays.asList(entry.getValue())); } - - return this; + } + } + + private void updateTemplateParams(Function setter, String point) { + Map simplifiedPath = new HashMap<>(); + for (Map.Entry> entry : templateParams.entrySet()) { + simplifiedPath.put(entry.getKey(), entry.getValue().getKey()); } - return this; + simplifiedPath = CoreHelper.updateValueByPointer(simplifiedPath, point, setter); + + for (Map.Entry entry : simplifiedPath.entrySet()) { + // Preserve the original boolean if it exists, otherwise set default (e.g., false) + Boolean originalFlag = templateParams.containsKey(entry.getKey()) + ? templateParams.get(entry.getKey()).getValue() + : false; + templateParams.put(entry.getKey(), new SimpleEntry<>(entry.getValue(), originalFlag)); + } } /** From 0f7912d172c5e54dfaa8e71e4d43eb2027b85a99 Mon Sep 17 00:00:00 2001 From: Asad Ali <14asadali@gmail.com> Date: Fri, 18 Apr 2025 23:02:38 +0500 Subject: [PATCH 14/20] adds tests for cursor and link pagination --- src/test/java/apimatic/core/EndToEndTest.java | 43 +++- .../type/pagination/CursorPaginationTest.java | 194 +++++++++++++++++- .../type/pagination/LinkPaginationTest.java | 169 ++++++++++++++- 3 files changed, 397 insertions(+), 9 deletions(-) diff --git a/src/test/java/apimatic/core/EndToEndTest.java b/src/test/java/apimatic/core/EndToEndTest.java index 792c05b4..57510066 100644 --- a/src/test/java/apimatic/core/EndToEndTest.java +++ b/src/test/java/apimatic/core/EndToEndTest.java @@ -12,6 +12,7 @@ import java.util.Arrays; import java.util.Collections; import java.util.HashMap; +import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.NoSuchElementException; @@ -33,6 +34,7 @@ import io.apimatic.core.types.pagination.CursorPagination; import io.apimatic.core.types.pagination.LinkPagination; import io.apimatic.core.types.pagination.OffsetPagination; +import io.apimatic.core.types.pagination.PagePagination; import io.apimatic.core.types.pagination.PaginatedData; import io.apimatic.core.types.pagination.PaginationDataManager; import io.apimatic.core.utilities.CoreHelper; @@ -182,17 +184,52 @@ public void testOffsetPaginationData() throws IOException, CoreApiException { @Test public void testPagePaginationData() throws IOException, CoreApiException { - verifyData(getPaginatedApiCall(new OffsetPagination("$request.query#/page")).execute()); + verifyData(getPaginatedApiCall(new PagePagination("$request.query#/page")).execute()); + verifyOnlyPages(getPaginatedApiCall(new PagePagination("$request.query#/page")).execute()); + } + + private void verifyOnlyPages(PaginatedData paginatedData) { + RecordPage expectedPage1 = new RecordPage(); + expectedPage1.data = Arrays.asList("apple", "mango", "orange"); + expectedPage1.pageInfo = "fruits"; + expectedPage1.nextLink = "https://localhost:3000/path?page=2"; + + RecordPage expectedPage2 = new RecordPage(); + expectedPage2.data = Arrays.asList("potato", "carrot", "tomato"); + expectedPage2.pageInfo = "vegitables"; + expectedPage2.nextLink = null; + + int pageNum = 0; + List expectedPages = Arrays.asList(expectedPage1, expectedPage2); + for (RecordPage p : paginatedData.pages()) { + assertEquals(expectedPages.get(pageNum).data, p.data); + assertEquals(expectedPages.get(pageNum).pageInfo, p.pageInfo); + assertEquals(expectedPages.get(pageNum).nextLink, p.nextLink); + pageNum++; + } + Iterator iterator = paginatedData.pages().iterator(); + Exception exception = assertThrows(NoSuchElementException.class, () -> { + while (iterator.hasNext()) { + iterator.next(); + } + iterator.next(); + }); + assertEquals("No more data available.", exception.getMessage()); } private void verifyData(PaginatedData paginatedData) { int index = 0; List expectedData = Arrays.asList("apple", "mango", "orange", "potato", "carrot", "tomato"); - while (paginatedData.hasNext()) { - String d = paginatedData.next(); + Iterator iterator = paginatedData.iterator(); + while (iterator.hasNext()) { + String d = iterator.next(); assertEquals(expectedData.get(index), d); index++; } + Exception exception = assertThrows(NoSuchElementException.class, () -> { + iterator.next(); + }); + assertEquals("No more data available.", exception.getMessage()); RecordPage expectedPage1 = new RecordPage(); expectedPage1.data = Arrays.asList("apple", "mango", "orange"); diff --git a/src/test/java/apimatic/core/type/pagination/CursorPaginationTest.java b/src/test/java/apimatic/core/type/pagination/CursorPaginationTest.java index cd7139f6..53c7b666 100644 --- a/src/test/java/apimatic/core/type/pagination/CursorPaginationTest.java +++ b/src/test/java/apimatic/core/type/pagination/CursorPaginationTest.java @@ -1,8 +1,10 @@ package apimatic.core.type.pagination; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; @@ -20,13 +22,12 @@ public class CursorPaginationTest { public MockitoRule initRule = MockitoJUnit.rule().silent(); @Test - public void testIsValid_withValidCursor_returnsTrue() { + public void testWithValidCursor_returnsTrue() { PaginatedData paginatedData = mock(PaginatedData.class); when(paginatedData.getLastRequestBuilder()) .thenReturn(new HttpRequest.Builder().queryParam(q -> q.key("cursor").value("abc"))); when(paginatedData.getLastResponseBody()).thenReturn("{\"next_cursor\": \"xyz123\"}"); - when(paginatedData.getLastResponseHeaders()).thenReturn("{}"); CursorPagination cursor = new CursorPagination("$response.body#/next_cursor", "$request.query#/cursor"); @@ -39,4 +40,193 @@ public void testIsValid_withValidCursor_returnsTrue() { }); } + @Test + public void testWithValidCursorAndDifferentType1_returnsTrue() { + PaginatedData paginatedData = mock(PaginatedData.class); + + when(paginatedData.getLastRequestBuilder()) + .thenReturn(new HttpRequest.Builder().queryParam(q -> q.key("cursor").value("abc"))); + when(paginatedData.getLastResponseBody()).thenReturn("{\"next_cursor\": 123}"); + + CursorPagination cursor = new CursorPagination("$response.body#/next_cursor", "$request.query#/cursor"); + + assertTrue(cursor.isValid(paginatedData)); + assertNotNull(cursor.getNextRequestBuilder()); + + cursor.getNextRequestBuilder().updateByReference("$request.query#/cursor", v -> { + assertEquals("123", v); + return v; + }); + } + + @Test + public void testWithValidCursorAndDifferentType2_returnsTrue() { + PaginatedData paginatedData = mock(PaginatedData.class); + + when(paginatedData.getLastRequestBuilder()) + .thenReturn(new HttpRequest.Builder().queryParam(q -> q.key("cursor").value(456))); + when(paginatedData.getLastResponseBody()).thenReturn("{\"next_cursor\": 123}"); + + CursorPagination cursor = new CursorPagination("$response.body#/next_cursor", "$request.query#/cursor"); + + assertTrue(cursor.isValid(paginatedData)); + assertNotNull(cursor.getNextRequestBuilder()); + + cursor.getNextRequestBuilder().updateByReference("$request.query#/cursor", v -> { + assertEquals("123", v); + return v; + }); + } + + @Test + public void testWithValidCursorButMissingInFirstRequest_returnsFalse() { + PaginatedData paginatedData = mock(PaginatedData.class); + + when(paginatedData.getLastRequestBuilder()).thenReturn(new HttpRequest.Builder()); + when(paginatedData.getLastResponseBody()).thenReturn("{\"next_cursor\": \"xyz123\"}"); + + CursorPagination cursor = new CursorPagination("$response.body#/next_cursor", "$request.query#/cursor"); + + assertFalse(cursor.isValid(paginatedData)); + assertNotNull(cursor.getNextRequestBuilder()); + + cursor.getNextRequestBuilder().updateByReference("$request.query#/cursor", v -> { + fail(); + return v; + }); + } + + @Test + public void testWithValidCursorFromResponseHeader_returnsTrue() { + PaginatedData paginatedData = mock(PaginatedData.class); + + when(paginatedData.getLastRequestBuilder()) + .thenReturn(new HttpRequest.Builder().queryParam(q -> q.key("cursor").value("abc"))); + when(paginatedData.getLastResponseHeaders()).thenReturn("{\"next_cursor\": \"xyz123\"}"); + + CursorPagination cursor = new CursorPagination("$response.headers#/next_cursor", "$request.query#/cursor"); + + assertTrue(cursor.isValid(paginatedData)); + assertNotNull(cursor.getNextRequestBuilder()); + + cursor.getNextRequestBuilder().updateByReference("$request.query#/cursor", v -> { + assertEquals("xyz123", v); + return v; + }); + } + + @Test + public void testWithValidCursorInTemplateParam_returnsTrue() { + PaginatedData paginatedData = mock(PaginatedData.class); + + when(paginatedData.getLastRequestBuilder()) + .thenReturn(new HttpRequest.Builder().templateParam(t -> t.key("cursor").value("abc"))); + when(paginatedData.getLastResponseBody()).thenReturn("{\"next_cursor\": \"xyz123\"}"); + + CursorPagination cursor = new CursorPagination("$response.body#/next_cursor", "$request.path#/cursor"); + + assertTrue(cursor.isValid(paginatedData)); + assertNotNull(cursor.getNextRequestBuilder()); + + cursor.getNextRequestBuilder().updateByReference("$request.path#/cursor", v -> { + assertEquals("xyz123", v); + return v; + }); + } + + @Test + public void testWithValidCursorInHeaderParam_returnsTrue() { + PaginatedData paginatedData = mock(PaginatedData.class); + + when(paginatedData.getLastRequestBuilder()) + .thenReturn(new HttpRequest.Builder().headerParam(h -> h.key("cursor").value("abc"))); + when(paginatedData.getLastResponseBody()).thenReturn("{\"next_cursor\": \"xyz123\"}"); + + CursorPagination cursor = new CursorPagination("$response.body#/next_cursor", "$request.headers#/cursor"); + + assertTrue(cursor.isValid(paginatedData)); + assertNotNull(cursor.getNextRequestBuilder()); + + cursor.getNextRequestBuilder().updateByReference("$request.headers#/cursor", v -> { + assertEquals("xyz123", v); + return v; + }); + } + + @Test + public void testWithInValidResponsePointer_returnsFalse() { + PaginatedData paginatedData = mock(PaginatedData.class); + + when(paginatedData.getLastRequestBuilder()) + .thenReturn(new HttpRequest.Builder().headerParam(h -> h.key("cursor").value("abc"))); + when(paginatedData.getLastResponseBody()).thenReturn("{\"next_cursor\": \"xyz123\"}"); + + CursorPagination cursor = new CursorPagination("$response.body#/next", "$request.headers#/cursor"); + + assertFalse(cursor.isValid(paginatedData)); + assertNotNull(cursor.getNextRequestBuilder()); + + cursor.getNextRequestBuilder().updateByReference("$request.headers#/cursor", v -> { + assertEquals("abc", v); + return v; + }); + } + + @Test + public void testWithMissingResponsePointer_returnsFalse() { + PaginatedData paginatedData = mock(PaginatedData.class); + + when(paginatedData.getLastRequestBuilder()) + .thenReturn(new HttpRequest.Builder().headerParam(h -> h.key("cursor").value("abc"))); + when(paginatedData.getLastResponseBody()).thenReturn("{\"next_cursor\": \"xyz123\"}"); + + CursorPagination cursor = new CursorPagination(null, "$request.headers#/cursor"); + + assertFalse(cursor.isValid(paginatedData)); + assertNotNull(cursor.getNextRequestBuilder()); + + cursor.getNextRequestBuilder().updateByReference("$request.headers#/cursor", v -> { + assertEquals("abc", v); + return v; + }); + } + + @Test + public void testWithInValidRequestPointer_returnsFalse() { + PaginatedData paginatedData = mock(PaginatedData.class); + + when(paginatedData.getLastRequestBuilder()) + .thenReturn(new HttpRequest.Builder().headerParam(h -> h.key("cursor").value("abc"))); + when(paginatedData.getLastResponseBody()).thenReturn("{\"next_cursor\": \"xyz123\"}"); + + CursorPagination cursor = new CursorPagination("$response.body#/next_cursor", "$request.headers#/next_cursor"); + + assertFalse(cursor.isValid(paginatedData)); + assertNotNull(cursor.getNextRequestBuilder()); + + cursor.getNextRequestBuilder().updateByReference("$request.headers#/cursor", v -> { + assertEquals("abc", v); + return v; + }); + } + + @Test + public void testWithMissingRequestPointer_returnsFalse() { + PaginatedData paginatedData = mock(PaginatedData.class); + + when(paginatedData.getLastRequestBuilder()) + .thenReturn(new HttpRequest.Builder().headerParam(h -> h.key("cursor").value("abc"))); + when(paginatedData.getLastResponseBody()).thenReturn("{\"next_cursor\": \"xyz123\"}"); + + CursorPagination cursor = new CursorPagination("$response.body#/next_cursor", null); + + assertFalse(cursor.isValid(paginatedData)); + assertNotNull(cursor.getNextRequestBuilder()); + + cursor.getNextRequestBuilder().updateByReference("$request.headers#/cursor", v -> { + assertEquals("abc", v); + return v; + }); + } + } diff --git a/src/test/java/apimatic/core/type/pagination/LinkPaginationTest.java b/src/test/java/apimatic/core/type/pagination/LinkPaginationTest.java index cec9e475..6f3f264e 100644 --- a/src/test/java/apimatic/core/type/pagination/LinkPaginationTest.java +++ b/src/test/java/apimatic/core/type/pagination/LinkPaginationTest.java @@ -1,8 +1,10 @@ package apimatic.core.type.pagination; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; @@ -18,23 +20,182 @@ public class LinkPaginationTest { @Rule public MockitoRule initRule = MockitoJUnit.rule().silent(); - + @Test - public void testIsValid_withValidLink_returnsTrue() { + public void testWithValidLink_returnsTrue() { PaginatedData paginatedData = mock(PaginatedData.class); when(paginatedData.getLastRequestBuilder()).thenReturn(new HttpRequest.Builder()); when(paginatedData.getLastResponseBody()).thenReturn("{\"next\": \"https://api.example.com?page=2\"}"); - when(paginatedData.getLastResponseHeaders()).thenReturn("{\"accept\": \"application/text\"}"); LinkPagination link = new LinkPagination("$response.body#/next"); assertTrue(link.isValid(paginatedData)); assertNotNull(link.getNextRequestBuilder()); + + link.getNextRequestBuilder().updateByReference("$request.query#/page", v -> + { + assertEquals("2", v); + return v; + }); + } + + @Test + public void testWithValidLinkImpactingOtherParams_returnsTrue() { + PaginatedData paginatedData = mock(PaginatedData.class); + + when(paginatedData.getLastRequestBuilder()).thenReturn(new HttpRequest.Builder() + .queryParam(q -> q.key("size").value(23)) + .queryParam(q -> q.key("page").value(1)) + .headerParam(h -> h.key("page").value(2))); + when(paginatedData.getLastResponseBody()).thenReturn("{\"next\": \"https://api.example.com?page=2\"}"); + + LinkPagination link = new LinkPagination("$response.body#/next"); + + assertTrue(link.isValid(paginatedData)); + assertNotNull(link.getNextRequestBuilder()); + + link.getNextRequestBuilder().updateByReference("$request.query#/page", v -> + { + assertEquals("2", v); + return v; + }); + + link.getNextRequestBuilder().updateByReference("$request.query#/size", v -> + { + assertEquals(23, v); + return v; + }); + + link.getNextRequestBuilder().updateByReference("$request.headers#/page", v -> + { + assertEquals(2, v); + return v; + }); + } + + @Test + public void testWithValidLinkFromResponseHeader_returnsTrue() { + PaginatedData paginatedData = mock(PaginatedData.class); + + when(paginatedData.getLastRequestBuilder()).thenReturn(new HttpRequest.Builder()); + when(paginatedData.getLastResponseHeaders()).thenReturn("{\"next\": \"https://api.example.com?page=2\"}"); + + LinkPagination link = new LinkPagination("$response.headers#/next"); + + assertTrue(link.isValid(paginatedData)); + assertNotNull(link.getNextRequestBuilder()); + link.getNextRequestBuilder().updateByReference("$request.query#/page", v -> { assertEquals("2", v); - // Assert and return original value without updating + return v; + }); + } + + @Test + public void testWithInValidLinkPointer_returnsFalse() { + PaginatedData paginatedData = mock(PaginatedData.class); + + when(paginatedData.getLastRequestBuilder()).thenReturn(new HttpRequest.Builder()); + when(paginatedData.getLastResponseBody()).thenReturn("{\"next\": \"https://api.example.com?page=2\"}"); + + LinkPagination link = new LinkPagination("$response.body#/next/href"); + + assertFalse(link.isValid(paginatedData)); + assertNotNull(link.getNextRequestBuilder()); + + link.getNextRequestBuilder().updateByReference("$request.query#/page", v -> + { + fail(); + return v; + }); + } + + @Test + public void testWithMissingResponse_returnsFalse() { + PaginatedData paginatedData = mock(PaginatedData.class); + + when(paginatedData.getLastRequestBuilder()).thenReturn(new HttpRequest.Builder()); + when(paginatedData.getLastResponseBody()).thenReturn(null); + + LinkPagination link = new LinkPagination("$response.body#/next/href"); + + assertFalse(link.isValid(paginatedData)); + assertNotNull(link.getNextRequestBuilder()); + + link.getNextRequestBuilder().updateByReference("$request.query#/page", v -> + { + fail(); + return v; + }); + } + + @Test + public void testWithMissingLinkPointer_returnsFalse() { + PaginatedData paginatedData = mock(PaginatedData.class); + + when(paginatedData.getLastRequestBuilder()).thenReturn(new HttpRequest.Builder()); + when(paginatedData.getLastResponseBody()).thenReturn("{\"next\": \"https://api.example.com?page=2\"}"); + + LinkPagination link = new LinkPagination(null); + + assertFalse(link.isValid(paginatedData)); + assertNotNull(link.getNextRequestBuilder()); + + link.getNextRequestBuilder().updateByReference("$request.query#/page", v -> + { + fail(); + return v; + }); + } + + @Test + public void testWithMultipleQueryParams_returnsTrue() { + PaginatedData paginatedData = mock(PaginatedData.class); + + when(paginatedData.getLastRequestBuilder()).thenReturn(new HttpRequest.Builder()); + when(paginatedData.getLastResponseBody()).thenReturn("{\"next\": \"https://api.example.com?page=2&size=5\"}"); + + LinkPagination link = new LinkPagination("$response.body#/next"); + + assertTrue(link.isValid(paginatedData)); + assertNotNull(link.getNextRequestBuilder()); + + link.getNextRequestBuilder().updateByReference("$request.query#/page", v -> + { + assertEquals("2", v); + return v; + }); + + link.getNextRequestBuilder().updateByReference("$request.query#/size", v -> + { + assertEquals("5", v); + return v; + }); + } + + @Test + public void testWithMultipleEncodedQueryParams_returnsTrue() { + PaginatedData paginatedData = mock(PaginatedData.class); + + when(paginatedData.getLastRequestBuilder()).thenReturn(new HttpRequest.Builder()); + when(paginatedData.getLastResponseBody()).thenReturn("{\"next\": \"https://api.example.com?page%20o=2%20a&size%20q=5^%214$#\"}"); + + LinkPagination link = new LinkPagination("$response.body#/next"); + + assertTrue(link.isValid(paginatedData)); + assertNotNull(link.getNextRequestBuilder()); + + link.getNextRequestBuilder().updateByReference("$request.query#/page o", v -> + { + assertEquals("2 a", v); + return v; + }); + + link.getNextRequestBuilder().updateByReference("$request.query#/size q", v -> + { + assertEquals("5^!4$#", v); return v; }); } From 31c075235e120bc0103e0c81b8a7b691f87c6b3c Mon Sep 17 00:00:00 2001 From: Asad Ali <14asadali@gmail.com> Date: Mon, 28 Apr 2025 15:33:21 +0500 Subject: [PATCH 15/20] uses type reference instead of class --- .../java/io/apimatic/core/ResponseHandler.java | 6 ++++-- .../core/types/pagination/PaginatedData.java | 14 ++++++++------ src/test/java/apimatic/core/EndToEndTest.java | 4 +++- 3 files changed, 15 insertions(+), 9 deletions(-) diff --git a/src/main/java/io/apimatic/core/ResponseHandler.java b/src/main/java/io/apimatic/core/ResponseHandler.java index b30e8694..615012ce 100644 --- a/src/main/java/io/apimatic/core/ResponseHandler.java +++ b/src/main/java/io/apimatic/core/ResponseHandler.java @@ -8,6 +8,8 @@ import java.util.regex.Matcher; import java.util.regex.Pattern; +import com.fasterxml.jackson.core.type.TypeReference; + import io.apimatic.core.configurations.http.request.EndpointConfiguration; import io.apimatic.core.types.CoreApiException; import io.apimatic.core.types.pagination.PaginatedData; @@ -344,11 +346,11 @@ public Builder apiRespon * @param the type wrapped by PaginatedIterable. * @return {@link ResponseHandler.Builder}. */ - public Builder paginatedDeserializer(Class pageClass, + public Builder paginatedDeserializer(TypeReference pageType, Function> converter, Function, ?> returnTypeGetter, PaginationDataManager... dataManagers) { this.paginationDeserializer = (res, ec) -> - new PaginatedData(pageClass, converter, res, ec, dataManagers) + new PaginatedData(pageType, converter, res, ec, dataManagers) .convert(returnTypeGetter); return this; } diff --git a/src/main/java/io/apimatic/core/types/pagination/PaginatedData.java b/src/main/java/io/apimatic/core/types/pagination/PaginatedData.java index eb5272e3..44fe0a78 100644 --- a/src/main/java/io/apimatic/core/types/pagination/PaginatedData.java +++ b/src/main/java/io/apimatic/core/types/pagination/PaginatedData.java @@ -8,6 +8,8 @@ import java.util.NoSuchElementException; import java.util.function.Function; +import com.fasterxml.jackson.core.type.TypeReference; + import io.apimatic.core.ApiCall; import io.apimatic.core.ErrorCase; import io.apimatic.core.HttpRequest; @@ -26,12 +28,12 @@ public class PaginatedData implements Iterator { private Response lastResponse; private EndpointConfiguration lastEndpointConfig; - private Class

pageClass; + private TypeReference

pageType; private Function> converter; private PaginationDataManager[] dataManagers; public PaginatedData(PaginatedData paginatedData) { - this.pageClass = paginatedData.pageClass; + this.pageType = paginatedData.pageType; this.converter = paginatedData.converter; this.dataManagers = paginatedData.dataManagers; @@ -43,9 +45,9 @@ public PaginatedData(PaginatedData paginatedData) { this.pages.addAll(paginatedData.pages); } - public PaginatedData(Class

pageClass, Function> converter, Response response, + public PaginatedData(TypeReference

pageType, Function> converter, Response response, EndpointConfiguration config, PaginationDataManager... dataManagers) throws IOException { - this.pageClass = pageClass; + this.pageType = pageType; this.converter = converter; this.dataManagers = dataManagers; @@ -54,7 +56,7 @@ public PaginatedData(Class

pageClass, Function> converter, Respons private void updateUsing(Response response, EndpointConfiguration endpointConfig) throws IOException { String responseBody = response.getBody(); - P page = CoreHelper.deserialize(responseBody, pageClass); + P page = CoreHelper.deserialize(responseBody, pageType); List newData = converter.apply(page); this.lastDataSize = newData.size(); @@ -169,7 +171,7 @@ private void fetchMoreData() { .globalErrorCase(Collections.singletonMap(ErrorCase.DEFAULT, ErrorCase.setReason(null, (reason, context) -> new CoreApiException(reason, context)))) - .nullify404(false).paginatedDeserializer(pageClass, converter, r -> r, dataManagers)) + .nullify404(false).paginatedDeserializer(pageType, converter, r -> r, dataManagers)) .build().execute(); updateUsing(result.lastResponse, result.lastEndpointConfig); diff --git a/src/test/java/apimatic/core/EndToEndTest.java b/src/test/java/apimatic/core/EndToEndTest.java index 57510066..d728b308 100644 --- a/src/test/java/apimatic/core/EndToEndTest.java +++ b/src/test/java/apimatic/core/EndToEndTest.java @@ -24,6 +24,8 @@ import org.mockito.junit.MockitoJUnit; import org.mockito.junit.MockitoRule; +import com.fasterxml.jackson.core.type.TypeReference; + import apimatic.core.exceptions.GlobalTestException; import apimatic.core.mocks.MockCoreConfig; import apimatic.core.type.pagination.RecordPage; @@ -542,7 +544,7 @@ public void onAfterResponse(Context context) { .headerParam(param -> param.key("accept").value("application/json")) .httpMethod(Method.GET)) .responseHandler(responseHandler -> responseHandler - .paginatedDeserializer(RecordPage.class, t -> t.data, r -> r, pagination) + .paginatedDeserializer(new TypeReference() {}, t -> t.data, r -> r, pagination) .nullify404(false) .globalErrorCase(Collections.emptyMap())) .endpointConfiguration( From 33afddf3dc825184def35a69c8664ac7ee554b66 Mon Sep 17 00:00:00 2001 From: Asad Ali <14asadali@gmail.com> Date: Tue, 29 Apr 2025 12:30:38 +0500 Subject: [PATCH 16/20] refactors based on sonar suggestion --- src/main/java/io/apimatic/core/ApiCall.java | 94 +++---- .../io/apimatic/core/ResponseHandler.java | 104 ++++---- .../http/request/EndpointConfiguration.java | 69 ++---- .../core/types/pagination/PaginatedData.java | 37 +-- .../pagination/PaginationDeserializer.java | 12 +- .../apimatic/core/ResponseHandlerTest.java | 234 +++++++++++------- .../request/EndpointConfigurationTest.java | 2 +- 7 files changed, 303 insertions(+), 249 deletions(-) diff --git a/src/main/java/io/apimatic/core/ApiCall.java b/src/main/java/io/apimatic/core/ApiCall.java index d5da862f..73f74db4 100644 --- a/src/main/java/io/apimatic/core/ApiCall.java +++ b/src/main/java/io/apimatic/core/ApiCall.java @@ -8,15 +8,15 @@ import io.apimatic.core.logger.SdkLoggerFactory; import io.apimatic.core.request.async.AsyncExecutor; import io.apimatic.core.types.CoreApiException; +import io.apimatic.coreinterfaces.http.Context; import io.apimatic.coreinterfaces.http.request.Request; import io.apimatic.coreinterfaces.http.response.Response; import io.apimatic.coreinterfaces.logger.ApiLogger; /** - * An API call, or API request, is a message sent to a server asking an API to - * provide a service or information. - * - * @param resource from server. + * An API call, or API request, is a message sent to a server asking an API to provide a service or + * information. + * @param resource from server. * @param in case of a problem or the connection was aborted. */ public final class ApiCall { @@ -27,9 +27,9 @@ public final class ApiCall private final GlobalConfiguration globalConfig; /** - * An instance of {@link Request} + * An instance of {@link HttpRequest.Builder} */ - private final Request request; + private final HttpRequest.Builder requestBuilder; /** * An instance of {@link ResponseHandler.Builder}. @@ -46,55 +46,60 @@ public final class ApiCall */ private final ApiLogger apiLogger; + /** * ApiCall constructor. - * - * @param coreHttpRequest Http request for the api call. - * @param responseHandler the handler for the response. - * @param endpointConfiguration endPoint configuration. + * @param globalConfig the required configuration to built the ApiCall. + * @param requestBuilder Http request builder for the api call. + * @param responseHandler the handler for the response. + * @param coreEndpointConfiguration endPoint configuration. */ - private ApiCall(final Request coreHttpRequest, final ResponseHandler responseHandler, - final EndpointConfiguration endpointConfiguration) { - this.request = coreHttpRequest; + private ApiCall(final GlobalConfiguration globalConfig, + final EndpointConfiguration endpointConfiguration, + final HttpRequest.Builder requestBuilder, + final ResponseHandler responseHandler) { + this.globalConfig = globalConfig; + this.requestBuilder = requestBuilder; this.responseHandler = responseHandler; this.endpointConfiguration = endpointConfiguration; - this.globalConfig = endpointConfiguration.getGlobalConfiguration(); this.apiLogger = SdkLoggerFactory.getLogger(globalConfig.getLoggingConfiguration()); } /** * Execute the ApiCall and returns the expected response. - * * @return instance of ResponseType. - * @throws IOException Signals that an I/O exception of some sort has - * occurred. + * @throws IOException Signals that an I/O exception of some sort has occurred. * @throws ExceptionType Represents error response from the server. */ public ResponseType execute() throws IOException, ExceptionType { + Request request = requestBuilder.build(globalConfig); apiLogger.logRequest(request); - Response httpResponse = globalConfig.getHttpClient().execute(request, endpointConfiguration); - apiLogger.logResponse(httpResponse); + Response response = globalConfig.getHttpClient().execute(request, endpointConfiguration); + apiLogger.logResponse(response); + + Context context = globalConfig.getCompatibilityFactory() + .createHttpContext(request, response); - return responseHandler.handle(request, httpResponse, endpointConfiguration); + return responseHandler.handle(context, endpointConfiguration, globalConfig, requestBuilder); } /** - * Execute the Api call asynchronously and returns the expected response in - * CompletableFuture. - * + * Execute the Api call asynchronously and returns the expected response in CompletableFuture. * @return the instance of {@link CompletableFuture}. */ public CompletableFuture executeAsync() { - return AsyncExecutor.makeHttpCallAsync(() -> request, + return AsyncExecutor.makeHttpCallAsync(() -> requestBuilder.build(globalConfig), request -> globalConfig.getHttpClient().executeAsync(request, endpointConfiguration), - (httpRequest, httpResponse) -> responseHandler.handle(httpRequest, httpResponse, endpointConfiguration), - apiLogger); + (request, response) -> { + Context context = globalConfig.getCompatibilityFactory() + .createHttpContext(request, response); + return responseHandler.handle(context, endpointConfiguration, globalConfig, requestBuilder); + }, apiLogger); } /** * Builder class for the {@link ApiCall} class. - * - * @param resource from server. + * @param resource from server. * @param Represents error response from the server. */ public static class Builder { @@ -111,12 +116,14 @@ public static class Builder responseHandlerBuilder = new ResponseHandler.Builder(); + private ResponseHandler.Builder responseHandlerBuilder = + new ResponseHandler.Builder(); /** * An instance of {@link EndpointConfiguration.Builder}. */ - private EndpointConfiguration.Builder endpointConfigurationBuilder = new EndpointConfiguration.Builder(); + private EndpointConfiguration.Builder endpointConfigurationBuilder = + new EndpointConfiguration.Builder(); /** * @param globalConfig the configuration of Http Request. @@ -131,18 +138,20 @@ public Builder globalConfig(GlobalConfiguration glo * @param action requestBuilder {@link Consumer}. * @return {@link ApiCall.Builder}. */ - public Builder requestBuilder(Consumer action) { + public Builder requestBuilder( + Consumer action) { requestBuilder = new HttpRequest.Builder(); action.accept(requestBuilder); return this; } /** - * @param requestBuilder request builder instance. + * @param builder requestBuilder {@link HttpRequest.Builder}. * @return {@link ApiCall.Builder}. */ - public Builder requestBuilder(HttpRequest.Builder requestBuilder) { - this.requestBuilder = requestBuilder; + public Builder requestBuilder( + HttpRequest.Builder builder) { + requestBuilder = builder; return this; } @@ -169,27 +178,24 @@ public Builder endpointConfiguration( } /** - * @param endpointConfiguration end point configuration instance. + * @param builder endpointConfigurationBuilder {@link EndpointConfiguration.Builder}. * @return {@link ApiCall.Builder}. */ public Builder endpointConfiguration( - EndpointConfiguration endpointConfiguration) { - endpointConfigurationBuilder = new EndpointConfiguration.Builder() - .arraySerializationFormat(endpointConfiguration.getArraySerializationFormat()) - .hasBinaryResponse(endpointConfiguration.hasBinaryResponse()) - .retryOption(endpointConfiguration.getRetryOption()); + EndpointConfiguration.Builder builder) { + endpointConfigurationBuilder = builder; return this; } /** * build the {@link ApiCall}. - * * @return the instance of {@link ApiCall}. * @throws IOException Signals that an I/O exception of some sort has occurred. */ public ApiCall build() throws IOException { - return new ApiCall(requestBuilder.build(globalConfig), - responseHandlerBuilder.build(), endpointConfigurationBuilder.build(globalConfig, requestBuilder)); + return new ApiCall(globalConfig, + endpointConfigurationBuilder.build(), requestBuilder, + responseHandlerBuilder.build()); } } -} +} \ No newline at end of file diff --git a/src/main/java/io/apimatic/core/ResponseHandler.java b/src/main/java/io/apimatic/core/ResponseHandler.java index 615012ce..7b49ddf0 100644 --- a/src/main/java/io/apimatic/core/ResponseHandler.java +++ b/src/main/java/io/apimatic/core/ResponseHandler.java @@ -18,7 +18,6 @@ import io.apimatic.core.utilities.CoreHelper; import io.apimatic.coreinterfaces.compatibility.CompatibilityFactory; import io.apimatic.coreinterfaces.http.Context; -import io.apimatic.coreinterfaces.http.request.Request; import io.apimatic.coreinterfaces.http.request.ResponseClassType; import io.apimatic.coreinterfaces.http.response.Response; import io.apimatic.coreinterfaces.type.functional.ContextInitializer; @@ -116,27 +115,27 @@ private ResponseHandler(final Map> localErrorCa /** * Processes an HttpResponse and returns some value corresponding to that * response. - * - * @param httpRequest Request which is made for endpoint. - * @param httpResponse Response which is received after execution. - * @param endpointConfiguration All endPoint level configuration. + * + * @param context Context which is made for endpoint. + * @param config All endPoint level configuration. + * @param globalConfig All apiCall level configuration. + * @param requestBuilder The requestBuilder that can re create current API Call. + * * @return An object of type ResponseType. * @throws IOException Signals that an I/O exception of some sort has * occurred. * @throws ExceptionType Represents error response from the server. */ - public ResponseType handle(Request httpRequest, Response httpResponse, EndpointConfiguration config) + public ResponseType handle(Context context, EndpointConfiguration config, + GlobalConfiguration globalConfig, HttpRequest.Builder requestBuilder) throws IOException, ExceptionType { - - Context httpContext = config.getGlobalConfiguration().getCompatibilityFactory().createHttpContext(httpRequest, - httpResponse); - // invoke the callback after response if its not null - if (config.getGlobalConfiguration().getHttpCallback() != null) { - config.getGlobalConfiguration().getHttpCallback().onAfterResponse(httpContext); + if (globalConfig.getHttpCallback() != null) { + // invoke the callback after response if its not null + globalConfig.getHttpCallback().onAfterResponse(context); } if (isNullify404Enabled) { - int responseCode = httpContext.getResponse().getStatusCode(); + int responseCode = context.getResponse().getStatusCode(); // return null on 404 if (responseCode == NOT_FOUND_STATUS_CODE) { return null; @@ -144,16 +143,18 @@ public ResponseType handle(Request httpRequest, Response httpResponse, EndpointC } // handle errors defined at the API level - validateResponse(httpContext); + validateResponse(context); - Object result = convertResponse(httpResponse, config); + Object result = convertResponse(context.getResponse(), requestBuilder, config, + globalConfig); if (responseClassType == ResponseClassType.API_RESPONSE || responseClassType == ResponseClassType.DYNAMIC_API_RESPONSE) { - return createApiResponse(httpResponse, config.getGlobalConfiguration().getCompatibilityFactory(), result); + return createApiResponse(context.getResponse(), globalConfig.getCompatibilityFactory(), + result); } - return applyContextInitializer(httpContext, result); + return applyContextInitializer(context, result); } @SuppressWarnings("unchecked") @@ -164,10 +165,11 @@ private ResponseType applyContextInitializer(Context httpContext, Object result) return (ResponseType) result; } - private Object convertResponse(Response response, EndpointConfiguration config) throws IOException { + private Object convertResponse(Response response, HttpRequest.Builder requestBuilder, + EndpointConfiguration config, GlobalConfiguration globalConfig) throws IOException { if (responseClassType == ResponseClassType.DYNAMIC_RESPONSE || responseClassType == ResponseClassType.DYNAMIC_API_RESPONSE) { - return createDynamicResponse(response, config.getGlobalConfiguration().getCompatibilityFactory()); + return createDynamicResponse(response, globalConfig.getCompatibilityFactory()); } if (config.hasBinaryResponse()) { @@ -183,19 +185,20 @@ private Object convertResponse(Response response, EndpointConfiguration config) } if (paginationDeserializer != null) { - return paginationDeserializer.apply(response, config); + return paginationDeserializer.apply(config, globalConfig, requestBuilder, response); } return null; } - private Object createDynamicResponse(Response httpResponse, CompatibilityFactory compatibilityFactory) { + private Object createDynamicResponse(Response httpResponse, + CompatibilityFactory compatibilityFactory) { return compatibilityFactory.createDynamicResponse(httpResponse); } @SuppressWarnings("unchecked") - private ResponseType createApiResponse(Response httpResponse, CompatibilityFactory compatibilityFactory, - Object innerValue) { + private ResponseType createApiResponse(Response httpResponse, + CompatibilityFactory compatibilityFactory, Object innerValue) { return (ResponseType) compatibilityFactory.createApiResponse(httpResponse.getStatusCode(), httpResponse.getHeaders(), innerValue); } @@ -203,7 +206,7 @@ private ResponseType createApiResponse(Response httpResponse, CompatibilityFacto /** * Validate the response and check that response contains the error code and * throw the corresponding exception - * + * * @param httpContext * @throws ExceptionType */ @@ -220,8 +223,8 @@ private void validateResponse(Context httpContext) throws ExceptionType { } } - private void throwConfiguredException(Map> errorCases, String errorCode, - Context httpContext) throws ExceptionType { + private void throwConfiguredException(Map> errorCases, + String errorCode, Context httpContext) throws ExceptionType { String defaultErrorCode = ""; Matcher match = Pattern.compile("^[(4|5)[0-9]]{3}").matcher(errorCode); if (match.find()) { @@ -287,7 +290,7 @@ public static class Builder localErrorCase(String statusCode, /** * Setter for the globalErrorCases. - * + * * @param globalErrorCases the global error cases for endpoints. * @return {@link ResponseHandler.Builder}. */ @@ -316,48 +319,55 @@ public Builder globalErrorCase( /** * Setter for the deserializer. - * + * * @param deserializer to deserialize the server response. * @return {@link ResponseHandler.Builder}. */ - public Builder deserializer(Deserializer deserializer) { + public Builder deserializer( + Deserializer deserializer) { this.deserializer = deserializer; return this; } /** * Setter for the deserializer. - * - * @param intermediateDeserializer to deserialize the api response. + * + * @param intermediateDeserializer to deserialize the api response. * @param the intermediate type of api response. * @return {@link ResponseHandler.Builder}. */ - public Builder apiResponseDeserializer( - Deserializer intermediateDeserializer) { + public Builder + apiResponseDeserializer( + Deserializer intermediateDeserializer) { this.intermediateDeserializer = intermediateDeserializer; return this; } /** * Setter for the deserializer to be used in pagination wrapper. - * - * @param converter to deserialize the server response. + * + * @param pageType TypeReference representing the type. + * @param converter Converter to convert Page into the list of InnerType. + * @param returnTypeGetter Converter to convert PaginatedData into the return type. + * @param dataManagers List of pagination data managers. * @param the type of the outer page. * @param the type wrapped by PaginatedIterable. + * * @return {@link ResponseHandler.Builder}. */ - public Builder paginatedDeserializer(TypeReference pageType, - Function> converter, Function, ?> returnTypeGetter, + public Builder paginatedDeserializer( + TypeReference pageType, Function> converter, + Function, ?> returnTypeGetter, PaginationDataManager... dataManagers) { - this.paginationDeserializer = (res, ec) -> - new PaginatedData(pageType, converter, res, ec, dataManagers) - .convert(returnTypeGetter); + this.paginationDeserializer = (config, globalConfig, reqBuilder, res) -> + new PaginatedData(config, globalConfig, reqBuilder, res, + pageType, converter, dataManagers).convert(returnTypeGetter); return this; } /** * Setter for the responseClassType. - * + * * @param responseClassType specify the response class type for result. * @return {@link ResponseHandler.Builder}. */ @@ -368,7 +378,7 @@ public Builder responseClassType(ResponseClassType /** * Setter for the {@link ContextInitializer}. - * + * * @param contextInitializer the context initializer in response models. * @return {@link ResponseHandler.Builder}. */ @@ -380,7 +390,7 @@ public Builder contextInitializer( /** * Setter for the nullify404. - * + * * @param isNullify404Enabled in case of 404 error return null or not. * @return {@link ResponseHandler.Builder}. */ @@ -391,7 +401,7 @@ public Builder nullify404(boolean isNullify404Enabl /** * Setter for the isNullableResponseType. - * + * * @param isNullableResponseType in case of nullable response type. * @return {@link ResponseHandler.Builder}. */ @@ -401,8 +411,8 @@ public Builder nullableResponseType(boolean isNulla } /** - * build the ResponseHandler. - * + * Build the ResponseHandler. + * * @return the instance of {@link ResponseHandler}. */ public ResponseHandler build() { diff --git a/src/main/java/io/apimatic/core/configurations/http/request/EndpointConfiguration.java b/src/main/java/io/apimatic/core/configurations/http/request/EndpointConfiguration.java index 6bc50a1f..cbcc01ae 100644 --- a/src/main/java/io/apimatic/core/configurations/http/request/EndpointConfiguration.java +++ b/src/main/java/io/apimatic/core/configurations/http/request/EndpointConfiguration.java @@ -1,7 +1,5 @@ package io.apimatic.core.configurations.http.request; -import io.apimatic.core.GlobalConfiguration; -import io.apimatic.core.HttpRequest; import io.apimatic.coreinterfaces.http.request.ArraySerializationFormat; import io.apimatic.coreinterfaces.http.request.configuration.CoreEndpointConfiguration; import io.apimatic.coreinterfaces.http.request.configuration.RetryOption; @@ -27,34 +25,19 @@ public class EndpointConfiguration implements CoreEndpointConfiguration { private final ArraySerializationFormat arraySerializationFormat; /** - * GlobalConfiguration applicable along with this EndpointConfiguration. - */ - private final GlobalConfiguration globalConfig; - - /** - * Mutable request builder for HTTP request re initialization. - */ - private final HttpRequest.Builder requestBuilder; - - /** - * @param hasBinary A boolean variable for binary response. - * @param retryOption Retry options enumeration for HTTP request. - * @param arraySerializationFormat Enumeration for all ArraySerialization - * formats. + * @param hasBinary A boolean variable for binary response. + * @param retryOption Retry options enumeration for HTTP request. + * @param arraySerializationFormat Enumeration for all ArraySerialization formats. */ public EndpointConfiguration(final boolean hasBinary, final RetryOption retryOption, - final ArraySerializationFormat arraySerializationFormat, final GlobalConfiguration globalConfig, - final HttpRequest.Builder requestBuilder) { + final ArraySerializationFormat arraySerializationFormat) { this.hasBinaryResponse = hasBinary; this.retryOption = retryOption; this.arraySerializationFormat = arraySerializationFormat; - this.globalConfig = globalConfig; - this.requestBuilder = requestBuilder; } /** * Retry options enumeration for HTTP request - * * @return the option for the retries {@link RetryOption}. */ public RetryOption getRetryOption() { @@ -63,7 +46,6 @@ public RetryOption getRetryOption() { /** * Endpoint response has the binary response or not. - * * @return the response is binary or not. */ public boolean hasBinaryResponse() { @@ -72,7 +54,6 @@ public boolean hasBinaryResponse() { /** * Enumeration for all ArraySerialization formats - * * @return the array serialization format. */ public ArraySerializationFormat getArraySerializationFormat() { @@ -80,21 +61,15 @@ public ArraySerializationFormat getArraySerializationFormat() { } /** - * GlobalConfiguration applicable along with this EndpointConfiguration. - * - * @return the global configuration. - */ - public GlobalConfiguration getGlobalConfiguration() { - return globalConfig; - } - - /** - * Mutable request builder for HTTP request re initialization. - * - * @return the request builder instance. + * Creates a builder initialized with the values from this instance. + * + * @return a new {@link Builder} pre-populated with this configuration's values. */ - public HttpRequest.Builder getRequestBuilder() { - return requestBuilder; + public Builder toBuilder() { + return new Builder() + .hasBinaryResponse(this.hasBinaryResponse) + .retryOption(this.retryOption) + .arraySerializationFormat(this.arraySerializationFormat); } public static class Builder { @@ -111,11 +86,11 @@ public static class Builder { /** * Enumeration for all ArraySerialization formats. */ - private ArraySerializationFormat arraySerializationFormat = ArraySerializationFormat.INDEXED; + private ArraySerializationFormat arraySerializationFormat = + ArraySerializationFormat.INDEXED; /** * Setter for the binary response. - * * @param hasBinary end point may have binary response. * @return {@link EndpointConfiguration.Builder}. */ @@ -126,7 +101,6 @@ public Builder hasBinaryResponse(boolean hasBinary) { /** * Setter for the {@link RetryOption}. - * * @param retryOption Retry options enumeration for HTTP request. * @return {@link EndpointConfiguration.Builder}. */ @@ -137,9 +111,7 @@ public Builder retryOption(RetryOption retryOption) { /** * Setter for the arraySerializationFormat. - * - * @param arraySerializationFormat Enumeration for all ArraySerialization - * formats. + * @param arraySerializationFormat Enumeration for all ArraySerialization formats. * @return {@link EndpointConfiguration.Builder}. */ public Builder arraySerializationFormat(ArraySerializationFormat arraySerializationFormat) { @@ -149,14 +121,11 @@ public Builder arraySerializationFormat(ArraySerializationFormat arraySerializat /** * Initialise the {@link EndpointConfiguration}. - * - * @param globalConfig - * @param requestBuilder * @return the {@link EndpointConfiguration} instance. */ - public EndpointConfiguration build(GlobalConfiguration globalConfig, HttpRequest.Builder requestBuilder) { - return new EndpointConfiguration(hasBinaryResponse, retryOption, arraySerializationFormat, globalConfig, - requestBuilder); + public EndpointConfiguration build() { + return new EndpointConfiguration(hasBinaryResponse, retryOption, + arraySerializationFormat); } } -} +} \ No newline at end of file diff --git a/src/main/java/io/apimatic/core/types/pagination/PaginatedData.java b/src/main/java/io/apimatic/core/types/pagination/PaginatedData.java index 44fe0a78..b89c72a6 100644 --- a/src/main/java/io/apimatic/core/types/pagination/PaginatedData.java +++ b/src/main/java/io/apimatic/core/types/pagination/PaginatedData.java @@ -12,6 +12,7 @@ import io.apimatic.core.ApiCall; import io.apimatic.core.ErrorCase; +import io.apimatic.core.GlobalConfiguration; import io.apimatic.core.HttpRequest; import io.apimatic.core.configurations.http.request.EndpointConfiguration; import io.apimatic.core.types.CoreApiException; @@ -26,49 +27,57 @@ public class PaginatedData implements Iterator { private List

pages = new ArrayList

(); private int lastDataSize; private Response lastResponse; - private EndpointConfiguration lastEndpointConfig; + private HttpRequest.Builder lastRequestBuilder; private TypeReference

pageType; private Function> converter; private PaginationDataManager[] dataManagers; + private EndpointConfiguration endpointConfig; + private GlobalConfiguration globalConfig; public PaginatedData(PaginatedData paginatedData) { this.pageType = paginatedData.pageType; this.converter = paginatedData.converter; this.dataManagers = paginatedData.dataManagers; + this.endpointConfig = paginatedData.endpointConfig; + this.globalConfig = paginatedData.globalConfig; this.lastDataSize = paginatedData.lastDataSize; this.lastResponse = paginatedData.lastResponse; - this.lastEndpointConfig = paginatedData.lastEndpointConfig; + this.lastRequestBuilder = paginatedData.lastRequestBuilder; this.data.addAll(paginatedData.data); this.pages.addAll(paginatedData.pages); } - public PaginatedData(TypeReference

pageType, Function> converter, Response response, - EndpointConfiguration config, PaginationDataManager... dataManagers) throws IOException { + public PaginatedData(EndpointConfiguration config, GlobalConfiguration globalConfig, + HttpRequest.Builder requestBuilder, Response response, TypeReference

pageType, + Function> converter, PaginationDataManager... dataManagers) throws IOException { this.pageType = pageType; this.converter = converter; this.dataManagers = dataManagers; + this.endpointConfig = config; + this.globalConfig = globalConfig; - updateUsing(response, config); + updateUsing(response, requestBuilder); } - private void updateUsing(Response response, EndpointConfiguration endpointConfig) throws IOException { + private void updateUsing(Response response, HttpRequest.Builder requestBuilder) + throws IOException { String responseBody = response.getBody(); P page = CoreHelper.deserialize(responseBody, pageType); List newData = converter.apply(page); this.lastDataSize = newData.size(); this.lastResponse = response; - this.lastEndpointConfig = endpointConfig; + this.lastRequestBuilder = requestBuilder; this.data.addAll(newData); this.pages.add(page); } public HttpRequest.Builder getLastRequestBuilder() { - return lastEndpointConfig.getRequestBuilder(); + return lastRequestBuilder; } public String getLastResponseBody() { @@ -162,11 +171,11 @@ private void fetchMoreData() { } try { - PaginatedData result = new ApiCall.Builder, CoreApiException>() - .endpointConfiguration(lastEndpointConfig).globalConfig( - lastEndpointConfig.getGlobalConfiguration()) - .requestBuilder( - manager.getNextRequestBuilder()) + PaginatedData result = + new ApiCall.Builder, CoreApiException>() + .endpointConfiguration(endpointConfig.toBuilder()) + .globalConfig(globalConfig) + .requestBuilder(manager.getNextRequestBuilder()) .responseHandler(res -> res .globalErrorCase(Collections.singletonMap(ErrorCase.DEFAULT, ErrorCase.setReason(null, @@ -174,7 +183,7 @@ private void fetchMoreData() { .nullify404(false).paginatedDeserializer(pageType, converter, r -> r, dataManagers)) .build().execute(); - updateUsing(result.lastResponse, result.lastEndpointConfig); + updateUsing(result.lastResponse, result.lastRequestBuilder); return; } catch (Exception e) { continue; diff --git a/src/main/java/io/apimatic/core/types/pagination/PaginationDeserializer.java b/src/main/java/io/apimatic/core/types/pagination/PaginationDeserializer.java index 299b1c87..3ea844e0 100644 --- a/src/main/java/io/apimatic/core/types/pagination/PaginationDeserializer.java +++ b/src/main/java/io/apimatic/core/types/pagination/PaginationDeserializer.java @@ -2,23 +2,27 @@ import java.io.IOException; +import io.apimatic.core.GlobalConfiguration; +import io.apimatic.core.HttpRequest; import io.apimatic.core.configurations.http.request.EndpointConfiguration; import io.apimatic.coreinterfaces.http.response.Response; /** * Functional Interface to apply the deserializer function. - * - * @param The type of the response to deserialize into. */ @FunctionalInterface public interface PaginationDeserializer { /** * Apply the deserialization function and returns the ResponseType response. * + * @param config The EndPoint configuration for paginated API Calls. + * @param globalConfig The EndPoint configuration for paginated API Calls. + * @param requestBuilder The requestBuilder to re create current API Call. * @param response The response of current API Call. - * @param config The EndPoint configuration for paginated API Calls. + * * @return The deserialized data. * @throws IOException Exception to be thrown while applying the function. */ - Object apply(Response response, EndpointConfiguration config) throws IOException; + Object apply(EndpointConfiguration config, GlobalConfiguration globalConfig, + HttpRequest.Builder requestBuilder, Response response) throws IOException; } \ No newline at end of file diff --git a/src/test/java/apimatic/core/ResponseHandlerTest.java b/src/test/java/apimatic/core/ResponseHandlerTest.java index 06f71c53..d29d2aa8 100644 --- a/src/test/java/apimatic/core/ResponseHandlerTest.java +++ b/src/test/java/apimatic/core/ResponseHandlerTest.java @@ -23,6 +23,7 @@ import apimatic.core.models.TestModel; import io.apimatic.core.ApiCall; import io.apimatic.core.ErrorCase; +import io.apimatic.core.HttpRequest; import io.apimatic.core.ResponseHandler; import io.apimatic.core.configurations.http.request.EndpointConfiguration; import io.apimatic.core.types.CoreApiException; @@ -109,6 +110,12 @@ public class ResponseHandlerTest extends MockCoreConfig { @Mock private EndpointConfiguration endpointSetting; + /** + * Mock of {@link HttpRequest.Builder}. + */ + @Mock + private HttpRequest.Builder requestBuilder; + /** * Mock of {@link ResponseHandler}. */ @@ -157,30 +164,26 @@ public class ResponseHandlerTest extends MockCoreConfig { @Before public void setup() throws IOException { setExpectations(); - prepareMockEndpointConfiguration(); - } - - private void prepareMockEndpointConfiguration() { - // stubs - when(endpointSetting.getGlobalConfiguration()).thenReturn(getMockGlobalConfig()); } @Test public void testDeserializerMethod() throws IOException, CoreApiException { - ResponseHandler coreResponseHandler = new ResponseHandler.Builder() - .deserializer(string -> new String(string)).build(); + ResponseHandler coreResponseHandler = + new ResponseHandler.Builder() + .deserializer(string -> new String(string)).build(); // stub when(coreHttpResponse.getStatusCode()).thenReturn(SUCCESS_CODE); when(coreHttpResponse.getBody()).thenReturn("bodyValue"); // verify - assertEquals(coreResponseHandler.handle(getCoreHttpRequest(), coreHttpResponse, endpointSetting), "bodyValue"); + assertEquals(coreResponseHandler.handle(context, endpointSetting, + getMockGlobalConfig(), requestBuilder), "bodyValue"); } @Test public void testDeserializerMethodUsingRawBody() throws IOException, CoreApiException { - ResponseHandler coreResponseHandler = new ResponseHandler.Builder() - .build(); + ResponseHandler coreResponseHandler = + new ResponseHandler.Builder().build(); String responseString = "bodyValue"; InputStream inputStream = new ByteArrayInputStream(responseString.getBytes()); @@ -191,14 +194,19 @@ public void testDeserializerMethodUsingRawBody() throws IOException, CoreApiExce when(coreHttpResponse.getRawBody()).thenReturn(inputStream); // verify - assertEquals(coreResponseHandler.handle(getCoreHttpRequest(), coreHttpResponse, endpointSetting), inputStream); + assertEquals(coreResponseHandler.handle(context, endpointSetting, + getMockGlobalConfig(), requestBuilder), inputStream); } + @Test public void testDeserializerMethodUsingContext() throws IOException, CoreApiException { - ResponseHandler coreResponseHandler = new ResponseHandler.Builder() - .deserializer(string -> CoreHelper.deserialize(string, TestModel.class)) - .contextInitializer((context, response) -> response.toBuilder().httpContext(context).build()).build(); + ResponseHandler coreResponseHandler = + new ResponseHandler.Builder() + .deserializer(string -> CoreHelper.deserialize(string, TestModel.class)) + .contextInitializer((context, response) -> response.toBuilder() + .httpContext(context).build()) + .build(); TestModel model = new TestModel.Builder("ali", "DEV").httpContext(context).build(); // stub @@ -206,40 +214,46 @@ public void testDeserializerMethodUsingContext() throws IOException, CoreApiExce when(coreHttpResponse.getBody()).thenReturn("{\"name\" : \"ali\", \"field\" : \"DEV\"}"); // verify - assertEquals(coreResponseHandler.handle(getCoreHttpRequest(), coreHttpResponse, endpointSetting).getContext(), - model.getContext()); + assertEquals(coreResponseHandler.handle(context, endpointSetting, + getMockGlobalConfig(), requestBuilder).getContext(), model.getContext()); } @Test public void testContextWithoutDeserializer() throws IOException, CoreApiException { - ResponseHandler coreResponseHandler = new ResponseHandler.Builder() - .contextInitializer((context, response) -> response.toBuilder().httpContext(context).build()).build(); + ResponseHandler coreResponseHandler = + new ResponseHandler.Builder().contextInitializer( + (context, response) -> response.toBuilder().httpContext(context).build()) + .build(); // stub when(coreHttpResponse.getStatusCode()).thenReturn(SUCCESS_CODE); when(coreHttpResponse.getBody()).thenReturn("{\"name\" : \"ali\", \"field\" : \"DEV\"}"); // verify - assertNull(coreResponseHandler.handle(getCoreHttpRequest(), coreHttpResponse, endpointSetting)); + assertNull(coreResponseHandler.handle(context, endpointSetting, + getMockGlobalConfig(), requestBuilder)); } @Test public void testDynamicResponseTypeMethod() throws IOException, CoreApiException { - ResponseHandler coreResponseHandler = new ResponseHandler.Builder() - .responseClassType(ResponseClassType.DYNAMIC_RESPONSE).build(); + ResponseHandler coreResponseHandler = + new ResponseHandler.Builder() + .responseClassType(ResponseClassType.DYNAMIC_RESPONSE).build(); // stub when(coreHttpResponse.getStatusCode()).thenReturn(SUCCESS_CODE); when(coreHttpResponse.getBody()).thenReturn("bodyValue"); // verify - assertEquals(coreResponseHandler.handle(getCoreHttpRequest(), coreHttpResponse, endpointSetting), dynamicType); + assertEquals(coreResponseHandler.handle(context, endpointSetting, + getMockGlobalConfig(), requestBuilder), dynamicType); } @Test public void testApiResponseTypeMethod() throws IOException, CoreApiException { - ResponseHandler, CoreApiException> coreResponseHandler = new ResponseHandler.Builder, CoreApiException>() - .responseClassType(ResponseClassType.API_RESPONSE) - .apiResponseDeserializer(response -> new String(response)).build(); + ResponseHandler, CoreApiException> coreResponseHandler = + new ResponseHandler.Builder, CoreApiException>() + .responseClassType(ResponseClassType.API_RESPONSE) + .apiResponseDeserializer(response -> new String(response)).build(); // stub when(coreHttpResponse.getStatusCode()).thenReturn(CREATED_SUCCESS_CODE); when(coreHttpResponse.getHeaders()).thenReturn(getHttpHeaders()); @@ -247,55 +261,66 @@ public void testApiResponseTypeMethod() throws IOException, CoreApiException { when(stringApiResponseType.getResult()).thenReturn("bodyValue"); // verify - assertEquals(coreResponseHandler.handle(getCoreHttpRequest(), coreHttpResponse, endpointSetting).getResult(), + assertEquals( + coreResponseHandler.handle(context, endpointSetting, + getMockGlobalConfig(), requestBuilder).getResult(), stringApiResponseType.getResult()); } @Test public void testDynamicApiResponseTypeMethod() throws IOException, CoreApiException { - ResponseHandler, CoreApiException> coreResponseHandler = new ResponseHandler.Builder, CoreApiException>() - .responseClassType(ResponseClassType.DYNAMIC_API_RESPONSE).build(); + ResponseHandler, CoreApiException> coreResponseHandler = + new ResponseHandler.Builder, CoreApiException>() + .responseClassType(ResponseClassType.DYNAMIC_API_RESPONSE).build(); // stub when(coreHttpResponse.getStatusCode()).thenReturn(CREATED_SUCCESS_CODE); when(coreHttpResponse.getHeaders()).thenReturn(getHttpHeaders()); when(dynamicApiResponseType.getResult()).thenReturn(dynamicType); // verify - assertEquals(coreResponseHandler.handle(getCoreHttpRequest(), coreHttpResponse, endpointSetting).getResult(), + assertEquals( + coreResponseHandler.handle(context, endpointSetting, + getMockGlobalConfig(), requestBuilder).getResult(), dynamicApiResponseType.getResult()); } @Test public void testDefaultTypeMethod() throws IOException, CoreApiException { - ResponseHandler coreResponseHandler = new ResponseHandler.Builder() - .build(); + ResponseHandler coreResponseHandler = + new ResponseHandler.Builder().build(); // stub when(coreHttpResponse.getStatusCode()).thenReturn(CREATED_SUCCESS_CODE); // verify - assertNull(coreResponseHandler.handle(getCoreHttpRequest(), coreHttpResponse, endpointSetting)); + assertNull(coreResponseHandler.handle(context, endpointSetting, + getMockGlobalConfig(), requestBuilder)); } @Test public void testNullify404() throws IOException, CoreApiException { - ResponseHandler coreResponseHandler = new ResponseHandler.Builder() - .nullify404(true).build(); + ResponseHandler coreResponseHandler = + new ResponseHandler.Builder().nullify404(true).build(); // stub when(coreHttpResponse.getStatusCode()).thenReturn(NOT_FOUND_STATUS_CODE); - when(getCompatibilityFactory().createHttpContext(getCoreHttpRequest(), coreHttpResponse)).thenReturn(context); + when(getCompatibilityFactory().createHttpContext(getCoreHttpRequest(), coreHttpResponse)) + .thenReturn(context); + // verify - assertNull(coreResponseHandler.handle(getCoreHttpRequest(), coreHttpResponse, endpointSetting)); + assertNull(coreResponseHandler.handle(context, endpointSetting, + getMockGlobalConfig(), requestBuilder)); } @Test public void testNullify404False() throws IOException, CoreApiException { - ResponseHandler coreResponseHandler = new ResponseHandler.Builder() - .nullify404(false).globalErrorCase(getGlobalErrorCases()).build(); + ResponseHandler coreResponseHandler = + new ResponseHandler.Builder().nullify404(false) + .globalErrorCase(getGlobalErrorCases()).build(); // stub when(coreHttpResponse.getStatusCode()).thenReturn(NOT_FOUND_STATUS_CODE); CoreApiException apiException = assertThrows(CoreApiException.class, () -> { - coreResponseHandler.handle(getCoreHttpRequest(), coreHttpResponse, endpointSetting); + coreResponseHandler.handle(context, endpointSetting, + getMockGlobalConfig(), requestBuilder); }); String expectedMessage = "Not found"; @@ -306,65 +331,76 @@ public void testNullify404False() throws IOException, CoreApiException { @Test public void testNullableResponseType() throws IOException, CoreApiException { - ResponseHandler coreResponseHandler = new ResponseHandler.Builder() - .nullableResponseType(true).build(); + ResponseHandler coreResponseHandler = + new ResponseHandler.Builder() + .nullableResponseType(true).build(); // stub when(coreHttpResponse.getStatusCode()).thenReturn(SUCCESS_CODE); when(coreHttpResponse.getBody()).thenReturn(null); - when(getCompatibilityFactory().createHttpContext(getCoreHttpRequest(), coreHttpResponse)).thenReturn(context); + when(getCompatibilityFactory().createHttpContext(getCoreHttpRequest(), coreHttpResponse)) + .thenReturn(context); // verify - assertNull(coreResponseHandler.handle(getCoreHttpRequest(), coreHttpResponse, endpointSetting)); + assertNull(coreResponseHandler.handle(context, endpointSetting, + getMockGlobalConfig(), requestBuilder)); when(coreHttpResponse.getBody()).thenReturn(""); // verify - assertNull(coreResponseHandler.handle(getCoreHttpRequest(), coreHttpResponse, endpointSetting)); + assertNull(coreResponseHandler.handle(context, endpointSetting, + getMockGlobalConfig(), requestBuilder)); when(coreHttpResponse.getBody()).thenReturn(" "); // verify - assertNull(coreResponseHandler.handle(getCoreHttpRequest(), coreHttpResponse, endpointSetting)); + assertNull(coreResponseHandler.handle(context, endpointSetting, + getMockGlobalConfig(), requestBuilder)); } @Test public void testNullableResponseTypeFalse() throws IOException, CoreApiException { - ResponseHandler coreResponseHandler = new ResponseHandler.Builder() - .nullableResponseType(false).deserializer(response -> Integer.parseInt(response)) - .globalErrorCase(getGlobalErrorCases()).build(); + ResponseHandler coreResponseHandler = + new ResponseHandler.Builder().nullableResponseType(false) + .deserializer(response -> Integer.parseInt(response)) + .globalErrorCase(getGlobalErrorCases()).build(); // stub when(coreHttpResponse.getStatusCode()).thenReturn(SUCCESS_CODE); String body = "50"; when(coreHttpResponse.getBody()).thenReturn(body); // verify - assertEquals(Integer.valueOf(body), - coreResponseHandler.handle(getCoreHttpRequest(), coreHttpResponse, endpointSetting)); + assertEquals(Integer.valueOf(body), coreResponseHandler.handle(context, endpointSetting, + getMockGlobalConfig(), requestBuilder)); when(coreHttpResponse.getBody()).thenReturn(""); assertThrows(NumberFormatException.class, () -> { - coreResponseHandler.handle(getCoreHttpRequest(), coreHttpResponse, endpointSetting); + coreResponseHandler.handle(context, endpointSetting, + getMockGlobalConfig(), requestBuilder); }); when(coreHttpResponse.getBody()).thenReturn(" "); assertThrows(NumberFormatException.class, () -> { - coreResponseHandler.handle(getCoreHttpRequest(), coreHttpResponse, endpointSetting); + coreResponseHandler.handle(context, endpointSetting, + getMockGlobalConfig(), requestBuilder); }); } @Test public void testLocalException() throws IOException, CoreApiException { - ResponseHandler coreResponseHandler = new ResponseHandler.Builder() - .localErrorCase("403", - ErrorCase.setReason("Forbidden", (reason, context) -> new CoreApiException(reason, context))) - .build(); + ResponseHandler coreResponseHandler = + new ResponseHandler.Builder() + .localErrorCase("403", + ErrorCase.setReason("Forbidden", + (reason, context) -> new CoreApiException(reason, context))) + .build(); // stub when(coreHttpResponse.getStatusCode()).thenReturn(FORBIDDEN_STATUS_CODE); CoreApiException apiException = assertThrows(CoreApiException.class, () -> { - coreResponseHandler.handle(getCoreHttpRequest(), coreHttpResponse, endpointSetting); + coreResponseHandler.handle(context, endpointSetting, + getMockGlobalConfig(), requestBuilder); }); String expectedMessage = "Forbidden"; @@ -375,16 +411,19 @@ public void testLocalException() throws IOException, CoreApiException { @Test public void testDefaultException() throws IOException, CoreApiException { - ResponseHandler coreResponseHandler = new ResponseHandler.Builder() - .localErrorCase("403", - ErrorCase.setReason("Forbidden", (reason, context) -> new CoreApiException(reason, context))) - .globalErrorCase(getGlobalErrorCases()).build(); + ResponseHandler coreResponseHandler = + new ResponseHandler.Builder() + .localErrorCase("403", + ErrorCase.setReason("Forbidden", + (reason, context) -> new CoreApiException(reason, context))) + .globalErrorCase(getGlobalErrorCases()).build(); // stub when(coreHttpResponse.getStatusCode()).thenReturn(INFORMATION_CODE); CoreApiException apiException = assertThrows(CoreApiException.class, () -> { - coreResponseHandler.handle(getCoreHttpRequest(), coreHttpResponse, endpointSetting); + coreResponseHandler.handle(context, endpointSetting, + getMockGlobalConfig(), requestBuilder); }); String expectedMessage = "Invalid response."; @@ -396,16 +435,21 @@ public void testDefaultException() throws IOException, CoreApiException { @Test public void testDefaultException1() throws IOException, CoreApiException { // when - ResponseHandler coreResponseHandler = new ResponseHandler.Builder() - .localErrorCase("403", - ErrorCase.setReason("Forbidden", (reason, context) -> new CoreApiException(reason, context))) - .globalErrorCase(getGlobalErrorCases()).build(); + ResponseHandler coreResponseHandler = + new ResponseHandler.Builder() + .localErrorCase("403", + ErrorCase.setReason("Forbidden", + (reason, context) -> new CoreApiException(reason, context))) + .globalErrorCase(getGlobalErrorCases()).build(); + // stub when(coreHttpResponse.getStatusCode()).thenReturn(RECEIVED_STATUS_CODE); + CoreApiException apiException = assertThrows(CoreApiException.class, () -> { - coreResponseHandler.handle(getCoreHttpRequest(), coreHttpResponse, endpointSetting); + coreResponseHandler.handle(context, endpointSetting, + getMockGlobalConfig(), requestBuilder); }); String expectedMessage = "Invalid response."; @@ -424,19 +468,24 @@ public void testDefaultException1() throws IOException, CoreApiException { @Test public void testGlobalException() throws IOException, CoreApiException { - ResponseHandler coreResponseHandler = new ResponseHandler.Builder() - .globalErrorCase(getGlobalErrorCases()).build(); - - String exceptionResponse = "{\"ServerMessage\" : \"Internal server error\" , \"ServerCode\" : 500 }"; - InputStream exceptionResponseStream = new ByteArrayInputStream(exceptionResponse.getBytes()); + ResponseHandler coreResponseHandler = + new ResponseHandler.Builder() + .globalErrorCase(getGlobalErrorCases()).build(); + + String exceptionResponse = + "{\"ServerMessage\" : \"Internal server error\" , \"ServerCode\" : 500 }"; + InputStream exceptionResponseStream = + new ByteArrayInputStream(exceptionResponse.getBytes()); // stub when(coreHttpResponse.getStatusCode()).thenReturn(BAD_REQUEST_CODE); when(coreHttpResponse.getRawBody()).thenReturn(exceptionResponseStream); when(endpointSetting.hasBinaryResponse()).thenReturn(true); + GlobalTestException apiException = assertThrows(GlobalTestException.class, () -> { - coreResponseHandler.handle(getCoreHttpRequest(), coreHttpResponse, endpointSetting); + coreResponseHandler.handle(context, endpointSetting, + getMockGlobalConfig(), requestBuilder); }); String expectedMessage = "Bad Request"; @@ -448,6 +497,7 @@ public void testGlobalException() throws IOException, CoreApiException { int expectedServerCode = INTERNAL_SERVER_ERROR; int actualSeverCode = apiException.getServerCode(); + assertEquals(actualMessage, expectedMessage); assertEquals(actualSeverMessage, expectedServerMessage); assertEquals(actualSeverCode, expectedServerCode); @@ -456,19 +506,20 @@ public void testGlobalException() throws IOException, CoreApiException { private Map> getGlobalErrorCases() { Map> globalErrorCase = new HashMap<>(); - globalErrorCase.put("400", - ErrorCase.setReason("Bad Request", (reason, context) -> new GlobalTestException(reason, context))); + globalErrorCase.put("400", ErrorCase.setReason("Bad Request", + (reason, context) -> new GlobalTestException(reason, context))); - globalErrorCase.put("404", - ErrorCase.setReason("Not found", (reason, context) -> new CoreApiException(reason, context))); + globalErrorCase.put("404", ErrorCase.setReason("Not found", + (reason, context) -> new CoreApiException(reason, context))); - globalErrorCase.put(ErrorCase.DEFAULT, - ErrorCase.setReason("Invalid response.", (reason, context) -> new CoreApiException(reason, context))); + globalErrorCase.put(ErrorCase.DEFAULT, ErrorCase.setReason("Invalid response.", + (reason, context) -> new CoreApiException(reason, context))); return globalErrorCase; } + private void prepareCoreConfigStub() throws IOException { when(getMockGlobalConfig().getBaseUri()).thenReturn(test -> getBaseUri(test)); when(getMockGlobalConfig().getCompatibilityFactory()).thenReturn(getCompatibilityFactory()); @@ -484,18 +535,23 @@ private void setExpectations() throws IOException { private void prepareCompatibilityStub() { when(getCompatibilityFactory().createHttpHeaders(anyMap())).thenReturn(getHttpHeaders()); - when(getCompatibilityFactory().createHttpRequest(any(Method.class), any(StringBuilder.class), - any(HttpHeaders.class), anyMap(), any(Object.class))).thenReturn(getCoreHttpRequest()); - when(getCompatibilityFactory().createHttpRequest(any(Method.class), any(StringBuilder.class), - any(HttpHeaders.class), anyMap(), anyList())).thenReturn(getCoreHttpRequest()); - when(getCompatibilityFactory().createHttpContext(getCoreHttpRequest(), coreHttpResponse)).thenReturn(context); - - when(getCompatibilityFactory().createDynamicResponse(coreHttpResponse)).thenReturn(dynamicType); + when(getCompatibilityFactory().createHttpRequest(any(Method.class), + any(StringBuilder.class), any(HttpHeaders.class), anyMap(), any(Object.class))) + .thenReturn(getCoreHttpRequest()); + when(getCompatibilityFactory().createHttpRequest(any(Method.class), + any(StringBuilder.class), any(HttpHeaders.class), anyMap(), anyList())) + .thenReturn(getCoreHttpRequest()); + when(getCompatibilityFactory().createHttpContext(getCoreHttpRequest(), coreHttpResponse)) + .thenReturn(context); + + when(getCompatibilityFactory().createDynamicResponse(coreHttpResponse)) + .thenReturn(dynamicType); - when(getCompatibilityFactory().createApiResponse(any(int.class), any(HttpHeaders.class), any(String.class))) - .thenReturn(stringApiResponseType); + when(getCompatibilityFactory().createApiResponse(any(int.class), any(HttpHeaders.class), + any(String.class))).thenReturn(stringApiResponseType); when(getCompatibilityFactory().createApiResponse(any(int.class), any(HttpHeaders.class), any(DynamicType.class))).thenReturn(dynamicApiResponseType); } + } diff --git a/src/test/java/apimatic/core/configurations/http/request/EndpointConfigurationTest.java b/src/test/java/apimatic/core/configurations/http/request/EndpointConfigurationTest.java index 98c5e04a..b06ec6aa 100644 --- a/src/test/java/apimatic/core/configurations/http/request/EndpointConfigurationTest.java +++ b/src/test/java/apimatic/core/configurations/http/request/EndpointConfigurationTest.java @@ -12,7 +12,7 @@ public class EndpointConfigurationTest { public void testEndpointConfiguration() { EndpointConfiguration configuration = new EndpointConfiguration(false, RetryOption.DEFAULT, - ArraySerializationFormat.INDEXED, null, null); + ArraySerializationFormat.INDEXED); assertEquals(configuration.getRetryOption(), RetryOption.DEFAULT); assertEquals(configuration.hasBinaryResponse(), false); } From 06a2e81dc0137adb04da9a3f4196de3921ddab7a Mon Sep 17 00:00:00 2001 From: Asad Ali <14asadali@gmail.com> Date: Tue, 29 Apr 2025 16:43:35 +0500 Subject: [PATCH 17/20] refactors for fixing all linting issues --- src/main/java/io/apimatic/core/ApiCall.java | 10 +- .../java/io/apimatic/core/HttpRequest.java | 20 ++- .../io/apimatic/core/ResponseHandler.java | 30 ++-- .../http/request/EndpointConfiguration.java | 2 +- .../types/pagination/CursorPagination.java | 12 +- .../core/types/pagination/LinkPagination.java | 9 +- .../types/pagination/OffsetPagination.java | 7 +- .../core/types/pagination/PagePagination.java | 5 +- .../core/types/pagination/PaginatedData.java | 73 +++++--- .../pagination/PaginationDataManager.java | 11 +- .../pagination/PaginationDeserializer.java | 4 +- .../apimatic/core/utilities/CoreHelper.java | 158 ++++++++++++------ src/test/java/apimatic/core/EndToEndTest.java | 90 +++++----- .../apimatic/core/mocks/HttpHeadersMock.java | 5 + .../type/pagination/CursorPaginationTest.java | 131 ++++++++------- .../type/pagination/LinkPaginationTest.java | 99 +++++------ .../type/pagination/OffsetPaginationTest.java | 82 +++++---- .../type/pagination/PagePaginationTest.java | 51 +++--- .../core/type/pagination/RecordPage.java | 72 +++++++- 19 files changed, 562 insertions(+), 309 deletions(-) diff --git a/src/main/java/io/apimatic/core/ApiCall.java b/src/main/java/io/apimatic/core/ApiCall.java index 73f74db4..daa1b4c2 100644 --- a/src/main/java/io/apimatic/core/ApiCall.java +++ b/src/main/java/io/apimatic/core/ApiCall.java @@ -50,9 +50,9 @@ public final class ApiCall /** * ApiCall constructor. * @param globalConfig the required configuration to built the ApiCall. + * @param endpointConfiguration The endPoint configuration. * @param requestBuilder Http request builder for the api call. * @param responseHandler the handler for the response. - * @param coreEndpointConfiguration endPoint configuration. */ private ApiCall(final GlobalConfiguration globalConfig, final EndpointConfiguration endpointConfiguration, @@ -89,11 +89,13 @@ public ResponseType execute() throws IOException, ExceptionType { */ public CompletableFuture executeAsync() { return AsyncExecutor.makeHttpCallAsync(() -> requestBuilder.build(globalConfig), - request -> globalConfig.getHttpClient().executeAsync(request, endpointConfiguration), + request -> globalConfig.getHttpClient() + .executeAsync(request, endpointConfiguration), (request, response) -> { Context context = globalConfig.getCompatibilityFactory() .createHttpContext(request, response); - return responseHandler.handle(context, endpointConfiguration, globalConfig, requestBuilder); + return responseHandler.handle(context, endpointConfiguration, globalConfig, + requestBuilder); }, apiLogger); } @@ -198,4 +200,4 @@ public ApiCall build() throws IOException { responseHandlerBuilder.build()); } } -} \ No newline at end of file +} diff --git a/src/main/java/io/apimatic/core/HttpRequest.java b/src/main/java/io/apimatic/core/HttpRequest.java index 8ad13aef..f87d1b5d 100644 --- a/src/main/java/io/apimatic/core/HttpRequest.java +++ b/src/main/java/io/apimatic/core/HttpRequest.java @@ -328,6 +328,13 @@ public static class Builder { */ private Parameter.Builder parameterBuilder = new Parameter.Builder(); + /** + * Update the request parameters using a setter. + * + * @param pointer A JSON pointer pointing to any request field. + * @param setter A function that takes in an old value and returns a new value. + * @return The updated instance of current request builder. + */ public Builder updateByReference(String pointer, Function setter) { if (pointer == null) { return this; @@ -336,7 +343,7 @@ public Builder updateByReference(String pointer, Function setter String[] pointerParts = pointer.split("#"); String prefix = pointerParts[0]; String point = pointerParts.length > 1 ? pointerParts[1] : ""; - + switch (prefix) { case "$request.path": updateTemplateParams(setter, point); @@ -347,9 +354,9 @@ public Builder updateByReference(String pointer, Function setter case "$request.headers": updateHeaderParams(setter, point); return this; + default: + return this; } - - return this; } @SuppressWarnings("unchecked") @@ -376,7 +383,8 @@ private void updateHeaderParams(Function setter, String point) { private void updateTemplateParams(Function setter, String point) { Map simplifiedPath = new HashMap<>(); - for (Map.Entry> entry : templateParams.entrySet()) { + for (Map.Entry> entry : + templateParams.entrySet()) { simplifiedPath.put(entry.getKey(), entry.getValue().getKey()); } @@ -387,7 +395,8 @@ private void updateTemplateParams(Function setter, String point) Boolean originalFlag = templateParams.containsKey(entry.getKey()) ? templateParams.get(entry.getKey()).getValue() : false; - templateParams.put(entry.getKey(), new SimpleEntry<>(entry.getValue(), originalFlag)); + templateParams.put(entry.getKey(), + new SimpleEntry<>(entry.getValue(), originalFlag)); } } @@ -441,7 +450,6 @@ public Builder queryParam(Map queryParameters) { return this; } - /** * To configure the query paramater. * @param action the query parameter {@link Consumer}. diff --git a/src/main/java/io/apimatic/core/ResponseHandler.java b/src/main/java/io/apimatic/core/ResponseHandler.java index 7b49ddf0..95673042 100644 --- a/src/main/java/io/apimatic/core/ResponseHandler.java +++ b/src/main/java/io/apimatic/core/ResponseHandler.java @@ -26,7 +26,7 @@ /** * Handler that encapsulates the process of generating a response object from a * Response. - * + * * @param The response to process. * @param in case of a problem. */ @@ -92,15 +92,19 @@ public final class ResponseHandler> localErrorCases, - final Map> globalErrorCases, final Deserializer deserializer, - final Deserializer intermediateDeserializer, final PaginationDeserializer paginationDeserializer, - final ResponseClassType responseClassType, final ContextInitializer contextInitializer, + final Map> globalErrorCases, + final Deserializer deserializer, + final Deserializer intermediateDeserializer, + final PaginationDeserializer paginationDeserializer, + final ResponseClassType responseClassType, + final ContextInitializer contextInitializer, final boolean isNullify404Enabled, final boolean isNullableResponseType) { this.localErrorCases = localErrorCases; this.globalErrorCases = globalErrorCases; @@ -158,7 +162,8 @@ public ResponseType handle(Context context, EndpointConfiguration config, } @SuppressWarnings("unchecked") - private ResponseType applyContextInitializer(Context httpContext, Object result) throws IOException { + private ResponseType applyContextInitializer(Context httpContext, + Object result) throws IOException { if (contextInitializer != null && deserializer != null) { result = contextInitializer.apply(httpContext, (ResponseType) result); } @@ -360,8 +365,8 @@ public Builder paginatedDeseriali Function, ?> returnTypeGetter, PaginationDataManager... dataManagers) { this.paginationDeserializer = (config, globalConfig, reqBuilder, res) -> - new PaginatedData(config, globalConfig, reqBuilder, res, - pageType, converter, dataManagers).convert(returnTypeGetter); + returnTypeGetter.apply(new PaginatedData(config, globalConfig, + reqBuilder, res, pageType, converter, dataManagers)); return this; } @@ -371,7 +376,8 @@ public Builder paginatedDeseriali * @param responseClassType specify the response class type for result. * @return {@link ResponseHandler.Builder}. */ - public Builder responseClassType(ResponseClassType responseClassType) { + public Builder responseClassType( + ResponseClassType responseClassType) { this.responseClassType = responseClassType; return this; } @@ -405,7 +411,8 @@ public Builder nullify404(boolean isNullify404Enabl * @param isNullableResponseType in case of nullable response type. * @return {@link ResponseHandler.Builder}. */ - public Builder nullableResponseType(boolean isNullableResponseType) { + public Builder nullableResponseType( + boolean isNullableResponseType) { this.isNullableResponseType = isNullableResponseType; return this; } @@ -416,8 +423,9 @@ public Builder nullableResponseType(boolean isNulla * @return the instance of {@link ResponseHandler}. */ public ResponseHandler build() { - return new ResponseHandler(localErrorCases, globalErrorCases, deserializer, - intermediateDeserializer, paginationDeserializer, responseClassType, contextInitializer, + return new ResponseHandler(localErrorCases, + globalErrorCases, deserializer, intermediateDeserializer, + paginationDeserializer, responseClassType, contextInitializer, isNullify404Enabled, isNullableResponseType); } } diff --git a/src/main/java/io/apimatic/core/configurations/http/request/EndpointConfiguration.java b/src/main/java/io/apimatic/core/configurations/http/request/EndpointConfiguration.java index cbcc01ae..42ccae35 100644 --- a/src/main/java/io/apimatic/core/configurations/http/request/EndpointConfiguration.java +++ b/src/main/java/io/apimatic/core/configurations/http/request/EndpointConfiguration.java @@ -128,4 +128,4 @@ public EndpointConfiguration build() { arraySerializationFormat); } } -} \ No newline at end of file +} diff --git a/src/main/java/io/apimatic/core/types/pagination/CursorPagination.java b/src/main/java/io/apimatic/core/types/pagination/CursorPagination.java index 805c6089..76fd6158 100644 --- a/src/main/java/io/apimatic/core/types/pagination/CursorPagination.java +++ b/src/main/java/io/apimatic/core/types/pagination/CursorPagination.java @@ -8,7 +8,11 @@ public class CursorPagination implements PaginationDataManager { private String input; private Builder nextReqBuilder; - public CursorPagination(String output, String input) { + /** + * @param output JsonPointer of a field received in the response, representing next cursor. + * @param input JsonPointer of a field in request, representing cursor. + */ + public CursorPagination(final String output, final String input) { this.output = output; this.input = input; } @@ -17,14 +21,14 @@ public CursorPagination(String output, String input) { public boolean isValid(PaginatedData paginatedData) { nextReqBuilder = paginatedData.getLastRequestBuilder(); - String cursorValue = CoreHelper.resolveResponsePointer(output, paginatedData.getLastResponseBody(), - paginatedData.getLastResponseHeaders()); + String cursorValue = CoreHelper.resolveResponsePointer(output, + paginatedData.getLastResponseBody(), paginatedData.getLastResponseHeaders()); if (cursorValue == null) { return false; } - final boolean[] isUpdated = { false }; + final boolean[] isUpdated = {false}; nextReqBuilder.updateByReference(input, old -> { isUpdated[0] = true; return cursorValue; diff --git a/src/main/java/io/apimatic/core/types/pagination/LinkPagination.java b/src/main/java/io/apimatic/core/types/pagination/LinkPagination.java index e55d74e8..623d4b80 100644 --- a/src/main/java/io/apimatic/core/types/pagination/LinkPagination.java +++ b/src/main/java/io/apimatic/core/types/pagination/LinkPagination.java @@ -7,7 +7,10 @@ public class LinkPagination implements PaginationDataManager { private String next; private Builder nextReqBuilder; - public LinkPagination(String next) { + /** + * @param next JsonPointer of a field in response, representing next request queryUrl. + */ + public LinkPagination(final String next) { this.next = next; } @@ -15,8 +18,8 @@ public LinkPagination(String next) { public boolean isValid(PaginatedData paginatedData) { nextReqBuilder = paginatedData.getLastRequestBuilder(); - String linkValue = CoreHelper.resolveResponsePointer(next, paginatedData.getLastResponseBody(), - paginatedData.getLastResponseHeaders()); + String linkValue = CoreHelper.resolveResponsePointer(next, + paginatedData.getLastResponseBody(), paginatedData.getLastResponseHeaders()); if (linkValue == null) { return false; diff --git a/src/main/java/io/apimatic/core/types/pagination/OffsetPagination.java b/src/main/java/io/apimatic/core/types/pagination/OffsetPagination.java index 1ff9d550..18ac4e9d 100644 --- a/src/main/java/io/apimatic/core/types/pagination/OffsetPagination.java +++ b/src/main/java/io/apimatic/core/types/pagination/OffsetPagination.java @@ -6,7 +6,10 @@ public class OffsetPagination implements PaginationDataManager { private String input; private Builder nextReqBuilder; - public OffsetPagination(String input) { + /** + * @param input JsonPointer of a field in request, representing offset. + */ + public OffsetPagination(final String input) { this.input = input; } @@ -18,7 +21,7 @@ public boolean isValid(PaginatedData paginatedData) { return false; } - final boolean[] isUpdated = { false }; + final boolean[] isUpdated = {false}; nextReqBuilder.updateByReference(input, old -> { int newValue = Integer.parseInt("" + old) + paginatedData.getLastDataSize(); isUpdated[0] = true; diff --git a/src/main/java/io/apimatic/core/types/pagination/PagePagination.java b/src/main/java/io/apimatic/core/types/pagination/PagePagination.java index 4a23a16d..3bce56e6 100644 --- a/src/main/java/io/apimatic/core/types/pagination/PagePagination.java +++ b/src/main/java/io/apimatic/core/types/pagination/PagePagination.java @@ -6,7 +6,10 @@ public class PagePagination implements PaginationDataManager { private String input; private Builder nextReqBuilder; - public PagePagination(String input) { + /** + * @param input JsonPointer of a field in request, representing page. + */ + public PagePagination(final String input) { this.input = input; } diff --git a/src/main/java/io/apimatic/core/types/pagination/PaginatedData.java b/src/main/java/io/apimatic/core/types/pagination/PaginatedData.java index b89c72a6..921d63a8 100644 --- a/src/main/java/io/apimatic/core/types/pagination/PaginatedData.java +++ b/src/main/java/io/apimatic/core/types/pagination/PaginatedData.java @@ -35,7 +35,10 @@ public class PaginatedData implements Iterator { private EndpointConfiguration endpointConfig; private GlobalConfiguration globalConfig; - public PaginatedData(PaginatedData paginatedData) { + /** + * @param paginatedData Existing instance to be cloned. + */ + public PaginatedData(final PaginatedData paginatedData) { this.pageType = paginatedData.pageType; this.converter = paginatedData.converter; this.dataManagers = paginatedData.dataManagers; @@ -50,9 +53,21 @@ public PaginatedData(PaginatedData paginatedData) { this.pages.addAll(paginatedData.pages); } - public PaginatedData(EndpointConfiguration config, GlobalConfiguration globalConfig, - HttpRequest.Builder requestBuilder, Response response, TypeReference

pageType, - Function> converter, PaginationDataManager... dataManagers) throws IOException { + /** + * @param config ApiCall configuration that provided this paginated data. + * @param globalConfig Global configuration that provided this paginated data. + * @param requestBuilder RequestBuilder that provided this paginated data. + * @param response Response corresponding to this paginated data instance. + * @param pageType TypeReference of page type P. + * @param converter PageType P to list of ItemType T converter + * @param dataManagers A list of data managers that provided this paginated data. + * + * @throws IOException + */ + public PaginatedData(final EndpointConfiguration config, final GlobalConfiguration globalConfig, + final HttpRequest.Builder requestBuilder, final Response response, + final TypeReference

pageType, final Function> converter, + final PaginationDataManager... dataManagers) throws IOException { this.pageType = pageType; this.converter = converter; this.dataManagers = dataManagers; @@ -76,25 +91,42 @@ private void updateUsing(Response response, HttpRequest.Builder requestBuilder) this.pages.add(page); } + /** + * @return RequestBuilder that provided the last page + */ public HttpRequest.Builder getLastRequestBuilder() { return lastRequestBuilder; } + /** + * @return Response body corresponding to the last page + */ public String getLastResponseBody() { return lastResponse.getBody(); } + /** + * @return Response headers corresponding to the last page + */ public String getLastResponseHeaders() { return CoreHelper.trySerialize(lastResponse.getHeaders().asSimpleMap()); } + /** + * @return Size of the last page + */ public int getLastDataSize() { return lastDataSize; } + /** + * @return Reset this instance and return a clone if its traversed before + */ public PaginatedData reset() { - if (currentIndex == 0) + if (currentIndex == 0) { return this; + } + return new PaginatedData(this); } @@ -118,12 +150,18 @@ public T next() { throw new NoSuchElementException("No more data available."); } + /** + * @return An iterable of items of type T + */ public Iterator iterator() { return reset(); } + /** + * @return An iterable of pages of type P + */ public Iterable

pages() { - PaginatedData data = reset(); + PaginatedData dataCopy = reset(); return new Iterable

() { @Override public Iterator

iterator() { @@ -132,15 +170,15 @@ public Iterator

iterator() { @Override public boolean hasNext() { - if (currentIndex < data.pages.size()) { + if (currentIndex < dataCopy.pages.size()) { return true; } - while (data.hasNext()) { - if (currentIndex < data.pages.size()) { + while (dataCopy.hasNext()) { + if (currentIndex < dataCopy.pages.size()) { return true; } - data.next(); + dataCopy.next(); } return false; @@ -148,8 +186,8 @@ public boolean hasNext() { @Override public P next() { - if (data.hasNext()) { - return data.pages.get(currentIndex++); + if (dataCopy.hasNext()) { + return dataCopy.pages.get(currentIndex++); } throw new NoSuchElementException("No more data available."); @@ -159,10 +197,6 @@ public P next() { }; } - public Object convert(Function, ?> returnTypeGetter) { - return returnTypeGetter.apply(this); - } - private void fetchMoreData() { for (PaginationDataManager manager : dataManagers) { @@ -178,9 +212,10 @@ private void fetchMoreData() { .requestBuilder(manager.getNextRequestBuilder()) .responseHandler(res -> res .globalErrorCase(Collections.singletonMap(ErrorCase.DEFAULT, - ErrorCase.setReason(null, - (reason, context) -> new CoreApiException(reason, context)))) - .nullify404(false).paginatedDeserializer(pageType, converter, r -> r, dataManagers)) + ErrorCase.setReason(null, (reason, context) -> + new CoreApiException(reason, context)))) + .nullify404(false) + .paginatedDeserializer(pageType, converter, r -> r, dataManagers)) .build().execute(); updateUsing(result.lastResponse, result.lastRequestBuilder); diff --git a/src/main/java/io/apimatic/core/types/pagination/PaginationDataManager.java b/src/main/java/io/apimatic/core/types/pagination/PaginationDataManager.java index a3957d28..577c96eb 100644 --- a/src/main/java/io/apimatic/core/types/pagination/PaginationDataManager.java +++ b/src/main/java/io/apimatic/core/types/pagination/PaginationDataManager.java @@ -4,7 +4,14 @@ public interface PaginationDataManager { - public abstract boolean isValid(PaginatedData paginatedData); + /** + * @param paginatedData Data to be checked for validity + * @return True if paginated data is valid to make another request + */ + boolean isValid(PaginatedData paginatedData); - public abstract Builder getNextRequestBuilder(); + /** + * @return RequestBuilder for the next API Call + */ + Builder getNextRequestBuilder(); } diff --git a/src/main/java/io/apimatic/core/types/pagination/PaginationDeserializer.java b/src/main/java/io/apimatic/core/types/pagination/PaginationDeserializer.java index 3ea844e0..81390625 100644 --- a/src/main/java/io/apimatic/core/types/pagination/PaginationDeserializer.java +++ b/src/main/java/io/apimatic/core/types/pagination/PaginationDeserializer.java @@ -14,7 +14,7 @@ public interface PaginationDeserializer { /** * Apply the deserialization function and returns the ResponseType response. - * + * * @param config The EndPoint configuration for paginated API Calls. * @param globalConfig The EndPoint configuration for paginated API Calls. * @param requestBuilder The requestBuilder to re create current API Call. @@ -25,4 +25,4 @@ public interface PaginationDeserializer { */ Object apply(EndpointConfiguration config, GlobalConfiguration globalConfig, HttpRequest.Builder requestBuilder, Response response) throws IOException; -} \ No newline at end of file +} diff --git a/src/main/java/io/apimatic/core/utilities/CoreHelper.java b/src/main/java/io/apimatic/core/utilities/CoreHelper.java index cde8a84b..b550bb79 100644 --- a/src/main/java/io/apimatic/core/utilities/CoreHelper.java +++ b/src/main/java/io/apimatic/core/utilities/CoreHelper.java @@ -1590,30 +1590,47 @@ public static String getQueryParametersFromUrl(String queryUrl) { return queryStringIndex != -1 ? queryUrl.substring(queryStringIndex + 1) : ""; } + /** + * Extracts query parameters from a given URL into a map. + * + * @param queryUrl The URL containing query parameters. + * @return A map of query parameter keys and values. + */ public static Map getQueryParameters(String queryUrl) { return Arrays.stream(getQueryParametersFromUrl(queryUrl).split("&")) .map(param -> param.split("=")) .collect(Collectors.toMap( - pair -> tryUrlDecode(pair[0]), - pair -> tryUrlDecode(pair[1]) + pair -> tryUrlDecode(pair[0]), + pair -> tryUrlDecode(pair[1]) )); } - + + /** + * Converts a JSON string to a {@link JsonStructure}. + * + * @param json The JSON string. + * @return The parsed {@link JsonStructure}, or null if parsing fails. + */ public static JsonStructure createJsonStructure(String json) { JsonReader jsonReader = Json.createReader(new StringReader(json)); JsonStructure jsonStructure = null; - try { jsonStructure = jsonReader.read(); } catch (Exception e) { // No need to do anything here } - jsonReader.close(); - return jsonStructure; } - + + /** + * Resolves a pointer within a JSON response body or headers. + * + * @param pointer The JSON pointer. + * @param jsonBody The response body. + * @param jsonHeaders The response headers. + * @return The resolved value as a string, or null if not found. + */ public static String resolveResponsePointer(String pointer, String jsonBody, String jsonHeaders) { if (pointer == null) { return null; @@ -1622,17 +1639,24 @@ public static String resolveResponsePointer(String pointer, String jsonBody, Str String[] pointerParts = pointer.split("#"); String prefix = pointerParts[0]; String point = pointerParts.length > 1 ? pointerParts[1] : ""; - + switch (prefix) { - case "$response.body": - return CoreHelper.getValueFromJson(point, jsonBody); - case "$response.headers": - return CoreHelper.getValueFromJson(point, jsonHeaders); + case "$response.body": + return CoreHelper.getValueFromJson(point, jsonBody); + case "$response.headers": + return CoreHelper.getValueFromJson(point, jsonHeaders); + default: + return null; } - - return null; } - + + /** + * Retrieves a value from a JSON string using a JSON pointer. + * + * @param pointer The pointer path. + * @param json The JSON string. + * @return The value as a string, or null if not found or invalid. + */ private static String getValueFromJson(String pointer, String json) { if (pointer == null || json == null) { return null; @@ -1644,86 +1668,126 @@ private static String getValueFromJson(String pointer, String json) { try { containsValue = jsonPointer.containsValue(jsonStructure); } catch (Exception e) { + // Ignore } - + if (jsonStructure == null || !containsValue) { return null; } JsonValue value = jsonPointer.getValue(jsonStructure); - if (value == JsonValue.NULL) { // Explicitly check for JsonValue.NULL + if (value == JsonValue.NULL) { return null; } - - if (value instanceof JsonString) { // Extract raw string value + + if (value instanceof JsonString) { return ((JsonString) value).getString(); } - return value.toString(); // Only convert toString() if it's not null + return value.toString(); } + /** + * Updates the value of an object by a JSON pointer using the provided updater function. + * + * @param The type of the object. + * @param value The object to update. + * @param pointer The JSON pointer. + * @param updater The function to apply to the value at the pointer. + * @return The updated object, or the original if any error occurs. + */ public static T updateValueByPointer(T value, String pointer, Function updater) { - if (value == null || pointer == "" || updater == null) return value; + if (value == null || "".equals(pointer) || updater == null) { + return value; + } try { - // Step 1: Convert input object to JSON string String json = serialize(value); - - // Step 2: Parse JSON into JsonStructure JsonStructure structure = createJsonStructure(json); - if (structure == null) return value; + if (structure == null) { + return value; + } JsonPointer jsonPointer = Json.createPointer(pointer); - if (!jsonPointer.containsValue(structure)) { - return value; // don't change if pointer doesn't exist + return value; } JsonValue oldJsonValue = jsonPointer.getValue(structure); Object oldValue = toObject(oldJsonValue); Object newValueRaw = updater.apply(oldValue); - if (newValueRaw == null) return value; + if (newValueRaw == null) { + return value; + } JsonValue newJsonValue = toJsonValue(newValueRaw); - if (newJsonValue == null) return value; + if (newJsonValue == null) { + return value; + } JsonStructure updated = jsonPointer.replace(structure, newJsonValue); - - // Step 3: Convert updated structure back to JSON string StringWriter writer = new StringWriter(); try (JsonWriter jsonWriter = Json.createWriter(writer)) { jsonWriter.write(updated); } String updatedJson = writer.toString(); - - // Step 4: Deserialize back to original type return deserialize(updatedJson, new TypeReference() {}); } catch (Exception e) { - return value; // fallback on error + return value; } } + /** + * Converts a {@link JsonValue} into a plain Java object. + * + * @param value The JsonValue. + * @return The equivalent Java object. + */ private static Object toObject(JsonValue value) { switch (value.getValueType()) { - case STRING: return ((JsonString) value).getString(); - case NUMBER: return ((JsonNumber) value).numberValue(); - case TRUE: return true; - case FALSE: return false; - case NULL: return null; - default: return value.toString(); // fallback for objects/arrays + case STRING: + return ((JsonString) value).getString(); + case NUMBER: + return ((JsonNumber) value).numberValue(); + case TRUE: + return true; + case FALSE: + return false; + case NULL: + return null; + default: + return value.toString(); } } + /** + * Converts a plain Java object into a {@link JsonValue}. + * + * @param obj The object to convert. + * @return The corresponding JsonValue or null if unsupported. + */ private static JsonValue toJsonValue(Object obj) { - if (obj == null) return null; - if (obj instanceof String) return Json.createValue((String) obj); - if (obj instanceof Integer) return Json.createValue((Integer) obj); - if (obj instanceof Long) return Json.createValue((Long) obj); - if (obj instanceof Double) return Json.createValue((Double) obj); - if (obj instanceof Boolean) return ((Boolean) obj) ? JsonValue.TRUE : JsonValue.FALSE; - return null; // unsupported types (could be extended) + if (obj == null) { + return null; + } + if (obj instanceof String) { + return Json.createValue((String) obj); + } + if (obj instanceof Integer) { + return Json.createValue((Integer) obj); + } + if (obj instanceof Long) { + return Json.createValue((Long) obj); + } + if (obj instanceof Double) { + return Json.createValue((Double) obj); + } + if (obj instanceof Boolean) { + return (Boolean) obj ? JsonValue.TRUE : JsonValue.FALSE; + } + return null; } } diff --git a/src/test/java/apimatic/core/EndToEndTest.java b/src/test/java/apimatic/core/EndToEndTest.java index d728b308..f116246b 100644 --- a/src/test/java/apimatic/core/EndToEndTest.java +++ b/src/test/java/apimatic/core/EndToEndTest.java @@ -168,22 +168,22 @@ public void testEndToEndSyncCall() throws IOException, CoreApiException { String actual = getApiCall().execute(); assertEquals(actual, expected); } - + @Test public void testLinkPaginationData() throws IOException, CoreApiException { verifyData(getPaginatedApiCall(new LinkPagination("$response.body#/next_link")).execute()); } - + @Test public void testCursorPaginationData() throws IOException, CoreApiException { verifyData(getPaginatedApiCall(new CursorPagination("$response.body#/page_info", "$request.path#/cursor")).execute()); } - + @Test public void testOffsetPaginationData() throws IOException, CoreApiException { verifyData(getPaginatedApiCall(new OffsetPagination("$request.headers#/offset")).execute()); } - + @Test public void testPagePaginationData() throws IOException, CoreApiException { verifyData(getPaginatedApiCall(new PagePagination("$request.query#/page")).execute()); @@ -192,23 +192,24 @@ public void testPagePaginationData() throws IOException, CoreApiException { private void verifyOnlyPages(PaginatedData paginatedData) { RecordPage expectedPage1 = new RecordPage(); - expectedPage1.data = Arrays.asList("apple", "mango", "orange"); - expectedPage1.pageInfo = "fruits"; - expectedPage1.nextLink = "https://localhost:3000/path?page=2"; - + expectedPage1.setData(Arrays.asList("apple", "mango", "orange")); + expectedPage1.setPageInfo("fruits"); + expectedPage1.setNextLink("https://localhost:3000/path?page=2"); + RecordPage expectedPage2 = new RecordPage(); - expectedPage2.data = Arrays.asList("potato", "carrot", "tomato"); - expectedPage2.pageInfo = "vegitables"; - expectedPage2.nextLink = null; - + expectedPage2.setData(Arrays.asList("potato", "carrot", "tomato")); + expectedPage2.setPageInfo("vegitables"); + expectedPage2.setNextLink(null); + int pageNum = 0; List expectedPages = Arrays.asList(expectedPage1, expectedPage2); for (RecordPage p : paginatedData.pages()) { - assertEquals(expectedPages.get(pageNum).data, p.data); - assertEquals(expectedPages.get(pageNum).pageInfo, p.pageInfo); - assertEquals(expectedPages.get(pageNum).nextLink, p.nextLink); + assertEquals(expectedPages.get(pageNum).getData(), p.getData()); + assertEquals(expectedPages.get(pageNum).getPageInfo(), p.getPageInfo()); + assertEquals(expectedPages.get(pageNum).getNextLink(), p.getNextLink()); pageNum++; } + Iterator iterator = paginatedData.pages().iterator(); Exception exception = assertThrows(NoSuchElementException.class, () -> { while (iterator.hasNext()) { @@ -221,34 +222,37 @@ private void verifyOnlyPages(PaginatedData paginatedData) { private void verifyData(PaginatedData paginatedData) { int index = 0; - List expectedData = Arrays.asList("apple", "mango", "orange", "potato", "carrot", "tomato"); + List expectedData = Arrays.asList( + "apple", "mango", "orange", "potato", "carrot", "tomato"); + Iterator iterator = paginatedData.iterator(); while (iterator.hasNext()) { String d = iterator.next(); assertEquals(expectedData.get(index), d); index++; } + Exception exception = assertThrows(NoSuchElementException.class, () -> { iterator.next(); }); assertEquals("No more data available.", exception.getMessage()); RecordPage expectedPage1 = new RecordPage(); - expectedPage1.data = Arrays.asList("apple", "mango", "orange"); - expectedPage1.pageInfo = "fruits"; - expectedPage1.nextLink = "https://localhost:3000/path?page=2"; - + expectedPage1.setData(Arrays.asList("apple", "mango", "orange")); + expectedPage1.setPageInfo("fruits"); + expectedPage1.setNextLink("https://localhost:3000/path?page=2"); + RecordPage expectedPage2 = new RecordPage(); - expectedPage2.data = Arrays.asList("potato", "carrot", "tomato"); - expectedPage2.pageInfo = "vegitables"; - expectedPage2.nextLink = null; - + expectedPage2.setData(Arrays.asList("potato", "carrot", "tomato")); + expectedPage2.setPageInfo("vegitables"); + expectedPage2.setNextLink(null); + int pageNum = 0; List expectedPages = Arrays.asList(expectedPage1, expectedPage2); for (RecordPage p : paginatedData.pages()) { - assertEquals(expectedPages.get(pageNum).data, p.data); - assertEquals(expectedPages.get(pageNum).pageInfo, p.pageInfo); - assertEquals(expectedPages.get(pageNum).nextLink, p.nextLink); + assertEquals(expectedPages.get(pageNum).getData(), p.getData()); + assertEquals(expectedPages.get(pageNum).getPageInfo(), p.getPageInfo()); + assertEquals(expectedPages.get(pageNum).getNextLink(), p.getNextLink()); pageNum++; } } @@ -494,7 +498,8 @@ public void testGlobalErrorTemplate5XX() throws IOException, CoreApiException { private ApiCall getApiCall() throws IOException { when(response.getBody()).thenReturn("\"Turtle\""); - return new ApiCall.Builder().globalConfig(getGlobalConfig(callback)) + return new ApiCall.Builder() + .globalConfig(getGlobalConfig(callback)) .requestBuilder(requestBuilder -> requestBuilder.server("https://localhost:3000") .path("/v2/bank-accounts") .queryParam(param -> param.key("cursor").value("cursor").isRequired(false)) @@ -513,16 +518,19 @@ private ApiCall getApiCall() throws IOException { .build(); } - private ApiCall, CoreApiException> getPaginatedApiCall(PaginationDataManager... pagination) throws IOException { - when(response.getBody()).thenReturn("{\"data\":[\"apple\",\"mango\",\"orange\"],\"page_info\":\"fruits\"," - + "\"next_link\":\"https://localhost:3000/path?page=2\"}"); + private ApiCall, CoreApiException> getPaginatedApiCall( + PaginationDataManager... pagination) throws IOException { + when(response.getBody()).thenReturn("{\"data\":[\"apple\",\"mango\",\"orange\"],\"" + + "page_info\":\"fruits\",\"next_link\":\"https://localhost:3000/path?page=2\"}"); when(response.getHeaders()).thenReturn(getHttpHeaders()); Callback callback = new Callback() { private int callNumber = 1; @Override public void onBeforeRequest(Request request) { if (callNumber > 1) { - when(response.getBody()).thenReturn("{\"data\":[\"potato\",\"carrot\",\"tomato\"],\"page_info\":\"vegitables\"}"); + when(response.getBody()).thenReturn( + "{\"data\":[\"potato\",\"carrot\",\"tomato\"]," + + "\"page_info\":\"vegitables\"}"); } if (callNumber > 2) { throw new NoSuchElementException("No more data available."); @@ -534,17 +542,20 @@ public void onAfterResponse(Context context) { // TODO Auto-generated method stub } }; - return new ApiCall.Builder, CoreApiException>().globalConfig(getGlobalConfig(callback)) + return new ApiCall.Builder, CoreApiException>() + .globalConfig(getGlobalConfig(callback)) .requestBuilder(requestBuilder -> requestBuilder.server("https://localhost:3000") .path("/path/{cursor}") - .templateParam(param -> param.key("cursor").value("cursor").isRequired(false)) + .templateParam(param -> param.key("cursor").value("cursor") + .isRequired(false)) .headerParam(param -> param.key("offset").value(1).isRequired(false)) .queryParam(param -> param.key("page").value(1).isRequired(false)) .formParam(param -> param.key("limit").value("limit").isRequired(false)) .headerParam(param -> param.key("accept").value("application/json")) .httpMethod(Method.GET)) .responseHandler(responseHandler -> responseHandler - .paginatedDeserializer(new TypeReference() {}, t -> t.data, r -> r, pagination) + .paginatedDeserializer(new TypeReference() {}, + t -> t.getData(), r -> r, pagination) .nullify404(false) .globalErrorCase(Collections.emptyMap())) .endpointConfiguration( @@ -557,7 +568,8 @@ private ApiCall getApiCallLocalErrorTemplate(String re int statusCode) throws IOException { when(response.getBody()).thenReturn(responseString); when(response.getStatusCode()).thenReturn(statusCode); - return new ApiCall.Builder().globalConfig(getGlobalConfig(callback)) + return new ApiCall.Builder() + .globalConfig(getGlobalConfig(callback)) .requestBuilder(requestBuilder -> requestBuilder.server("https://localhost:3000") .path("/v2/bank-accounts") .queryParam(param -> param.key("cursor").value("cursor").isRequired(false)) @@ -587,7 +599,8 @@ private ApiCall getApiCallGlobalErrorTemplate(String r int statusCode) throws IOException { when(response.getBody()).thenReturn(responseString); when(response.getStatusCode()).thenReturn(statusCode); - return new ApiCall.Builder().globalConfig(getGlobalConfig(callback)) + return new ApiCall.Builder() + .globalConfig(getGlobalConfig(callback)) .requestBuilder(requestBuilder -> requestBuilder.server("https://localhost:3000") .path("/v2/bank-accounts") .queryParam(param -> param.key("cursor").value("cursor").isRequired(false)) @@ -614,7 +627,8 @@ private ApiCall getApiCallGlobalErrorTemplateWithHeade when(response.getHeaders()).thenReturn(getHttpHeaders()); when(getHttpHeaders().has("content-type")).thenReturn(true); when(getHttpHeaders().value("content-type")).thenReturn("application/json"); - return new ApiCall.Builder().globalConfig(getGlobalConfig(callback)) + return new ApiCall.Builder() + .globalConfig(getGlobalConfig(callback)) .requestBuilder(requestBuilder -> requestBuilder.server("https://localhost:3000") .path("/v2/bank-accounts") .queryParam(param -> param.key("cursor").value("cursor").isRequired(false)) diff --git a/src/test/java/apimatic/core/mocks/HttpHeadersMock.java b/src/test/java/apimatic/core/mocks/HttpHeadersMock.java index 54b83585..d6663cbd 100644 --- a/src/test/java/apimatic/core/mocks/HttpHeadersMock.java +++ b/src/test/java/apimatic/core/mocks/HttpHeadersMock.java @@ -26,6 +26,11 @@ public class HttpHeadersMock { @Mock private HttpHeaders httpHeaders; + /** + * Setup the httpHeaders mock instance. + * + * @throws IOException + */ @Before public void setup() throws IOException { when(httpHeaders.asSimpleMap()).thenReturn(new HashMap()); diff --git a/src/test/java/apimatic/core/type/pagination/CursorPaginationTest.java b/src/test/java/apimatic/core/type/pagination/CursorPaginationTest.java index 53c7b666..bc2d39f9 100644 --- a/src/test/java/apimatic/core/type/pagination/CursorPaginationTest.java +++ b/src/test/java/apimatic/core/type/pagination/CursorPaginationTest.java @@ -17,19 +17,31 @@ import io.apimatic.core.types.pagination.CursorPagination; import io.apimatic.core.types.pagination.PaginatedData; +/** + * Unit tests for the {@link CursorPagination} class. + * This class tests the behavior of cursor pagination logic. + */ public class CursorPaginationTest { + @Rule public MockitoRule initRule = MockitoJUnit.rule().silent(); + private static final String CURSOR_KEY = "cursor"; + + /** + * Test with a valid cursor value. + */ @Test - public void testWithValidCursor_returnsTrue() { + public void testWithValidCursorReturnsTrue() { PaginatedData paginatedData = mock(PaginatedData.class); when(paginatedData.getLastRequestBuilder()) - .thenReturn(new HttpRequest.Builder().queryParam(q -> q.key("cursor").value("abc"))); + .thenReturn(new HttpRequest.Builder().queryParam( + q -> q.key(CURSOR_KEY).value("abc"))); when(paginatedData.getLastResponseBody()).thenReturn("{\"next_cursor\": \"xyz123\"}"); - CursorPagination cursor = new CursorPagination("$response.body#/next_cursor", "$request.query#/cursor"); + CursorPagination cursor = new CursorPagination("$response.body#/next_cursor", + "$request.query#/cursor"); assertTrue(cursor.isValid(paginatedData)); assertNotNull(cursor.getNextRequestBuilder()); @@ -40,15 +52,20 @@ public void testWithValidCursor_returnsTrue() { }); } + /** + * Test with a valid cursor but different response type (integer). + */ @Test - public void testWithValidCursorAndDifferentType1_returnsTrue() { + public void testWithValidCursorAndDifferentTypeReturnsTrueA() { PaginatedData paginatedData = mock(PaginatedData.class); when(paginatedData.getLastRequestBuilder()) - .thenReturn(new HttpRequest.Builder().queryParam(q -> q.key("cursor").value("abc"))); + .thenReturn(new HttpRequest.Builder().queryParam( + q -> q.key(CURSOR_KEY).value("abc"))); when(paginatedData.getLastResponseBody()).thenReturn("{\"next_cursor\": 123}"); - CursorPagination cursor = new CursorPagination("$response.body#/next_cursor", "$request.query#/cursor"); + CursorPagination cursor = new CursorPagination("$response.body#/next_cursor", + "$request.query#/cursor"); assertTrue(cursor.isValid(paginatedData)); assertNotNull(cursor.getNextRequestBuilder()); @@ -60,14 +77,16 @@ public void testWithValidCursorAndDifferentType1_returnsTrue() { } @Test - public void testWithValidCursorAndDifferentType2_returnsTrue() { + public void testWithValidCursorAndDifferentTypeReturnsTrueB() { PaginatedData paginatedData = mock(PaginatedData.class); when(paginatedData.getLastRequestBuilder()) - .thenReturn(new HttpRequest.Builder().queryParam(q -> q.key("cursor").value(456))); + .thenReturn(new HttpRequest.Builder().queryParam( + q -> q.key("cursor").value(456))); when(paginatedData.getLastResponseBody()).thenReturn("{\"next_cursor\": 123}"); - CursorPagination cursor = new CursorPagination("$response.body#/next_cursor", "$request.query#/cursor"); + CursorPagination cursor = new CursorPagination("$response.body#/next_cursor", + "$request.query#/cursor"); assertTrue(cursor.isValid(paginatedData)); assertNotNull(cursor.getNextRequestBuilder()); @@ -79,7 +98,7 @@ public void testWithValidCursorAndDifferentType2_returnsTrue() { } @Test - public void testWithValidCursorButMissingInFirstRequest_returnsFalse() { + public void testWithValidCursorButMissingInFirstRequestReturnsFalse() { PaginatedData paginatedData = mock(PaginatedData.class); when(paginatedData.getLastRequestBuilder()).thenReturn(new HttpRequest.Builder()); @@ -96,72 +115,44 @@ public void testWithValidCursorButMissingInFirstRequest_returnsFalse() { }); } + /** + * Test with valid cursor from a response body and different type. + */ @Test - public void testWithValidCursorFromResponseHeader_returnsTrue() { + public void testWithValidCursorFromResponseBodyReturnsTrue() { PaginatedData paginatedData = mock(PaginatedData.class); when(paginatedData.getLastRequestBuilder()) - .thenReturn(new HttpRequest.Builder().queryParam(q -> q.key("cursor").value("abc"))); - when(paginatedData.getLastResponseHeaders()).thenReturn("{\"next_cursor\": \"xyz123\"}"); + .thenReturn(new HttpRequest.Builder().queryParam( + q -> q.key(CURSOR_KEY).value("abc"))); + when(paginatedData.getLastResponseBody()).thenReturn("{\"next_cursor\": 123}"); - CursorPagination cursor = new CursorPagination("$response.headers#/next_cursor", "$request.query#/cursor"); + CursorPagination cursor = new CursorPagination("$response.body#/next_cursor", + "$request.query#/cursor"); assertTrue(cursor.isValid(paginatedData)); assertNotNull(cursor.getNextRequestBuilder()); cursor.getNextRequestBuilder().updateByReference("$request.query#/cursor", v -> { - assertEquals("xyz123", v); - return v; - }); - } - - @Test - public void testWithValidCursorInTemplateParam_returnsTrue() { - PaginatedData paginatedData = mock(PaginatedData.class); - - when(paginatedData.getLastRequestBuilder()) - .thenReturn(new HttpRequest.Builder().templateParam(t -> t.key("cursor").value("abc"))); - when(paginatedData.getLastResponseBody()).thenReturn("{\"next_cursor\": \"xyz123\"}"); - - CursorPagination cursor = new CursorPagination("$response.body#/next_cursor", "$request.path#/cursor"); - - assertTrue(cursor.isValid(paginatedData)); - assertNotNull(cursor.getNextRequestBuilder()); - - cursor.getNextRequestBuilder().updateByReference("$request.path#/cursor", v -> { - assertEquals("xyz123", v); - return v; - }); - } - - @Test - public void testWithValidCursorInHeaderParam_returnsTrue() { - PaginatedData paginatedData = mock(PaginatedData.class); - - when(paginatedData.getLastRequestBuilder()) - .thenReturn(new HttpRequest.Builder().headerParam(h -> h.key("cursor").value("abc"))); - when(paginatedData.getLastResponseBody()).thenReturn("{\"next_cursor\": \"xyz123\"}"); - - CursorPagination cursor = new CursorPagination("$response.body#/next_cursor", "$request.headers#/cursor"); - - assertTrue(cursor.isValid(paginatedData)); - assertNotNull(cursor.getNextRequestBuilder()); - - cursor.getNextRequestBuilder().updateByReference("$request.headers#/cursor", v -> { - assertEquals("xyz123", v); + assertEquals("123", v); return v; }); } + /** + * Test case where the response pointer is invalid. + */ @Test - public void testWithInValidResponsePointer_returnsFalse() { + public void testWithInvalidResponsePointerReturnsFalse() { PaginatedData paginatedData = mock(PaginatedData.class); when(paginatedData.getLastRequestBuilder()) - .thenReturn(new HttpRequest.Builder().headerParam(h -> h.key("cursor").value("abc"))); + .thenReturn(new HttpRequest.Builder().headerParam( + h -> h.key(CURSOR_KEY).value("abc"))); when(paginatedData.getLastResponseBody()).thenReturn("{\"next_cursor\": \"xyz123\"}"); - CursorPagination cursor = new CursorPagination("$response.body#/next", "$request.headers#/cursor"); + CursorPagination cursor = new CursorPagination("$response.body#/next", + "$request.headers#/cursor"); assertFalse(cursor.isValid(paginatedData)); assertNotNull(cursor.getNextRequestBuilder()); @@ -172,12 +163,16 @@ public void testWithInValidResponsePointer_returnsFalse() { }); } + /** + * Test with missing response pointer. + */ @Test - public void testWithMissingResponsePointer_returnsFalse() { + public void testWithMissingResponsePointerReturnsFalse() { PaginatedData paginatedData = mock(PaginatedData.class); when(paginatedData.getLastRequestBuilder()) - .thenReturn(new HttpRequest.Builder().headerParam(h -> h.key("cursor").value("abc"))); + .thenReturn(new HttpRequest.Builder().headerParam( + h -> h.key(CURSOR_KEY).value("abc"))); when(paginatedData.getLastResponseBody()).thenReturn("{\"next_cursor\": \"xyz123\"}"); CursorPagination cursor = new CursorPagination(null, "$request.headers#/cursor"); @@ -191,15 +186,20 @@ public void testWithMissingResponsePointer_returnsFalse() { }); } + /** + * Test case with invalid request pointer. + */ @Test - public void testWithInValidRequestPointer_returnsFalse() { + public void testWithInvalidRequestPointerReturnsFalse() { PaginatedData paginatedData = mock(PaginatedData.class); when(paginatedData.getLastRequestBuilder()) - .thenReturn(new HttpRequest.Builder().headerParam(h -> h.key("cursor").value("abc"))); + .thenReturn(new HttpRequest.Builder().headerParam( + h -> h.key(CURSOR_KEY).value("abc"))); when(paginatedData.getLastResponseBody()).thenReturn("{\"next_cursor\": \"xyz123\"}"); - CursorPagination cursor = new CursorPagination("$response.body#/next_cursor", "$request.headers#/next_cursor"); + CursorPagination cursor = new CursorPagination("$response.body#/next_cursor", + "$request.headers#/next_cursor"); assertFalse(cursor.isValid(paginatedData)); assertNotNull(cursor.getNextRequestBuilder()); @@ -210,12 +210,16 @@ public void testWithInValidRequestPointer_returnsFalse() { }); } + /** + * Test with missing request pointer. + */ @Test - public void testWithMissingRequestPointer_returnsFalse() { + public void testWithMissingRequestPointerReturnsFalse() { PaginatedData paginatedData = mock(PaginatedData.class); when(paginatedData.getLastRequestBuilder()) - .thenReturn(new HttpRequest.Builder().headerParam(h -> h.key("cursor").value("abc"))); + .thenReturn(new HttpRequest.Builder().headerParam( + h -> h.key(CURSOR_KEY).value("abc"))); when(paginatedData.getLastResponseBody()).thenReturn("{\"next_cursor\": \"xyz123\"}"); CursorPagination cursor = new CursorPagination("$response.body#/next_cursor", null); @@ -228,5 +232,4 @@ public void testWithMissingRequestPointer_returnsFalse() { return v; }); } - } diff --git a/src/test/java/apimatic/core/type/pagination/LinkPaginationTest.java b/src/test/java/apimatic/core/type/pagination/LinkPaginationTest.java index 6f3f264e..79564784 100644 --- a/src/test/java/apimatic/core/type/pagination/LinkPaginationTest.java +++ b/src/test/java/apimatic/core/type/pagination/LinkPaginationTest.java @@ -17,187 +17,190 @@ import io.apimatic.core.types.pagination.LinkPagination; import io.apimatic.core.types.pagination.PaginatedData; +/** + * Unit tests for the LinkPagination class. + */ public class LinkPaginationTest { + + /** + * Silent rule for Mockito initialization. + */ @Rule public MockitoRule initRule = MockitoJUnit.rule().silent(); @Test - public void testWithValidLink_returnsTrue() { + public void testValidLinkReturnsTrue() { PaginatedData paginatedData = mock(PaginatedData.class); when(paginatedData.getLastRequestBuilder()).thenReturn(new HttpRequest.Builder()); - when(paginatedData.getLastResponseBody()).thenReturn("{\"next\": \"https://api.example.com?page=2\"}"); + when(paginatedData.getLastResponseBody()) + .thenReturn("{\"next\": \"https://api.example.com?page=2\"}"); LinkPagination link = new LinkPagination("$response.body#/next"); - + assertTrue(link.isValid(paginatedData)); assertNotNull(link.getNextRequestBuilder()); - link.getNextRequestBuilder().updateByReference("$request.query#/page", v -> - { + link.getNextRequestBuilder().updateByReference("$request.query#/page", v -> { assertEquals("2", v); return v; }); } @Test - public void testWithValidLinkImpactingOtherParams_returnsTrue() { + public void testValidLinkWithAdditionalParamsReturnsTrue() { PaginatedData paginatedData = mock(PaginatedData.class); when(paginatedData.getLastRequestBuilder()).thenReturn(new HttpRequest.Builder() .queryParam(q -> q.key("size").value(23)) .queryParam(q -> q.key("page").value(1)) .headerParam(h -> h.key("page").value(2))); - when(paginatedData.getLastResponseBody()).thenReturn("{\"next\": \"https://api.example.com?page=2\"}"); + + when(paginatedData.getLastResponseBody()) + .thenReturn("{\"next\": \"https://api.example.com?page=2\"}"); LinkPagination link = new LinkPagination("$response.body#/next"); - + assertTrue(link.isValid(paginatedData)); assertNotNull(link.getNextRequestBuilder()); - link.getNextRequestBuilder().updateByReference("$request.query#/page", v -> - { + link.getNextRequestBuilder().updateByReference("$request.query#/page", v -> { assertEquals("2", v); return v; }); - link.getNextRequestBuilder().updateByReference("$request.query#/size", v -> - { + link.getNextRequestBuilder().updateByReference("$request.query#/size", v -> { assertEquals(23, v); return v; }); - link.getNextRequestBuilder().updateByReference("$request.headers#/page", v -> - { + link.getNextRequestBuilder().updateByReference("$request.headers#/page", v -> { assertEquals(2, v); return v; }); } @Test - public void testWithValidLinkFromResponseHeader_returnsTrue() { + public void testValidLinkFromHeaderReturnsTrue() { PaginatedData paginatedData = mock(PaginatedData.class); when(paginatedData.getLastRequestBuilder()).thenReturn(new HttpRequest.Builder()); - when(paginatedData.getLastResponseHeaders()).thenReturn("{\"next\": \"https://api.example.com?page=2\"}"); + when(paginatedData.getLastResponseHeaders()) + .thenReturn("{\"next\": \"https://api.example.com?page=2\"}"); LinkPagination link = new LinkPagination("$response.headers#/next"); - + assertTrue(link.isValid(paginatedData)); assertNotNull(link.getNextRequestBuilder()); - link.getNextRequestBuilder().updateByReference("$request.query#/page", v -> - { + link.getNextRequestBuilder().updateByReference("$request.query#/page", v -> { assertEquals("2", v); return v; }); } @Test - public void testWithInValidLinkPointer_returnsFalse() { + public void testInvalidPointerReturnsFalse() { PaginatedData paginatedData = mock(PaginatedData.class); when(paginatedData.getLastRequestBuilder()).thenReturn(new HttpRequest.Builder()); - when(paginatedData.getLastResponseBody()).thenReturn("{\"next\": \"https://api.example.com?page=2\"}"); + when(paginatedData.getLastResponseBody()) + .thenReturn("{\"next\": \"https://api.example.com?page=2\"}"); LinkPagination link = new LinkPagination("$response.body#/next/href"); - + assertFalse(link.isValid(paginatedData)); assertNotNull(link.getNextRequestBuilder()); - link.getNextRequestBuilder().updateByReference("$request.query#/page", v -> - { + link.getNextRequestBuilder().updateByReference("$request.query#/page", v -> { fail(); return v; }); } @Test - public void testWithMissingResponse_returnsFalse() { + public void testMissingResponseReturnsFalse() { PaginatedData paginatedData = mock(PaginatedData.class); when(paginatedData.getLastRequestBuilder()).thenReturn(new HttpRequest.Builder()); when(paginatedData.getLastResponseBody()).thenReturn(null); LinkPagination link = new LinkPagination("$response.body#/next/href"); - + assertFalse(link.isValid(paginatedData)); assertNotNull(link.getNextRequestBuilder()); - link.getNextRequestBuilder().updateByReference("$request.query#/page", v -> - { + link.getNextRequestBuilder().updateByReference("$request.query#/page", v -> { fail(); return v; }); } @Test - public void testWithMissingLinkPointer_returnsFalse() { + public void testMissingPointerReturnsFalse() { PaginatedData paginatedData = mock(PaginatedData.class); when(paginatedData.getLastRequestBuilder()).thenReturn(new HttpRequest.Builder()); - when(paginatedData.getLastResponseBody()).thenReturn("{\"next\": \"https://api.example.com?page=2\"}"); + when(paginatedData.getLastResponseBody()) + .thenReturn("{\"next\": \"https://api.example.com?page=2\"}"); LinkPagination link = new LinkPagination(null); - + assertFalse(link.isValid(paginatedData)); assertNotNull(link.getNextRequestBuilder()); - link.getNextRequestBuilder().updateByReference("$request.query#/page", v -> - { + link.getNextRequestBuilder().updateByReference("$request.query#/page", v -> { fail(); return v; }); } @Test - public void testWithMultipleQueryParams_returnsTrue() { + public void testMultipleQueryParamsReturnsTrue() { PaginatedData paginatedData = mock(PaginatedData.class); when(paginatedData.getLastRequestBuilder()).thenReturn(new HttpRequest.Builder()); - when(paginatedData.getLastResponseBody()).thenReturn("{\"next\": \"https://api.example.com?page=2&size=5\"}"); + when(paginatedData.getLastResponseBody()) + .thenReturn("{\"next\": \"https://api.example.com?page=2&size=5\"}"); LinkPagination link = new LinkPagination("$response.body#/next"); - + assertTrue(link.isValid(paginatedData)); assertNotNull(link.getNextRequestBuilder()); - link.getNextRequestBuilder().updateByReference("$request.query#/page", v -> - { + link.getNextRequestBuilder().updateByReference("$request.query#/page", v -> { assertEquals("2", v); return v; }); - link.getNextRequestBuilder().updateByReference("$request.query#/size", v -> - { + link.getNextRequestBuilder().updateByReference("$request.query#/size", v -> { assertEquals("5", v); return v; }); } @Test - public void testWithMultipleEncodedQueryParams_returnsTrue() { + public void testEncodedQueryParamsReturnsTrue() { PaginatedData paginatedData = mock(PaginatedData.class); when(paginatedData.getLastRequestBuilder()).thenReturn(new HttpRequest.Builder()); - when(paginatedData.getLastResponseBody()).thenReturn("{\"next\": \"https://api.example.com?page%20o=2%20a&size%20q=5^%214$#\"}"); + when(paginatedData.getLastResponseBody()) + .thenReturn("{\"next\": \"https://api.example.com?page%20o=2%20a&" + + "size%20q=5^%214$#\"}"); LinkPagination link = new LinkPagination("$response.body#/next"); - + assertTrue(link.isValid(paginatedData)); assertNotNull(link.getNextRequestBuilder()); - link.getNextRequestBuilder().updateByReference("$request.query#/page o", v -> - { + link.getNextRequestBuilder().updateByReference("$request.query#/page o", v -> { assertEquals("2 a", v); return v; }); - link.getNextRequestBuilder().updateByReference("$request.query#/size q", v -> - { + link.getNextRequestBuilder().updateByReference("$request.query#/size q", v -> { assertEquals("5^!4$#", v); return v; }); } - } diff --git a/src/test/java/apimatic/core/type/pagination/OffsetPaginationTest.java b/src/test/java/apimatic/core/type/pagination/OffsetPaginationTest.java index d0e21378..b9adce65 100644 --- a/src/test/java/apimatic/core/type/pagination/OffsetPaginationTest.java +++ b/src/test/java/apimatic/core/type/pagination/OffsetPaginationTest.java @@ -19,17 +19,30 @@ import io.apimatic.core.types.pagination.OffsetPagination; import io.apimatic.core.types.pagination.PaginatedData; +/** + * Unit tests for {@link OffsetPagination}. + */ public class OffsetPaginationTest { + private static final int INITIAL_OFFSET = 3; + private static final int PAGE_SIZE = 100; + private static final int OFFSET_PLUS_PAGE = 103; + private static final int OFFSET_VAL_PLUS_ONE = 101; + private static final int OFFSET_STRING_PLUS_PAGE = 105; + private static final int NUMERIC_OFFSET = 5; + + /** + * JUnit rule to initialize Mockito annotations. + */ @Rule public MockitoRule initRule = MockitoJUnit.rule().silent(); @Test - public void testWithValidOffsetHeader_returnsTrue() { + public void testValidOffsetHeaderReturnsTrue() { PaginatedData paginatedData = mock(PaginatedData.class); when(paginatedData.getLastRequestBuilder()) - .thenReturn(new HttpRequest.Builder().headerParam(h -> h.key("offset").value(3))); - when(paginatedData.getLastDataSize()).thenReturn(100); + .thenReturn(new HttpRequest.Builder().headerParam(h -> h.key("offset").value(INITIAL_OFFSET))); + when(paginatedData.getLastDataSize()).thenReturn(PAGE_SIZE); OffsetPagination offset = new OffsetPagination("$request.headers#/offset"); @@ -37,18 +50,18 @@ public void testWithValidOffsetHeader_returnsTrue() { assertNotNull(offset.getNextRequestBuilder()); offset.getNextRequestBuilder().updateByReference("$request.headers#/offset", v -> { - assertEquals(103, v); + assertEquals(OFFSET_PLUS_PAGE, v); return v; }); } @Test - public void testWithValidOffsetTemplate_returnsTrue() { + public void testValidOffsetTemplateReturnsTrue() { PaginatedData paginatedData = mock(PaginatedData.class); when(paginatedData.getLastRequestBuilder()) - .thenReturn(new HttpRequest.Builder().templateParam(t -> t.key("offset").value(3))); - when(paginatedData.getLastDataSize()).thenReturn(100); + .thenReturn(new HttpRequest.Builder().templateParam(t -> t.key("offset").value(INITIAL_OFFSET))); + when(paginatedData.getLastDataSize()).thenReturn(PAGE_SIZE); OffsetPagination offset = new OffsetPagination("$request.path#/offset"); @@ -56,18 +69,18 @@ public void testWithValidOffsetTemplate_returnsTrue() { assertNotNull(offset.getNextRequestBuilder()); offset.getNextRequestBuilder().updateByReference("$request.path#/offset", v -> { - assertEquals(103, v); + assertEquals(OFFSET_PLUS_PAGE, v); return v; }); } @Test - public void testWithValidOffset_returnsTrue() { + public void testValidOffsetReturnsTrue() { PaginatedData paginatedData = mock(PaginatedData.class); when(paginatedData.getLastRequestBuilder()) - .thenReturn(new HttpRequest.Builder().queryParam(q -> q.key("offset").value(3))); - when(paginatedData.getLastDataSize()).thenReturn(100); + .thenReturn(new HttpRequest.Builder().queryParam(q -> q.key("offset").value(INITIAL_OFFSET))); + when(paginatedData.getLastDataSize()).thenReturn(PAGE_SIZE); OffsetPagination offset = new OffsetPagination("$request.query#/offset"); @@ -75,23 +88,23 @@ public void testWithValidOffset_returnsTrue() { assertNotNull(offset.getNextRequestBuilder()); offset.getNextRequestBuilder().updateByReference("$request.query#/offset", v -> { - assertEquals(103, v); + assertEquals(OFFSET_PLUS_PAGE, v); return v; }); } @Test - public void testWithValidOffsetAsInnerField_returnsTrue() { + public void testValidOffsetAsInnerFieldReturnsTrue() { PaginatedData paginatedData = mock(PaginatedData.class); when(paginatedData.getLastRequestBuilder()).thenReturn( - new HttpRequest.Builder().queryParam(q -> q.key("offset").value(new HashMap() { - private static final long serialVersionUID = 1L; - { - put("val", "1"); - } - }))); - when(paginatedData.getLastDataSize()).thenReturn(100); + new HttpRequest.Builder().queryParam(q -> q.key("offset").value(new HashMap() { + private static final long serialVersionUID = 1L; + { + put("val", "1"); + } + }))); + when(paginatedData.getLastDataSize()).thenReturn(PAGE_SIZE); OffsetPagination offset = new OffsetPagination("$request.query#/offset/val"); @@ -99,18 +112,18 @@ public void testWithValidOffsetAsInnerField_returnsTrue() { assertNotNull(offset.getNextRequestBuilder()); offset.getNextRequestBuilder().updateByReference("$request.query#/offset/val", v -> { - assertEquals(101, v); + assertEquals(OFFSET_VAL_PLUS_ONE, v); return v; }); } @Test - public void testWithValidStringOffset_returnsTrue() { + public void testValidStringOffsetReturnsTrue() { PaginatedData paginatedData = mock(PaginatedData.class); when(paginatedData.getLastRequestBuilder()) - .thenReturn(new HttpRequest.Builder().queryParam(q -> q.key("offset").value("5"))); - when(paginatedData.getLastDataSize()).thenReturn(100); + .thenReturn(new HttpRequest.Builder().queryParam(q -> q.key("offset").value("5"))); + when(paginatedData.getLastDataSize()).thenReturn(PAGE_SIZE); OffsetPagination offset = new OffsetPagination("$request.query#/offset"); @@ -118,18 +131,18 @@ public void testWithValidStringOffset_returnsTrue() { assertNotNull(offset.getNextRequestBuilder()); offset.getNextRequestBuilder().updateByReference("$request.query#/offset", v -> { - assertEquals(105, v); + assertEquals(OFFSET_STRING_PLUS_PAGE, v); return v; }); } @Test - public void testWithInValidStringOffset_returnsFalse() { + public void testInvalidStringOffsetReturnsFalse() { PaginatedData paginatedData = mock(PaginatedData.class); when(paginatedData.getLastRequestBuilder()) - .thenReturn(new HttpRequest.Builder().queryParam(q -> q.key("offset").value("5a"))); - when(paginatedData.getLastDataSize()).thenReturn(100); + .thenReturn(new HttpRequest.Builder().queryParam(q -> q.key("offset").value("5a"))); + when(paginatedData.getLastDataSize()).thenReturn(PAGE_SIZE); OffsetPagination offset = new OffsetPagination("$request.query#/offset"); @@ -143,11 +156,11 @@ public void testWithInValidStringOffset_returnsFalse() { } @Test - public void testWithMissingOffset_returnsFalse() { + public void testMissingOffsetReturnsFalse() { PaginatedData paginatedData = mock(PaginatedData.class); when(paginatedData.getLastRequestBuilder()).thenReturn(new HttpRequest.Builder()); - when(paginatedData.getLastDataSize()).thenReturn(100); + when(paginatedData.getLastDataSize()).thenReturn(PAGE_SIZE); OffsetPagination offset = new OffsetPagination("$request.query#/offset"); @@ -161,12 +174,12 @@ public void testWithMissingOffset_returnsFalse() { } @Test - public void testWithNullOffset_returnsFalse() { + public void testNullOffsetReturnsFalse() { PaginatedData paginatedData = mock(PaginatedData.class); when(paginatedData.getLastRequestBuilder()) - .thenReturn(new HttpRequest.Builder().queryParam(q -> q.key("offset").value(5))); - when(paginatedData.getLastDataSize()).thenReturn(100); + .thenReturn(new HttpRequest.Builder().queryParam(q -> q.key("offset").value(NUMERIC_OFFSET))); + when(paginatedData.getLastDataSize()).thenReturn(PAGE_SIZE); OffsetPagination offset = new OffsetPagination(null); @@ -174,9 +187,8 @@ public void testWithNullOffset_returnsFalse() { assertNotNull(offset.getNextRequestBuilder()); offset.getNextRequestBuilder().updateByReference("$request.query#/offset", v -> { - assertEquals(5, v); + assertEquals(NUMERIC_OFFSET, v); return v; }); } - } diff --git a/src/test/java/apimatic/core/type/pagination/PagePaginationTest.java b/src/test/java/apimatic/core/type/pagination/PagePaginationTest.java index 9c201e00..9b7504f0 100644 --- a/src/test/java/apimatic/core/type/pagination/PagePaginationTest.java +++ b/src/test/java/apimatic/core/type/pagination/PagePaginationTest.java @@ -19,16 +19,23 @@ import io.apimatic.core.types.pagination.PagePagination; import io.apimatic.core.types.pagination.PaginatedData; +/** Unit tests for PagePagination. */ public class PagePaginationTest { + + /** + * Mockito rule for initializing mocks. + */ @Rule public MockitoRule initRule = MockitoJUnit.rule().silent(); @Test - public void testWithValidPageHeader_returnsTrue() { + public void testWithValidPageHeaderReturnsTrue() { PaginatedData paginatedData = mock(PaginatedData.class); + final int initialPage = 3; + final int nextPage = 4; when(paginatedData.getLastRequestBuilder()) - .thenReturn(new HttpRequest.Builder().headerParam(h -> h.key("page").value(3))); + .thenReturn(new HttpRequest.Builder().headerParam(h -> h.key("page").value(initialPage))); PagePagination page = new PagePagination("$request.headers#/page"); @@ -36,17 +43,19 @@ public void testWithValidPageHeader_returnsTrue() { assertNotNull(page.getNextRequestBuilder()); page.getNextRequestBuilder().updateByReference("$request.headers#/page", v -> { - assertEquals(4, v); + assertEquals(nextPage, v); return v; }); } @Test - public void testWithValidPageTemplate_returnsTrue() { + public void testWithValidPageTemplateReturnsTrue() { PaginatedData paginatedData = mock(PaginatedData.class); + final int initialPage = 3; + final int nextPage = 4; when(paginatedData.getLastRequestBuilder()) - .thenReturn(new HttpRequest.Builder().templateParam(t -> t.key("page").value(3))); + .thenReturn(new HttpRequest.Builder().templateParam(t -> t.key("page").value(initialPage))); PagePagination page = new PagePagination("$request.path#/page"); @@ -54,17 +63,19 @@ public void testWithValidPageTemplate_returnsTrue() { assertNotNull(page.getNextRequestBuilder()); page.getNextRequestBuilder().updateByReference("$request.path#/page", v -> { - assertEquals(4, v); + assertEquals(nextPage, v); return v; }); } @Test - public void testWithValidPage_returnsTrue() { + public void testWithValidPageReturnsTrue() { PaginatedData paginatedData = mock(PaginatedData.class); + final int initialPage = 3; + final int nextPage = 4; when(paginatedData.getLastRequestBuilder()) - .thenReturn(new HttpRequest.Builder().queryParam(q -> q.key("page").value(3))); + .thenReturn(new HttpRequest.Builder().queryParam(q -> q.key("page").value(initialPage))); PagePagination page = new PagePagination("$request.query#/page"); @@ -72,13 +83,13 @@ public void testWithValidPage_returnsTrue() { assertNotNull(page.getNextRequestBuilder()); page.getNextRequestBuilder().updateByReference("$request.query#/page", v -> { - assertEquals(4, v); + assertEquals(nextPage, v); return v; }); } @Test - public void testWithValidPageAsInnerField_returnsTrue() { + public void testWithValidPageAsInnerFieldReturnsTrue() { PaginatedData paginatedData = mock(PaginatedData.class); when(paginatedData.getLastRequestBuilder()).thenReturn( @@ -101,11 +112,13 @@ public void testWithValidPageAsInnerField_returnsTrue() { } @Test - public void testWithValidStringPage_returnsTrue() { + public void testWithValidStringPageReturnsTrue() { PaginatedData paginatedData = mock(PaginatedData.class); + final String current = "5"; + final int next = 6; when(paginatedData.getLastRequestBuilder()) - .thenReturn(new HttpRequest.Builder().queryParam(q -> q.key("page").value("5"))); + .thenReturn(new HttpRequest.Builder().queryParam(q -> q.key("page").value(current))); PagePagination page = new PagePagination("$request.query#/page"); @@ -113,13 +126,13 @@ public void testWithValidStringPage_returnsTrue() { assertNotNull(page.getNextRequestBuilder()); page.getNextRequestBuilder().updateByReference("$request.query#/page", v -> { - assertEquals(6, v); + assertEquals(next, v); return v; }); } @Test - public void testWithInValidStringPage_returnsFalse() { + public void testWithInvalidStringPageReturnsFalse() { PaginatedData paginatedData = mock(PaginatedData.class); when(paginatedData.getLastRequestBuilder()) @@ -137,7 +150,7 @@ public void testWithInValidStringPage_returnsFalse() { } @Test - public void testWithMissingPage_returnsFalse() { + public void testWithMissingPageReturnsFalse() { PaginatedData paginatedData = mock(PaginatedData.class); when(paginatedData.getLastRequestBuilder()).thenReturn(new HttpRequest.Builder()); @@ -154,11 +167,12 @@ public void testWithMissingPage_returnsFalse() { } @Test - public void testWithNullPage_returnsFalse() { + public void testWithNullPageReturnsFalse() { PaginatedData paginatedData = mock(PaginatedData.class); + final int current = 5; when(paginatedData.getLastRequestBuilder()) - .thenReturn(new HttpRequest.Builder().queryParam(q -> q.key("page").value(5))); + .thenReturn(new HttpRequest.Builder().queryParam(q -> q.key("page").value(current))); PagePagination page = new PagePagination(null); @@ -166,9 +180,8 @@ public void testWithNullPage_returnsFalse() { assertNotNull(page.getNextRequestBuilder()); page.getNextRequestBuilder().updateByReference("$request.query#/page", v -> { - assertEquals(5, v); + assertEquals(current, v); return v; }); } - } diff --git a/src/test/java/apimatic/core/type/pagination/RecordPage.java b/src/test/java/apimatic/core/type/pagination/RecordPage.java index 7b259d8c..52f21130 100644 --- a/src/test/java/apimatic/core/type/pagination/RecordPage.java +++ b/src/test/java/apimatic/core/type/pagination/RecordPage.java @@ -4,14 +4,80 @@ import com.fasterxml.jackson.annotation.JsonSetter; +/** + * Represents a page of records with pagination details. + */ public class RecordPage { + + /** + * The list of data items on the current page. + */ @JsonSetter("data") - public List data; + private List data; + /** + * Information about the current page, such as offset or cursor. + */ @JsonSetter("page_info") - public String pageInfo; + private String pageInfo; + /** + * The link to the next page of results, if available. + */ @JsonSetter("next_link") - public String nextLink; + private String nextLink; + + /** + * Gets the data items on the current page. + * + * @return the data list. + */ + public List getData() { + return data; + } + + /** + * Sets the data items on the current page. + * + * @param data the data list. + */ + public void setData(List data) { + this.data = data; + } + + /** + * Gets the page information. + * + * @return the page info. + */ + public String getPageInfo() { + return pageInfo; + } + + /** + * Sets the page information. + * + * @param pageInfo the page info. + */ + public void setPageInfo(String pageInfo) { + this.pageInfo = pageInfo; + } + + /** + * Gets the link to the next page. + * + * @return the next link. + */ + public String getNextLink() { + return nextLink; + } + /** + * Sets the link to the next page. + * + * @param nextLink the next link. + */ + public void setNextLink(String nextLink) { + this.nextLink = nextLink; + } } From b4ad9de323930952e97d2d6442292e8f8070d06b Mon Sep 17 00:00:00 2001 From: Asad Ali <14asadali@gmail.com> Date: Tue, 29 Apr 2025 16:59:44 +0500 Subject: [PATCH 18/20] more linting fixes --- .../java/io/apimatic/core/HttpRequest.java | 19 ++++++++-------- .../core/types/pagination/PagePagination.java | 2 +- .../apimatic/core/utilities/CoreHelper.java | 8 ++++--- src/test/java/apimatic/core/EndToEndTest.java | 9 ++++---- .../type/pagination/CursorPaginationTest.java | 9 ++++++-- .../type/pagination/LinkPaginationTest.java | 5 +++-- .../type/pagination/OffsetPaginationTest.java | 18 ++++++++++----- .../type/pagination/PagePaginationTest.java | 22 +++++++++++++------ .../core/type/pagination/RecordPage.java | 2 +- 9 files changed, 59 insertions(+), 35 deletions(-) diff --git a/src/main/java/io/apimatic/core/HttpRequest.java b/src/main/java/io/apimatic/core/HttpRequest.java index f87d1b5d..b3339d82 100644 --- a/src/main/java/io/apimatic/core/HttpRequest.java +++ b/src/main/java/io/apimatic/core/HttpRequest.java @@ -374,7 +374,7 @@ private void updateHeaderParams(Function setter, String point) { for (Map.Entry entry : simplifiedHeaders.entrySet()) { if (entry.getValue() instanceof List) { - headerParams.put(entry.getKey(), (List)entry.getValue()); + headerParams.put(entry.getKey(), (List) entry.getValue()); } else { headerParams.put(entry.getKey(), Arrays.asList(entry.getValue())); } @@ -383,13 +383,13 @@ private void updateHeaderParams(Function setter, String point) { private void updateTemplateParams(Function setter, String point) { Map simplifiedPath = new HashMap<>(); - for (Map.Entry> entry : - templateParams.entrySet()) { + for (Map.Entry> entry + : templateParams.entrySet()) { simplifiedPath.put(entry.getKey(), entry.getValue().getKey()); } - + simplifiedPath = CoreHelper.updateValueByPointer(simplifiedPath, point, setter); - + for (Map.Entry entry : simplifiedPath.entrySet()) { // Preserve the original boolean if it exists, otherwise set default (e.g., false) Boolean originalFlag = templateParams.containsKey(entry.getKey()) @@ -399,7 +399,7 @@ private void updateTemplateParams(Function setter, String point) new SimpleEntry<>(entry.getValue(), originalFlag)); } } - + /** * Base uri server address. * @param server the base uri address. @@ -566,7 +566,7 @@ public Builder arraySerializationFormat(ArraySerializationFormat arraySerializat this.arraySerializationFormat = arraySerializationFormat; return this; } - + private Map> getHeaderParams() { Map> converted = new HashMap<>(); @@ -609,8 +609,9 @@ public Request build(GlobalConfiguration coreConfig) throws IOException { Authentication authentication = authBuilder.build(coreConfig.getAuthentications()); HttpRequest coreRequest = new HttpRequest(coreConfig, server, path, httpMethod, authentication, - queryParams, templateParams, getHeaderParams(), formParams, formParamaters, - body, bodySerializer, bodyParameters, arraySerializationFormat); + queryParams, templateParams, getHeaderParams(), formParams, + formParamaters, body, bodySerializer, bodyParameters, + arraySerializationFormat); Request coreHttpRequest = coreRequest.getCoreHttpRequest(); if (coreConfig.getHttpCallback() != null) { diff --git a/src/main/java/io/apimatic/core/types/pagination/PagePagination.java b/src/main/java/io/apimatic/core/types/pagination/PagePagination.java index 3bce56e6..147b0ef3 100644 --- a/src/main/java/io/apimatic/core/types/pagination/PagePagination.java +++ b/src/main/java/io/apimatic/core/types/pagination/PagePagination.java @@ -21,7 +21,7 @@ public boolean isValid(PaginatedData paginatedData) { return false; } - final boolean[] isUpdated = { false }; + final boolean[] isUpdated = {false}; nextReqBuilder.updateByReference(input, old -> { int newValue = Integer.parseInt("" + old) + 1; isUpdated[0] = true; diff --git a/src/main/java/io/apimatic/core/utilities/CoreHelper.java b/src/main/java/io/apimatic/core/utilities/CoreHelper.java index b550bb79..d4e52fe9 100644 --- a/src/main/java/io/apimatic/core/utilities/CoreHelper.java +++ b/src/main/java/io/apimatic/core/utilities/CoreHelper.java @@ -1589,7 +1589,7 @@ public static String getQueryParametersFromUrl(String queryUrl) { int queryStringIndex = queryUrl.indexOf('?'); return queryStringIndex != -1 ? queryUrl.substring(queryStringIndex + 1) : ""; } - + /** * Extracts query parameters from a given URL into a map. * @@ -1631,7 +1631,8 @@ public static JsonStructure createJsonStructure(String json) { * @param jsonHeaders The response headers. * @return The resolved value as a string, or null if not found. */ - public static String resolveResponsePointer(String pointer, String jsonBody, String jsonHeaders) { + public static String resolveResponsePointer(String pointer, String jsonBody, + String jsonHeaders) { if (pointer == null) { return null; } @@ -1697,7 +1698,8 @@ private static String getValueFromJson(String pointer, String json) { * @param updater The function to apply to the value at the pointer. * @return The updated object, or the original if any error occurs. */ - public static T updateValueByPointer(T value, String pointer, Function updater) { + public static T updateValueByPointer(T value, String pointer, + Function updater) { if (value == null || "".equals(pointer) || updater == null) { return value; } diff --git a/src/test/java/apimatic/core/EndToEndTest.java b/src/test/java/apimatic/core/EndToEndTest.java index f116246b..8fa775a6 100644 --- a/src/test/java/apimatic/core/EndToEndTest.java +++ b/src/test/java/apimatic/core/EndToEndTest.java @@ -176,7 +176,8 @@ public void testLinkPaginationData() throws IOException, CoreApiException { @Test public void testCursorPaginationData() throws IOException, CoreApiException { - verifyData(getPaginatedApiCall(new CursorPagination("$response.body#/page_info", "$request.path#/cursor")).execute()); + verifyData(getPaginatedApiCall(new CursorPagination("$response.body#/page_info", + "$request.path#/cursor")).execute()); } @Test @@ -523,8 +524,8 @@ private ApiCall, CoreApiException> getPaginate when(response.getBody()).thenReturn("{\"data\":[\"apple\",\"mango\",\"orange\"],\"" + "page_info\":\"fruits\",\"next_link\":\"https://localhost:3000/path?page=2\"}"); when(response.getHeaders()).thenReturn(getHttpHeaders()); - Callback callback = new Callback() { - private int callNumber = 1; + Callback pageCallback = new Callback() { + private int callNumber = 1; @Override public void onBeforeRequest(Request request) { if (callNumber > 1) { @@ -543,7 +544,7 @@ public void onAfterResponse(Context context) { } }; return new ApiCall.Builder, CoreApiException>() - .globalConfig(getGlobalConfig(callback)) + .globalConfig(getGlobalConfig(pageCallback)) .requestBuilder(requestBuilder -> requestBuilder.server("https://localhost:3000") .path("/path/{cursor}") .templateParam(param -> param.key("cursor").value("cursor") diff --git a/src/test/java/apimatic/core/type/pagination/CursorPaginationTest.java b/src/test/java/apimatic/core/type/pagination/CursorPaginationTest.java index bc2d39f9..6516c80f 100644 --- a/src/test/java/apimatic/core/type/pagination/CursorPaginationTest.java +++ b/src/test/java/apimatic/core/type/pagination/CursorPaginationTest.java @@ -23,6 +23,9 @@ */ public class CursorPaginationTest { + /** + * Silent rule for Mockito initialization. + */ @Rule public MockitoRule initRule = MockitoJUnit.rule().silent(); @@ -79,10 +82,11 @@ public void testWithValidCursorAndDifferentTypeReturnsTrueA() { @Test public void testWithValidCursorAndDifferentTypeReturnsTrueB() { PaginatedData paginatedData = mock(PaginatedData.class); + final int current = 456; when(paginatedData.getLastRequestBuilder()) .thenReturn(new HttpRequest.Builder().queryParam( - q -> q.key("cursor").value(456))); + q -> q.key("cursor").value(current))); when(paginatedData.getLastResponseBody()).thenReturn("{\"next_cursor\": 123}"); CursorPagination cursor = new CursorPagination("$response.body#/next_cursor", @@ -104,7 +108,8 @@ public void testWithValidCursorButMissingInFirstRequestReturnsFalse() { when(paginatedData.getLastRequestBuilder()).thenReturn(new HttpRequest.Builder()); when(paginatedData.getLastResponseBody()).thenReturn("{\"next_cursor\": \"xyz123\"}"); - CursorPagination cursor = new CursorPagination("$response.body#/next_cursor", "$request.query#/cursor"); + CursorPagination cursor = new CursorPagination("$response.body#/next_cursor", + "$request.query#/cursor"); assertFalse(cursor.isValid(paginatedData)); assertNotNull(cursor.getNextRequestBuilder()); diff --git a/src/test/java/apimatic/core/type/pagination/LinkPaginationTest.java b/src/test/java/apimatic/core/type/pagination/LinkPaginationTest.java index 79564784..3f9ebf2d 100644 --- a/src/test/java/apimatic/core/type/pagination/LinkPaginationTest.java +++ b/src/test/java/apimatic/core/type/pagination/LinkPaginationTest.java @@ -50,9 +50,10 @@ public void testValidLinkReturnsTrue() { @Test public void testValidLinkWithAdditionalParamsReturnsTrue() { PaginatedData paginatedData = mock(PaginatedData.class); + final int pageSize = 456; when(paginatedData.getLastRequestBuilder()).thenReturn(new HttpRequest.Builder() - .queryParam(q -> q.key("size").value(23)) + .queryParam(q -> q.key("size").value(pageSize)) .queryParam(q -> q.key("page").value(1)) .headerParam(h -> h.key("page").value(2))); @@ -70,7 +71,7 @@ public void testValidLinkWithAdditionalParamsReturnsTrue() { }); link.getNextRequestBuilder().updateByReference("$request.query#/size", v -> { - assertEquals(23, v); + assertEquals(pageSize, v); return v; }); diff --git a/src/test/java/apimatic/core/type/pagination/OffsetPaginationTest.java b/src/test/java/apimatic/core/type/pagination/OffsetPaginationTest.java index b9adce65..61138340 100644 --- a/src/test/java/apimatic/core/type/pagination/OffsetPaginationTest.java +++ b/src/test/java/apimatic/core/type/pagination/OffsetPaginationTest.java @@ -41,7 +41,8 @@ public void testValidOffsetHeaderReturnsTrue() { PaginatedData paginatedData = mock(PaginatedData.class); when(paginatedData.getLastRequestBuilder()) - .thenReturn(new HttpRequest.Builder().headerParam(h -> h.key("offset").value(INITIAL_OFFSET))); + .thenReturn(new HttpRequest.Builder().headerParam( + h -> h.key("offset").value(INITIAL_OFFSET))); when(paginatedData.getLastDataSize()).thenReturn(PAGE_SIZE); OffsetPagination offset = new OffsetPagination("$request.headers#/offset"); @@ -60,7 +61,8 @@ public void testValidOffsetTemplateReturnsTrue() { PaginatedData paginatedData = mock(PaginatedData.class); when(paginatedData.getLastRequestBuilder()) - .thenReturn(new HttpRequest.Builder().templateParam(t -> t.key("offset").value(INITIAL_OFFSET))); + .thenReturn(new HttpRequest.Builder().templateParam( + t -> t.key("offset").value(INITIAL_OFFSET))); when(paginatedData.getLastDataSize()).thenReturn(PAGE_SIZE); OffsetPagination offset = new OffsetPagination("$request.path#/offset"); @@ -79,7 +81,8 @@ public void testValidOffsetReturnsTrue() { PaginatedData paginatedData = mock(PaginatedData.class); when(paginatedData.getLastRequestBuilder()) - .thenReturn(new HttpRequest.Builder().queryParam(q -> q.key("offset").value(INITIAL_OFFSET))); + .thenReturn(new HttpRequest.Builder().queryParam( + q -> q.key("offset").value(INITIAL_OFFSET))); when(paginatedData.getLastDataSize()).thenReturn(PAGE_SIZE); OffsetPagination offset = new OffsetPagination("$request.query#/offset"); @@ -122,7 +125,8 @@ public void testValidStringOffsetReturnsTrue() { PaginatedData paginatedData = mock(PaginatedData.class); when(paginatedData.getLastRequestBuilder()) - .thenReturn(new HttpRequest.Builder().queryParam(q -> q.key("offset").value("5"))); + .thenReturn(new HttpRequest.Builder().queryParam( + q -> q.key("offset").value("5"))); when(paginatedData.getLastDataSize()).thenReturn(PAGE_SIZE); OffsetPagination offset = new OffsetPagination("$request.query#/offset"); @@ -141,7 +145,8 @@ public void testInvalidStringOffsetReturnsFalse() { PaginatedData paginatedData = mock(PaginatedData.class); when(paginatedData.getLastRequestBuilder()) - .thenReturn(new HttpRequest.Builder().queryParam(q -> q.key("offset").value("5a"))); + .thenReturn(new HttpRequest.Builder().queryParam( + q -> q.key("offset").value("5a"))); when(paginatedData.getLastDataSize()).thenReturn(PAGE_SIZE); OffsetPagination offset = new OffsetPagination("$request.query#/offset"); @@ -178,7 +183,8 @@ public void testNullOffsetReturnsFalse() { PaginatedData paginatedData = mock(PaginatedData.class); when(paginatedData.getLastRequestBuilder()) - .thenReturn(new HttpRequest.Builder().queryParam(q -> q.key("offset").value(NUMERIC_OFFSET))); + .thenReturn(new HttpRequest.Builder().queryParam( + q -> q.key("offset").value(NUMERIC_OFFSET))); when(paginatedData.getLastDataSize()).thenReturn(PAGE_SIZE); OffsetPagination offset = new OffsetPagination(null); diff --git a/src/test/java/apimatic/core/type/pagination/PagePaginationTest.java b/src/test/java/apimatic/core/type/pagination/PagePaginationTest.java index 9b7504f0..662133a6 100644 --- a/src/test/java/apimatic/core/type/pagination/PagePaginationTest.java +++ b/src/test/java/apimatic/core/type/pagination/PagePaginationTest.java @@ -19,7 +19,9 @@ import io.apimatic.core.types.pagination.PagePagination; import io.apimatic.core.types.pagination.PaginatedData; -/** Unit tests for PagePagination. */ +/** + * Unit tests for PagePagination. + */ public class PagePaginationTest { /** @@ -35,7 +37,8 @@ public void testWithValidPageHeaderReturnsTrue() { final int nextPage = 4; when(paginatedData.getLastRequestBuilder()) - .thenReturn(new HttpRequest.Builder().headerParam(h -> h.key("page").value(initialPage))); + .thenReturn(new HttpRequest.Builder().headerParam( + h -> h.key("page").value(initialPage))); PagePagination page = new PagePagination("$request.headers#/page"); @@ -55,7 +58,8 @@ public void testWithValidPageTemplateReturnsTrue() { final int nextPage = 4; when(paginatedData.getLastRequestBuilder()) - .thenReturn(new HttpRequest.Builder().templateParam(t -> t.key("page").value(initialPage))); + .thenReturn(new HttpRequest.Builder().templateParam( + t -> t.key("page").value(initialPage))); PagePagination page = new PagePagination("$request.path#/page"); @@ -75,7 +79,8 @@ public void testWithValidPageReturnsTrue() { final int nextPage = 4; when(paginatedData.getLastRequestBuilder()) - .thenReturn(new HttpRequest.Builder().queryParam(q -> q.key("page").value(initialPage))); + .thenReturn(new HttpRequest.Builder().queryParam( + q -> q.key("page").value(initialPage))); PagePagination page = new PagePagination("$request.query#/page"); @@ -93,7 +98,8 @@ public void testWithValidPageAsInnerFieldReturnsTrue() { PaginatedData paginatedData = mock(PaginatedData.class); when(paginatedData.getLastRequestBuilder()).thenReturn( - new HttpRequest.Builder().queryParam(q -> q.key("page").value(new HashMap() { + new HttpRequest.Builder().queryParam(q -> q.key("page") + .value(new HashMap() { private static final long serialVersionUID = 1L; { put("val", "1"); @@ -118,7 +124,8 @@ public void testWithValidStringPageReturnsTrue() { final int next = 6; when(paginatedData.getLastRequestBuilder()) - .thenReturn(new HttpRequest.Builder().queryParam(q -> q.key("page").value(current))); + .thenReturn(new HttpRequest.Builder().queryParam( + q -> q.key("page").value(current))); PagePagination page = new PagePagination("$request.query#/page"); @@ -172,7 +179,8 @@ public void testWithNullPageReturnsFalse() { final int current = 5; when(paginatedData.getLastRequestBuilder()) - .thenReturn(new HttpRequest.Builder().queryParam(q -> q.key("page").value(current))); + .thenReturn(new HttpRequest.Builder().queryParam( + q -> q.key("page").value(current))); PagePagination page = new PagePagination(null); diff --git a/src/test/java/apimatic/core/type/pagination/RecordPage.java b/src/test/java/apimatic/core/type/pagination/RecordPage.java index 52f21130..328f3012 100644 --- a/src/test/java/apimatic/core/type/pagination/RecordPage.java +++ b/src/test/java/apimatic/core/type/pagination/RecordPage.java @@ -38,7 +38,7 @@ public List getData() { /** * Sets the data items on the current page. - * + * * @param data the data list. */ public void setData(List data) { From 63c3ce793451c24c4e7b915fcc961e8a688a4fda Mon Sep 17 00:00:00 2001 From: Asad Ali <14asadali@gmail.com> Date: Tue, 29 Apr 2025 17:33:29 +0500 Subject: [PATCH 19/20] sonar qube fixes --- src/main/java/io/apimatic/core/HttpRequest.java | 8 ++++---- .../core/types/pagination/PaginatedData.java | 1 - .../java/io/apimatic/core/utilities/CoreHelper.java | 4 ++-- .../core/type/pagination/OffsetPaginationTest.java | 13 +++++++------ 4 files changed, 13 insertions(+), 13 deletions(-) diff --git a/src/main/java/io/apimatic/core/HttpRequest.java b/src/main/java/io/apimatic/core/HttpRequest.java index b3339d82..b787bbf2 100644 --- a/src/main/java/io/apimatic/core/HttpRequest.java +++ b/src/main/java/io/apimatic/core/HttpRequest.java @@ -12,7 +12,7 @@ import java.util.Map.Entry; import java.util.Set; import java.util.function.Consumer; -import java.util.function.Function; +import java.util.function.UnaryOperator; import io.apimatic.core.authentication.AuthBuilder; import io.apimatic.core.exceptions.AuthValidationException; @@ -335,7 +335,7 @@ public static class Builder { * @param setter A function that takes in an old value and returns a new value. * @return The updated instance of current request builder. */ - public Builder updateByReference(String pointer, Function setter) { + public Builder updateByReference(String pointer, UnaryOperator setter) { if (pointer == null) { return this; } @@ -360,7 +360,7 @@ public Builder updateByReference(String pointer, Function setter } @SuppressWarnings("unchecked") - private void updateHeaderParams(Function setter, String point) { + private void updateHeaderParams(UnaryOperator setter, String point) { Map simplifiedHeaders = new HashMap<>(); for (Entry> entry : headerParams.entrySet()) { if (entry.getValue().size() == 1) { @@ -381,7 +381,7 @@ private void updateHeaderParams(Function setter, String point) { } } - private void updateTemplateParams(Function setter, String point) { + private void updateTemplateParams(UnaryOperator setter, String point) { Map simplifiedPath = new HashMap<>(); for (Map.Entry> entry : templateParams.entrySet()) { diff --git a/src/main/java/io/apimatic/core/types/pagination/PaginatedData.java b/src/main/java/io/apimatic/core/types/pagination/PaginatedData.java index 921d63a8..c54bb9d6 100644 --- a/src/main/java/io/apimatic/core/types/pagination/PaginatedData.java +++ b/src/main/java/io/apimatic/core/types/pagination/PaginatedData.java @@ -221,7 +221,6 @@ private void fetchMoreData() { updateUsing(result.lastResponse, result.lastRequestBuilder); return; } catch (Exception e) { - continue; } } } diff --git a/src/main/java/io/apimatic/core/utilities/CoreHelper.java b/src/main/java/io/apimatic/core/utilities/CoreHelper.java index d4e52fe9..c268f602 100644 --- a/src/main/java/io/apimatic/core/utilities/CoreHelper.java +++ b/src/main/java/io/apimatic/core/utilities/CoreHelper.java @@ -26,7 +26,7 @@ import java.util.Map; import java.util.Set; import java.util.UUID; -import java.util.function.Function; +import java.util.function.UnaryOperator; import java.util.regex.Matcher; import java.util.regex.Pattern; import java.util.stream.Collectors; @@ -1699,7 +1699,7 @@ private static String getValueFromJson(String pointer, String json) { * @return The updated object, or the original if any error occurs. */ public static T updateValueByPointer(T value, String pointer, - Function updater) { + UnaryOperator updater) { if (value == null || "".equals(pointer) || updater == null) { return value; } diff --git a/src/test/java/apimatic/core/type/pagination/OffsetPaginationTest.java b/src/test/java/apimatic/core/type/pagination/OffsetPaginationTest.java index 61138340..b8e32b86 100644 --- a/src/test/java/apimatic/core/type/pagination/OffsetPaginationTest.java +++ b/src/test/java/apimatic/core/type/pagination/OffsetPaginationTest.java @@ -101,12 +101,13 @@ public void testValidOffsetAsInnerFieldReturnsTrue() { PaginatedData paginatedData = mock(PaginatedData.class); when(paginatedData.getLastRequestBuilder()).thenReturn( - new HttpRequest.Builder().queryParam(q -> q.key("offset").value(new HashMap() { - private static final long serialVersionUID = 1L; - { - put("val", "1"); - } - }))); + new HttpRequest.Builder().queryParam(q -> q.key("offset").value( + new HashMap() { + private static final long serialVersionUID = 1L; + { + put("val", "1"); + } + }))); when(paginatedData.getLastDataSize()).thenReturn(PAGE_SIZE); OffsetPagination offset = new OffsetPagination("$request.query#/offset/val"); From 21e6b946a3cf60c0a89c79b77f076ce91ecf16cf Mon Sep 17 00:00:00 2001 From: Asad Ali <14asadali@gmail.com> Date: Wed, 30 Apr 2025 17:00:38 +0500 Subject: [PATCH 20/20] refactor: resolved pr commments --- .../types/pagination/CursorPagination.java | 4 ++-- .../core/types/pagination/LinkPagination.java | 2 +- .../types/pagination/OffsetPagination.java | 2 +- .../core/types/pagination/PagePagination.java | 2 +- .../core/types/pagination/PaginatedData.java | 19 +++++++++---------- 5 files changed, 14 insertions(+), 15 deletions(-) diff --git a/src/main/java/io/apimatic/core/types/pagination/CursorPagination.java b/src/main/java/io/apimatic/core/types/pagination/CursorPagination.java index 76fd6158..41b6ae8a 100644 --- a/src/main/java/io/apimatic/core/types/pagination/CursorPagination.java +++ b/src/main/java/io/apimatic/core/types/pagination/CursorPagination.java @@ -4,8 +4,8 @@ import io.apimatic.core.utilities.CoreHelper; public class CursorPagination implements PaginationDataManager { - private String output; - private String input; + private final String output; + private final String input; private Builder nextReqBuilder; /** diff --git a/src/main/java/io/apimatic/core/types/pagination/LinkPagination.java b/src/main/java/io/apimatic/core/types/pagination/LinkPagination.java index 623d4b80..4f6ed76d 100644 --- a/src/main/java/io/apimatic/core/types/pagination/LinkPagination.java +++ b/src/main/java/io/apimatic/core/types/pagination/LinkPagination.java @@ -4,7 +4,7 @@ import io.apimatic.core.utilities.CoreHelper; public class LinkPagination implements PaginationDataManager { - private String next; + private final String next; private Builder nextReqBuilder; /** diff --git a/src/main/java/io/apimatic/core/types/pagination/OffsetPagination.java b/src/main/java/io/apimatic/core/types/pagination/OffsetPagination.java index 18ac4e9d..6a14791d 100644 --- a/src/main/java/io/apimatic/core/types/pagination/OffsetPagination.java +++ b/src/main/java/io/apimatic/core/types/pagination/OffsetPagination.java @@ -3,7 +3,7 @@ import io.apimatic.core.HttpRequest.Builder; public class OffsetPagination implements PaginationDataManager { - private String input; + private final String input; private Builder nextReqBuilder; /** diff --git a/src/main/java/io/apimatic/core/types/pagination/PagePagination.java b/src/main/java/io/apimatic/core/types/pagination/PagePagination.java index 147b0ef3..2e1f6163 100644 --- a/src/main/java/io/apimatic/core/types/pagination/PagePagination.java +++ b/src/main/java/io/apimatic/core/types/pagination/PagePagination.java @@ -3,7 +3,7 @@ import io.apimatic.core.HttpRequest.Builder; public class PagePagination implements PaginationDataManager { - private String input; + private final String input; private Builder nextReqBuilder; /** diff --git a/src/main/java/io/apimatic/core/types/pagination/PaginatedData.java b/src/main/java/io/apimatic/core/types/pagination/PaginatedData.java index c54bb9d6..e0024121 100644 --- a/src/main/java/io/apimatic/core/types/pagination/PaginatedData.java +++ b/src/main/java/io/apimatic/core/types/pagination/PaginatedData.java @@ -23,17 +23,17 @@ public class PaginatedData implements Iterator { private int currentIndex = 0; - private List data = new ArrayList(); - private List

pages = new ArrayList

(); + private final List data = new ArrayList(); + private final List

pages = new ArrayList

(); private int lastDataSize; private Response lastResponse; private HttpRequest.Builder lastRequestBuilder; - private TypeReference

pageType; - private Function> converter; - private PaginationDataManager[] dataManagers; - private EndpointConfiguration endpointConfig; - private GlobalConfiguration globalConfig; + private final TypeReference

pageType; + private final Function> converter; + private final PaginationDataManager[] dataManagers; + private final EndpointConfiguration endpointConfig; + private final GlobalConfiguration globalConfig; /** * @param paginatedData Existing instance to be cloned. @@ -212,15 +212,14 @@ private void fetchMoreData() { .requestBuilder(manager.getNextRequestBuilder()) .responseHandler(res -> res .globalErrorCase(Collections.singletonMap(ErrorCase.DEFAULT, - ErrorCase.setReason(null, (reason, context) -> - new CoreApiException(reason, context)))) + ErrorCase.setReason(null, CoreApiException::new))) .nullify404(false) .paginatedDeserializer(pageType, converter, r -> r, dataManagers)) .build().execute(); updateUsing(result.lastResponse, result.lastRequestBuilder); return; - } catch (Exception e) { + } catch (Exception ignored) { } } }