Skip to content

Commit d436ca1

Browse files
jerzykrlkvelo
authored andcommitted
Add fine-grained HTTP error exceptions (OpenFeign#825)
1 parent e772985 commit d436ca1

3 files changed

Lines changed: 234 additions & 1 deletion

File tree

core/src/main/java/feign/FeignException.java

Lines changed: 136 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,46 @@ public static FeignException errorStatus(String methodKey, Response response) {
7575
} catch (IOException ignored) { // NOPMD
7676
}
7777

78-
return new FeignException(response.status(), message, body);
78+
return errorStatus(response.status(), message, body);
79+
}
80+
81+
private static FeignException errorStatus(int status, String message, byte[] body) {
82+
switch (status) {
83+
case 400:
84+
return new BadRequest(message, body);
85+
case 401:
86+
return new Unauthorized(message, body);
87+
case 403:
88+
return new Forbidden(message, body);
89+
case 404:
90+
return new NotFound(message, body);
91+
case 405:
92+
return new MethodNotAllowed(message, body);
93+
case 406:
94+
return new NotAcceptable(message, body);
95+
case 409:
96+
return new Conflict(message, body);
97+
case 410:
98+
return new Gone(message, body);
99+
case 415:
100+
return new UnsupportedMediaType(message, body);
101+
case 429:
102+
return new TooManyRequests(message, body);
103+
case 422:
104+
return new UnprocessableEntity(message, body);
105+
case 500:
106+
return new InternalServerError(message, body);
107+
case 501:
108+
return new NotImplemented(message, body);
109+
case 502:
110+
return new BadGateway(message, body);
111+
case 503:
112+
return new ServiceUnavailable(message, body);
113+
case 504:
114+
return new GatewayTimeout(message, body);
115+
default:
116+
return new FeignException(status, message, body);
117+
}
79118
}
80119

81120
static FeignException errorExecuting(Request request, IOException cause) {
@@ -85,4 +124,100 @@ static FeignException errorExecuting(Request request, IOException cause) {
85124
cause,
86125
null);
87126
}
127+
128+
public static class BadRequest extends FeignException {
129+
public BadRequest(String message, byte[] body) {
130+
super(400, message, body);
131+
}
132+
}
133+
134+
public static class Unauthorized extends FeignException {
135+
public Unauthorized(String message, byte[] body) {
136+
super(401, message, body);
137+
}
138+
}
139+
140+
public static class Forbidden extends FeignException {
141+
public Forbidden(String message, byte[] body) {
142+
super(403, message, body);
143+
}
144+
}
145+
146+
public static class NotFound extends FeignException {
147+
public NotFound(String message, byte[] body) {
148+
super(404, message, body);
149+
}
150+
}
151+
152+
public static class MethodNotAllowed extends FeignException {
153+
public MethodNotAllowed(String message, byte[] body) {
154+
super(405, message, body);
155+
}
156+
}
157+
158+
public static class NotAcceptable extends FeignException {
159+
public NotAcceptable(String message, byte[] body) {
160+
super(406, message, body);
161+
}
162+
}
163+
164+
public static class Conflict extends FeignException {
165+
public Conflict(String message, byte[] body) {
166+
super(409, message, body);
167+
}
168+
}
169+
170+
public static class Gone extends FeignException {
171+
public Gone(String message, byte[] body) {
172+
super(410, message, body);
173+
}
174+
}
175+
176+
public static class UnsupportedMediaType extends FeignException {
177+
public UnsupportedMediaType(String message, byte[] body) {
178+
super(415, message, body);
179+
}
180+
}
181+
182+
public static class TooManyRequests extends FeignException {
183+
public TooManyRequests(String message, byte[] body) {
184+
super(429, message, body);
185+
}
186+
}
187+
188+
public static class UnprocessableEntity extends FeignException {
189+
public UnprocessableEntity(String message, byte[] body) {
190+
super(422, message, body);
191+
}
192+
}
193+
194+
public static class InternalServerError extends FeignException {
195+
public InternalServerError(String message, byte[] body) {
196+
super(500, message, body);
197+
}
198+
}
199+
200+
public static class NotImplemented extends FeignException {
201+
public NotImplemented(String message, byte[] body) {
202+
super(501, message, body);
203+
}
204+
}
205+
206+
public static class BadGateway extends FeignException {
207+
public BadGateway(String message, byte[] body) {
208+
super(502, message, body);
209+
}
210+
}
211+
212+
public static class ServiceUnavailable extends FeignException {
213+
public ServiceUnavailable(String message, byte[] body) {
214+
super(503, message, body);
215+
}
216+
}
217+
218+
public static class GatewayTimeout extends FeignException {
219+
public GatewayTimeout(String message, byte[] body) {
220+
super(504, message, body);
221+
}
222+
}
88223
}

core/src/test/java/feign/FeignBuilderTest.java

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,22 @@ public void testUrlPathConcatNoPathOnRequestLine() throws Exception {
130130
assertThat(server.takeRequest()).hasPath("/");
131131
}
132132

133+
@Test
134+
public void testHttpNotFoundError() {
135+
server.enqueue(new MockResponse().setResponseCode(404));
136+
137+
String url = "http://localhost:" + server.getPort() + "/";
138+
TestInterface api = Feign.builder().target(TestInterface.class, url);
139+
140+
try {
141+
api.getBodyAsString();
142+
failBecauseExceptionWasNotThrown(FeignException.class);
143+
} catch (FeignException.NotFound e) {
144+
assertThat(e.status()).isEqualTo(404);
145+
}
146+
147+
}
148+
133149
@Test
134150
public void testUrlPathConcatNoInitialSlashOnPath() throws Exception {
135151
server.enqueue(new MockResponse().setBody("response data"));
@@ -433,6 +449,9 @@ interface TestInterface {
433449
@RequestLine("GET api/thing")
434450
Response getNoInitialSlashOnSlash();
435451

452+
@RequestLine("GET api/thing")
453+
String getBodyAsString();
454+
436455
@RequestLine(value = "GET /api/querymap/object")
437456
String queryMapEncoded(@QueryMap Object object);
438457

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
/**
2+
* Copyright 2012-2018 The Feign Authors
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
5+
* in compliance with the License. You may obtain a copy of the License at
6+
*
7+
* http://www.apache.org/licenses/LICENSE-2.0
8+
*
9+
* Unless required by applicable law or agreed to in writing, software distributed under the License
10+
* is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
11+
* or implied. See the License for the specific language governing permissions and limitations under
12+
* the License.
13+
*/
14+
package feign.codec;
15+
16+
import feign.FeignException;
17+
import feign.Request;
18+
import feign.Request.HttpMethod;
19+
import feign.Response;
20+
import feign.Util;
21+
import org.junit.Test;
22+
import org.junit.runner.RunWith;
23+
import org.junit.runners.Parameterized;
24+
import java.util.Collection;
25+
import java.util.Collections;
26+
import java.util.LinkedHashMap;
27+
import java.util.Map;
28+
import static org.assertj.core.api.Assertions.assertThat;
29+
30+
@RunWith(Parameterized.class)
31+
public class DefaultErrorDecoderHttpErrorTest {
32+
33+
@Parameterized.Parameters(name = "error: [{0}], exception: [{1}]")
34+
public static Object[][] errorCodes() {
35+
return new Object[][] {
36+
{400, FeignException.BadRequest.class},
37+
{401, FeignException.Unauthorized.class},
38+
{403, FeignException.Forbidden.class},
39+
{404, FeignException.NotFound.class},
40+
{405, FeignException.MethodNotAllowed.class},
41+
{406, FeignException.NotAcceptable.class},
42+
{409, FeignException.Conflict.class},
43+
{429, FeignException.TooManyRequests.class},
44+
{422, FeignException.UnprocessableEntity.class},
45+
{500, FeignException.InternalServerError.class},
46+
{501, FeignException.NotImplemented.class},
47+
{502, FeignException.BadGateway.class},
48+
{503, FeignException.ServiceUnavailable.class},
49+
{504, FeignException.GatewayTimeout.class},
50+
{599, FeignException.class},
51+
};
52+
}
53+
54+
@Parameterized.Parameter
55+
public int httpStatus;
56+
57+
@Parameterized.Parameter(1)
58+
public Class expectedExceptionClass;
59+
60+
private ErrorDecoder errorDecoder = new ErrorDecoder.Default();
61+
62+
private Map<String, Collection<String>> headers = new LinkedHashMap<>();
63+
64+
@Test
65+
public void testExceptionIsHttpSpecific() throws Throwable {
66+
Response response = Response.builder()
67+
.status(httpStatus)
68+
.reason("anything")
69+
.request(Request.create(HttpMethod.GET, "/api", Collections.emptyMap(), null, Util.UTF_8))
70+
.headers(headers)
71+
.build();
72+
73+
Exception exception = errorDecoder.decode("Service#foo()", response);
74+
75+
assertThat(exception).isInstanceOf(expectedExceptionClass);
76+
assertThat(((FeignException) exception).status()).isEqualTo(httpStatus);
77+
}
78+
79+
}

0 commit comments

Comments
 (0)