Skip to content

Commit 18d4d53

Browse files
authored
Merge pull request #12 from pluggyai/development
Close version 0.3.0
2 parents bfc2018 + b24dc01 commit 18d4d53

File tree

12 files changed

+286
-124
lines changed

12 files changed

+286
-124
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ Currently, the package is available in Github Packages, so make sure to have the
1818
<dependency>
1919
<groupId>ai.pluggy</groupId>
2020
<artifactId>pluggy-java</artifactId>
21-
<version>0.2.0</version>
21+
<version>0.3.0</version>
2222
</dependency>
2323
```
2424

pom.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55

66
<groupId>ai.pluggy</groupId>
77
<artifactId>pluggy-java</artifactId>
8-
<version>0.2.0</version>
8+
<version>0.3.0</version>
99

1010
<packaging>jar</packaging>
1111

src/main/java/ai/pluggy/client/PluggyApiService.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
import ai.pluggy.client.request.AccountsRequest;
44
import ai.pluggy.client.request.ConnectorsSearchRequest;
55
import ai.pluggy.client.request.CreateItemRequest;
6-
import ai.pluggy.client.request.DateFilters;
6+
import ai.pluggy.client.request.TransactionsSearchRequest;
77
import ai.pluggy.client.request.UpdateItemRequest;
88
import ai.pluggy.client.response.Account;
99
import ai.pluggy.client.response.AccountsResponse;
@@ -64,7 +64,7 @@ Call<ItemResponse> updateItem(@Path("id") String itemId,
6464

6565
@GET("/transactions")
6666
Call<TransactionsResponse> getTransactions(@Query("accountId") String accountId,
67-
@QueryMap DateFilters dateFilters);
67+
@QueryMap TransactionsSearchRequest transactionsSearchRequest);
6868

6969
@GET("/transactions/{id}")
7070
Call<Transaction> getTransaction(@Path("id") String transactionId);

src/main/java/ai/pluggy/client/PluggyClient.java

Lines changed: 46 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44
import static ai.pluggy.utils.Asserts.assertValidUrl;
55

66
import ai.pluggy.client.auth.ApiKeyAuthInterceptor;
7-
import ai.pluggy.client.auth.TokenProvider;
87
import ai.pluggy.client.response.AuthResponse;
98
import ai.pluggy.client.response.ErrorResponse;
109
import ai.pluggy.exception.PluggyException;
@@ -17,20 +16,17 @@
1716
import java.util.concurrent.TimeUnit;
1817
import okhttp3.MediaType;
1918
import okhttp3.OkHttpClient;
19+
import okhttp3.OkHttpClient.Builder;
2020
import okhttp3.Request;
2121
import okhttp3.RequestBody;
2222
import okhttp3.ResponseBody;
23-
import org.apache.logging.log4j.LogManager;
24-
import org.apache.logging.log4j.Logger;
2523
import retrofit2.Retrofit;
2624
import retrofit2.converter.gson.GsonConverterFactory;
2725

2826
public final class PluggyClient {
2927

30-
private static final Logger logger = LogManager.getLogger(PluggyClient.class);
31-
28+
public static String AUTH_URL_PATH = "/auth";
3229
private String baseUrl;
33-
private String authUrlPath = "/auth";
3430

3531
private String clientId;
3632
private String clientSecret;
@@ -109,14 +105,22 @@ public static class PluggyClientBuilder {
109105
private static Integer DEFAULT_HTTP_CONNECT_TIMEOUT_SECONDS = 10;
110106
private static Integer DEFAULT_HTTP_READ_TIMEOUT_SECONDS = 180;
111107

112-
private String authPath = "/auth";
113108
private String clientId;
114109
private String clientSecret;
115110
private String baseUrl;
111+
private Builder okHttpClientBuilder;
112+
private boolean disableDefaultAuthInterceptor = false;
113+
114+
private PluggyClientBuilder() {
115+
// init OkHttpClient.Builder instance, to expose it for configurability
116+
this.okHttpClientBuilder = new Builder()
117+
.readTimeout(DEFAULT_HTTP_READ_TIMEOUT_SECONDS, TimeUnit.SECONDS)
118+
.connectTimeout(DEFAULT_HTTP_CONNECT_TIMEOUT_SECONDS, TimeUnit.SECONDS);
119+
}
116120

117121
public PluggyClientBuilder clientIdAndSecret(String clientId, String clientSecret) {
118-
assertNotNull(clientId, "client id");
119-
assertNotNull(clientSecret, "secret");
122+
assertNotNull(clientId, "clientId");
123+
assertNotNull(clientSecret, "clientSecret");
120124
this.clientId = clientId;
121125
this.clientSecret = clientSecret;
122126
return this;
@@ -128,16 +132,37 @@ public PluggyClientBuilder baseUrl(String baseUrl) {
128132
return this;
129133
}
130134

135+
/**
136+
* Opt-out from provided default ApiKeyAuthInterceptor, which takes care of apiKey authorization,
137+
* by requesting a new apiKey token when it's not set, or by reactively refreshing an existing
138+
* one and retrying a request in case of 401 or 403 unauthorized error responses.
139+
*
140+
* In case of opt-out, the client will have to provide it's own auth interceptor implementation,
141+
* which has to take care of including the "x-api-key" auth header to each http request.
142+
*/
143+
public PluggyClientBuilder noAuthInterceptor() {
144+
this.disableDefaultAuthInterceptor = true;
145+
return this;
146+
}
147+
148+
/**
149+
* Provides access to the OkHttpClient.Builder instance,
150+
* for more complex builds and configurations (interceptors, SSL, etc.)
151+
*/
152+
public OkHttpClient.Builder okHttpClientBuilder() {
153+
return okHttpClientBuilder;
154+
}
155+
131156
private OkHttpClient buildOkHttpClient(String baseUrl) {
132-
String authUrlPath = baseUrl + authPath;
157+
if (!disableDefaultAuthInterceptor) {
158+
// use ApiKeyAuthInterceptor, unless decided to opt-out and use your own
159+
// auth header interceptor implementation.
160+
String authUrlPath = baseUrl + PluggyClient.AUTH_URL_PATH;
161+
this.okHttpClientBuilder
162+
.addInterceptor(new ApiKeyAuthInterceptor(authUrlPath, clientId, clientSecret));
163+
}
133164

134-
OkHttpClient httpClient = new OkHttpClient.Builder()
135-
.readTimeout(DEFAULT_HTTP_READ_TIMEOUT_SECONDS, TimeUnit.SECONDS)
136-
.connectTimeout(DEFAULT_HTTP_CONNECT_TIMEOUT_SECONDS, TimeUnit.SECONDS)
137-
.addInterceptor(
138-
new ApiKeyAuthInterceptor(authUrlPath, clientId, clientSecret, new TokenProvider()))
139-
.build();
140-
return httpClient;
165+
return okHttpClientBuilder.build();
141166
}
142167

143168
private Gson buildGson() {
@@ -148,7 +173,7 @@ private Gson buildGson() {
148173

149174
public PluggyClient build() {
150175
if (clientId == null || clientSecret == null) {
151-
throw new IllegalArgumentException("Must set a clientId and secret.");
176+
throw new IllegalArgumentException("Must set a clientId and clientSecret.");
152177
}
153178

154179
if (baseUrl == null) {
@@ -159,6 +184,7 @@ public PluggyClient build() {
159184
}
160185

161186
OkHttpClient httpClient = buildOkHttpClient(baseUrl);
187+
162188
Retrofit retrofit = new Retrofit.Builder()
163189
.baseUrl(baseUrl)
164190
.validateEagerly(true)
@@ -199,7 +225,7 @@ public String authenticate() throws IOException {
199225
RequestBody body = RequestBody.create(jsonBody, mediaType);
200226

201227
Request request = new Request.Builder()
202-
.url(this.baseUrl + this.authUrlPath)
228+
.url(this.baseUrl + AUTH_URL_PATH)
203229
.post(body)
204230
.addHeader("content-type", "application/json")
205231
.addHeader("cache-control", "no-cache")
@@ -218,6 +244,7 @@ public String authenticate() throws IOException {
218244
assertNotNull(responseBody, "response.body()");
219245
authResponse = gson.fromJson(responseBody.string(), AuthResponse.class);
220246
}
247+
221248
return authResponse.getApiKey();
222249
}
223250

src/main/java/ai/pluggy/client/auth/ApiKeyAuthInterceptor.java

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -30,12 +30,16 @@ public class ApiKeyAuthInterceptor implements Interceptor {
3030

3131
private TokenProvider tokenProvider;
3232

33+
public ApiKeyAuthInterceptor(String authUrl, String clientId, String clientSecret) {
34+
this(authUrl, clientId, clientSecret, new TokenProvider());
35+
}
36+
3337
public ApiKeyAuthInterceptor(String authUrl, String clientId, String clientSecret,
3438
TokenProvider tokenProvider) {
3539
assertNotNull(clientId, "clientId");
36-
assertNotNull(clientId, "clientSecret");
37-
assertNotNull(clientId, "authUrl");
38-
assertNotNull(clientId, "tokenProvider");
40+
assertNotNull(clientSecret, "clientSecret");
41+
assertNotNull(authUrl, "authUrl");
42+
assertNotNull(tokenProvider, "tokenProvider");
3943
this.authUrl = authUrl;
4044
this.clientId = clientId;
4145
this.clientSecret = clientSecret;
@@ -91,10 +95,13 @@ private Request requestWithAuth(Request originalRequest, String apiKey) {
9195

9296
private Response proceedWithAuthRetry(@NotNull Chain chain, Request originalRequest)
9397
throws IOException {
98+
// attempt to proceed with original request
9499
Response response = chain.proceed(originalRequest);
100+
101+
// check auth errors
95102
boolean isResponseAuthError = response.code() != 403 && response.code() != 401;
96103
if (isResponseAuthError) {
97-
// no auth problems -> response OK
104+
// no auth problems -> response was OK
98105
return response;
99106
}
100107

src/main/java/ai/pluggy/client/request/DateFilters.java

Lines changed: 0 additions & 30 deletions
This file was deleted.
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
package ai.pluggy.client.request;
2+
3+
import static ai.pluggy.utils.Asserts.assertNotNull;
4+
import static ai.pluggy.utils.Asserts.assertValidDateString;
5+
6+
import java.time.format.DateTimeFormatter;
7+
import java.util.HashMap;
8+
9+
public class TransactionsSearchRequest extends HashMap<String, Object> {
10+
11+
public static final DateTimeFormatter DATE_PARAM_FORMAT = DateTimeFormatter.ISO_DATE;
12+
13+
/**
14+
* @param fromDate String - from date in 'YYYY-MM-DD' format
15+
* @return this instance, useful to continue adding params
16+
*/
17+
public TransactionsSearchRequest from(String fromDate) {
18+
validateDateString(fromDate, "from");
19+
put("from", fromDate);
20+
return this;
21+
}
22+
23+
/**
24+
* @param fromDate String - from date in 'YYYY-MM-DD' format
25+
* @return this instance, useful to continue adding params
26+
*/
27+
public TransactionsSearchRequest to(String fromDate) {
28+
validateDateString(fromDate, "to");
29+
put("to", fromDate);
30+
return this;
31+
}
32+
33+
/**
34+
* @param page Integer - page number to fetch, starting at page=1.
35+
* @return this instance, useful to continue adding params
36+
*/
37+
public TransactionsSearchRequest page(Integer page) {
38+
assertNotNull(page, "page");
39+
put("page", page);
40+
return this;
41+
}
42+
43+
/**
44+
* @param pageSize Integer - page size value, indicates max items to fetch per page.
45+
* @return this instance, useful to continue adding params
46+
*/
47+
public TransactionsSearchRequest pageSize(Integer pageSize) {
48+
assertNotNull(pageSize, "pageSize");
49+
put("pageSize", pageSize);
50+
return this;
51+
}
52+
53+
54+
private void validateDateString(String dateString, String name) {
55+
assertValidDateString(dateString, DATE_PARAM_FORMAT, name);
56+
}
57+
58+
public Integer getPage() {
59+
if (!containsKey("page")) {
60+
return null;
61+
}
62+
return (Integer) get("page");
63+
}
64+
65+
public Integer getPageSize() {
66+
if (!containsKey("pageSize")) {
67+
return null;
68+
}
69+
return (Integer) get("pageSize");
70+
}
71+
72+
public String getFrom() {
73+
if (!containsKey("from")) {
74+
return null;
75+
}
76+
return (String) get("from");
77+
}
78+
79+
public String getTo() {
80+
if (!containsKey("to")) {
81+
return null;
82+
}
83+
return (String) get("to");
84+
}
85+
}

src/main/java/ai/pluggy/utils/Asserts.java

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
package ai.pluggy.utils;
22

3+
import java.time.LocalDate;
4+
import java.time.format.DateTimeFormatter;
5+
import java.time.format.DateTimeParseException;
36
import okhttp3.HttpUrl;
47

58
public abstract class Asserts {
@@ -21,7 +24,7 @@ public static void assertNotNull(Object value, String name) {
2124
* Asserts that a value is a valid URL.
2225
*
2326
* @param value the value to check.
24-
* @param name the name of the parameter, used when creating the exception message.
27+
* @param name the name of the parameter, used when creating the exception message.
2528
* @throws IllegalArgumentException if the value is null or is not a valid URL.
2629
*/
2730
public static void assertValidUrl(String value, String name) {
@@ -30,4 +33,24 @@ public static void assertValidUrl(String value, String name) {
3033
}
3134
}
3235

36+
37+
/**
38+
* Asserts that a value is a valid URL.
39+
*
40+
* @param value the value to check.
41+
* @param name the name of the parameter, used when creating the exception message.
42+
* @throws IllegalArgumentException if the value is null or is not a valid URL.
43+
*/
44+
public static void assertValidDateString(String value, DateTimeFormatter dateFormat,
45+
String name) {
46+
String invalidValueErrorMsg = String.format(
47+
"Invalid date string format '%s' for field '%s', expected format: '%s'!",
48+
value, name, dateFormat);
49+
50+
try {
51+
LocalDate.parse(value, dateFormat);
52+
} catch (DateTimeParseException e) {
53+
throw new IllegalArgumentException(invalidValueErrorMsg);
54+
}
55+
}
3356
}

0 commit comments

Comments
 (0)