Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add default query parameters that are appended to every URL #39

Closed
wants to merge 2 commits into from
Closed
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 18 additions & 14 deletions src/main/java/io/github/sashirestela/cleverclient/CleverClient.java
Original file line number Diff line number Diff line change
@@ -4,6 +4,7 @@

import java.net.http.HttpClient;
import java.util.List;
import java.util.Map;
import java.util.Optional;

import org.slf4j.Logger;
@@ -35,23 +36,25 @@ public class CleverClient {
/**
* Constructor to create an instance of CleverClient.
*
* @param baseUrl Root of the url of the API service to call.
* at least one of baseUrl and the deprecated urlBase is mandatory.
* in case both are specified and different baseUrl takes precedence
* @param baseUrl Root of the url of the API service to call.
* at least one of baseUrl and the deprecated urlBase is mandatory.
* in case both are specified and different baseUrl takes precedence
* @param urlBase [[ Deprecated ]] Root of the url of the API service to call.
* it is here for backward compatibility only. It will be removed in
* a future version. use `baseUrl()` instead.
* @param headers Http headers for all the API service. Header's name and
* value must be individual entries in the list. Optional.
* @param httpClient Custom Java's HttpClient component. One is created by
* default if none is passed. Optional.
* @param endOfStream Text used to mark the final of streams when handling
* server sent events (SSE). Optional.
* @param defaultQueryParams query params that are added to each URL. Optional.
*
* @param urlBase [[ Deprecated ]] Root of the url of the API service to call.
* it is here for backward compatibility only. It will be removed in
* a future version. use `baseUrl()` instead.
* @param headers Http headers for all the API service. Header's name and
* value must be individual entries in the list. Optional.
* @param httpClient Custom Java's HttpClient component. One is created by
* default if none is passed. Optional.
* @param endOfStream Text used to mark the final of streams when handling
* server sent events (SSE). Optional.
*/
@Builder
public CleverClient(String baseUrl, String urlBase, @Singular List<String> headers, HttpClient httpClient,
String endOfStream) {
String endOfStream, Map<String, ?> defaultQueryParams
) {
if (isNullOrEmpty(baseUrl) && isNullOrEmpty(urlBase)) {
throw new CleverClientException("At least one of baseUrl and urlBase is mandatory.", null, null);
}
@@ -62,7 +65,8 @@ public CleverClient(String baseUrl, String urlBase, @Singular List<String> heade
}
this.httpClient = Optional.ofNullable(httpClient).orElse(HttpClient.newHttpClient());
CleverClientSSE.setEndOfStream(endOfStream);
this.httpProcessor = new HttpProcessor(this.baseUrl, this.headers, this.httpClient);
this.httpProcessor = new HttpProcessor(
this.baseUrl, this.headers, this.httpClient, defaultQueryParams);
logger.debug("CleverClient has been created.");
}

Original file line number Diff line number Diff line change
@@ -8,6 +8,7 @@
import java.util.ArrayList;
import java.util.List;

import java.util.Map;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

@@ -25,6 +26,8 @@ public class HttpProcessor implements InvocationHandler {
private final HttpClient httpClient;
private final String baseUrl;
private final List<String> headers;
private final Map<String, ?> defaultQueryParams;


/**
* Constructor to create an instance of HttpProcessor.
@@ -33,10 +36,11 @@ public class HttpProcessor implements InvocationHandler {
* @param baseUrl Root of the url of the API service to call.
* @param headers Http headers for all the API service.
*/
public HttpProcessor(String baseUrl, List<String> headers, HttpClient httpClient) {
public HttpProcessor(String baseUrl, List<String> headers, HttpClient httpClient, Map<String, ?> defaultQueryParams) {
this.baseUrl = baseUrl;
this.headers = headers;
this.httpClient = httpClient;
this.defaultQueryParams = defaultQueryParams;
}

/**
@@ -106,7 +110,9 @@ private Object resolve(Method method, Object[] arguments) {
var interfaceMetadata = InterfaceMetadataStore.one().get(method.getDeclaringClass());
var methodMetadata = interfaceMetadata.getMethodBySignature().get(method.toString());
var urlMethod = interfaceMetadata.getFullUrlByMethod(methodMetadata);
var url = baseUrl + URLBuilder.one().build(urlMethod, methodMetadata, arguments);
var url = baseUrl + URLBuilder.one().build(
urlMethod, methodMetadata, arguments, this.defaultQueryParams);

var httpMethod = methodMetadata.getHttpAnnotationName();
var returnType = methodMetadata.getReturnType();
var isMultipart = methodMetadata.isMultipart();
Original file line number Diff line number Diff line change
@@ -23,15 +23,28 @@ public static URLBuilder one() {
return urlBuilder;
}

public String build(String urlMethod, MethodMetadata methodMetadata, Object[] arguments) {
public String build(String urlMethod, MethodMetadata methodMetadata, Object[] arguments,
Map<String, ?> defaultQueryParams
) {
var url = urlMethod;
var pathParameters = methodMetadata.getPathParameters();
var queryParameters = methodMetadata.getQueryParameters();
if (pathParameters.isEmpty() && queryParameters.isEmpty()) {
if (pathParameters.isEmpty() && queryParameters.isEmpty() && defaultQueryParams.isEmpty()) {
return url;
}
url = replacePathParams(url, pathParameters, arguments);
url = includeQueryParams(url, queryParameters, arguments);
if (defaultQueryParams.isEmpty()) {
return url;
}

// append default query params
var questionMarkIndex = url.indexOf('?');
var queryParams = questionMarkIndex != -1 ? url.substring(questionMarkIndex) : "";
url = questionMarkIndex != -1 ? url.substring(0, questionMarkIndex) : url;
var sb = new StringBuilder(queryParams);
appendQueryParams(defaultQueryParams, sb);
url += sb.toString();
return url;
}

Original file line number Diff line number Diff line change
@@ -19,6 +19,7 @@
import java.net.http.HttpResponse;
import java.nio.file.Paths;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionException;
import java.util.stream.Stream;
@@ -39,7 +40,7 @@ class HttpProcessorTest {

@BeforeEach
void init() {
httpProcessor = new HttpProcessor("https://api.demmo", List.of(), httpClient);
httpProcessor = new HttpProcessor("https://api.demmo", List.of(), httpClient, Map.of());
}

@Test
Original file line number Diff line number Diff line change
@@ -27,11 +27,27 @@ void shouldReturnUrlWithoutChangesWhenDoesNotContainPathOrQueryParams() {
when(methodMetadata.getPathParameters()).thenReturn(List.of());
when(methodMetadata.getQueryParameters()).thenReturn(List.of());

var actualUrl = urlBuilder.build(url, methodMetadata, null);
var actualUrl = urlBuilder.build(url, methodMetadata, null, Map.of());
var expectedUrl = url;
assertEquals(expectedUrl, actualUrl);
}

@Test
void shouldReturnUrlWithoutChangesWhenDoesNotContainPathOrQueryParamsWithDefaultQueryParams() {
var url = "/api/domain/entities";

when(methodMetadata.getPathParameters()).thenReturn(List.of());
when(methodMetadata.getQueryParameters()).thenReturn(List.of());

when(methodMetadata.getPathParameters()).thenReturn(List.of());
when(methodMetadata.getQueryParameters()).thenReturn(List.of());

var defaultQueryParams = Map.of("api-version", "2023-05-15");
var actualUrl = urlBuilder.build(url, methodMetadata, null, defaultQueryParams);
var expectedUrl = url + "?api-version=2023-05-15";
assertEquals(expectedUrl, actualUrl);
}

@Test
void shouldReturnReplacedUrlWithPathParamsWhenUrlContainsPathParams() {
var url = "/api/domain/entities/{entityId}/details/{detailId}";
@@ -56,7 +72,8 @@ void shouldReturnReplacedUrlWithPathParamsWhenUrlContainsPathParams() {
when(methodMetadata.getPathParameters()).thenReturn(paramsList);
when(methodMetadata.getQueryParameters()).thenReturn(List.of());

var actualUrl = urlBuilder.build(url, methodMetadata, new Object[] { null, 101, null, 201 });
var actualUrl = urlBuilder.build(url, methodMetadata, new Object[] { null, 101, null, 201 },
Map.of());
var expectedUrl = "/api/domain/entities/101/details/201";
assertEquals(expectedUrl, actualUrl);
}
@@ -93,11 +110,52 @@ void shouldReturnReplacedUrlWithQueryParamsWhenMethodContainsQueryParams() {
when(methodMetadata.getPathParameters()).thenReturn(List.of());
when(methodMetadata.getQueryParameters()).thenReturn(paramsList);

var actualUrl = urlBuilder.build(url, methodMetadata, new Object[] { null, "name", null, 20 });
var actualUrl = urlBuilder.build(url, methodMetadata, new Object[] { null, "name", null, 20 },
Map.of());
var expectedUrl = "/api/domain/entities?sortedBy=name&rowsPerPage=20";
assertEquals(expectedUrl, actualUrl);
}

@Test
void shouldReturnReplacedUrlWithQueryParamsWhenMethodContainsQueryParamsAndDefaultQueryParameters() {
var url = "/api/domain/entities";
var paramsList = List.of(
ParameterMetadata.builder()
.index(1)
.annotation(AnnotationMetadata.builder()
.name("Query")
.isHttpMethod(false)
.valueByField(Map.of("value", "sortedBy"))
.build())
.build(),
ParameterMetadata.builder()
.index(2)
.annotation(AnnotationMetadata.builder()
.name("Query")
.isHttpMethod(false)
.valueByField(Map.of("value", "filterBy"))
.build())
.build(),
ParameterMetadata.builder()
.index(3)
.annotation(AnnotationMetadata.builder()
.name("Query")
.isHttpMethod(false)
.valueByField(Map.of("value", "rowsPerPage"))
.build())
.build());

when(methodMetadata.getPathParameters()).thenReturn(List.of());
when(methodMetadata.getQueryParameters()).thenReturn(paramsList);

var defaultQueryParams = Map.of("api-version", "2023-05-15");

var actualUrl = urlBuilder.build(url, methodMetadata, new Object[] { null, "name", null, 20 },
defaultQueryParams);
var expectedUrl = "/api/domain/entities?sortedBy=name&rowsPerPage=20&api-version=2023-05-15";
assertEquals(expectedUrl, actualUrl);
}

@Test
void shouldReturnReplacedUrlWithQueryParamsWhenMethodContainsQueryParamsForPojos() {
var url = "/api/domain/entities";
@@ -122,7 +180,8 @@ void shouldReturnReplacedUrlWithQueryParamsWhenMethodContainsQueryParamsForPojos
when(methodMetadata.getPathParameters()).thenReturn(List.of());
when(methodMetadata.getQueryParameters()).thenReturn(paramsList);

var actualUrl = urlBuilder.build(url, methodMetadata, new Object[] { new Pagination(10, 3), "fullname" });
var actualUrl = urlBuilder.build(url, methodMetadata, new Object[] { new Pagination(10, 3), "fullname" },
Map.of());
var expectedUrl = "/api/domain/entities?size=10&page=3&sortedBy=fullname";
assertEquals(expectedUrl, actualUrl);
}