Reputation: 511
I have an API which will return a failure in form of custom response message, while still sending a Http 200 response. Example:
Status: 200
Body: {
"code":404,
"message":"Data not found",
"data":{},
"status":"ERROR"
}
My current implementeation of the ErrorDecoder:
public class ApiFeignErrorDecoder implements ErrorDecoder {
private final Gson gson;
public ApiFeignErrorDecoder(Gson gson) {
this.gson = gson;
}
@Override
public Exception decode(String methodKey, Response response) {
try {
ApiResponse apiResponse = gson.fromJson(response.body().asReader(), BaseApiResponse.class);
ResponseStatus status = apiResponse.getStatus();
switch (status) {
case SUCCESS:
return null;
case FAIL:
return new Exception("Failure message recorded");
case ERROR:
return new Exception("Error message recorded");
default:
return new Exception("No suitable status found.");
}
} catch (IOException e) {
e.printStackTrace();
return new ErrorDecoder.Default().decode(methodKey, response);
}
}
}
The problem I have is Feign/Spring will only switch into the ErrorDecoder if the HttpStatus is > 300. The JavaDocs from the error decoder also describe this limitation:
"Error handling Responses where Response.status() is not in the 2xx range are classified as errors,addressed by the ErrorDecoder. That said, certain RPC apis return errors defined in the Response.body() even on a 200 status. For example, in the DynECT api, a job still running condition is returned with a 200 status, encoded in json. When scenarios like this occur, you should raise an application-specific exception (which may be retryable)."
My question now is what/how exactly can I implement these or is there a way to extend the ErrorDecoder in such a way that I will be able to handle these error messages. I think I should be able to put them into the either the Decoder or even Implement/override the HttpClient but I'm not sure what the correct/best way is.
Upvotes: 6
Views: 19670
Reputation: 7991
Like ErrorDecoder
, there's SpringDecoder with which you can write custom logic to handle success responses.
private class ResponseDecoder extends SpringDecoder {
public ResponseDecoder(ObjectFactory<HttpMessageConverters> messageConverters) {
super(messageConverters);
}
@Override
public Object decode(Response response, Type type) throws IOException, FeignException {
try {
ApiResponse apiResponse = gson.fromJson(response.body().asReader(), BaseApiResponse.class);
ResponseStatus status = apiResponse.getStatus();
switch (status) {
case SUCCESS:
return null;
case FAIL:
throw new FeignException(FAIL, "Failure message recorded");
case ERROR:
throw new FeignException(ERROR, "Error message recorded");
default:
throw new FeignException(YOUR_DEFAULT_ERROR_CODE, "No suitable status found.");
}
} catch (IOException e) {
e.printStackTrace();
return null; // return accordingly to your use-case
}
}
}
Feign Client Interface:
@FeignClient(value = "foo", configuration = FooClientConfig.class)
public interface FooClient {
//Your mappings
}
Feign Client Custom Configuration:
@Configuration
public class FooClientConfig {
@Bean
public Decoder feignDecoder() {
// if you're using gson, add gson converter here instead
HttpMessageConverter jacksonConverter = new MappingJackson2HttpMessageConverter(customObjectMapper());
ObjectFactory<HttpMessageConverters> objectFactory = () -> new HttpMessageConverters(jacksonConverter);
return new ResponseEntityDecoder(new ResponseDecoder(objectFactory));
}
public ObjectMapper customObjectMapper(){
ObjectMapper objectMapper = new ObjectMapper();
//Customize as much as you want
return objectMapper;
}
}
Upvotes: 5