I have a class which takes enum values like Male,Female #POST . when I sent a wrong value like 'male' instead of 'Male' it shows me 400 Bad Request with this message in rest client : Can not construct instance of constants.Constants$GenderEnum from String value 'male': value not one of declared Enum instance names
at [Source: org.apache.catalina.connector.CoyoteInputStream#718a453d; line: 7, column: 23] (through reference chain: valueobjects.ConsumerValueObject["gender"])
My Rest End Point Looks like below :
#Consumes("application/json")
#Produces("application/json")
#POST
public Response addConsumer(ConsumerValueObject consumerVO)
Here ConsumerValueObject holds the enum.
How to suppress that error message in Rest client? I tried with ExceptionMapper but it did not help!I need to suppress the message due to security issues!
This is the Jackson response from either JsonParseExceptionMapper or JsonMappingExceptionMapper. These classes come with the dependency
<dependency>
<groupId>com.fasterxml.jackson.jaxrs</groupId>
<artifactId>jackson-jaxrs-json-provider</artifactId>
<version>${2.x.version}</version>
</dependency>
Whether you have this explicit dependency or you have the resteasy-jackson2-provider (which uses the above under the hood), most likely the mappers are registered implicitly through classpath scanning. For instance you have an empty Application class.
#ApplicationPath("/")
public class ResteasyApplication extends Application {}
This will cause disovery/registration through classpath scanning. If you don't have either of those dependencies, and if you are in Wildfly, I am not exactly sure how they are registered, but that is what's happening.
You could write/register your own ExceptionMappers for the JsonParseException and JsonMappingException
#Provider
public class JsonMappingExceptionMapper
implements ExceptionMapper<JsonMappingException> {
#Override
public Response toResponse(JsonMappingException e) {
return Response.status(Response.Status.BAD_REQUEST).build();
}
}
but from what I have tested, it's a tossup as to which one will be registered, yours or Jackson's. The mappers are put into a Set (so unordered), then pushed into a Map, so only one get's pushed in. The order in which they are pushed in like I said is a tossup.
I guess this is really only a partial answer, as I have not been able to find a solution that is guaranteed to use your mapper, aside from registering all your classes explicitly (ultimately disabling the classpath scanning), but that is a hassle.
But now the fight has been narrowed down. I will try again some more if I get a chance later
UPDATE
So this is not a solution, just a semi-proof-of-concept to show how we can get it to use our ExceptionMapper.
import org.jboss.resteasy.spi.ResteasyProviderFactory;
import com.fasterxml.jackson.databind.JsonMappingException;
import com.my.pkg.JsonMappingExceptionMapper;
#Path("/init")
public class InitResource {
#GET
public Response init() {
ResteasyProviderFactory factory = ResteasyProviderFactory.getInstance();
factory.getExceptionMappers().put(JsonMappingException.class,
new JsonMappingExceptionMapper());
return Response.ok("Done!").build();
}
}
Once we hit the init endpoint for first time, our JsonMappingExcpetionMapper will register, and override the existing one, whether it is Jackson's or ours.
Of course we would not want to do this for real, it's just showing how to override the mapper. The thing I can't figure out is where to put this code. I've tried a ServletContextListener, in the Application constructor, in a Feature with a low priority. I can't figure it out. None of the above occur before RESTeasy does its final registration.
Do you really want to supress the error message or do you want to fix the actual probelm?
You can actually catch all thrown exception with a custom exception mapper like
#Provider
public class CustomExceptionMapper implements ExceptionMapper<Throwable> {
#Override
public Response toResponse(Throwable t) {
return Response.ok().build();
}
}
though, this will handle all caught exceptions and return a 200 OK which tricks clients to think that the request actually succeeded - which was not the case! Instead of Throwable you should be able to catch the concrete exception (even if it is a RuntimeException) as well - maybe you have not declared it as provider or did not specify the correct exception class?
Though, as already mentioned returning a different status code for an exception is generally bad practice and should be avoided. Fixing the actual problem is probably more suitable in that case.
JAX-RS provides MessageBodyReader and MessageBodyWriter interfaces which you can declare to un/marshall an inputstream to an object or an object to return to an output-stream. The official documentation on MessageBodyReader has more detailed information on that regard.
One implementation therefore could be the following steps:
Read the input-stream to f.e. string
Replace all "male" or "female" tokens with their upper-case version
Parse the string to a json-representation (using org.json.JSONObject f.e)
Use ObjectMapper to convert the JSON representation to a Java object
return the mapped object
This works if the input failure is just a simple upper/lower case issue. If there are typos or semantically alternative available, which are not yet in your enum, you need to put in a bit more effort.
If you, however, fail to create a proper object representation, you should return a user-failure (something in the 400 range) to the client to inform the client that something went wrong.
Related
I have a project with an Swagger API and its server code was generated by swagger-codegen-2.4.24 for language jaxrs.
The code generated has an abstract class suffixed "*ApiService" that defines a series of methods, each corresponding to each operation defined on the Swagger specification of the API.
Each method has a javax.ws.rs.core.SecurityContext interface local variable.
Now, on my custom class which extends "*ApiService", that obviously has javax.ws.rs.core.SecurityContext class local variable, I need to fetch the value of request header "X-Forwarded-For".
If I debug my custom class I see that SecurityContext interface is an instance of org.glassfish.jersey.server.internal.process.SecurityContextInjectee, which has the header I need.
How do I get that information, since I'm not able to work with SecurityContextInjectee since it's private?
I realize that if classes generated by swagger-codegen added javax.servlet.http.HttpServletRequest class, besides SecurityContext, it would be possible to have access to the request parameters, but I didn't see any jaxrs parameter that allows that.
Looking forward for your comments.
In every specification version you can define a header like one of the possible parameter locations.
So, one possible solution, will be to define the header in the methods you required in the request parameters sections:
parameters:
-
name: X-Forwarded-For
description: X-Formarwed-For header.
schema:
type: string
in: header
Or, in JSON notation:
"parameters": [
{
"name": "X-Forwarded-For",
"description": "X-Formarwed-For header.",
"schema": {
"type": "string"
},
"in": "header"
}
]
I am aware that perhaps it is a less maintainable solution because you will need to include the header in every request, but maybe you could mitigate that fact with inheritance in your services implementation.
There is an open Github issue asking for the behavior you described, handling the header processing in a general way.
One suitable option, suggested as well in this related SO answer, could be modifying the Mustache templates used in the API code generation and include within them the required headers processing. Please, be aware that this will do your code less maintainable and you will have the risk of perform some change that breaks the compatibility with the official Swagger Codegen repository. I am not sure in Swagger Codegen, but in the OpenAPI generator there is an option to override the used templates without modifying the actual provided in the official distribution. Please, see this related SO question.
Although it seems that is no longer the case, at least in older versions of Jersey in which the class was public, you could try accessing the requestContext internal variable in org.glassfish.jersey.server.internal.process.SecurityContextInjectee by reflection as well, although I think that workaround makes your application very implementation dependent. In any case, perhaps you could define an utility method like this that you could reuse in your services implementation:
public static String getXForwardedForHeaderValue(final SecurityContext securityContext) {
SecurityContextInjectee securityContextImpl = (SecurityContextInjectee) securityContext;
Field requestContextField = SecurityContextInjectee.class.getDeclaredField("requestContext");
requestContextField.setAccessible(true);
ContainerRequestContext requestContext = requestContextField.get(securityContextImpl);
String xForwardedForHeaderValue = requestContext.getHeaderString("X-Forwarded-For");
return xForwardedForHeaderValue;
}
Finally, another possibility could be using a filter that process your header. If required you could pass the header value using for instance a thread local variable to the underlying services. The idea would be something like the following.
First, define a convenient object that wraps your ThreadLocal value:
public class XForwardedForHeaderHolder{
private static final ThreadLocal<String> value = new ThreadLocal<String>();
public static void setXForwardedForHeader(String xForwardedFor) {
value.set(xForwardedFor);
}
public static String getXForwardedForHeader() {
return value.get();
}
public static void clean() {
value.remove();
}
}
Next, create a ContainerRequestFilter. This filter will read header from the information received in the HTTP request being processed:
import java.io.IOException;
import javax.ws.rs.container.ContainerRequestContext;
import javax.ws.rs.container.ContainerRequestFilter;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.SecurityContext;
import javax.ws.rs.ext.Provider;
#Provider
public class XForwardedForHeaderRequestFilter implements ContainerRequestFilter {
#Override
public void filter(ContainerRequestContext requestContext)
throws IOException {
String xForwardedForHeaderValue = requestContext.getHeaderString("X-Forwarded-For");
XForwardedForHeaderHolder.setXForwardedForHeader(
xForwardedForHeaderValue
);
}
}
Finally, consume the value in your services implementation:
String xForwardedForHeaderValue = XForwardedForHeaderHolder.getXForwardedForHeader();
// Clean up
XForwardedForHeaderHolder.clean();
A word of caution: on one hand, the filter registration should work properly but it could depend on the JAXRS version you are using and Swagger itself; on the other, the solution assume that the filter will provide, in the thread local variable, the right header for every request to the underlying services, in other words, that there are not any threading related issue. I think it should be the case, but it is something that need to be tested.
A method from an object implementing an interface is not getting executed without explicit casting.
Broader context is that I'm trying to decorate RestAssured DSL assertions to anotate in a test report from an ad-hoc testing framework, so code like:
~then().assertThat().statusCode(200).body(matcher)
makes anotations in that sense in the test report.
As for decorator pattern I had little success (I assume my limited skills at developing), so I'm trying another simpler approach, which is passing the TestReport object in the then call, touching the Rest Assured codebase, so now the syntax can be:
~then(report).assertThat().statusCode(200).body(matcher)
Down to specific point where I'm puzzled, which is apparently broken polymorphism, I tried setting a breakpoint at specific class (RestAssuredResponseImplementation) checked this is the actual type at runtime and assessed that it is not working without casting, throwing a java.lang.AbstractMethodError from the right method?!! io.restassured.internal.RestAssuredResponseImpl.then(TestCaseReport) which, of course, is not abstract.
Involved snippets are;
For the test definition:
#Test
public void test1(){
Validatable validatable = given().
header("Authorization", "Basic ASDFqwertyssW8=").
when().
get("https://some-site.org/login");
validatable.then(report).
assertThat().statusCode(200);
}
report object is provided, and, if you cast validatable object changing the line to:
((RestAssuredResponseImpl)validatable).then(report).
everything works fine, otherwise I'm getting the aforementioned exception:
java.lang.AbstractMethodError: io.restassured.internal.RestAssuredResponseImpl.then(Report;)Lio/restassured/response/ValidatableResponseOptions;
Changes in RestAssured:
interface Validatable T extends ValidatableResponseOptions adds:
T then(Report report);
class ReportingValidatableResponseImpl extends ValidatableResponseImpl:
private TestCaseReport testCaseReport;
ReportingValidatableResponseImpl(String contentType, ResponseParserRegistrar rpr, RestAssuredConfig config, Response response, ExtractableResponse<Response> extractableResponse, LogRepository logRepository, TestCaseReport testCaseReport) {
super(contentType, rpr, config, response, extractableResponse, logRepository);
this.testCaseReport = testCaseReport;
}
#Override
ValidatableResponse statusCode(int expectedStatusCode) {
testCaseReport.logMessage(LogStatus.INFO,"Asserting status code is " + expectedStatusCode)
super.statusCode(expectedStatusCode)
}
Implementation on RestAssuredResponseImpl adds:
ValidatableResponse then(TestCaseReport testCaseReport) {
return new ReportingValidatableResponseImpl(contentType, rpr, config, this, this, logRepository, testCaseReport);
}
If this is not a blunder from my side and I'm not missing something obvious, I would say this is breaking polymorphism and something very strange is happening here (I don't know where or how, since there is jUnit, Groovy involved, which implies heavy usage of reflection)
That's the use of interfaces, if at runtime, an object satisfies the interface, it doesn't matter which actual object it is.
I'm really puzzled about this, hope someone can bring some insight.
Thank you in advance.
My question should be quite simple.
I have a Spring Boot REST API.
#GetMapping("/customer")
public Customer getCustomer() {
return service.getCustomer()
}
My controller returns List<Customer> and works well. But now I want to return another object with the errors that can happen when gathering the customers. Let's say it's called GenericErrorClass
So to return this I need to create a class that groups List and GenericErrorClass and return that class, right?
that will work but now I have Account, Product, etc... I don't think it makes sence create a class for each one of them.
How can I build a custom object without creating classes and return that as json from the rest controller?
Don't do that.
Throw your exception, or let it escape from your call stack. Use #ControllerAdvice (or #RestControllerAdvice) with #ExceptionHandler instead.
You can extend the abstract class ResponseEntityExceptionHandler, and provide additional handling methods. On the long run that will matter for a clean application design.
If you intend to return the error with a status code of 200, I'd like to understand why. I witness developers serving out responses for errored requests with 200 just because handling the HTTP error in another code branch at client side seems "difficult".
At first, you should know that there is already a similar class org.springframework.http.ResponseEntity, which object you could return as a response of your API. It can wrap your response body - List, Account, Product, etc. with the possibility to override default Http status.
So, based on this example you can write your own simple wrapper class like this:
public class Response<T>
{
private GenericErrorClass error;
private T body;
// constructors + getters + setters
}
and when your API method should return List<Customer> you will return Response<List<Customer>> , in the same way other objects.
However, I would recommend you to catch exceptions and send detailed error message + corresponding error code to API client. This is much better from a design point of view.
To implement this here is a good read.
If you have time to think about the design of your API, I would recommend this guide.
Generally you would return error information when the request is not succeeded, that coincides with a 4/5xx error code. Usually with Spring you manage this situation with exception handlers, as shown here where you can define a different response body. There is also another good practice: use envelopes to manage all responses, I will show you an example
{
status: 200,
message: 'user retrieved',
result:
[
{foo1:"bar1"},
.....
]
}
OR
{
status: 400,
message: 'bad request',
error:
{
reason : "wrong field xxx in request"
}
}
in this way clients can process the request and provide useful info to users. To do this you have to define a class that is used for all responses and should encapsulate the result or the error
With java validation constraints, i.e.
#NotNull, #Size, etc
You can add a message field that your api can return to the client. Is there anyway to add additional fields such as a custom code?
The problem I have is that every possible error needs it's own message and code return to the client. (By code I mean a custom one in the response body, not a http status code).
I.e.
{
message: foo can not be null,
code: 10001
}
The only thing I can think of is to use custom validator classes on every single field which would be quite a lot of work, or have a giant if/else block that sets the code based on the message.
Can anyone think of a nicer solution?
Thanks in advance for any help :)
You can use the payload() parameter defined by all constraint types.
You'd have to declare a class type for each one of your error codes:
public interface Error_01 {}
And then:
public class SomeValidatedClass {
#NotNull(payload=Error_01.class)
private String someField;
}
We are building a Java SDK to simplify the access to one of our services that provide a REST API. This SDK is to be used by 3rd-party developers. I am struggling to find the best pattern to implement the error handling in the SDK that better fits the Java language.
Let's say we have the rest endpoint: GET /photos/{photoId}.
This may return the following HTTP status codes:
401 : The user is not authenticated
403 : The user does not have permission to access this photo
404 : There's no photo with that id
The service looks something like this:
interface RestService {
public Photo getPhoto(String photoID);
}
In the code above I am not addressing the error handling yet. I obviously want to provide a way for the client of the sdk to know which error happened, to potentially recover from it. Error handling in Java is done using Exceptions, so let's go with that. However, what is the best way to do this using exceptions?
1. Have a single exception with information about the error.
public Photo getPhoto(String photoID) throws RestServiceException;
public class RestServiceException extends Exception {
int statusCode;
...
}
The client of the sdk could then do something like this:
try {
Photo photo = getPhoto("photo1");
}
catch(RestServiceException e) {
swtich(e.getStatusCode()) {
case 401 : handleUnauthenticated(); break;
case 403 : handleUnauthorized(); break;
case 404 : handleNotFound(); break;
}
}
However I don't really like this solution mainly for 2 reasons:
By looking at the method's signature the developer has no idea what kind of error situations he may need to handle.
The developer needs to deal directly with the HTTP status codes and know what they mean in the context of this method (obviously if they are correctly used, a lot of the times the meaning is known, however that may not always be the case).
2. Have a class hierarchy of errors
The method signature remains:
public Photo getPhoto(String photoID) throws RestServiceException;
But now we create exceptions for each error type:
public class UnauthenticatedException extends RestServiceException;
public class UnauthorizedException extends RestServiceException;
public class NotFoundException extends RestServiceException;
Now the client of the SDK could then do something like this:
try {
Photo photo = getPhoto("photo1");
}
catch(UnauthenticatedException e) {
handleUnauthorized();
}
catch(UnauthorizedException e) {
handleUnauthenticated();
}
catch(NotFoundException e) {
handleNotFound();
}
With this approach the developer does not need to know about the HTTP status codes that generated the errors, he only has to handle Java Exceptions. Another advantage is that the developer may only catch the exceptions he wants to handle (unlike the previous situation where it would have to catch the single Exception (RestServiceException) and only then decide if he wants to deal with it or not).
However, there's still one problem. By looking at the method's signature the developer still has no idea about the kind of errors he may need to handle because we only have the super class in the method's signature.
3. Have a class hierarchy of errors + list them in the method's signature
Ok, so what comes to mind now is to change the method's signature to:
public Photo getPhoto(String photoID) throws UnauthenticatedException, UnauthorizedException, NotFoundException;
However, it is possible that in the future new error situations could be added to this rest endpoint. That would mean adding a new Exception to the method's signature and that would be a breaking change to the java api. We would like to have a more robust solution that would not result in breaking changes to the api in the situation described.
4. Have a class hierarchy of errors (using Unchecked exceptions) + list them in the method's signature
So, what about Unchecked exceptions? If we change the RestServiceException to extend the RuntimeException:
public class RestServiceException extends RuntimeException
And we keep the method's signature:
public Photo getPhoto(String photoID) throws UnauthenticatedException, UnauthorizedException, NotFoundException;
This way I can add new exceptions to the method's signature without breaking existing code.
However, with this solution the developer is not forced to catch any exception and won't notice that there are error situations he needs to handle until he carefully reads the documentation (yeah, right!) or noticed the Exceptions that are in the method's signature.
What's the best practice for error handling in these kind of situations?
Are there other (better) alternatives to the ones I mentioned?
Exception handling alternatives: Callbacks
I don't know if it's a better alternative, but you could use callbacks. You can make some methods optional by providing a default implementation. Take a look to this:
/**
* Example 1.
* Some callbacks will be always executed even if they fail or
* not, all the request will finish.
* */
RestRequest request = RestRequest.get("http://myserver.com/photos/31",
Photo.class, new RestCallback(){
//I know that this error could be triggered, so I override the method.
#Override
public void onUnauthorized() {
//Handle this error, maybe pop up a login windows (?)
}
//I always must override this method.
#Override
public void onFinish () {
//Do some UI updates...
}
}).send();
This is how the callback class looks like:
public abstract class RestCallback {
public void onUnauthorized() {
//Override this method is optional.
}
public abstract void onFinish(); //Override this method is obligatory.
public void onError() {
//Override this method is optional.
}
public void onBadParamsError() {
//Override this method is optional.
}
}
Doing something like this you could define an request life-cycle, and manage every state of the request. You can make some methods optional to implement or not. You can get some general errors and give the chance at the user to implements the handling, like in the onError.
How can I define clearly what exceptions handle?
If you ask me, the best approach is draw the life-cycle of the request, something like this:
This is only a poor example, but the important it's keep in mind that all the methods implementation, could be or not, optionals. If onAuthenticationError is obligatory, not neccesarily the onBadUsername will be too, and viceversa. This is the point that makes this callbacks so flexible.
And how I implement the Http client?
Well I don't know much about http clients, I always use the apache HttpClient, but there's not a lot of differences between the http clients, the most have a little more or a little fewer features, but in the end, they are all just the same. Just pick up the http method, put the url, the params, and send. For this example I will use the apache HttpClient
public class RestRequest {
Gson gson = new Gson();
public <T> T post(String url, Class<T> clazz,
List<NameValuePair> parameters, RestCallback callback) {
// Create a new HttpClient and Post Header
HttpClient httpclient = new DefaultHttpClient();
HttpPost httppost = new HttpPost(url);
try {
// Add your data
httppost.setEntity(new UrlEncodedFormEntity(parameters));
// Execute HTTP Post Request
HttpResponse response = httpclient.execute(httppost);
StringBuilder json = inputStreamToString(response.getEntity()
.getContent());
T gsonObject = gson.fromJson(json.toString(), clazz);
callback.onSuccess(); // Everything has gone OK
return gsonObject;
} catch (HttpResponseException e) {
// Here are the http error codes!
callback.onError();
switch (e.getStatusCode()) {
case 401:
callback.onAuthorizationError();
break;
case 403:
callback.onPermissionRefuse();
break;
case 404:
callback.onNonExistingPhoto();
break;
}
e.printStackTrace();
} catch (ConnectTimeoutException e) {
callback.onTimeOutError();
e.printStackTrace();
} catch (MalformedJsonException e) {
callback.onMalformedJson();
}
return null;
}
// Fast Implementation
private StringBuilder inputStreamToString(InputStream is)
throws IOException {
String line = "";
StringBuilder total = new StringBuilder();
// Wrap a BufferedReader around the InputStream
BufferedReader rd = new BufferedReader(new InputStreamReader(is));
// Read response until the end
while ((line = rd.readLine()) != null) {
total.append(line);
}
// Return full string
return total;
}
}
This is an example implementation of the RestRequest. This is only one simple example, theres a lot of topics to discuss when you are making your own rest client. For example, "what kind of json library use to parse?", "are you working for android or for java?" (this is important because I don't know if android supports some features of java 7 like multi-catch exceptions, and there's some technologies that isn't availabe for java or android and viceversa).
But the best that I can say you is code the sdk api in terms of the user, note that the lines to make the rest request are few.
Hope this helps! Bye :]
It seems you are doing things by "hand".
I would recommend you0 give a try to Apache CXF.
It's a neat implementation the JAX-RS API that enables you to almost forget about REST. It plays well with (also recommended) Spring.
You simply write classes that implement your interfaces (API). What you need to do is to annotate the methods and parameters of your interfaces with JAX-RS annotations.
Then, CXF does the magic.
You throw normal Exceptions in your java code, and then use exception mapper on server/nd or client to translate between them and HTTP Status code.
This way, on server/Java client side, you only deal with regular 100% Java exception, and CXF handles the HTTP for you: You have both the benefits of a clear REST API and a Java Client ready to be used by your users.
The client can either be generated from your WDSL, or built at runtime from introspection of the interface annotations.
See :
http://cxf.apache.org/docs/jax-rs-basics.html#JAX-RSBasics-Exceptionhandling
http://cxf.apache.org/docs/how-do-i-develop-a-client.html
In our application, we have defined and mapped a set of error codes and their counterpart Exceptions :
4XX Expected / Functional excecption (like bad arguments, empty sets, etc)
5XX Unexpected / Unrecovable RunTimeException for internal errors that "should not happen"
It follows both REST and Java standards.
I've seen libraries that combine your suggestions 2 and 3, e.g.
public Photo getPhoto(String photoID) throws RestServiceException, UnauthenticatedException, UnauthorizedException, NotFoundException;
This way, when you add a new checked exception that extends RestServiceException, you're not changing the method's contract and any code using it still compiles.
Compared to a callback or unchecked exception solution, an advantage is that this ensures your new error will be handled by the client code, even if it's only as a general error. In a callback, nothing would happen, and with an unchecked exception, your client application might crash.
The solution may vary depending on your needs.
If it is supposed that there could appear unpredictable new exception types in the future, your second solution with checked exception hierarchy and method that throw their superclass RestServiceException is the best one. All known subclasses should be listed in the javadoc like Subclasses: {#link UnauthenticatedException}, ..., to let developers know what kind of of exceptions there could hide. It should be noticed that if some method could throw only few exceptions from this list, they should be described in the javadoc of this method using #throws.
This solution is also applicable in the case when all appearances of RestServiceException means that any of it's subclasses could hide behind it. But in this case, if RestServiceException subclasses hasn't their specific fields and methods, your first solution is preferrable, but with some modifications:
public class RestServiceException extends Exception {
private final Type type;
public Type getType();
...
public static enum Type {
UNAUTHENTICATED,
UNAUTHORISED,
NOT_FOUND;
}
}
Also there is a good practice to create alternative method that will throw unchecked exception that wraps RestServiceException exeption itself for usage within ‘all-or-nothing’ business logic.
public Photo getPhotoUnchecked(String photoID) {
try {
return getPhoto(photoID);
catch (RestServiceException ex) {
throw new RestServiceUncheckedException(ex);
}
}
It all comes down to how informative your API error responses are. The more informative the error handling of the API is, the more informative the exception handling can be. I would believe the exceptions would only be as informative as the errors returned from the API.
Example:
{ "status":404,"code":2001,"message":"Photo could not be found."}
Following your first suggestion, if the Exception contained both the status and the API error code, the developer has a better understanding of what he needs to do and more option when it comes to exception handling. If the exception also contained the error message that was returned, as well, the developer shouldn't even need to reference the documentation.