Skip to content

Commit 406fc4e

Browse files
committed
Exception handling improvement
1 parent cb3b7c2 commit 406fc4e

File tree

5 files changed

+287
-15
lines changed

5 files changed

+287
-15
lines changed
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
package io.getstream.client.exception;
2+
3+
/**
4+
* In case you have exceed your allowed calls per time interval (i.e. 1min, 15min or 1hr)
5+
*/
6+
public class RateLimitExceededException extends StreamClientException {
7+
8+
public RateLimitExceededException() {
9+
super();
10+
}
11+
12+
public RateLimitExceededException(String message) {
13+
super(message);
14+
}
15+
16+
public RateLimitExceededException(String message, Throwable cause) {
17+
super(message, cause);
18+
}
19+
20+
public RateLimitExceededException(Throwable cause) {
21+
super(cause);
22+
}
23+
24+
protected RateLimitExceededException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
25+
super(message, cause, enableSuppression, writableStackTrace);
26+
}
27+
}

stream-repo-apache/src/main/java/io/getstream/client/apache/repo/handlers/StreamExceptionHandler.java

Lines changed: 26 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,13 @@
22

33
import com.fasterxml.jackson.databind.ObjectMapper;
44
import io.getstream.client.exception.AuthenticationFailedException;
5-
import io.getstream.client.exception.InvalidOrMissingInputException;
6-
import io.getstream.client.exception.StreamClientException;
75
import io.getstream.client.exception.InternalServerException;
6+
import io.getstream.client.exception.InvalidOrMissingInputException;
7+
import io.getstream.client.exception.RateLimitExceededException;
88
import io.getstream.client.exception.ResourceNotFoundException;
9+
import io.getstream.client.exception.StreamClientException;
910
import io.getstream.client.model.beans.StreamErrorResponse;
1011
import org.apache.http.client.methods.CloseableHttpResponse;
11-
import org.apache.http.util.EntityUtils;
1212

1313
import java.io.IOException;
1414

@@ -38,7 +38,15 @@ public StreamExceptionHandler(ObjectMapper objectMapper) {
3838
* @throws StreamClientException in case of functional or server-side exception
3939
*/
4040
public void handleResponseCode(final CloseableHttpResponse response) throws IOException, StreamClientException {
41-
switch (response.getStatusLine().getStatusCode()) {
41+
int statusCode = response.getStatusLine().getStatusCode();
42+
if (statusCode < 200 || statusCode > 299) {
43+
parseException(response);
44+
}
45+
}
46+
47+
private void parseException(final CloseableHttpResponse response) throws IOException, StreamClientException {
48+
int statusCode = response.getStatusLine().getStatusCode();
49+
switch (statusCode) {
4250
case 400:
4351
throw buildException(new InvalidOrMissingInputException(), response);
4452
case 401:
@@ -47,19 +55,27 @@ public void handleResponseCode(final CloseableHttpResponse response) throws IOEx
4755
throw buildException(new AuthenticationFailedException(), response);
4856
case 404:
4957
throw buildException(new ResourceNotFoundException(), response);
58+
case 429:
59+
throw buildException(new RateLimitExceededException(), response);
5060
case 500:
5161
throw buildException(new InternalServerException(), response);
62+
default:
63+
StreamClientException e = new InternalServerException();
64+
e.setCode(statusCode);
65+
e.setHttpStatusCode(statusCode);
66+
throw e;
5267
}
5368
}
5469

5570
private StreamClientException buildException(StreamClientException exception,
5671
CloseableHttpResponse response) throws IOException {
57-
String responseMessage = EntityUtils.toString(response.getEntity());
58-
StreamErrorResponse error = objectMapper.readValue(responseMessage, StreamErrorResponse.class);
59-
exception.setCode(error.getCode());
60-
exception.setHttpStatusCode(error.getStatusCode());
61-
exception.setDetail(error.getDetail());
62-
exception.setExceptionField(error.getException());
72+
StreamErrorResponse error = objectMapper.readValue(response.getEntity().getContent(), StreamErrorResponse.class);
73+
if (null != error) {
74+
exception.setCode(error.getCode());
75+
exception.setHttpStatusCode(response.getStatusLine().getStatusCode());
76+
exception.setDetail(error.getDetail());
77+
exception.setExceptionField(error.getException());
78+
}
6379
return exception;
6480
}
6581
}
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
package io.getstream.client.apache.repo.handlers;
2+
3+
import com.fasterxml.jackson.databind.ObjectMapper;
4+
import io.getstream.client.exception.AuthenticationFailedException;
5+
import io.getstream.client.exception.InternalServerException;
6+
import io.getstream.client.exception.InvalidOrMissingInputException;
7+
import io.getstream.client.exception.RateLimitExceededException;
8+
import io.getstream.client.exception.ResourceNotFoundException;
9+
import io.getstream.client.exception.StreamClientException;
10+
import org.apache.http.ProtocolVersion;
11+
import org.apache.http.client.methods.CloseableHttpResponse;
12+
import org.apache.http.entity.StringEntity;
13+
import org.apache.http.message.BasicStatusLine;
14+
import org.junit.Test;
15+
16+
import java.io.IOException;
17+
18+
import static org.mockito.Mockito.mock;
19+
import static org.mockito.Mockito.when;
20+
21+
public class StreamExceptionHandlerTest {
22+
23+
private final StreamExceptionHandler exceptionHandler;
24+
25+
public StreamExceptionHandlerTest() {
26+
exceptionHandler = new StreamExceptionHandler(mock(ObjectMapper.class));
27+
}
28+
29+
@Test(expected = RateLimitExceededException.class)
30+
public void testError429() throws IOException, StreamClientException {
31+
CloseableHttpResponse response = mock(CloseableHttpResponse.class);
32+
when(response.getStatusLine()).thenReturn(new BasicStatusLine(new ProtocolVersion("HTTP", 1, 1), 429, ""));
33+
when(response.getEntity()).thenReturn(new StringEntity(""));
34+
exceptionHandler.handleResponseCode(response);
35+
}
36+
37+
@Test(expected = InvalidOrMissingInputException.class)
38+
public void testError400() throws IOException, StreamClientException {
39+
CloseableHttpResponse response = mock(CloseableHttpResponse.class);
40+
when(response.getStatusLine()).thenReturn(new BasicStatusLine(new ProtocolVersion("HTTP", 1, 1), 400, ""));
41+
when(response.getEntity()).thenReturn(new StringEntity(""));
42+
exceptionHandler.handleResponseCode(response);
43+
}
44+
45+
@Test(expected = AuthenticationFailedException.class)
46+
public void testError401() throws IOException, StreamClientException {
47+
CloseableHttpResponse response = mock(CloseableHttpResponse.class);
48+
when(response.getStatusLine()).thenReturn(new BasicStatusLine(new ProtocolVersion("HTTP", 1, 1), 401, ""));
49+
when(response.getEntity()).thenReturn(new StringEntity(""));
50+
exceptionHandler.handleResponseCode(response);
51+
}
52+
53+
@Test(expected = AuthenticationFailedException.class)
54+
public void testError403() throws IOException, StreamClientException {
55+
CloseableHttpResponse response = mock(CloseableHttpResponse.class);
56+
when(response.getStatusLine()).thenReturn(new BasicStatusLine(new ProtocolVersion("HTTP", 1, 1), 403, ""));
57+
when(response.getEntity()).thenReturn(new StringEntity(""));
58+
exceptionHandler.handleResponseCode(response);
59+
}
60+
61+
@Test(expected = ResourceNotFoundException.class)
62+
public void testError404() throws IOException, StreamClientException {
63+
CloseableHttpResponse response = mock(CloseableHttpResponse.class);
64+
when(response.getStatusLine()).thenReturn(new BasicStatusLine(new ProtocolVersion("HTTP", 1, 1), 404, ""));
65+
when(response.getEntity()).thenReturn(new StringEntity(""));
66+
exceptionHandler.handleResponseCode(response);
67+
}
68+
69+
@Test(expected = InternalServerException.class)
70+
public void testError500() throws IOException, StreamClientException {
71+
CloseableHttpResponse response = mock(CloseableHttpResponse.class);
72+
when(response.getStatusLine()).thenReturn(new BasicStatusLine(new ProtocolVersion("HTTP", 1, 1), 500, ""));
73+
when(response.getEntity()).thenReturn(new StringEntity(""));
74+
exceptionHandler.handleResponseCode(response);
75+
}
76+
77+
@Test(expected = StreamClientException.class)
78+
public void testError503() throws IOException, StreamClientException {
79+
CloseableHttpResponse response = mock(CloseableHttpResponse.class);
80+
when(response.getStatusLine()).thenReturn(new BasicStatusLine(new ProtocolVersion("HTTP", 1, 1), 503, ""));
81+
when(response.getEntity()).thenReturn(new StringEntity(""));
82+
exceptionHandler.handleResponseCode(response);
83+
}
84+
85+
@Test
86+
public void testOk() throws IOException, StreamClientException {
87+
CloseableHttpResponse response = mock(CloseableHttpResponse.class);
88+
when(response.getStatusLine()).thenReturn(new BasicStatusLine(new ProtocolVersion("HTTP", 1, 1), 201, ""));
89+
when(response.getEntity()).thenReturn(new StringEntity(""));
90+
exceptionHandler.handleResponseCode(response);
91+
}
92+
}

stream-repo-okhttp/src/main/java/io/getstream/client/okhttp/repo/handlers/StreamExceptionHandler.java

Lines changed: 23 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import io.getstream.client.exception.AuthenticationFailedException;
66
import io.getstream.client.exception.InternalServerException;
77
import io.getstream.client.exception.InvalidOrMissingInputException;
8+
import io.getstream.client.exception.RateLimitExceededException;
89
import io.getstream.client.exception.ResourceNotFoundException;
910
import io.getstream.client.exception.StreamClientException;
1011
import io.getstream.client.model.beans.StreamErrorResponse;
@@ -37,7 +38,15 @@ public StreamExceptionHandler(ObjectMapper objectMapper) {
3738
* @throws StreamClientException in case of functional or server-side exception
3839
*/
3940
public void handleResponseCode(final Response response) throws IOException, StreamClientException {
40-
switch (response.code()) {
41+
int statusCode = response.code();
42+
if (statusCode < 200 || statusCode > 299) {
43+
parseException(response);
44+
}
45+
}
46+
47+
private void parseException(Response response) throws IOException, StreamClientException {
48+
int statusCode = response.code();
49+
switch (statusCode) {
4150
case 400:
4251
throw buildException(new InvalidOrMissingInputException(), response);
4352
case 401:
@@ -46,18 +55,27 @@ public void handleResponseCode(final Response response) throws IOException, Stre
4655
throw buildException(new AuthenticationFailedException(), response);
4756
case 404:
4857
throw buildException(new ResourceNotFoundException(), response);
58+
case 429:
59+
throw buildException(new RateLimitExceededException(), response);
4960
case 500:
5061
throw buildException(new InternalServerException(), response);
62+
default:
63+
StreamClientException e = new InternalServerException();
64+
e.setCode(statusCode);
65+
e.setHttpStatusCode(statusCode);
66+
throw e;
5167
}
5268
}
5369

5470
private StreamClientException buildException(StreamClientException exception,
5571
Response response) throws IOException {
5672
StreamErrorResponse error = objectMapper.readValue(response.body().byteStream(), StreamErrorResponse.class);
57-
exception.setCode(error.getCode());
58-
exception.setHttpStatusCode(error.getStatusCode());
59-
exception.setDetail(error.getDetail());
60-
exception.setExceptionField(error.getException());
73+
if (null != error) {
74+
exception.setCode(error.getCode());
75+
exception.setHttpStatusCode(error.getStatusCode());
76+
exception.setDetail(error.getDetail());
77+
exception.setExceptionField(error.getException());
78+
}
6179
return exception;
6280
}
6381
}
Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
package io.getstream.client.okhttp.repo.handlers;
2+
3+
import com.fasterxml.jackson.databind.ObjectMapper;
4+
import com.squareup.okhttp.MediaType;
5+
import com.squareup.okhttp.Protocol;
6+
import com.squareup.okhttp.Request;
7+
import com.squareup.okhttp.Response;
8+
import com.squareup.okhttp.internal.http.RealResponseBody;
9+
import io.getstream.client.exception.AuthenticationFailedException;
10+
import io.getstream.client.exception.InternalServerException;
11+
import io.getstream.client.exception.InvalidOrMissingInputException;
12+
import io.getstream.client.exception.RateLimitExceededException;
13+
import io.getstream.client.exception.ResourceNotFoundException;
14+
import io.getstream.client.exception.StreamClientException;
15+
import org.junit.Test;
16+
17+
import java.io.IOException;
18+
19+
import static org.mockito.Mockito.mock;
20+
21+
public class StreamExceptionHandlerTest {
22+
23+
public static final String APPLICATION_JSON = "application/json";
24+
public static final String FAKE_URL = "http://www.example.com";
25+
26+
private final StreamExceptionHandler exceptionHandler;
27+
28+
public StreamExceptionHandlerTest() {
29+
exceptionHandler = new StreamExceptionHandler(mock(ObjectMapper.class));
30+
}
31+
32+
@Test(expected = RateLimitExceededException.class)
33+
public void testError429() throws IOException, StreamClientException {
34+
exceptionHandler.handleResponseCode(new Response.Builder()
35+
.code(429)
36+
.message("")
37+
.body(RealResponseBody.create(MediaType.parse(APPLICATION_JSON), ""))
38+
.request(new Request.Builder().url(FAKE_URL).build())
39+
.protocol(Protocol.HTTP_1_1)
40+
.build());
41+
}
42+
43+
@Test(expected = InvalidOrMissingInputException.class)
44+
public void testError400() throws IOException, StreamClientException {
45+
exceptionHandler.handleResponseCode(new Response.Builder()
46+
.code(400)
47+
.message("")
48+
.body(RealResponseBody.create(MediaType.parse(APPLICATION_JSON), ""))
49+
.request(new Request.Builder().url(FAKE_URL).build())
50+
.protocol(Protocol.HTTP_1_1)
51+
.build());
52+
}
53+
54+
@Test(expected = AuthenticationFailedException.class)
55+
public void testError401() throws IOException, StreamClientException {
56+
exceptionHandler.handleResponseCode(new Response.Builder()
57+
.code(401)
58+
.message("")
59+
.body(RealResponseBody.create(MediaType.parse(APPLICATION_JSON), ""))
60+
.request(new Request.Builder().url(FAKE_URL).build())
61+
.protocol(Protocol.HTTP_1_1)
62+
.build());
63+
}
64+
65+
@Test(expected = AuthenticationFailedException.class)
66+
public void testError403() throws IOException, StreamClientException {
67+
exceptionHandler.handleResponseCode(new Response.Builder()
68+
.code(403)
69+
.message("")
70+
.body(RealResponseBody.create(MediaType.parse(APPLICATION_JSON), ""))
71+
.request(new Request.Builder().url(FAKE_URL).build())
72+
.protocol(Protocol.HTTP_1_1)
73+
.build());
74+
}
75+
76+
@Test(expected = ResourceNotFoundException.class)
77+
public void testError404() throws IOException, StreamClientException {
78+
exceptionHandler.handleResponseCode(new Response.Builder()
79+
.code(404)
80+
.message("")
81+
.body(RealResponseBody.create(MediaType.parse(APPLICATION_JSON), ""))
82+
.request(new Request.Builder().url(FAKE_URL).build())
83+
.protocol(Protocol.HTTP_1_1)
84+
.build());
85+
}
86+
87+
@Test(expected = InternalServerException.class)
88+
public void testError500() throws IOException, StreamClientException {
89+
exceptionHandler.handleResponseCode(new Response.Builder()
90+
.code(500)
91+
.message("")
92+
.body(RealResponseBody.create(MediaType.parse(APPLICATION_JSON), ""))
93+
.request(new Request.Builder().url(FAKE_URL).build())
94+
.protocol(Protocol.HTTP_1_1)
95+
.build());
96+
}
97+
98+
@Test(expected = StreamClientException.class)
99+
public void testError503() throws IOException, StreamClientException {
100+
exceptionHandler.handleResponseCode(new Response.Builder()
101+
.code(503)
102+
.message("")
103+
.body(RealResponseBody.create(MediaType.parse(APPLICATION_JSON), ""))
104+
.request(new Request.Builder().url(FAKE_URL).build())
105+
.protocol(Protocol.HTTP_1_1)
106+
.build());
107+
}
108+
109+
@Test
110+
public void testOk() throws IOException, StreamClientException {
111+
exceptionHandler.handleResponseCode(new Response.Builder()
112+
.code(201)
113+
.message("")
114+
.body(RealResponseBody.create(MediaType.parse(APPLICATION_JSON), ""))
115+
.request(new Request.Builder().url(FAKE_URL).build())
116+
.protocol(Protocol.HTTP_1_1)
117+
.build());
118+
}
119+
}

0 commit comments

Comments
 (0)