Skip to content

Commit c15a250

Browse files
committed
Modified RateLimitException
1 parent 8f61fa5 commit c15a250

6 files changed

Lines changed: 254 additions & 15 deletions

File tree

src/main/java/com/auth0/exception/RateLimitException.java

Lines changed: 88 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -17,27 +17,24 @@ public class RateLimitException extends APIException {
1717
private final long limit;
1818
private final long remaining;
1919
private final long reset;
20-
private final TokenQuotaBucket clientQuotaLimit;
21-
private final TokenQuotaBucket organizationQuotaLimit;
20+
21+
private TokenQuotaBucket clientQuotaLimit;
22+
private TokenQuotaBucket organizationQuotaLimit;
2223

2324
private static final int STATUS_CODE_TOO_MANY_REQUEST = 429;
2425

25-
public RateLimitException(long limit, long remaining, long reset, TokenQuotaBucket clientQuotaLimit, TokenQuotaBucket organizationQuotaLimit, Map<String, Object> values) {
26+
public RateLimitException(long limit, long remaining, long reset, Map<String, Object> values) {
2627
super(values, STATUS_CODE_TOO_MANY_REQUEST);
2728
this.limit = limit;
2829
this.remaining = remaining;
2930
this.reset = reset;
30-
this.clientQuotaLimit = clientQuotaLimit;
31-
this.organizationQuotaLimit = organizationQuotaLimit;
3231
}
3332

34-
public RateLimitException(long limit, long remaining, long reset, TokenQuotaBucket clientQuotaLimit, TokenQuotaBucket organizationQuotaLimit) {
33+
public RateLimitException(long limit, long remaining, long reset) {
3534
super("Rate limit reached", STATUS_CODE_TOO_MANY_REQUEST, null);
3635
this.limit = limit;
3736
this.remaining = remaining;
3837
this.reset = reset;
39-
this.clientQuotaLimit = clientQuotaLimit;
40-
this.organizationQuotaLimit = organizationQuotaLimit;
4138
}
4239

4340
/**
@@ -80,4 +77,87 @@ public TokenQuotaBucket getOrganizationQuotaLimit() {
8077
return organizationQuotaLimit;
8178
}
8279

80+
/**
81+
* Builder class for creating instances of RateLimitException.
82+
*/
83+
public static class Builder {
84+
private long limit;
85+
private long remaining;
86+
private long reset;
87+
private TokenQuotaBucket clientQuotaLimit;
88+
private TokenQuotaBucket organizationQuotaLimit;
89+
private Map<String, Object> values;
90+
91+
/**
92+
* Constructor for the Builder.
93+
* @param limit The maximum number of requests available in the current time frame.
94+
* @param remaining The number of remaining requests in the current time frame.
95+
* @param reset The UNIX timestamp of the expected time when the rate limit will reset.
96+
*/
97+
public Builder(long limit, long remaining, long reset) {
98+
this.limit = limit;
99+
this.remaining = remaining;
100+
this.reset = reset;
101+
}
102+
103+
/**
104+
* Constructor for the Builder.
105+
* @param limit The maximum number of requests available in the current time frame.
106+
* @param remaining The number of remaining requests in the current time frame.
107+
* @param reset The UNIX timestamp of the expected time when the rate limit will reset.
108+
* @param values The values map.
109+
*/
110+
public Builder(long limit, long remaining, long reset, Map<String, Object> values) {
111+
this.limit = limit;
112+
this.remaining = remaining;
113+
this.reset = reset;
114+
this.values = values;
115+
}
116+
117+
/**
118+
* Sets the client quota limit.
119+
* @param clientQuotaLimit The client quota limit.
120+
* @return The Builder instance.
121+
*/
122+
public Builder clientQuotaLimit(TokenQuotaBucket clientQuotaLimit) {
123+
this.clientQuotaLimit = clientQuotaLimit;
124+
return this;
125+
}
126+
127+
/**
128+
* Sets the organization quota limit.
129+
* @param organizationQuotaLimit The organization quota limit.
130+
* @return The Builder instance.
131+
*/
132+
public Builder organizationQuotaLimit(TokenQuotaBucket organizationQuotaLimit) {
133+
this.organizationQuotaLimit = organizationQuotaLimit;
134+
return this;
135+
}
136+
137+
/**
138+
* Sets the values map.
139+
* @param values The values map.
140+
* @return The Builder instance.
141+
*/
142+
public Builder values(Map<String, Object> values) {
143+
this.values = values;
144+
return this;
145+
}
146+
147+
public RateLimitException build() {
148+
RateLimitException exception = (this.values != null)
149+
? new RateLimitException(this.limit, this.remaining, this.reset, this.values)
150+
: new RateLimitException(this.limit, this.remaining, this.reset);
151+
152+
if(this.clientQuotaLimit != null) {
153+
exception.clientQuotaLimit = this.clientQuotaLimit;
154+
}
155+
if(this.organizationQuotaLimit != null) {
156+
exception.organizationQuotaLimit = this.organizationQuotaLimit;
157+
}
158+
159+
return exception;
160+
}
161+
}
162+
83163
}

src/main/java/com/auth0/net/BaseRequest.java

Lines changed: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -223,7 +223,7 @@ private RateLimitException createRateLimitException(Auth0HttpResponse response)
223223
TokenQuotaBucket clientQuotaLimit = null;
224224
TokenQuotaBucket organizationQuotaLimit = null;
225225

226-
if (response.getHeaders().containsKey("x-rate-limit-remaining") && response.getHeaders().get("x-rate-limit-remaining").equals("0")) {
226+
if (remaining == 0) {
227227
clientQuotaLimit = HttpResponseHeadersUtils.getClientQuotaLimit(response.getHeaders());
228228
organizationQuotaLimit = HttpResponseHeadersUtils.getOrganizationQuotaLimit(response.getHeaders());
229229
}
@@ -232,9 +232,30 @@ private RateLimitException createRateLimitException(Auth0HttpResponse response)
232232
MapType mapType = mapper.getTypeFactory().constructMapType(HashMap.class, String.class, Object.class);
233233
try {
234234
Map<String, Object> values = mapper.readValue(payload, mapType);
235-
return new RateLimitException(limit, remaining, reset, clientQuotaLimit, organizationQuotaLimit, values);
235+
236+
RateLimitException.Builder builder = new RateLimitException.Builder(limit, remaining, reset, values);
237+
238+
if (clientQuotaLimit != null) {
239+
builder.clientQuotaLimit(clientQuotaLimit);
240+
}
241+
242+
if (organizationQuotaLimit != null) {
243+
builder.organizationQuotaLimit(organizationQuotaLimit);
244+
}
245+
246+
return builder.build();
236247
} catch (IOException e) {
237-
return new RateLimitException(limit, remaining, reset, clientQuotaLimit, organizationQuotaLimit);
248+
RateLimitException.Builder builder = new RateLimitException.Builder(limit, remaining, reset);
249+
250+
if (clientQuotaLimit != null) {
251+
builder.clientQuotaLimit(clientQuotaLimit);
252+
}
253+
254+
if (organizationQuotaLimit != null) {
255+
builder.organizationQuotaLimit(organizationQuotaLimit);
256+
}
257+
258+
return builder.build();
238259
}
239260
}
240261

src/main/java/com/auth0/utils/HttpResponseHeadersUtils.java

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,9 @@ public class HttpResponseHeadersUtils {
1414
* @return a TokenQuotaBucket containing client rate limits, or null if not present.
1515
*/
1616
public static TokenQuotaBucket getClientQuotaLimit(Map<String, String> headers) {
17-
String quotaHeader = headers.get("X-Quota-Client-Limit");
17+
String quotaHeader = headers.get("auth0-Quota-Client-Limit");
1818
if( quotaHeader == null) {
19-
quotaHeader = headers.get("x-quota-client-limit");
19+
quotaHeader = headers.get("auth0-quota-client-limit");
2020
}
2121
if (quotaHeader != null) {
2222
return parseQuota(quotaHeader);
@@ -31,9 +31,9 @@ public static TokenQuotaBucket getClientQuotaLimit(Map<String, String> headers)
3131
* @return a TokenQuotaBucket containing organization rate limits, or null if not present.
3232
*/
3333
public static TokenQuotaBucket getOrganizationQuotaLimit(Map<String, String> headers) {
34-
String quotaHeader = headers.get("X-Quota-Organization-Limit");
34+
String quotaHeader = headers.get("auth0-Quota-Organization-Limit");
3535
if( quotaHeader == null) {
36-
quotaHeader = headers.get("x-quota-organization-limit");
36+
quotaHeader = headers.get("auth0-quota-organization-limit");
3737
}
3838
if (quotaHeader != null) {
3939
return parseQuota(quotaHeader);

src/test/java/com/auth0/client/MockServer.java

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package com.auth0.client;
22

3+
import com.auth0.net.TokenQuotaBucket;
34
import com.fasterxml.jackson.databind.ObjectMapper;
45
import com.fasterxml.jackson.databind.type.CollectionType;
56
import com.fasterxml.jackson.databind.type.MapType;
@@ -230,6 +231,36 @@ public void rateLimitReachedResponse(long limit, long remaining, long reset, Str
230231
server.enqueue(response);
231232
}
232233

234+
public void rateLimitReachedResponse(long limit, long remaining, long reset,
235+
String clientQuotaLimit, String organizationQuotaLimit) throws IOException {
236+
rateLimitReachedResponse(limit, remaining, reset, null, clientQuotaLimit, organizationQuotaLimit);
237+
}
238+
239+
public void rateLimitReachedResponse(long limit, long remaining, long reset, String path, String clientQuotaLimit, String organizationQuotaLimit) throws IOException {
240+
MockResponse response = new MockResponse().setResponseCode(429);
241+
if (limit != -1) {
242+
response.addHeader("x-ratelimit-limit", String.valueOf(limit));
243+
}
244+
if (remaining != -1) {
245+
response.addHeader("x-ratelimit-remaining", String.valueOf(remaining));
246+
}
247+
if (reset != -1) {
248+
response.addHeader("x-ratelimit-reset", String.valueOf(reset));
249+
}
250+
if (clientQuotaLimit != null) {
251+
response.addHeader("auth0-quota-client-limit", clientQuotaLimit);
252+
}
253+
if (organizationQuotaLimit != null) {
254+
response.addHeader("auth0-quota-organization-limit", organizationQuotaLimit);
255+
}
256+
if (path != null) {
257+
response
258+
.addHeader("Content-Type", "application/json")
259+
.setBody(readTextFile(path));
260+
}
261+
server.enqueue(response);
262+
}
263+
233264
public void textResponse(String path, int statusCode) throws IOException {
234265
MockResponse response = new MockResponse()
235266
.setResponseCode(statusCode)

src/test/java/com/auth0/net/BaseRequestTest.java

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
import com.auth0.exception.Auth0Exception;
88
import com.auth0.exception.RateLimitException;
99
import com.auth0.json.auth.TokenHolder;
10+
import com.auth0.json.auth.TokenQuotaLimit;
1011
import com.auth0.net.client.*;
1112
import com.fasterxml.jackson.core.JsonParseException;
1213
import com.fasterxml.jackson.core.JsonProcessingException;
@@ -197,6 +198,8 @@ public void shouldAddHeaders() throws Exception {
197198
request.addParameter("non_empty", "body");
198199
request.addHeader("Extra-Info", "this is a test");
199200
request.addHeader("Authorization", "Bearer my_access_token");
201+
request.addHeader("X-Client-Quota", getTokenQuotaString());
202+
request.addHeader("X-Organization-Quota", getTokenQuotaString());
200203

201204
server.jsonResponse(AUTH_TOKENS, 200);
202205
request.execute().getBody();
@@ -425,6 +428,33 @@ public void shouldParseRateLimitException() throws Exception {
425428
assertThat(rateLimitException.getReset(), Matchers.is(5L));
426429
}
427430

431+
@Test
432+
public void shouldParseRateLimitExceptionWithZeroRemaining() throws Exception {
433+
BaseRequest<List> request = new BaseRequest<>(client, tokenProvider, server.getBaseUrl(), HttpMethod.GET, listType);
434+
server.rateLimitReachedResponse(100, 0, 5, RATE_LIMIT_ERROR, getTokenQuotaString(), getTokenQuotaString());
435+
Exception exception = null;
436+
try {
437+
request.execute().getBody();
438+
server.takeRequest();
439+
} catch (Exception e) {
440+
exception = e;
441+
}
442+
assertThat(exception, Matchers.is(notNullValue()));
443+
assertThat(exception, Matchers.is(Matchers.instanceOf(RateLimitException.class)));
444+
assertThat(exception.getCause(), Matchers.is(nullValue()));
445+
assertThat(exception.getMessage(), Matchers.is("Request failed with status code 429: Global limit has been reached"));
446+
RateLimitException rateLimitException = (RateLimitException) exception;
447+
assertThat(rateLimitException.getDescription(), Matchers.is("Global limit has been reached"));
448+
assertThat(rateLimitException.getError(), Matchers.is("too_many_requests"));
449+
assertThat(rateLimitException.getValue("non_existing_key"), Matchers.is(nullValue()));
450+
assertThat(rateLimitException.getStatusCode(), Matchers.is(429));
451+
assertThat(rateLimitException.getLimit(), Matchers.is(100L));
452+
assertThat(rateLimitException.getRemaining(), Matchers.is(0L));
453+
assertThat(rateLimitException.getReset(), Matchers.is(5L));
454+
assertThat(rateLimitException.getClientQuotaLimit().getPerDay().getQuota(), Matchers.is(getTokenQuota().getPerDay().getQuota()));
455+
assertThat(rateLimitException.getOrganizationQuotaLimit().getPerDay().getQuota(), Matchers.is(getTokenQuota().getPerDay().getQuota()));
456+
}
457+
428458
@Test
429459
public void shouldDefaultRateLimitsHeadersWhenMissing() throws Exception {
430460
BaseRequest<List> request = new BaseRequest<>(client, tokenProvider, server.getBaseUrl(), HttpMethod.GET, listType);
@@ -448,5 +478,31 @@ public void shouldDefaultRateLimitsHeadersWhenMissing() throws Exception {
448478
assertThat(rateLimitException.getLimit(), Matchers.is(-1L));
449479
assertThat(rateLimitException.getRemaining(), Matchers.is(-1L));
450480
assertThat(rateLimitException.getReset(), Matchers.is(-1L));
481+
assertThat(rateLimitException.getClientQuotaLimit(), Matchers.is(nullValue()));
482+
assertThat(rateLimitException.getOrganizationQuotaLimit(), Matchers.is(nullValue()));
483+
}
484+
485+
private TokenQuotaBucket getTokenQuota() {
486+
TokenQuotaLimit perHourLimit = new TokenQuotaLimit(100, 80, 3600);
487+
TokenQuotaLimit perDayLimit = new TokenQuotaLimit(100, 90, 86400);
488+
return new TokenQuotaBucket(perHourLimit, perDayLimit);
489+
}
490+
491+
public String getTokenQuotaString() {
492+
TokenQuotaLimit perHourLimit = new TokenQuotaLimit(100, 80, 3600);
493+
TokenQuotaLimit perDayLimit = new TokenQuotaLimit(100, 90, 86400);
494+
495+
StringBuilder builder = new StringBuilder();
496+
497+
builder.append(String.format("b=per_hour;q=%d;r=%d;t=%d",
498+
perHourLimit.getQuota(), perHourLimit.getRemaining(), perHourLimit.getTime()));
499+
500+
if (builder.length() > 0) {
501+
builder.append(",");
502+
}
503+
builder.append(String.format("b=per_day;q=%d;r=%d;t=%d",
504+
perDayLimit.getQuota(), perDayLimit.getRemaining(), perDayLimit.getTime()));
505+
506+
return builder.toString();
451507
}
452508
}

src/test/java/com/auth0/net/MultipartRequestTest.java

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
import com.auth0.exception.Auth0Exception;
77
import com.auth0.exception.RateLimitException;
88
import com.auth0.json.auth.TokenHolder;
9+
import com.auth0.json.auth.TokenQuotaLimit;
910
import com.auth0.net.client.Auth0HttpClient;
1011
import com.auth0.net.client.Auth0MultipartRequestBody;
1112
import com.auth0.net.client.DefaultHttpClient;
@@ -18,6 +19,7 @@
1819
import com.fasterxml.jackson.databind.JsonMappingException;
1920
import com.fasterxml.jackson.databind.ObjectMapper;
2021
import okhttp3.mockwebserver.RecordedRequest;
22+
import org.hamcrest.Matchers;
2123
import org.junit.jupiter.api.AfterEach;
2224
import org.junit.jupiter.api.BeforeEach;
2325
import org.junit.jupiter.api.Test;
@@ -360,6 +362,32 @@ public void shouldParseRateLimitsHeaders() throws Exception {
360362
assertThat(rateLimitException.getReset(), is(5L));
361363
}
362364

365+
@Test
366+
public void shouldParseRateLimitsWithAllHeaders() throws Exception {
367+
MultipartRequest<List> request = new MultipartRequest<>(client, tokenProvider, server.getBaseUrl(), HttpMethod.POST, listType);
368+
request.addPart("non_empty", "body");
369+
server.rateLimitReachedResponse(100, 10, 5, getTokenQuotaString(), getTokenQuotaString());
370+
Exception exception = null;
371+
try {
372+
request.execute().getBody();
373+
server.takeRequest();
374+
} catch (Exception e) {
375+
exception = e;
376+
}
377+
assertThat(exception, is(notNullValue()));
378+
assertThat(exception, is(instanceOf(RateLimitException.class)));
379+
assertThat(exception.getCause(), is(nullValue()));
380+
assertThat(exception.getMessage(), is("Request failed with status code 429: Rate limit reached"));
381+
RateLimitException rateLimitException = (RateLimitException) exception;
382+
assertThat(rateLimitException.getDescription(), is("Rate limit reached"));
383+
assertThat(rateLimitException.getError(), is(nullValue()));
384+
assertThat(rateLimitException.getValue("non_existing_key"), is(nullValue()));
385+
assertThat(rateLimitException.getStatusCode(), is(429));
386+
assertThat(rateLimitException.getLimit(), is(100L));
387+
assertThat(rateLimitException.getRemaining(), is(10L));
388+
assertThat(rateLimitException.getReset(), is(5L));
389+
}
390+
363391
@Test
364392
public void shouldDefaultRateLimitsHeadersWhenMissing() throws Exception {
365393
MultipartRequest<List> request = new MultipartRequest<>(client, tokenProvider, server.getBaseUrl(), HttpMethod.POST, listType);
@@ -384,6 +412,29 @@ public void shouldDefaultRateLimitsHeadersWhenMissing() throws Exception {
384412
assertThat(rateLimitException.getLimit(), is(-1L));
385413
assertThat(rateLimitException.getRemaining(), is(-1L));
386414
assertThat(rateLimitException.getReset(), is(-1L));
415+
assertThat(rateLimitException.getClientQuotaLimit(), Matchers.is(nullValue()));
416+
assertThat(rateLimitException.getOrganizationQuotaLimit(), Matchers.is(nullValue()));
387417
}
388418

419+
public String getTokenQuotaString() {
420+
TokenQuotaLimit perHourLimit = new TokenQuotaLimit(100, 80, 3600);
421+
TokenQuotaLimit perDayLimit = new TokenQuotaLimit(100, 90, 86400);
422+
423+
StringBuilder builder = new StringBuilder();
424+
425+
if (perHourLimit != null) {
426+
builder.append(String.format("b=per_hour;q=%d;r=%d;t=%d",
427+
perHourLimit.getQuota(), perHourLimit.getRemaining(), perHourLimit.getTime()));
428+
}
429+
430+
if (perDayLimit != null) {
431+
if (builder.length() > 0) {
432+
builder.append(",");
433+
}
434+
builder.append(String.format("b=per_day;q=%d;r=%d;t=%d",
435+
perDayLimit.getQuota(), perDayLimit.getRemaining(), perDayLimit.getTime()));
436+
}
437+
438+
return builder.toString();
439+
}
389440
}

0 commit comments

Comments
 (0)