diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..2c1946c --- /dev/null +++ b/.gitignore @@ -0,0 +1,35 @@ +HELP.md +target/ +!.mvn/wrapper/maven-wrapper.jar +!**/src/main/**/target/ +!**/src/test/**/target/ + +### STS ### +.apt_generated +.classpath +.factorypath +.project +.settings +.springBeans +.sts4-cache + +### IntelliJ IDEA ### +.idea +*.iws +*.iml +*.ipr + +### NetBeans ### +/nbproject/private/ +/nbbuild/ +/dist/ +/nbdist/ +/.nb-gradle/ +build/ +!**/src/main/**/build/ +!**/src/test/**/build/ + +### VS Code ### +.vscode/ + +settings.xml \ No newline at end of file diff --git a/LICENSE b/LICENSE index 261eeb9..7a4a3ea 100644 --- a/LICENSE +++ b/LICENSE @@ -1,3 +1,4 @@ + Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ @@ -198,4 +199,4 @@ distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and - limitations under the License. + limitations under the License. \ No newline at end of file diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..5a0498c --- /dev/null +++ b/pom.xml @@ -0,0 +1,120 @@ + + + + 4.0.0 + CAE HTTP Client + Meant for enabling applications to use HTTP at ease, without having to compromise the clean state of the overall architecture. + https://github.com/clean-arch-enablers-project/cae-utils-http-client/blob/main/README.md + com.clean-arch-enablers + http-client + 0.0.1 + jar + + + Apache License, Version 2.0 + http://www.apache.org/licenses/LICENSE-2.0.txt + + + + https://github.com/clean-arch-enablers-project/cae-utils-http-client + + + + Zé Lúcio Jr. + joselucioalmeidajunior@gmail.com + https://github.com/zeluciojr + + + + + 11 + 11 + UTF-8 + + + + + org.projectlombok + lombok + 1.18.24 + + + com.fasterxml.jackson.core + jackson-databind + 2.16.1 + + + org.junit.jupiter + junit-jupiter-engine + 5.9.0 + test + + + org.mockito + mockito-core + 4.8.0 + test + + + org.mockito + mockito-junit-jupiter + 4.8.0 + test + + + + + + org.sonatype.central + central-publishing-maven-plugin + 0.4.0 + true + + central + true + + + + org.apache.maven.plugins + maven-javadoc-plugin + 3.3.0 + + + attach-javadocs + + jar + + + + + + org.apache.maven.plugins + maven-source-plugin + 3.2.1 + + + attach-sources + + jar + + + + + + org.apache.maven.plugins + maven-gpg-plugin + 1.6 + + + sign-artifacts + verify + + sign + + + + + + + diff --git a/src/main/java/com/clean_arch_enablers/http_client/ExceptionHandler.java b/src/main/java/com/clean_arch_enablers/http_client/ExceptionHandler.java new file mode 100644 index 0000000..210f0de --- /dev/null +++ b/src/main/java/com/clean_arch_enablers/http_client/ExceptionHandler.java @@ -0,0 +1,12 @@ +package com.clean_arch_enablers.http_client; + +@FunctionalInterface +public interface ExceptionHandler { + + /** + * Method which will be called when handling an exception + * @param exception the exception to handle + */ + void handle(Exception exception); + +} diff --git a/src/main/java/com/clean_arch_enablers/http_client/HttpExceptionHandlersByExceptionTypeFactory.java b/src/main/java/com/clean_arch_enablers/http_client/HttpExceptionHandlersByExceptionTypeFactory.java new file mode 100644 index 0000000..93a5cb6 --- /dev/null +++ b/src/main/java/com/clean_arch_enablers/http_client/HttpExceptionHandlersByExceptionTypeFactory.java @@ -0,0 +1,10 @@ +package com.clean_arch_enablers.http_client; + +import com.clean_arch_enablers.http_client.commons.HandlersFactory; + +import java.util.Map; + +public interface HttpExceptionHandlersByExceptionTypeFactory extends HandlersFactory, ExceptionHandler>> { } + + + diff --git a/src/main/java/com/clean_arch_enablers/http_client/HttpRequestBuilder.java b/src/main/java/com/clean_arch_enablers/http_client/HttpRequestBuilder.java new file mode 100644 index 0000000..18d3125 --- /dev/null +++ b/src/main/java/com/clean_arch_enablers/http_client/HttpRequestBuilder.java @@ -0,0 +1,12 @@ +package com.clean_arch_enablers.http_client; + +public interface HttpRequestBuilder extends HttpRequestBuilderForHandlers, HttpRequestBuilderForRetrying{ + + HttpRequestBuilder headerOf(String key, String value); + HttpRequestBuilder headersFactory(HttpRequestHeaderFactory httpRequestHeaderFactory); + HttpRequestBuilder pathVariableOf(String pathVariablePlaceholder, String pathVariableValue); + HttpRequestBuilder queryParameterOf(String queryParameterName, String queryParameterValue); + HttpRequestBuilder proxyAddress(String host, Integer port); + HttpRequestModel finishBuildingModel(); + +} diff --git a/src/main/java/com/clean_arch_enablers/http_client/HttpRequestBuilderForHandlers.java b/src/main/java/com/clean_arch_enablers/http_client/HttpRequestBuilderForHandlers.java new file mode 100644 index 0000000..41d3385 --- /dev/null +++ b/src/main/java/com/clean_arch_enablers/http_client/HttpRequestBuilderForHandlers.java @@ -0,0 +1,46 @@ +package com.clean_arch_enablers.http_client; + +/** + * Builder component for attaching handlers to the request model. + */ +public interface HttpRequestBuilderForHandlers { + + + /** + * Attaches a handler by the number which represents the HTTP status code + * @param statusCode the number of the HTTP status code: 404, 400, 503... + * @param httpResponseHandler an arrow function which implements the Functional Interface of HttpResponseHandler + * @return the builder instance for further attachments + */ + HttpRequestBuilder handlerByHttpStatusCode(Integer statusCode, HttpResponseHandler httpResponseHandler); + + /** + * Attaches a group of handlers by the numbers which represent HTTP statuses code from within the factory + * @param httpResponseHandlersByStatusCodeFactory the factory from which the handlers will be made + * @return the builder instance for further attachments + */ + HttpRequestBuilder handlersByHttpStatusCodeFactory(HttpResponseHandlersByStatusCodeFactory httpResponseHandlersByStatusCodeFactory); + + /** + * Attaches a handler for any unsuccessful HTTP response + * @param httpResponseHandler an arrow function which implements the Functional Interface of HttpResponseHandler + * @return the builder instance for further attachments + */ + HttpRequestBuilder handlerForAnyUnsuccessfulResponse(HttpResponseHandler httpResponseHandler); + + /** + * Attaches a group of handlers by types of exceptions from within the factory + * @param httpExceptionHandlersByExceptionTypeFactory the factory from which the handlers will be made + * @return the builder instance for further attachments + */ + HttpRequestBuilder handlersByExceptionTypeFactory(HttpExceptionHandlersByExceptionTypeFactory httpExceptionHandlersByExceptionTypeFactory); + + /** + * Attaches a handler by type of exception + * @param exceptionType the type of the exception which will trigger the execution of the following handler + * @param exceptionHandler an arrow function which implements the Functional Interface of ExceptionHandler + * @return the builder instance for further attachments + */ + HttpRequestBuilder handlerByExceptionType(Class exceptionType, ExceptionHandler exceptionHandler); + +} diff --git a/src/main/java/com/clean_arch_enablers/http_client/HttpRequestBuilderForRetrying.java b/src/main/java/com/clean_arch_enablers/http_client/HttpRequestBuilderForRetrying.java new file mode 100644 index 0000000..dc9a268 --- /dev/null +++ b/src/main/java/com/clean_arch_enablers/http_client/HttpRequestBuilderForRetrying.java @@ -0,0 +1,16 @@ +package com.clean_arch_enablers.http_client; + +import com.clean_arch_enablers.http_client.commons.RetriersByExceptionTypeFactory; + +public interface HttpRequestBuilderForRetrying { + + HttpRequestBuilder retrierByHttpStatusCode(Integer statusCode, RetrierModel retrierModel); + HttpRequestBuilder retriersByHttpStatusCodeFactory(RetriersByStatusCodeFactory retriersByStatusCodeFactory); + HttpRequestBuilder retriersByExceptionTypeFactory(RetriersByExceptionTypeFactory retriersByExceptionTypeFactory); + HttpRequestBuilder retrierByExceptionType(Class exceptionType, RetrierModel retrierModel); + +} + + + + diff --git a/src/main/java/com/clean_arch_enablers/http_client/HttpRequestHeaderFactory.java b/src/main/java/com/clean_arch_enablers/http_client/HttpRequestHeaderFactory.java new file mode 100644 index 0000000..f19efce --- /dev/null +++ b/src/main/java/com/clean_arch_enablers/http_client/HttpRequestHeaderFactory.java @@ -0,0 +1,16 @@ +package com.clean_arch_enablers.http_client; + +import java.util.Map; + +/** + * Factory for HTTP Request Headers + */ +public interface HttpRequestHeaderFactory { + + /** + * This method will be called under the hood when an instance of this type is passed during the building process of a HttpRequestModel instance. + * @return a map in which the keys are the HTTP Headers and the values are their respective HTTP Header Values + */ + Map makeHeaders(); + +} diff --git a/src/main/java/com/clean_arch_enablers/http_client/HttpRequestMethod.java b/src/main/java/com/clean_arch_enablers/http_client/HttpRequestMethod.java new file mode 100644 index 0000000..48ce4f7 --- /dev/null +++ b/src/main/java/com/clean_arch_enablers/http_client/HttpRequestMethod.java @@ -0,0 +1,11 @@ +package com.clean_arch_enablers.http_client; + + +import com.clean_arch_enablers.http_client.implementations.AbstractHttpRequestModel; +import com.clean_arch_enablers.http_client.implementations.exceptions.RetryNeededOnExceptionThrownException; + +public interface HttpRequestMethod { + + HttpResponse execute(AbstractHttpRequestModel httpRequestModel) throws RetryNeededOnExceptionThrownException; + +} diff --git a/src/main/java/com/clean_arch_enablers/http_client/HttpRequestModel.java b/src/main/java/com/clean_arch_enablers/http_client/HttpRequestModel.java new file mode 100644 index 0000000..0aa4fc3 --- /dev/null +++ b/src/main/java/com/clean_arch_enablers/http_client/HttpRequestModel.java @@ -0,0 +1,10 @@ +package com.clean_arch_enablers.http_client; + +import com.fasterxml.jackson.core.type.TypeReference; + +public interface HttpRequestModel { + + T sendRequestReturning(Class typeOfExpectedResponseBody); + + T sendRequestReturning(TypeReference typeReference); +} diff --git a/src/main/java/com/clean_arch_enablers/http_client/HttpRequestStarter.java b/src/main/java/com/clean_arch_enablers/http_client/HttpRequestStarter.java new file mode 100644 index 0000000..79e5e6b --- /dev/null +++ b/src/main/java/com/clean_arch_enablers/http_client/HttpRequestStarter.java @@ -0,0 +1,12 @@ +package com.clean_arch_enablers.http_client; + +import java.net.http.HttpRequest; + +public interface HttpRequestStarter { + + HttpRequestBuilder startGetRequestFor(String url); + HttpRequestBuilder startPostRequestFor(String url, HttpRequest.BodyPublisher body); + HttpRequestBuilder startPutRequestFor(String url, HttpRequest.BodyPublisher body); + HttpRequestBuilder startDeleteRequestFor(String url); + +} diff --git a/src/main/java/com/clean_arch_enablers/http_client/HttpResponse.java b/src/main/java/com/clean_arch_enablers/http_client/HttpResponse.java new file mode 100644 index 0000000..202c111 --- /dev/null +++ b/src/main/java/com/clean_arch_enablers/http_client/HttpResponse.java @@ -0,0 +1,48 @@ +package com.clean_arch_enablers.http_client; + +import com.fasterxml.jackson.core.type.TypeReference; + +import java.util.function.Consumer; + +public interface HttpResponse{ + + /** + * Retrieve the status code of the response + * @return a number of some HTTP Status Code: 200, 204, 400, 403... + */ + Integer getStatusCode(); + + /** + * Gets the request responsible for generating this response + * @return the HTTP request model object + */ + HttpRequestModel getHttpRequest(); + + /** + * Gets the response body as the parameterized type + * @param bodyType the desired type for the body + * @return the instance of the desired type + * @param the known type to map the body to + */ + T getBodyAs(Class bodyType); + + /** + * Gets the response body as the parameterized type + * @param typeOfExpectedResponseBody the desired type for the body + * @return the instance of the desired type + * @param the known type to map the body to + */ + T getBodyAs(TypeReference typeOfExpectedResponseBody); + + /** + * + * @return whether the response needs handling + */ + boolean needsHandling(); + + /** + * If the instance needs handling it will execute the Consumer received as parameter + * @param checkOnResponse the action to be executed in case of needing handling + */ + void ifNeedsHandling(Consumer checkOnResponse); +} diff --git a/src/main/java/com/clean_arch_enablers/http_client/HttpResponseHandler.java b/src/main/java/com/clean_arch_enablers/http_client/HttpResponseHandler.java new file mode 100644 index 0000000..4c53038 --- /dev/null +++ b/src/main/java/com/clean_arch_enablers/http_client/HttpResponseHandler.java @@ -0,0 +1,12 @@ +package com.clean_arch_enablers.http_client; + +@FunctionalInterface +public interface HttpResponseHandler { + + /** + * Method called when handling an HTTP Response + * @param httpResponse the HTTP response to be handled + */ + void handle(HttpResponse httpResponse); + +} diff --git a/src/main/java/com/clean_arch_enablers/http_client/HttpResponseHandlersByStatusCodeFactory.java b/src/main/java/com/clean_arch_enablers/http_client/HttpResponseHandlersByStatusCodeFactory.java new file mode 100644 index 0000000..fb292a6 --- /dev/null +++ b/src/main/java/com/clean_arch_enablers/http_client/HttpResponseHandlersByStatusCodeFactory.java @@ -0,0 +1,7 @@ +package com.clean_arch_enablers.http_client; + +import com.clean_arch_enablers.http_client.commons.HandlersFactory; + +import java.util.Map; + +public interface HttpResponseHandlersByStatusCodeFactory extends HandlersFactory> { } diff --git a/src/main/java/com/clean_arch_enablers/http_client/RetrierModel.java b/src/main/java/com/clean_arch_enablers/http_client/RetrierModel.java new file mode 100644 index 0000000..811f2c3 --- /dev/null +++ b/src/main/java/com/clean_arch_enablers/http_client/RetrierModel.java @@ -0,0 +1,22 @@ +package com.clean_arch_enablers.http_client; + +import lombok.AccessLevel; +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import lombok.Setter; + +@Getter +@Setter +@RequiredArgsConstructor(access = AccessLevel.PRIVATE) +public class RetrierModel { + + private final Integer originalLimitForRetrying; + private Integer retriesRemaining; + + public static RetrierModel withLimitOf(Integer limitForRetrying){ + var retrier = new RetrierModel(limitForRetrying); + retrier.setRetriesRemaining(retrier.getOriginalLimitForRetrying()); + return retrier; + } + +} diff --git a/src/main/java/com/clean_arch_enablers/http_client/RetriersByStatusCodeFactory.java b/src/main/java/com/clean_arch_enablers/http_client/RetriersByStatusCodeFactory.java new file mode 100644 index 0000000..d8af2b5 --- /dev/null +++ b/src/main/java/com/clean_arch_enablers/http_client/RetriersByStatusCodeFactory.java @@ -0,0 +1,8 @@ +package com.clean_arch_enablers.http_client; + +import com.clean_arch_enablers.http_client.commons.RetriersFactory; + +import java.util.Map; + +public interface RetriersByStatusCodeFactory extends RetriersFactory> { +} diff --git a/src/main/java/com/clean_arch_enablers/http_client/RetryCounter.java b/src/main/java/com/clean_arch_enablers/http_client/RetryCounter.java new file mode 100644 index 0000000..5b94fc0 --- /dev/null +++ b/src/main/java/com/clean_arch_enablers/http_client/RetryCounter.java @@ -0,0 +1,6 @@ +package com.clean_arch_enablers.http_client; + +public interface RetryCounter { + boolean thereIsRetryAvailable(); + void decreaseRetriesAvailable(); +} diff --git a/src/main/java/com/clean_arch_enablers/http_client/commons/HandlersFactory.java b/src/main/java/com/clean_arch_enablers/http_client/commons/HandlersFactory.java new file mode 100644 index 0000000..5da9a99 --- /dev/null +++ b/src/main/java/com/clean_arch_enablers/http_client/commons/HandlersFactory.java @@ -0,0 +1,11 @@ +package com.clean_arch_enablers.http_client.commons; + +public interface HandlersFactory { + + /** + * Factory method for creating handlers + * @return the handlers + */ + T makeHandlers(); + +} diff --git a/src/main/java/com/clean_arch_enablers/http_client/commons/RetriersByExceptionTypeFactory.java b/src/main/java/com/clean_arch_enablers/http_client/commons/RetriersByExceptionTypeFactory.java new file mode 100644 index 0000000..1f4c7ea --- /dev/null +++ b/src/main/java/com/clean_arch_enablers/http_client/commons/RetriersByExceptionTypeFactory.java @@ -0,0 +1,9 @@ +package com.clean_arch_enablers.http_client.commons; + + +import com.clean_arch_enablers.http_client.RetrierModel; + +import java.util.Map; + +public interface RetriersByExceptionTypeFactory extends RetriersFactory, RetrierModel>> { +} diff --git a/src/main/java/com/clean_arch_enablers/http_client/commons/RetriersFactory.java b/src/main/java/com/clean_arch_enablers/http_client/commons/RetriersFactory.java new file mode 100644 index 0000000..b9ae425 --- /dev/null +++ b/src/main/java/com/clean_arch_enablers/http_client/commons/RetriersFactory.java @@ -0,0 +1,5 @@ +package com.clean_arch_enablers.http_client.commons; + +public interface RetriersFactory { + T makeRetriers(); +} diff --git a/src/main/java/com/clean_arch_enablers/http_client/implementations/AbstractHttpRequestBuilder.java b/src/main/java/com/clean_arch_enablers/http_client/implementations/AbstractHttpRequestBuilder.java new file mode 100644 index 0000000..59c049b --- /dev/null +++ b/src/main/java/com/clean_arch_enablers/http_client/implementations/AbstractHttpRequestBuilder.java @@ -0,0 +1,11 @@ +package com.clean_arch_enablers.http_client.implementations; + +import com.clean_arch_enablers.http_client.HttpRequestBuilder; +import lombok.RequiredArgsConstructor; + +@RequiredArgsConstructor +public abstract class AbstractHttpRequestBuilder implements HttpRequestBuilder { + + protected final AbstractHttpRequestModel httpRequest; + +} diff --git a/src/main/java/com/clean_arch_enablers/http_client/implementations/AbstractHttpRequestModel.java b/src/main/java/com/clean_arch_enablers/http_client/implementations/AbstractHttpRequestModel.java new file mode 100644 index 0000000..ae4ffed --- /dev/null +++ b/src/main/java/com/clean_arch_enablers/http_client/implementations/AbstractHttpRequestModel.java @@ -0,0 +1,26 @@ +package com.clean_arch_enablers.http_client.implementations; + +import com.clean_arch_enablers.http_client.*; + +import java.net.http.HttpRequest.BodyPublisher; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +public abstract class AbstractHttpRequestModel implements HttpRequestModel { + + protected String uri; + protected final List pathVariables = new ArrayList<>(); + protected final List queryParameters = new ArrayList<>(); + protected final Map headers = new HashMap<>(); + protected BodyPublisher body; + protected HttpRequestMethod method; + protected ProxyAddressModel proxyAddress; + protected HttpResponseHandler genericResponseHandler; + protected final Map responseHandlersByStatusCode = new HashMap<>(); + protected final Map, ExceptionHandler> exceptionHandlersByExceptionType = new HashMap<>(); + protected final Map retryCountersByStatusCode = new HashMap<>(); + protected final Map, RetryCounter> retryCountersByExceptionType = new HashMap<>(); + +} diff --git a/src/main/java/com/clean_arch_enablers/http_client/implementations/AbstractHttpResponse.java b/src/main/java/com/clean_arch_enablers/http_client/implementations/AbstractHttpResponse.java new file mode 100644 index 0000000..2c86791 --- /dev/null +++ b/src/main/java/com/clean_arch_enablers/http_client/implementations/AbstractHttpResponse.java @@ -0,0 +1,12 @@ +package com.clean_arch_enablers.http_client.implementations; + +import com.clean_arch_enablers.http_client.HttpRequestModel; +import com.clean_arch_enablers.http_client.HttpResponse; +import lombok.RequiredArgsConstructor; + +@RequiredArgsConstructor +public abstract class AbstractHttpResponse implements HttpResponse { + + protected final HttpRequestModel httpRequestModel; + protected final java.net.http.HttpResponse unwrappedHttpResponse; +} diff --git a/src/main/java/com/clean_arch_enablers/http_client/implementations/ExceptionThrownByHttpRequestChecker.java b/src/main/java/com/clean_arch_enablers/http_client/implementations/ExceptionThrownByHttpRequestChecker.java new file mode 100644 index 0000000..34e0349 --- /dev/null +++ b/src/main/java/com/clean_arch_enablers/http_client/implementations/ExceptionThrownByHttpRequestChecker.java @@ -0,0 +1,27 @@ +package com.clean_arch_enablers.http_client.implementations; + +import com.clean_arch_enablers.http_client.implementations.exceptions.RetryNeededOnExceptionThrownException; +import lombok.AccessLevel; +import lombok.AllArgsConstructor; + +import static java.util.Optional.ofNullable; + +@AllArgsConstructor(access = AccessLevel.PRIVATE) +public class ExceptionThrownByHttpRequestChecker { + + private final AbstractHttpRequestModel httpRequestModel; + + public static ExceptionThrownByHttpRequestChecker of(AbstractHttpRequestModel httpRequestModel) { + return new ExceptionThrownByHttpRequestChecker(httpRequestModel); + } + + public void checkOn(Exception e) { + var retryCounterByExceptionType = ofNullable(this.httpRequestModel.retryCountersByExceptionType.get(e.getClass())); + if (retryCounterByExceptionType.isPresent() && retryCounterByExceptionType.get().thereIsRetryAvailable()) { + retryCounterByExceptionType.get().decreaseRetriesAvailable(); + throw new RetryNeededOnExceptionThrownException(e.getClass()); + } + var handlerByThisException = ofNullable(this.httpRequestModel.exceptionHandlersByExceptionType.get(e.getClass())); + handlerByThisException.ifPresent(handler -> handler.handle(e)); + } +} diff --git a/src/main/java/com/clean_arch_enablers/http_client/implementations/FinalHttpRequestExecutor.java b/src/main/java/com/clean_arch_enablers/http_client/implementations/FinalHttpRequestExecutor.java new file mode 100644 index 0000000..c74243f --- /dev/null +++ b/src/main/java/com/clean_arch_enablers/http_client/implementations/FinalHttpRequestExecutor.java @@ -0,0 +1,43 @@ +package com.clean_arch_enablers.http_client.implementations; + +import com.clean_arch_enablers.http_client.implementations.exceptions.IORuntimeException; +import com.clean_arch_enablers.http_client.implementations.exceptions.InterruptedRuntimeException; +import lombok.AccessLevel; +import lombok.NoArgsConstructor; + +import java.io.IOException; +import java.net.InetSocketAddress; +import java.net.ProxySelector; +import java.net.http.HttpClient; +import java.net.http.HttpRequest; +import java.net.http.HttpResponse; +import java.util.Optional; + +@NoArgsConstructor(access = AccessLevel.PRIVATE) +public class FinalHttpRequestExecutor { + + private AbstractHttpRequestModel httpRequestModel; + + public static FinalHttpRequestExecutor of(AbstractHttpRequestModel httpRequestModel){ + var executor = new FinalHttpRequestExecutor(); + executor.httpRequestModel = httpRequestModel; + return executor; + } + + public HttpResponse execute(HttpRequest finalRequest){ + try { + return this.createClient().send(finalRequest, HttpResponse.BodyHandlers.ofString()); + } catch (IOException e) { + throw new IORuntimeException(e); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + throw new InterruptedRuntimeException(e); + } + } + + private HttpClient createClient() { + var client = HttpClient.newBuilder(); + Optional.ofNullable(this.httpRequestModel.proxyAddress).ifPresent(proxyAddress -> client.proxy(ProxySelector.of(new InetSocketAddress(proxyAddress.getHost(), proxyAddress.getPort())))); + return client.build(); + } +} diff --git a/src/main/java/com/clean_arch_enablers/http_client/implementations/FinalHttpRequestFactory.java b/src/main/java/com/clean_arch_enablers/http_client/implementations/FinalHttpRequestFactory.java new file mode 100644 index 0000000..17c8983 --- /dev/null +++ b/src/main/java/com/clean_arch_enablers/http_client/implementations/FinalHttpRequestFactory.java @@ -0,0 +1,65 @@ +package com.clean_arch_enablers.http_client.implementations; + +import lombok.AccessLevel; +import lombok.NoArgsConstructor; + +import java.net.URI; +import java.net.http.HttpRequest; +import java.util.List; +import java.util.stream.Collectors; + +@NoArgsConstructor(access = AccessLevel.PRIVATE) +public class FinalHttpRequestFactory { + + public static HttpRequest makeFinalRequestForGetMethodFrom(AbstractHttpRequestModel httpRequestModel) { + var finalHttpRequestBuilder = HttpRequest.newBuilder().GET(); + return finallyBuildIt(finalHttpRequestBuilder, httpRequestModel); + } + + public static HttpRequest makeFinalRequestForPostMethodFrom(AbstractHttpRequestModel httpRequestModel) { + var finalHttpRequestBuilder = HttpRequest.newBuilder().POST(httpRequestModel.body); + return finallyBuildIt(finalHttpRequestBuilder, httpRequestModel); + } + + public static HttpRequest makeFinalRequestForPutMethodFrom(AbstractHttpRequestModel httpRequestModel) { + var finalHttpRequestBuilder = HttpRequest.newBuilder().PUT(httpRequestModel.body); + return finallyBuildIt(finalHttpRequestBuilder, httpRequestModel); + } + + public static HttpRequest makeFinalRequestForDeleteMethodFrom(AbstractHttpRequestModel httpRequestModel) { + var finalHttpRequestBuilder = HttpRequest.newBuilder().DELETE(); + return finallyBuildIt(finalHttpRequestBuilder, httpRequestModel); + } + + private static HttpRequest finallyBuildIt(HttpRequest.Builder finalHttpRequestBuilder, AbstractHttpRequestModel httpRequestModel) { + setUriInto(finalHttpRequestBuilder, httpRequestModel); + setHeadersInto(finalHttpRequestBuilder, httpRequestModel); + return finalHttpRequestBuilder.build(); + } + + private static void setUriInto(HttpRequest.Builder finalHttpRequestBuilder, AbstractHttpRequestModel httpRequestModel) { + setPathVariablesInto(httpRequestModel); + var uriBuilder = new StringBuilder(httpRequestModel.uri); + setQueryParametersInto(uriBuilder, httpRequestModel.queryParameters); + var finalUriString = uriBuilder.toString(); + var finalUri = URI.create(finalUriString); + finalHttpRequestBuilder.uri(finalUri); + } + + private static void setPathVariablesInto(AbstractHttpRequestModel requestModel) { + requestModel.pathVariables.forEach(pathVariable -> pathVariable.buildPathVariableInto(requestModel)); + } + + private static void setQueryParametersInto(StringBuilder uriBuilder, List queryParameters) { + if (!queryParameters.isEmpty()){ + var firstQueryParam = queryParameters.get(0).buildQueryParameter(); + var otherQueryParams = queryParameters.subList(1, queryParameters.size()).stream().map(HttpRequestQueryParameter::buildQueryParameter).collect(Collectors.toList()); + uriBuilder.append("?".concat(firstQueryParam)); + otherQueryParams.stream().map("&"::concat).forEach(uriBuilder::append); + } + } + + private static void setHeadersInto(HttpRequest.Builder finalHttpRequestBuilder, AbstractHttpRequestModel httpRequestModel) { + httpRequestModel.headers.forEach(finalHttpRequestBuilder::header); + } +} diff --git a/src/main/java/com/clean_arch_enablers/http_client/implementations/HttpRequestBuilderImplementation.java b/src/main/java/com/clean_arch_enablers/http_client/implementations/HttpRequestBuilderImplementation.java new file mode 100644 index 0000000..f44c4d7 --- /dev/null +++ b/src/main/java/com/clean_arch_enablers/http_client/implementations/HttpRequestBuilderImplementation.java @@ -0,0 +1,105 @@ +package com.clean_arch_enablers.http_client.implementations; + + +import com.clean_arch_enablers.http_client.*; +import com.clean_arch_enablers.http_client.commons.RetriersByExceptionTypeFactory; + +public class HttpRequestBuilderImplementation extends AbstractHttpRequestBuilder { + + public static HttpRequestBuilder of(AbstractHttpRequestModel httpRequest){ + return new HttpRequestBuilderImplementation(httpRequest); + } + + private HttpRequestBuilderImplementation(AbstractHttpRequestModel httpRequest) { + super(httpRequest); + } + + @Override + public HttpRequestBuilder headerOf(String key, String value) { + this.httpRequest.headers.put(key, value); + return this; + } + + @Override + public HttpRequestBuilder headersFactory(HttpRequestHeaderFactory httpRequestHeaderFactory) { + httpRequestHeaderFactory.makeHeaders().forEach(this.httpRequest.headers::put); + return this; + } + + @Override + public HttpRequestBuilder pathVariableOf(String pathVariablePlaceholder, String pathVariableValue) { + this.httpRequest.pathVariables.add(new HttpRequestPathVariable(pathVariablePlaceholder, pathVariableValue)); + return this; + } + + @Override + public HttpRequestBuilder queryParameterOf(String queryParameterName, String queryParameterValue) { + this.httpRequest.queryParameters.add(new HttpRequestQueryParameter(queryParameterName, queryParameterValue)); + return this; + } + + @Override + public HttpRequestBuilder proxyAddress(String host, Integer port) { + this.httpRequest.proxyAddress = ProxyAddressModel.builder().host(host).port(port).build(); + return this; + } + + @Override + public HttpRequestModel finishBuildingModel() { + return this.httpRequest; + } + + @Override + public HttpRequestBuilder handlerByHttpStatusCode(Integer statusCode, HttpResponseHandler httpResponseHandler) { + this.httpRequest.responseHandlersByStatusCode.put(statusCode, httpResponseHandler); + return this; + } + + @Override + public HttpRequestBuilder handlersByHttpStatusCodeFactory(HttpResponseHandlersByStatusCodeFactory httpResponseHandlersByStatusCodeFactory) { + httpResponseHandlersByStatusCodeFactory.makeHandlers().forEach(this.httpRequest.responseHandlersByStatusCode::put); + return this; + } + + @Override + public HttpRequestBuilder handlerForAnyUnsuccessfulResponse(HttpResponseHandler httpResponseHandler) { + this.httpRequest.genericResponseHandler = httpResponseHandler; + return this; + } + + @Override + public HttpRequestBuilder handlerByExceptionType(Class exceptionType, ExceptionHandler exceptionHandler) { + this.httpRequest.exceptionHandlersByExceptionType.put(exceptionType, exceptionHandler); + return this; + } + + @Override + public HttpRequestBuilder handlersByExceptionTypeFactory(HttpExceptionHandlersByExceptionTypeFactory httpExceptionHandlersByExceptionTypeFactory) { + httpExceptionHandlersByExceptionTypeFactory.makeHandlers().forEach(this.httpRequest.exceptionHandlersByExceptionType::put); + return this; + } + + @Override + public HttpRequestBuilder retrierByHttpStatusCode(Integer statusCode, RetrierModel retrierModel) { + this.httpRequest.retryCountersByStatusCode.put(statusCode, RetryCounterImplementation.of(retrierModel)); + return this; + } + + @Override + public HttpRequestBuilder retriersByHttpStatusCodeFactory(RetriersByStatusCodeFactory retriersByStatusCodeFactory) { + retriersByStatusCodeFactory.makeRetriers().forEach((statusCode, retrierModel) -> this.httpRequest.retryCountersByStatusCode.put(statusCode, RetryCounterImplementation.of(retrierModel))); + return this; + } + + @Override + public HttpRequestBuilder retrierByExceptionType(Class exceptionType, RetrierModel retrierModel) { + this.httpRequest.retryCountersByExceptionType.put(exceptionType, RetryCounterImplementation.of(retrierModel)); + return this; + } + + @Override + public HttpRequestBuilder retriersByExceptionTypeFactory(RetriersByExceptionTypeFactory retriersByExceptionTypeFactory) { + retriersByExceptionTypeFactory.makeRetriers().forEach((exceptionType, retrierModel) -> this.httpRequest.retryCountersByExceptionType.put(exceptionType, RetryCounterImplementation.of(retrierModel))); + return this; + } +} diff --git a/src/main/java/com/clean_arch_enablers/http_client/implementations/HttpRequestDeleteMethod.java b/src/main/java/com/clean_arch_enablers/http_client/implementations/HttpRequestDeleteMethod.java new file mode 100644 index 0000000..82cab0a --- /dev/null +++ b/src/main/java/com/clean_arch_enablers/http_client/implementations/HttpRequestDeleteMethod.java @@ -0,0 +1,22 @@ +package com.clean_arch_enablers.http_client.implementations; + + +import com.clean_arch_enablers.http_client.HttpRequestMethod; +import com.clean_arch_enablers.http_client.HttpResponse; +import com.clean_arch_enablers.http_client.implementations.exceptions.RetryNeededOnExceptionThrownException; + +public class HttpRequestDeleteMethod implements HttpRequestMethod { + + @Override + public HttpResponse execute(AbstractHttpRequestModel httpRequestModel) throws RetryNeededOnExceptionThrownException { + var finalRequest = FinalHttpRequestFactory.makeFinalRequestForDeleteMethodFrom(httpRequestModel); + try { + var unwrappedResponse = FinalHttpRequestExecutor.of(httpRequestModel).execute(finalRequest); + return new HttpResponseImplementation(httpRequestModel, unwrappedResponse); + } + catch (Exception e) { + ExceptionThrownByHttpRequestChecker.of(httpRequestModel).checkOn(e); + throw e; + } + } +} diff --git a/src/main/java/com/clean_arch_enablers/http_client/implementations/HttpRequestExecutionManager.java b/src/main/java/com/clean_arch_enablers/http_client/implementations/HttpRequestExecutionManager.java new file mode 100644 index 0000000..e36ea55 --- /dev/null +++ b/src/main/java/com/clean_arch_enablers/http_client/implementations/HttpRequestExecutionManager.java @@ -0,0 +1,30 @@ +package com.clean_arch_enablers.http_client.implementations; + +import com.clean_arch_enablers.http_client.HttpResponse; +import com.clean_arch_enablers.http_client.implementations.exceptions.RetryNeededOnExceptionThrownException; +import com.clean_arch_enablers.http_client.implementations.exceptions.RetryNeededOnHttpStatusCodeException; +import lombok.AccessLevel; +import lombok.NoArgsConstructor; + +@NoArgsConstructor(access = AccessLevel.PRIVATE) +class HttpRequestExecutionManager { + + protected AbstractHttpRequestModel httpRequest; + + static HttpRequestExecutionManager of(AbstractHttpRequestModel httpRequest){ + var manager = new HttpRequestExecutionManager(); + manager.httpRequest = httpRequest; + return manager; + } + + protected HttpResponse run() { + try { + var response = this.httpRequest.method.execute(this.httpRequest); + response.ifNeedsHandling(HttpResponseStatusCodeChecker.of(this.httpRequest)::checkOutHandlersFor); + return response; + } catch (RetryNeededOnHttpStatusCodeException | RetryNeededOnExceptionThrownException exception) { + return this.run(); + } + } + +} diff --git a/src/main/java/com/clean_arch_enablers/http_client/implementations/HttpRequestGetMethod.java b/src/main/java/com/clean_arch_enablers/http_client/implementations/HttpRequestGetMethod.java new file mode 100644 index 0000000..ffcc896 --- /dev/null +++ b/src/main/java/com/clean_arch_enablers/http_client/implementations/HttpRequestGetMethod.java @@ -0,0 +1,22 @@ +package com.clean_arch_enablers.http_client.implementations; + + +import com.clean_arch_enablers.http_client.HttpRequestMethod; +import com.clean_arch_enablers.http_client.HttpResponse; +import com.clean_arch_enablers.http_client.implementations.exceptions.RetryNeededOnExceptionThrownException; + +public class HttpRequestGetMethod implements HttpRequestMethod { + + @Override + public HttpResponse execute(AbstractHttpRequestModel httpRequestModel) throws RetryNeededOnExceptionThrownException { + var finalRequest = FinalHttpRequestFactory.makeFinalRequestForGetMethodFrom(httpRequestModel); + try { + var unwrappedResponse = FinalHttpRequestExecutor.of(httpRequestModel).execute(finalRequest); + return new HttpResponseImplementation(httpRequestModel, unwrappedResponse); + } + catch (Exception exception) { + ExceptionThrownByHttpRequestChecker.of(httpRequestModel).checkOn(exception); + throw exception; + } + } +} diff --git a/src/main/java/com/clean_arch_enablers/http_client/implementations/HttpRequestModelImplementation.java b/src/main/java/com/clean_arch_enablers/http_client/implementations/HttpRequestModelImplementation.java new file mode 100644 index 0000000..95e0810 --- /dev/null +++ b/src/main/java/com/clean_arch_enablers/http_client/implementations/HttpRequestModelImplementation.java @@ -0,0 +1,37 @@ +package com.clean_arch_enablers.http_client.implementations; + +import com.clean_arch_enablers.http_client.HttpRequestMethod; +import com.fasterxml.jackson.core.type.TypeReference; +import lombok.AccessLevel; +import lombok.NoArgsConstructor; + +import java.net.http.HttpRequest.BodyPublisher; + +@NoArgsConstructor(access = AccessLevel.PRIVATE) +public class HttpRequestModelImplementation extends AbstractHttpRequestModel { + + public static AbstractHttpRequestModel of(String uri, HttpRequestMethod method){ + var httpRequest = new HttpRequestModelImplementation(); + httpRequest.uri = uri; + httpRequest.method = method; + return httpRequest; + } + + public static AbstractHttpRequestModel of(String uri, HttpRequestMethod method, BodyPublisher body){ + var httpRequest = new HttpRequestModelImplementation(); + httpRequest.uri = uri; + httpRequest.method = method; + httpRequest.body = body; + return httpRequest; + } + + @Override + public T sendRequestReturning(Class typeOfExpectedResponseBody) { + return HttpRequestExecutionManager.of(this).run().getBodyAs(typeOfExpectedResponseBody); + } + + @Override + public T sendRequestReturning(TypeReference typeOfExpectedResponseBody) { + return HttpRequestExecutionManager.of(this).run().getBodyAs(typeOfExpectedResponseBody); + } +} diff --git a/src/main/java/com/clean_arch_enablers/http_client/implementations/HttpRequestPathVariable.java b/src/main/java/com/clean_arch_enablers/http_client/implementations/HttpRequestPathVariable.java new file mode 100644 index 0000000..ac00491 --- /dev/null +++ b/src/main/java/com/clean_arch_enablers/http_client/implementations/HttpRequestPathVariable.java @@ -0,0 +1,24 @@ +package com.clean_arch_enablers.http_client.implementations; + +import lombok.AllArgsConstructor; + +@AllArgsConstructor +public class HttpRequestPathVariable { + + private final String pathVariablePlaceholder; + private final String pathVariableValue; + + void buildPathVariableInto(AbstractHttpRequestModel requestModel){ + var placeholder = this.pathVariablePlaceholder.replace("{", "").replace("}", ""); + if (requestModel.uri.contains(placeholder)) + requestModel.uri = requestModel.uri.replace("{" + placeholder + "}", this.pathVariableValue); + else + throw new NoPathVariablePlaceholderFound(placeholder, requestModel.uri); + } + + public static class NoPathVariablePlaceholderFound extends RuntimeException { + public NoPathVariablePlaceholderFound(String placeholder, String uri) { + super("No placeholder '" + placeholder + "' was found in the URI '" + uri + "'. Make sure such placeholder is present."); + } + } +} diff --git a/src/main/java/com/clean_arch_enablers/http_client/implementations/HttpRequestPostMethod.java b/src/main/java/com/clean_arch_enablers/http_client/implementations/HttpRequestPostMethod.java new file mode 100644 index 0000000..5e1ae88 --- /dev/null +++ b/src/main/java/com/clean_arch_enablers/http_client/implementations/HttpRequestPostMethod.java @@ -0,0 +1,22 @@ +package com.clean_arch_enablers.http_client.implementations; + + +import com.clean_arch_enablers.http_client.HttpRequestMethod; +import com.clean_arch_enablers.http_client.HttpResponse; +import com.clean_arch_enablers.http_client.implementations.exceptions.RetryNeededOnExceptionThrownException; + +public class HttpRequestPostMethod implements HttpRequestMethod { + + @Override + public HttpResponse execute(AbstractHttpRequestModel httpRequestModel) throws RetryNeededOnExceptionThrownException { + var finalRequest = FinalHttpRequestFactory.makeFinalRequestForPostMethodFrom(httpRequestModel); + try { + var unwrappedResponse = FinalHttpRequestExecutor.of(httpRequestModel).execute(finalRequest); + return new HttpResponseImplementation(httpRequestModel, unwrappedResponse); + } + catch (Exception e) { + ExceptionThrownByHttpRequestChecker.of(httpRequestModel).checkOn(e); + throw e; + } + } +} diff --git a/src/main/java/com/clean_arch_enablers/http_client/implementations/HttpRequestPutMethod.java b/src/main/java/com/clean_arch_enablers/http_client/implementations/HttpRequestPutMethod.java new file mode 100644 index 0000000..e9abedb --- /dev/null +++ b/src/main/java/com/clean_arch_enablers/http_client/implementations/HttpRequestPutMethod.java @@ -0,0 +1,22 @@ +package com.clean_arch_enablers.http_client.implementations; + + +import com.clean_arch_enablers.http_client.HttpRequestMethod; +import com.clean_arch_enablers.http_client.HttpResponse; +import com.clean_arch_enablers.http_client.implementations.exceptions.RetryNeededOnExceptionThrownException; + +public class HttpRequestPutMethod implements HttpRequestMethod { + + @Override + public HttpResponse execute(AbstractHttpRequestModel httpRequestModel) throws RetryNeededOnExceptionThrownException { + var finalRequest = FinalHttpRequestFactory.makeFinalRequestForPutMethodFrom(httpRequestModel); + try { + var unwrappedResponse = FinalHttpRequestExecutor.of(httpRequestModel).execute(finalRequest); + return new HttpResponseImplementation(httpRequestModel, unwrappedResponse); + } + catch (Exception e) { + ExceptionThrownByHttpRequestChecker.of(httpRequestModel).checkOn(e); + throw e; + } + } +} diff --git a/src/main/java/com/clean_arch_enablers/http_client/implementations/HttpRequestQueryParameter.java b/src/main/java/com/clean_arch_enablers/http_client/implementations/HttpRequestQueryParameter.java new file mode 100644 index 0000000..3163bdf --- /dev/null +++ b/src/main/java/com/clean_arch_enablers/http_client/implementations/HttpRequestQueryParameter.java @@ -0,0 +1,15 @@ +package com.clean_arch_enablers.http_client.implementations; + +import lombok.AllArgsConstructor; + +@AllArgsConstructor +public class HttpRequestQueryParameter { + + private final String queryParameterName; + private final String queryParameterValue; + + String buildQueryParameter(){ + return this.queryParameterName.concat("=").concat(this.queryParameterValue); + } + +} diff --git a/src/main/java/com/clean_arch_enablers/http_client/implementations/HttpRequestStarterImplementation.java b/src/main/java/com/clean_arch_enablers/http_client/implementations/HttpRequestStarterImplementation.java new file mode 100644 index 0000000..62ced0e --- /dev/null +++ b/src/main/java/com/clean_arch_enablers/http_client/implementations/HttpRequestStarterImplementation.java @@ -0,0 +1,30 @@ +package com.clean_arch_enablers.http_client.implementations; + + +import com.clean_arch_enablers.http_client.HttpRequestBuilder; +import com.clean_arch_enablers.http_client.HttpRequestStarter; + +import java.net.http.HttpRequest; + +public class HttpRequestStarterImplementation implements HttpRequestStarter { + + @Override + public HttpRequestBuilder startGetRequestFor(String url) { + return HttpRequestBuilderImplementation.of(HttpRequestModelImplementation.of(url, new HttpRequestGetMethod())); + } + + @Override + public HttpRequestBuilder startPostRequestFor(String url, HttpRequest.BodyPublisher body) { + return HttpRequestBuilderImplementation.of(HttpRequestModelImplementation.of(url, new HttpRequestPostMethod(), body)); + } + + @Override + public HttpRequestBuilder startPutRequestFor(String url, HttpRequest.BodyPublisher body) { + return HttpRequestBuilderImplementation.of(HttpRequestModelImplementation.of(url, new HttpRequestPutMethod(), body)); + } + + @Override + public HttpRequestBuilder startDeleteRequestFor(String url) { + return HttpRequestBuilderImplementation.of(HttpRequestModelImplementation.of(url, new HttpRequestDeleteMethod())); + } +} diff --git a/src/main/java/com/clean_arch_enablers/http_client/implementations/HttpResponseImplementation.java b/src/main/java/com/clean_arch_enablers/http_client/implementations/HttpResponseImplementation.java new file mode 100644 index 0000000..b32a660 --- /dev/null +++ b/src/main/java/com/clean_arch_enablers/http_client/implementations/HttpResponseImplementation.java @@ -0,0 +1,60 @@ +package com.clean_arch_enablers.http_client.implementations; + +import com.clean_arch_enablers.http_client.HttpRequestModel; +import com.clean_arch_enablers.http_client.HttpResponse; +import com.clean_arch_enablers.http_client.implementations.exceptions.JsonProcessingRuntimeException; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.core.type.TypeReference; + +import java.util.function.Consumer; + +public class HttpResponseImplementation extends AbstractHttpResponse{ + + public HttpResponseImplementation(HttpRequestModel httpRequestModel, java.net.http.HttpResponse unwrappedHttpResponse) { + super(httpRequestModel, unwrappedHttpResponse); + } + + @Override + public Integer getStatusCode() { + return this.unwrappedHttpResponse.statusCode(); + } + + @Override + public HttpRequestModel getHttpRequest() { + return this.httpRequestModel; + } + + @Override + public T getBodyAs(Class bodyType) { + try{ + return ObjectMapperFactory.createNewObjectMapper().readValue(this.unwrappedHttpResponse.body(), bodyType); + } catch (Exception exception){ + throw new JsonProcessingRuntimeException(exception.getMessage()); + } + } + + @Override + public T getBodyAs(TypeReference bodyType) { + try{ + return ObjectMapperFactory.createNewObjectMapper().readValue(this.unwrappedHttpResponse.body(), bodyType); + } catch (JsonProcessingException exception){ + throw new JsonProcessingRuntimeException(exception.getMessage()); + } + } + + @Override + public boolean needsHandling() { + return this.isNot2xx(); + } + + @Override + public void ifNeedsHandling(Consumer checkOnResponse) { + if (this.needsHandling()) + checkOnResponse.accept(this); + } + + private boolean isNot2xx() { + var statusString = this.getStatusCode().toString(); + return (statusString.charAt(0) != '2'); + } +} diff --git a/src/main/java/com/clean_arch_enablers/http_client/implementations/HttpResponseStatusCodeChecker.java b/src/main/java/com/clean_arch_enablers/http_client/implementations/HttpResponseStatusCodeChecker.java new file mode 100644 index 0000000..3a7803d --- /dev/null +++ b/src/main/java/com/clean_arch_enablers/http_client/implementations/HttpResponseStatusCodeChecker.java @@ -0,0 +1,42 @@ +package com.clean_arch_enablers.http_client.implementations; + +import com.clean_arch_enablers.http_client.HttpResponse; +import com.clean_arch_enablers.http_client.HttpResponseHandler; +import com.clean_arch_enablers.http_client.implementations.exceptions.NoResponseHandlersAvailableForExecutionException; +import com.clean_arch_enablers.http_client.implementations.exceptions.RetryNeededOnHttpStatusCodeException; +import lombok.AccessLevel; +import lombok.RequiredArgsConstructor; + +import static java.util.Optional.ofNullable; + +@RequiredArgsConstructor(access = AccessLevel.PRIVATE) +public class HttpResponseStatusCodeChecker { + + private final AbstractHttpRequestModel httpRequest; + private HttpResponse httpResponse; + + public static HttpResponseStatusCodeChecker of(AbstractHttpRequestModel httpRequest){ + return new HttpResponseStatusCodeChecker(httpRequest); + } + + public void checkOutHandlersFor(HttpResponse response) throws RetryNeededOnHttpStatusCodeException { + this.httpResponse = response; + var statusCodeReceived = response.getStatusCode(); + var retryCounterForStatusCodeReceived = ofNullable(this.httpRequest.retryCountersByStatusCode.get(statusCodeReceived)); + if (retryCounterForStatusCodeReceived.isPresent() && retryCounterForStatusCodeReceived.get().thereIsRetryAvailable()){ + retryCounterForStatusCodeReceived.get().decreaseRetriesAvailable(); + throw new RetryNeededOnHttpStatusCodeException(response); + } + this.executeHandlerIfAnyIsPresentForTheResponseReceived(); + } + + private void executeHandlerIfAnyIsPresentForTheResponseReceived() { + var statusCodeReceived = this.httpResponse.getStatusCode(); + var handler = ofNullable(this.httpRequest.responseHandlersByStatusCode.get(statusCodeReceived)).orElseGet(this::getGenericHandler); + handler.handle(this.httpResponse); + } + + private HttpResponseHandler getGenericHandler() { + return ofNullable(this.httpRequest.genericResponseHandler).orElseThrow(() -> new NoResponseHandlersAvailableForExecutionException(this.httpResponse)); + } +} diff --git a/src/main/java/com/clean_arch_enablers/http_client/implementations/ObjectMapperFactory.java b/src/main/java/com/clean_arch_enablers/http_client/implementations/ObjectMapperFactory.java new file mode 100644 index 0000000..ed16302 --- /dev/null +++ b/src/main/java/com/clean_arch_enablers/http_client/implementations/ObjectMapperFactory.java @@ -0,0 +1,16 @@ +package com.clean_arch_enablers.http_client.implementations; + +import com.fasterxml.jackson.databind.DeserializationFeature; +import com.fasterxml.jackson.databind.ObjectMapper; +import lombok.AccessLevel; +import lombok.NoArgsConstructor; + +@NoArgsConstructor(access = AccessLevel.PRIVATE) +public class ObjectMapperFactory { + + public static ObjectMapper createNewObjectMapper() { + return new ObjectMapper() + .configure(DeserializationFeature.USE_JAVA_ARRAY_FOR_JSON_ARRAY, true) + .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); + } +} diff --git a/src/main/java/com/clean_arch_enablers/http_client/implementations/ProxyAddressModel.java b/src/main/java/com/clean_arch_enablers/http_client/implementations/ProxyAddressModel.java new file mode 100644 index 0000000..1d5c5c7 --- /dev/null +++ b/src/main/java/com/clean_arch_enablers/http_client/implementations/ProxyAddressModel.java @@ -0,0 +1,13 @@ +package com.clean_arch_enablers.http_client.implementations; + +import lombok.Builder; +import lombok.Getter; + +@Getter +@Builder +public class ProxyAddressModel { + + private final String host; + private final Integer port; + +} diff --git a/src/main/java/com/clean_arch_enablers/http_client/implementations/RetryCounterImplementation.java b/src/main/java/com/clean_arch_enablers/http_client/implementations/RetryCounterImplementation.java new file mode 100644 index 0000000..17c3de6 --- /dev/null +++ b/src/main/java/com/clean_arch_enablers/http_client/implementations/RetryCounterImplementation.java @@ -0,0 +1,26 @@ +package com.clean_arch_enablers.http_client.implementations; + +import com.clean_arch_enablers.http_client.RetrierModel; +import com.clean_arch_enablers.http_client.RetryCounter; +import lombok.AccessLevel; +import lombok.AllArgsConstructor; + +@AllArgsConstructor(access = AccessLevel.PRIVATE) +public class RetryCounterImplementation implements RetryCounter { + + private final RetrierModel retrierModel; + + public static RetryCounter of(RetrierModel retrierModel){ + return new RetryCounterImplementation(retrierModel); + } + + @Override + public boolean thereIsRetryAvailable() { + return this.retrierModel.getRetriesRemaining() > 0; + } + + @Override + public void decreaseRetriesAvailable() { + this.retrierModel.setRetriesRemaining(this.retrierModel.getRetriesRemaining()-1); + } +} diff --git a/src/main/java/com/clean_arch_enablers/http_client/implementations/exceptions/IORuntimeException.java b/src/main/java/com/clean_arch_enablers/http_client/implementations/exceptions/IORuntimeException.java new file mode 100644 index 0000000..2c549f3 --- /dev/null +++ b/src/main/java/com/clean_arch_enablers/http_client/implementations/exceptions/IORuntimeException.java @@ -0,0 +1,11 @@ +package com.clean_arch_enablers.http_client.implementations.exceptions; + +import java.io.IOException; + +public class IORuntimeException extends RuntimeException{ + + public IORuntimeException(IOException exception){ + super(exception.getMessage()); + } + +} diff --git a/src/main/java/com/clean_arch_enablers/http_client/implementations/exceptions/InterruptedRuntimeException.java b/src/main/java/com/clean_arch_enablers/http_client/implementations/exceptions/InterruptedRuntimeException.java new file mode 100644 index 0000000..8dff32b --- /dev/null +++ b/src/main/java/com/clean_arch_enablers/http_client/implementations/exceptions/InterruptedRuntimeException.java @@ -0,0 +1,7 @@ +package com.clean_arch_enablers.http_client.implementations.exceptions; + +public class InterruptedRuntimeException extends RuntimeException { + public InterruptedRuntimeException(InterruptedException e) { + super(e.getMessage()); + } +} diff --git a/src/main/java/com/clean_arch_enablers/http_client/implementations/exceptions/JsonProcessingRuntimeException.java b/src/main/java/com/clean_arch_enablers/http_client/implementations/exceptions/JsonProcessingRuntimeException.java new file mode 100644 index 0000000..456dac5 --- /dev/null +++ b/src/main/java/com/clean_arch_enablers/http_client/implementations/exceptions/JsonProcessingRuntimeException.java @@ -0,0 +1,7 @@ +package com.clean_arch_enablers.http_client.implementations.exceptions; + +public class JsonProcessingRuntimeException extends RuntimeException { + public JsonProcessingRuntimeException(String message) { + super(message); + } +} diff --git a/src/main/java/com/clean_arch_enablers/http_client/implementations/exceptions/NoResponseHandlersAvailableForExecutionException.java b/src/main/java/com/clean_arch_enablers/http_client/implementations/exceptions/NoResponseHandlersAvailableForExecutionException.java new file mode 100644 index 0000000..448bae5 --- /dev/null +++ b/src/main/java/com/clean_arch_enablers/http_client/implementations/exceptions/NoResponseHandlersAvailableForExecutionException.java @@ -0,0 +1,17 @@ +package com.clean_arch_enablers.http_client.implementations.exceptions; + + +import com.clean_arch_enablers.http_client.HttpResponse; + +public class NoResponseHandlersAvailableForExecutionException extends RuntimeException { + + public NoResponseHandlersAvailableForExecutionException(HttpResponse httpResponse) { + super("No response handlers were available, not even the most generic one. " + .concat("For handling any not successful response set a generic handler at the request building process; ") + .concat("if you find interesting to have a specific response handler for the status code received (") + .concat(httpResponse.getStatusCode().toString()) + .concat("), set a specific one, also at the request building process. ") + ); + } + +} diff --git a/src/main/java/com/clean_arch_enablers/http_client/implementations/exceptions/RetryNeededOnExceptionThrownException.java b/src/main/java/com/clean_arch_enablers/http_client/implementations/exceptions/RetryNeededOnExceptionThrownException.java new file mode 100644 index 0000000..9548254 --- /dev/null +++ b/src/main/java/com/clean_arch_enablers/http_client/implementations/exceptions/RetryNeededOnExceptionThrownException.java @@ -0,0 +1,15 @@ +package com.clean_arch_enablers.http_client.implementations.exceptions; + +import lombok.Getter; + +@Getter +public class RetryNeededOnExceptionThrownException extends RuntimeException{ + + private final Class exceptionType; + + public RetryNeededOnExceptionThrownException(Class exceptionType){ + super("Retry needed on ".concat(exceptionType.getName()).concat(" being thrown")); + this.exceptionType = exceptionType; + } + +} diff --git a/src/main/java/com/clean_arch_enablers/http_client/implementations/exceptions/RetryNeededOnHttpStatusCodeException.java b/src/main/java/com/clean_arch_enablers/http_client/implementations/exceptions/RetryNeededOnHttpStatusCodeException.java new file mode 100644 index 0000000..cc6de72 --- /dev/null +++ b/src/main/java/com/clean_arch_enablers/http_client/implementations/exceptions/RetryNeededOnHttpStatusCodeException.java @@ -0,0 +1,16 @@ +package com.clean_arch_enablers.http_client.implementations.exceptions; + +import com.clean_arch_enablers.http_client.HttpResponse; +import lombok.Getter; + +@Getter +public class RetryNeededOnHttpStatusCodeException extends RuntimeException{ + + private final HttpResponse httpResponse; + + public RetryNeededOnHttpStatusCodeException(HttpResponse httpResponse){ + super("Retry needed on ".concat(httpResponse.getStatusCode().toString()).concat(" received")); + this.httpResponse = httpResponse; + } + +} diff --git a/src/main/java/com/clean_arch_enablers/http_client/utils/ThreadIdRetrievementUtil.java b/src/main/java/com/clean_arch_enablers/http_client/utils/ThreadIdRetrievementUtil.java new file mode 100644 index 0000000..0508c52 --- /dev/null +++ b/src/main/java/com/clean_arch_enablers/http_client/utils/ThreadIdRetrievementUtil.java @@ -0,0 +1,16 @@ +package com.clean_arch_enablers.http_client.utils; + +import lombok.AccessLevel; +import lombok.NoArgsConstructor; + +@NoArgsConstructor(access = AccessLevel.PRIVATE) +public class ThreadIdRetrievementUtil { + + public static String retrieveThreadId(){ + return "Current thread: " + .concat(String.valueOf(Thread.currentThread().getId()) + .concat(":") + .concat(Thread.currentThread().getName())); + } + +} diff --git a/src/test/java/com/clean_arch_enablers/http_client/RetrierModelTest.java b/src/test/java/com/clean_arch_enablers/http_client/RetrierModelTest.java new file mode 100644 index 0000000..a4daa9a --- /dev/null +++ b/src/test/java/com/clean_arch_enablers/http_client/RetrierModelTest.java @@ -0,0 +1,26 @@ +package com.clean_arch_enablers.http_client; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.junit.jupiter.MockitoExtension; + +@ExtendWith(MockitoExtension.class) +class RetrierModelTest { + + @Test + @DisplayName("Should not return null reference when static constructor invoked") + void shouldNotReturnNullReferenceWhenStaticConstructorInvoked(){ + Assertions.assertNotNull(RetrierModel.withLimitOf(6)); + } + + @Test + @DisplayName("Should fill both fields when instantiated via static constructor") + void shouldFillBothFieldsWhenInstantiatedViaStaticConstructor(){ + var retrierModel = RetrierModel.withLimitOf(4); + Assertions.assertNotNull(retrierModel.getOriginalLimitForRetrying()); + Assertions.assertNotNull(retrierModel.getRetriesRemaining()); + } + +} diff --git a/src/test/java/com/clean_arch_enablers/http_client/implementations/ExceptionThrownByHttpRequestCheckerTest.java b/src/test/java/com/clean_arch_enablers/http_client/implementations/ExceptionThrownByHttpRequestCheckerTest.java new file mode 100644 index 0000000..024b176 --- /dev/null +++ b/src/test/java/com/clean_arch_enablers/http_client/implementations/ExceptionThrownByHttpRequestCheckerTest.java @@ -0,0 +1,59 @@ +package com.clean_arch_enablers.http_client.implementations; + +import com.clean_arch_enablers.http_client.RetrierModel; +import com.clean_arch_enablers.http_client.implementations.exceptions.JsonProcessingRuntimeException; +import com.clean_arch_enablers.http_client.implementations.exceptions.RetryNeededOnExceptionThrownException; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.junit.jupiter.MockitoExtension; + +import java.util.Arrays; + +@ExtendWith(MockitoExtension.class) +class ExceptionThrownByHttpRequestCheckerTest { + + AbstractHttpRequestModel abstractHttpRequestModel; + + @BeforeEach + void setUp(){ + this.abstractHttpRequestModel = HttpRequestModelImplementation.of("http://localhost:8080/tutstuts", new HttpRequestGetMethod()); + this.abstractHttpRequestModel.retryCountersByExceptionType.put(RuntimeException.class, RetryCounterImplementation.of(RetrierModel.withLimitOf(4))); + this.abstractHttpRequestModel.exceptionHandlersByExceptionType.put(Exception.class, this::doNothing); + this.abstractHttpRequestModel.retryCountersByExceptionType.put(Exception.class, RetryCounterImplementation.of(RetrierModel.withLimitOf(4))); + this.abstractHttpRequestModel.retryCountersByExceptionType.put(ClassNotFoundException.class, RetryCounterImplementation.of(RetrierModel.withLimitOf(4))); + } + + private void doNothing(Exception exception) { } + + @Test + @DisplayName("Should not return null reference when static constructor invoked") + void shouldNotReturnNullReferenceWhenStaticConstructorInvoked(){ + Assertions.assertNotNull(ExceptionThrownByHttpRequestChecker.of(this.abstractHttpRequestModel)); + } + + @Test + @DisplayName("Should throw RetryNeededOnExceptionThrownException") + void shouldThrowRetryNeededOnExceptionThrownException(){ + Arrays.asList(new ClassNotFoundException(), new Exception(), new RuntimeException()).forEach(exceptionThrown -> { + var checker = ExceptionThrownByHttpRequestChecker.of(this.abstractHttpRequestModel); + Assertions.assertThrows(RetryNeededOnExceptionThrownException.class, () -> checker.checkOn(exceptionThrown)); + }); + + } + + @Test + @DisplayName("Should not throw RetryNeededOnExceptionThrownException") + void shouldNotThrowRetryNeededOnExceptionThrownException(){ + Arrays.asList(new JsonProcessingRuntimeException(""), new InterruptedException()).forEach(exceptionThrown -> { + var checker = ExceptionThrownByHttpRequestChecker.of(this.abstractHttpRequestModel); + Assertions.assertDoesNotThrow(() -> checker.checkOn(exceptionThrown)); + }); + + } + + + +} diff --git a/src/test/java/com/clean_arch_enablers/http_client/implementations/FinalHttpRequestFactoryTest.java b/src/test/java/com/clean_arch_enablers/http_client/implementations/FinalHttpRequestFactoryTest.java new file mode 100644 index 0000000..fe96e1f --- /dev/null +++ b/src/test/java/com/clean_arch_enablers/http_client/implementations/FinalHttpRequestFactoryTest.java @@ -0,0 +1,81 @@ +package com.clean_arch_enablers.http_client.implementations; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.junit.jupiter.MockitoExtension; + +import java.net.http.HttpRequest; + +@ExtendWith(MockitoExtension.class) +class FinalHttpRequestFactoryTest { + + @Test + @DisplayName("Should set query params right") + void shouldSetQueryParamsRight(){ + var request = HttpRequestModelImplementation.of("http://localhost:8080/uhg", new HttpRequestGetMethod()); + var queryParam1 = new HttpRequestQueryParameter("testKey", "testValue"); + var queryParam2 = new HttpRequestQueryParameter("testKey2", "testValue2"); + request.queryParameters.add(queryParam1); + request.queryParameters.add(queryParam2); + var finalHttpRequest = FinalHttpRequestFactory.makeFinalRequestForGetMethodFrom(request); + var queryParams = finalHttpRequest.uri().getQuery(); + Assertions.assertTrue(queryParams.contains(queryParam1.buildQueryParameter())); + Assertions.assertTrue(queryParams.contains(queryParam2.buildQueryParameter())); + } + + @Test + @DisplayName("Should set path variables right") + void shouldSetPathVariablesRight(){ + var request = HttpRequestModelImplementation.of("http://localhost:8080/uhg/companies/{companyId}/users/{userId}/history", new HttpRequestGetMethod()); + var pathVariable1 = new HttpRequestPathVariable("companyId", "62333"); + var pathVariable2 = new HttpRequestPathVariable("userId", "34234"); + request.pathVariables.add(pathVariable1); + request.pathVariables.add(pathVariable2); + var finalHttpRequest = FinalHttpRequestFactory.makeFinalRequestForGetMethodFrom(request); + var uri = finalHttpRequest.uri().toString(); + Assertions.assertEquals("http://localhost:8080/uhg/companies/62333/users/34234/history", uri); + } + + @Test + @DisplayName("Should set body right") + void shouldSetBodyRight(){ + var request = HttpRequestModelImplementation.of("http://localhost:8080/uhg", new HttpRequestPostMethod()); + request.body = HttpRequest.BodyPublishers.ofString("{testField:\"testValue\"}"); + var finalHttpRequest = FinalHttpRequestFactory.makeFinalRequestForPostMethodFrom(request); + var bodyFromFinalRequest = finalHttpRequest.bodyPublisher().orElseThrow(); + Assertions.assertEquals(request.body, bodyFromFinalRequest); + } + + @Test + @DisplayName("Should make instance for get method") + void shouldMakeInstanceForGetMethod(){ + var request = HttpRequestModelImplementation.of("http://localhost:8080/uhg", new HttpRequestGetMethod()); + Assertions.assertNotNull(FinalHttpRequestFactory.makeFinalRequestForGetMethodFrom(request)); + } + + @Test + @DisplayName("Should make instance for delete method") + void shouldMakeInstanceForDeleteMethod(){ + var request = HttpRequestModelImplementation.of("http://localhost:8080/uhg", new HttpRequestDeleteMethod()); + Assertions.assertNotNull(FinalHttpRequestFactory.makeFinalRequestForDeleteMethodFrom(request)); + } + + @Test + @DisplayName("Should make instance for post method") + void shouldMakeInstanceForPostMethod(){ + var request = HttpRequestModelImplementation.of("http://localhost:8080/uhg", new HttpRequestPostMethod()); + request.body = HttpRequest.BodyPublishers.ofString("{testField:\"testValue\"}"); + Assertions.assertNotNull(FinalHttpRequestFactory.makeFinalRequestForPostMethodFrom(request)); + } + + @Test + @DisplayName("Should make instance for put method") + void shouldMakeInstanceForPutMethod(){ + var request = HttpRequestModelImplementation.of("http://localhost:8080/uhg", new HttpRequestPutMethod()); + request.body = HttpRequest.BodyPublishers.ofString("{testField:\"testValue\"}"); + Assertions.assertNotNull(FinalHttpRequestFactory.makeFinalRequestForPutMethodFrom(request)); + } + +} diff --git a/src/test/java/com/clean_arch_enablers/http_client/implementations/HttpRequestBuilderImplementationTest.java b/src/test/java/com/clean_arch_enablers/http_client/implementations/HttpRequestBuilderImplementationTest.java new file mode 100644 index 0000000..17b7af2 --- /dev/null +++ b/src/test/java/com/clean_arch_enablers/http_client/implementations/HttpRequestBuilderImplementationTest.java @@ -0,0 +1,383 @@ +package com.clean_arch_enablers.http_client.implementations; + +import com.clean_arch_enablers.http_client.*; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.junit.jupiter.MockitoExtension; + +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; + +@ExtendWith(MockitoExtension.class) +class HttpRequestBuilderImplementationTest { + + @Test + @DisplayName("Should add header correctly") + void shouldAddHeaderCorrectly(){ + var request = HttpRequestModelImplementation.of("http://localhost:8080/users", new HttpRequestGetMethod()); + var builder = HttpRequestBuilderImplementation.of(request); + var key = "chuchu"; + builder.headerOf(key, "Duda"); + Assertions.assertNotNull(request.headers.get(key)); + } + + @Test + @DisplayName("Should return builder instance when finishing method of adding header") + void shouldReturnBuilderInstanceWhenFinishingMethodOfAddingHeader(){ + var request = HttpRequestModelImplementation.of("http://localhost:8080/users", new HttpRequestGetMethod()); + var builder = HttpRequestBuilderImplementation.of(request); + var returnedBuilder = builder.headerOf("chuchu", "Duda"); + Assertions.assertNotNull(returnedBuilder); + Assertions.assertEquals(builder, returnedBuilder); + } + + private static class HeadersFactoryForTestingMatters implements HttpRequestHeaderFactory { + + static final String KEY_1 = "chuchu"; + static final String KEY_2 = "biridinho"; + static final String KEY_3 = "biridao"; + + @Override + public Map makeHeaders() { + var headers = new HashMap(); + headers.put(KEY_1, "Duda"); + headers.put(KEY_2, "Zena"); + headers.put(KEY_3, "Mingau"); + return headers; + } + } + + @Test + @DisplayName("Should add headers factory correctly") + void shouldAddHeadersFactoryCorrectly(){ + var request = HttpRequestModelImplementation.of("http://localhost:8080/users", new HttpRequestGetMethod()); + var builder = HttpRequestBuilderImplementation.of(request); + builder.headersFactory(new HeadersFactoryForTestingMatters()); + Arrays.asList(HeadersFactoryForTestingMatters.KEY_1, + HeadersFactoryForTestingMatters.KEY_2, + HeadersFactoryForTestingMatters.KEY_3) + .forEach(key -> Assertions.assertNotNull(request.headers.get(key))); + } + + @Test + @DisplayName("Should return builder instance when finishing method of adding headers factory") + void shouldReturnBuilderInstanceWhenFinishingMethodOfAddingHeaderFactory(){ + var request = HttpRequestModelImplementation.of("http://localhost:8080/users", new HttpRequestGetMethod()); + var builder = HttpRequestBuilderImplementation.of(request); + var returnedBuilder = builder.headersFactory(new HeadersFactoryForTestingMatters()); + Assertions.assertNotNull(returnedBuilder); + Assertions.assertEquals(builder, returnedBuilder); + } + + @Test + @DisplayName("Should add path variable correctly") + void shouldAddPathVariableCorrectly(){ + var request = HttpRequestModelImplementation.of("http://localhost:8080/users/test/{testId}", new HttpRequestGetMethod()); + var builder = HttpRequestBuilderImplementation.of(request); + builder.pathVariableOf("testId", "testValue"); + Assertions.assertFalse(request.pathVariables.isEmpty()); + request.pathVariables.get(0).buildPathVariableInto(request); + Assertions.assertEquals("http://localhost:8080/users/test/testValue", request.uri); + } + + @Test + @DisplayName("Should return builder instance when finishing method of adding path variable") + void shouldReturnBuilderInstanceWhenFinishingMethodOfAddingPathVariable(){ + var request = HttpRequestModelImplementation.of("http://localhost:8080/users", new HttpRequestGetMethod()); + var builder = HttpRequestBuilderImplementation.of(request); + var returnedBuilder = builder.pathVariableOf("test", "testValue"); + Assertions.assertNotNull(returnedBuilder); + Assertions.assertEquals(builder, returnedBuilder); + } + + @Test + @DisplayName("Should add query param correctly") + void shouldAddQueryParamCorrectly(){ + var request = HttpRequestModelImplementation.of("http://localhost:8080/users", new HttpRequestGetMethod()); + var builder = HttpRequestBuilderImplementation.of(request); + builder.queryParameterOf("test", "testValue"); + Assertions.assertFalse(request.queryParameters.isEmpty()); + Assertions.assertEquals("test=testValue", request.queryParameters.get(0).buildQueryParameter()); + } + + @Test + @DisplayName("Should return builder instance when finishing method of adding query param") + void shouldReturnBuilderInstanceWhenFinishingMethodOfAddingQueryParam(){ + var request = HttpRequestModelImplementation.of("http://localhost:8080/users", new HttpRequestGetMethod()); + var builder = HttpRequestBuilderImplementation.of(request); + var returnedBuilder = builder.queryParameterOf("test", "testValue"); + Assertions.assertNotNull(returnedBuilder); + Assertions.assertEquals(builder, returnedBuilder); + } + + @Test + @DisplayName("Should finish building process returning request model instance") + void shouldFinishBuildingProcessReturningRequestModelInstance(){ + var request = HttpRequestModelImplementation.of("http://localhost:8080/users", new HttpRequestGetMethod()); + var builder = HttpRequestBuilderImplementation.of(request); + var resultFromBuilding = builder.finishBuildingModel(); + Assertions.assertNotNull(resultFromBuilding); + Assertions.assertEquals(request, resultFromBuilding); + } + + @Test + @DisplayName("Should add response handler by status code correctly") + void shouldAddResponseHandlerByStatusCodeCorrectly(){ + var request = HttpRequestModelImplementation.of("http://localhost:8080/users", new HttpRequestGetMethod()); + var builder = HttpRequestBuilderImplementation.of(request); + var statusCode = 400; + var handler = (HttpResponseHandler) response -> System.out.println("hello, 400"); + builder.handlerByHttpStatusCode(statusCode, handler); + Assertions.assertFalse(request.responseHandlersByStatusCode.isEmpty()); + Assertions.assertEquals(handler, request.responseHandlersByStatusCode.get(statusCode)); + } + + @Test + @DisplayName("Should return builder instance when finishing method of adding handler by status code") + void shouldReturnBuilderInstanceWhenFinishingMethodOfAddingResponseHandlerByStatusCode(){ + var request = HttpRequestModelImplementation.of("http://localhost:8080/users", new HttpRequestGetMethod()); + var builder = HttpRequestBuilderImplementation.of(request); + var statusCode = 400; + var handler = (HttpResponseHandler) response -> System.out.println("hello, 400"); + var returnedBuilder = builder.handlerByHttpStatusCode(statusCode, handler); + Assertions.assertNotNull(returnedBuilder); + Assertions.assertEquals(builder, returnedBuilder); + } + + private static class HttpResponseHandlersByStatusCodeFactoryForTestingMatters implements HttpResponseHandlersByStatusCodeFactory { + + static final Integer KEY_1 = 400; + static final Integer KEY_2 = 401; + static final Integer KEY_3 = 403; + + @Override + public Map makeHandlers() { + var handlers = new HashMap(); + handlers.put(KEY_1, response -> System.out.println("Hello, 400")); + handlers.put(KEY_2, response -> System.out.println("Hello, 401")); + handlers.put(KEY_3, response -> System.out.println("Hello, 403")); + return handlers; + } + } + + @Test + @DisplayName("Should add response handlers by status code factory correctly") + void shouldAddHttpResponseHandlersByStatusCodeFactoryCorrectly(){ + var request = HttpRequestModelImplementation.of("http://localhost:8080/users", new HttpRequestGetMethod()); + var builder = HttpRequestBuilderImplementation.of(request); + builder.handlersByHttpStatusCodeFactory(new HttpResponseHandlersByStatusCodeFactoryForTestingMatters()); + Arrays.asList(HttpResponseHandlersByStatusCodeFactoryForTestingMatters.KEY_1, + HttpResponseHandlersByStatusCodeFactoryForTestingMatters.KEY_2, + HttpResponseHandlersByStatusCodeFactoryForTestingMatters.KEY_3) + .forEach(key -> Assertions.assertNotNull(request.responseHandlersByStatusCode.get(key))); + } + + @Test + @DisplayName("Should return builder instance when finishing method of adding response handlers by status code factory") + void shouldReturnBuilderInstanceWhenFinishingMethodOfAddingHttpResponseHandlersByStatusCodeFactory(){ + var request = HttpRequestModelImplementation.of("http://localhost:8080/users", new HttpRequestGetMethod()); + var builder = HttpRequestBuilderImplementation.of(request); + var returnedBuilder = builder.handlersByHttpStatusCodeFactory(new HttpResponseHandlersByStatusCodeFactoryForTestingMatters()); + Assertions.assertNotNull(returnedBuilder); + Assertions.assertEquals(builder, returnedBuilder); + } + + @Test + @DisplayName("Should add response handler for any not successful response correctly") + void shouldAddResponseHandlerForAnyNotSuccessfulResponseCorrectly(){ + var request = HttpRequestModelImplementation.of("http://localhost:8080/users", new HttpRequestGetMethod()); + var builder = HttpRequestBuilderImplementation.of(request); + var handler = (HttpResponseHandler) response -> System.out.println("hello, 400"); + builder.handlerForAnyUnsuccessfulResponse(handler); + Assertions.assertNotNull(request.genericResponseHandler); + Assertions.assertEquals(handler, request.genericResponseHandler); + } + + @Test + @DisplayName("Should return builder instance when finishing method of adding response handler for any not successful response") + void shouldReturnBuilderInstanceWhenFinishingMethodOfAddingResponseHandlerForAnyNotSuccessfulResponse(){ + var request = HttpRequestModelImplementation.of("http://localhost:8080/users", new HttpRequestGetMethod()); + var builder = HttpRequestBuilderImplementation.of(request); + var handler = (HttpResponseHandler) response -> System.out.println("hello, 400"); + var returnedBuilder = builder.handlerForAnyUnsuccessfulResponse(handler); + Assertions.assertNotNull(returnedBuilder); + Assertions.assertEquals(builder, returnedBuilder); + } + + @Test + @DisplayName("Should add response handler by exception type correctly") + void shouldAddResponseHandlerByExceptionTypeCorrectly(){ + var request = HttpRequestModelImplementation.of("http://localhost:8080/users", new HttpRequestGetMethod()); + var builder = HttpRequestBuilderImplementation.of(request); + var handler = (ExceptionHandler) exception -> System.out.println("hello, exception"); + var exceptionToHandle = RuntimeException.class; + builder.handlerByExceptionType(exceptionToHandle, handler); + Assertions.assertFalse(request.exceptionHandlersByExceptionType.isEmpty()); + Assertions.assertEquals(handler, request.exceptionHandlersByExceptionType.get(exceptionToHandle)); + } + + @Test + @DisplayName("Should return builder instance when finishing method of adding response handler by exception type") + void shouldReturnBuilderInstanceWhenFinishingMethodOfAddingResponseHandlerByExceptionType(){ + var request = HttpRequestModelImplementation.of("http://localhost:8080/users", new HttpRequestGetMethod()); + var builder = HttpRequestBuilderImplementation.of(request); + var handler = (ExceptionHandler) exception -> System.out.println("hello, exception"); + var exceptionToHandle = RuntimeException.class; + var returnedBuilder = builder.handlerByExceptionType(exceptionToHandle, handler); + Assertions.assertNotNull(returnedBuilder); + Assertions.assertEquals(builder, returnedBuilder); + } + + @Test + @DisplayName("Should add retrier by status code correctly") + void shouldAddRetrierByHttpStatusCodeCorrectly(){ + var request = HttpRequestModelImplementation.of("http://localhost:8080/users", new HttpRequestGetMethod()); + var builder = HttpRequestBuilderImplementation.of(request); + var retrier = RetrierModel.withLimitOf(4); + var statusCode = 503; + builder.retrierByHttpStatusCode(statusCode, retrier); + Assertions.assertFalse(request.retryCountersByStatusCode.isEmpty()); + Assertions.assertTrue(request.retryCountersByStatusCode.get(statusCode).thereIsRetryAvailable()); + } + + @Test + @DisplayName("Should return builder instance when finishing method of adding retrier by status code") + void shouldReturnBuilderInstanceWhenFinishingMethodOfAddingRetrierByHttpStatusCode(){ + var request = HttpRequestModelImplementation.of("http://localhost:8080/users", new HttpRequestGetMethod()); + var builder = HttpRequestBuilderImplementation.of(request); + var retrier = RetrierModel.withLimitOf(4); + var statusCode = 503; + var returnedBuilder = builder.retrierByHttpStatusCode(statusCode, retrier); + Assertions.assertNotNull(returnedBuilder); + Assertions.assertEquals(builder, returnedBuilder); + } + + @Test + @DisplayName("Should add retrier by exception type correctly") + void shouldAddRetrierByExceptionTypeCorrectly(){ + var request = HttpRequestModelImplementation.of("http://localhost:8080/users", new HttpRequestGetMethod()); + var builder = HttpRequestBuilderImplementation.of(request); + var retrier = RetrierModel.withLimitOf(4); + var exceptionType = RuntimeException.class; + builder.retrierByExceptionType(exceptionType, retrier); + Assertions.assertFalse(request.retryCountersByExceptionType.isEmpty()); + Assertions.assertTrue(request.retryCountersByExceptionType.get(exceptionType).thereIsRetryAvailable()); + } + + @Test + @DisplayName("Should return builder instance when finishing method of adding retrier by exception type") + void shouldReturnBuilderInstanceWhenFinishingMethodOfAddingRetrierByExceptionType(){ + var request = HttpRequestModelImplementation.of("http://localhost:8080/users", new HttpRequestGetMethod()); + var builder = HttpRequestBuilderImplementation.of(request); + var retrier = RetrierModel.withLimitOf(4); + var exceptionType = RuntimeException.class; + var returnedBuilder = builder.retrierByExceptionType(exceptionType, retrier); + Assertions.assertNotNull(returnedBuilder); + Assertions.assertEquals(builder, returnedBuilder); + } + + private static class HttpExceptionHandlersByExceptionTypeFactoryForTestingMatters implements HttpExceptionHandlersByExceptionTypeFactory { + + static final Class KEY_1 = RuntimeException.class; + static final Class KEY_2 = InterruptedException.class; + static final Class KEY_3 = Exception.class; + + + @Override + public Map, ExceptionHandler> makeHandlers() { + var handlers = new HashMap, ExceptionHandler>(); + handlers.put(KEY_1, exception -> System.out.println("Hello, RuntimeException")); + handlers.put(KEY_2, exception -> System.out.println("Hello, InterruptedException")); + handlers.put(KEY_3, exception -> System.out.println("Hello, Exception")); + return handlers; + } + } + + @Test + @DisplayName("Should add http response handlers by exception type factory correctly") + void shouldAddHttpResponseHandlersByExceptionTypeFactoryCorrectly(){ + var request = HttpRequestModelImplementation.of("http://localhost:8080/users", new HttpRequestGetMethod()); + var builder = HttpRequestBuilderImplementation.of(request); + builder.handlersByExceptionTypeFactory(new HttpExceptionHandlersByExceptionTypeFactoryForTestingMatters()); + Arrays.asList(HttpExceptionHandlersByExceptionTypeFactoryForTestingMatters.KEY_1, + HttpExceptionHandlersByExceptionTypeFactoryForTestingMatters.KEY_2, + HttpExceptionHandlersByExceptionTypeFactoryForTestingMatters.KEY_3) + .forEach(key -> Assertions.assertNotNull(request.exceptionHandlersByExceptionType.get(key))); + } + + @Test + @DisplayName("Should return builder instance when finishing method of adding http response handlers by exception type factory") + void shouldReturnBuilderInstanceWhenFinishingMethodOfAddingHttpResponseHandlersByExceptionTypeFactory(){ + var request = HttpRequestModelImplementation.of("http://localhost:8080/users", new HttpRequestGetMethod()); + var builder = HttpRequestBuilderImplementation.of(request); + var returnedBuilder = builder.handlersByExceptionTypeFactory(new HttpExceptionHandlersByExceptionTypeFactoryForTestingMatters()); + Assertions.assertNotNull(returnedBuilder); + Assertions.assertEquals(builder, returnedBuilder); + } + + private static class RetriersByStatusCodeFactoryForTestingMatters implements RetriersByStatusCodeFactory { + + static final Integer KEY_1 = 400; + static final Integer KEY_2 = 401; + static final Integer KEY_3 = 403; + + + @Override + public Map makeRetriers() { + var retriers = new HashMap(); + retriers.put(KEY_1, RetrierModel.withLimitOf(4)); + retriers.put(KEY_2, RetrierModel.withLimitOf(8)); + retriers.put(KEY_3, RetrierModel.withLimitOf(16)); + return retriers; + } + } + + @Test + @DisplayName("Should add retriers by status code factory correctly") + void shouldAddRetriersByStatusCodeFactoryCorrectly(){ + var request = HttpRequestModelImplementation.of("http://localhost:8080/users", new HttpRequestGetMethod()); + var builder = HttpRequestBuilderImplementation.of(request); + builder.retriersByHttpStatusCodeFactory(new RetriersByStatusCodeFactoryForTestingMatters()); + Arrays.asList(RetriersByStatusCodeFactoryForTestingMatters.KEY_1, + RetriersByStatusCodeFactoryForTestingMatters.KEY_2, + RetriersByStatusCodeFactoryForTestingMatters.KEY_3) + .forEach(key -> Assertions.assertNotNull(request.retryCountersByStatusCode.get(key))); + } + + @Test + @DisplayName("Should return builder instance when finishing method of adding retriers by status code factory") + void shouldReturnBuilderInstanceWhenFinishingMethodOfAddingRetriersByStatusCodeFactory(){ + var request = HttpRequestModelImplementation.of("http://localhost:8080/users", new HttpRequestGetMethod()); + var builder = HttpRequestBuilderImplementation.of(request); + var returnedBuilder = builder.retriersByHttpStatusCodeFactory(new RetriersByStatusCodeFactoryForTestingMatters()); + Assertions.assertNotNull(returnedBuilder); + Assertions.assertEquals(builder, returnedBuilder); + } + + @Test + @DisplayName("Should set proxy address to the request model") + void shouldSetProxyAddressToTheRequestModel(){ + var host = "tururu.com"; + var port = 8080; + var request = HttpRequestModelImplementation.of("http://localhost:8080/users", new HttpRequestGetMethod()); + var builder = HttpRequestBuilderImplementation.of(request); + builder.proxyAddress(host, port); + Assertions.assertNotNull(request.proxyAddress); + Assertions.assertNotNull(request.proxyAddress.getHost()); + Assertions.assertNotNull(request.proxyAddress.getPort()); + Assertions.assertEquals(host, request.proxyAddress.getHost()); + Assertions.assertEquals(port, request.proxyAddress.getPort()); + } + + @Test + @DisplayName("Should return the builder instance after 'andAddProxyAddress' has been called") + void shouldReturnTheBuilderInstanceAfterAndAddProxyAddressHasBeenCalled(){ + var request = HttpRequestModelImplementation.of("http://localhost:8080/users", new HttpRequestGetMethod()); + var builder = HttpRequestBuilderImplementation.of(request).proxyAddress("tururu.com", 8080); + Assertions.assertNotNull(builder); + } + +} diff --git a/src/test/java/com/clean_arch_enablers/http_client/implementations/HttpRequestStarterImplementationTest.java b/src/test/java/com/clean_arch_enablers/http_client/implementations/HttpRequestStarterImplementationTest.java new file mode 100644 index 0000000..bab5584 --- /dev/null +++ b/src/test/java/com/clean_arch_enablers/http_client/implementations/HttpRequestStarterImplementationTest.java @@ -0,0 +1,41 @@ +package com.clean_arch_enablers.http_client.implementations; + +import com.clean_arch_enablers.http_client.HttpRequestStarter; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.junit.jupiter.MockitoExtension; + +import java.net.http.HttpRequest; + +@ExtendWith(MockitoExtension.class) +class HttpRequestStarterImplementationTest { + + HttpRequestStarter httpRequestStarter = new HttpRequestStarterImplementation(); + + @Test + @DisplayName("Should return non null instance of builder when starting for delete method") + void shouldReturnNonNullInstanceOfBuilderWhenStartingForDeleteMethod(){ + Assertions.assertNotNull(this.httpRequestStarter.startDeleteRequestFor("http://localhost:2222")); + } + + @Test + @DisplayName("Should return non null instance of builder when starting for get method") + void shouldReturnNonNullInstanceOfBuilderWhenStartingForGetMethod(){ + Assertions.assertNotNull(this.httpRequestStarter.startGetRequestFor("http://localhost:2222")); + } + + @Test + @DisplayName("Should return non null instance of builder when starting for post method") + void shouldReturnNonNullInstanceOfBuilderWhenStartingForPostMethod(){ + Assertions.assertNotNull(this.httpRequestStarter.startPostRequestFor("http://localhost:2222", HttpRequest.BodyPublishers.ofString("{someRandomJsonField: \"someRandomValue\"}"))); + } + + @Test + @DisplayName("Should return non null instance of builder when starting for put method") + void shouldReturnNonNullInstanceOfBuilderWhenStartingForPutMethod(){ + Assertions.assertNotNull(this.httpRequestStarter.startPutRequestFor("http://localhost:2222", HttpRequest.BodyPublishers.ofString("{someRandomJsonField: \"someRandomValue\"}"))); + } + +} diff --git a/src/test/java/com/clean_arch_enablers/http_client/implementations/HttpResponseImplementationTest.java b/src/test/java/com/clean_arch_enablers/http_client/implementations/HttpResponseImplementationTest.java new file mode 100644 index 0000000..64c4662 --- /dev/null +++ b/src/test/java/com/clean_arch_enablers/http_client/implementations/HttpResponseImplementationTest.java @@ -0,0 +1,97 @@ +package com.clean_arch_enablers.http_client.implementations; + +import com.clean_arch_enablers.http_client.HttpRequestModel; +import com.clean_arch_enablers.http_client.HttpResponse; +import com.fasterxml.jackson.core.type.TypeReference; +import lombok.Getter; +import lombok.Setter; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.junit.jupiter.MockitoExtension; + +import java.util.List; + +@ExtendWith(MockitoExtension.class) +class HttpResponseImplementationTest { + + HttpResponseImplementation httpResponse; + @Mock + HttpRequestModel httpRequestModel; + @Mock + java.net.http.HttpResponse unwrappedHttpResponse; + + @BeforeEach + void setUp(){ + this.httpResponse = new HttpResponseImplementation(this.httpRequestModel, this.unwrappedHttpResponse); + } + + @Test + @DisplayName("Should return the status code correctly") + void shouldReturnTheStatusCodeCorrectly(){ + var expected = 200; + Mockito.when(this.unwrappedHttpResponse.statusCode()).thenReturn(expected); + var actual = this.httpResponse.getStatusCode(); + Assertions.assertEquals(expected, actual); + } + + @Test + @DisplayName("Should return the request model correctly") + void shouldReturnTheRequestModelCorrectly(){ + Assertions.assertEquals(this.httpRequestModel, this.httpResponse.getHttpRequest()); + } + + @Getter + @Setter + private static class TestingDecoding{ + private String test; + } + + @Test + @DisplayName("Should return body decoded to the specified type as parameter") + void shouldReturnBodyDecodedToTheSpecifiedTypeAsParameter(){ + Mockito.when(this.unwrappedHttpResponse.body()).thenReturn("{\"test\":\"value\"}"); + var result = this.httpResponse.getBodyAs(TestingDecoding.class); + Assertions.assertNotNull(result); + Assertions.assertEquals("value", result.getTest()); + } + + @Test + @DisplayName("Should return body decoded to the specified type reference as parameter") + void shouldReturnBodyDecodedToTheSpecifiedTypeReferenceAsParameter(){ + Mockito.when(this.unwrappedHttpResponse.body()).thenReturn("[{\"test\":\"value\"}]"); + var result = this.httpResponse.getBodyAs(new TypeReference>() {}); + Assertions.assertNotNull(result); + Assertions.assertEquals(1, result.size()); + Assertions.assertEquals("value", result.get(0).getTest()); + } + + @Test + @DisplayName("Should perform the action") + void shouldPerformTheAction(){ + var textBuilder = new StringBuilder("0"); + Mockito.when(this.unwrappedHttpResponse.statusCode()).thenReturn(400); + this.httpResponse.ifNeedsHandling(response -> this.concatSomeChars(response, textBuilder)); + var finalText = textBuilder.toString(); + Assertions.assertEquals("00", finalText); + } + + @Test + @DisplayName("Should not perform the action") + void shouldNotPerformTheAction(){ + var textBuilder = new StringBuilder("0"); + Mockito.when(this.unwrappedHttpResponse.statusCode()).thenReturn(200); + this.httpResponse.ifNeedsHandling(response -> this.concatSomeChars(response, textBuilder)); + var finalText = textBuilder.toString(); + Assertions.assertEquals("0", finalText); + } + + private void concatSomeChars(HttpResponse httpResponse, StringBuilder textBuilder) { + textBuilder.append("0"); + } + +} diff --git a/src/test/java/com/clean_arch_enablers/http_client/implementations/HttpResponseStatusCodeCheckerTest.java b/src/test/java/com/clean_arch_enablers/http_client/implementations/HttpResponseStatusCodeCheckerTest.java new file mode 100644 index 0000000..e6e1038 --- /dev/null +++ b/src/test/java/com/clean_arch_enablers/http_client/implementations/HttpResponseStatusCodeCheckerTest.java @@ -0,0 +1,65 @@ +package com.clean_arch_enablers.http_client.implementations; + +import com.clean_arch_enablers.http_client.HttpResponse; +import com.clean_arch_enablers.http_client.RetrierModel; +import com.clean_arch_enablers.http_client.implementations.exceptions.NoResponseHandlersAvailableForExecutionException; +import com.clean_arch_enablers.http_client.implementations.exceptions.RetryNeededOnHttpStatusCodeException; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.junit.jupiter.MockitoExtension; + +@ExtendWith(MockitoExtension.class) +class HttpResponseStatusCodeCheckerTest { + + HttpResponseStatusCodeChecker httpResponseStatusCodeChecker; + AbstractHttpRequestModel httpRequestModel; + @Mock + HttpResponse response; + + @BeforeEach + void setUp(){ + this.httpRequestModel = HttpRequestModelImplementation.of("", new HttpRequestGetMethod()); + this.httpResponseStatusCodeChecker = HttpResponseStatusCodeChecker.of(this.httpRequestModel); + } + + @Test + @DisplayName("Should throw parameterized exception type for the received status code when there is no retrier set") + void shouldThrowParameterizedExceptionTypeForTheReceivedStatusCodeWhenThereIsNoRetrierSet(){ + Mockito.when(this.response.getStatusCode()).thenReturn(400); + this.httpRequestModel.responseHandlersByStatusCode.put(400, httpResponse -> {throw new RuntimeException();}); + Assertions.assertThrows(RuntimeException.class, () -> this.httpResponseStatusCodeChecker.checkOutHandlersFor(this.response)); + } + + @Test + @DisplayName("Should throw RetryNeededOnHttpStatusCodeException for the received status code when there is retrier set") + void shouldThrowRetryNeededOnHttpStatusCodeExceptionForTheReceivedStatusCodeWhenThereIsRetrierSet(){ + Mockito.when(this.response.getStatusCode()).thenReturn(400); + this.httpRequestModel.responseHandlersByStatusCode.put(400, httpResponse -> {throw new RuntimeException();}); + this.httpRequestModel.retryCountersByStatusCode.put(400, RetryCounterImplementation.of(RetrierModel.withLimitOf(4))); + Assertions.assertThrows(RetryNeededOnHttpStatusCodeException.class, () -> this.httpResponseStatusCodeChecker.checkOutHandlersFor(this.response)); + } + + @Test + @DisplayName("Should throw no response handlers available for execution exception") + void shouldThrowNoResponseHandlersAvailableForExecutionException(){ + Mockito.when(this.response.getStatusCode()).thenReturn(400); + Assertions.assertThrows(NoResponseHandlersAvailableForExecutionException.class, () -> this.httpResponseStatusCodeChecker.checkOutHandlersFor(this.response)); + } + + @Test + @DisplayName("Should just execute the handler for the received status code when there is no retrier set") + void shouldJustExecuteTheHandlerForTheReceivedStatusCodeWhenThereIsNoRetrierSet(){ + var stringBuilder = new StringBuilder("0"); + Mockito.when(this.response.getStatusCode()).thenReturn(400); + this.httpRequestModel.responseHandlersByStatusCode.put(400, httpResponse -> stringBuilder.append("0")); + this.httpResponseStatusCodeChecker.checkOutHandlersFor(this.response); + var result = stringBuilder.toString(); + Assertions.assertEquals(2, result.length()); + } + +} diff --git a/src/test/java/com/clean_arch_enablers/http_client/utils/ThreadIdRetrievementUtilTest.java b/src/test/java/com/clean_arch_enablers/http_client/utils/ThreadIdRetrievementUtilTest.java new file mode 100644 index 0000000..d68d9da --- /dev/null +++ b/src/test/java/com/clean_arch_enablers/http_client/utils/ThreadIdRetrievementUtilTest.java @@ -0,0 +1,30 @@ +package com.clean_arch_enablers.http_client.utils; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.junit.jupiter.MockitoExtension; + +@ExtendWith(MockitoExtension.class) +class ThreadIdRetrievementUtilTest { + + @Test + @DisplayName("Should return filled string") + void shouldReturnFilledString(){ + Assertions.assertFalse(ThreadIdRetrievementUtil.retrieveThreadId().isEmpty()); + } + + @Test + @DisplayName("Should not return a blank string") + void shouldNotReturnBlankString(){ + Assertions.assertFalse(ThreadIdRetrievementUtil.retrieveThreadId().isBlank()); + } + + @Test + @DisplayName("Should not return null reference") + void shouldNotReturnNullReference(){ + Assertions.assertNotNull(ThreadIdRetrievementUtil.retrieveThreadId()); + } + +}