diff --git a/src/main/java/com/twilio/exception/ApiException.java b/src/main/java/com/twilio/exception/ApiException.java index 6b73fcaccf..f61bb925ba 100644 --- a/src/main/java/com/twilio/exception/ApiException.java +++ b/src/main/java/com/twilio/exception/ApiException.java @@ -7,9 +7,12 @@ public class ApiException extends TwilioException { private static final long serialVersionUID = -3228320166955630014L; private final Integer code; - private final String moreInfo; - private final Integer status; - private final Map details; + private String moreInfo; + private Integer status; + private Map details; + private Integer httpStatusCode; + private Boolean userError; + private Map params; /** * Create a new API Exception. @@ -40,6 +43,7 @@ public ApiException(final String message, final Integer status) { this(message, null, null, status, null); } + /** * Create a new API Exception. * @@ -58,6 +62,18 @@ public ApiException(final String message, final Integer code, final String moreI this.details = null; } + + public ApiException(final Integer code, final String message, final Integer httpStatusCode, final Boolean userError, + final Throwable cause, String moreInfo, Integer status, Map details) { + super(message, cause); + this.code = code; + this.httpStatusCode = httpStatusCode; + this.userError = userError; + this.moreInfo = moreInfo; + this.status = status; + this.details = details; + this.params = null; + } /** * Create a new API Exception. * @@ -71,6 +87,18 @@ public ApiException(final RestException restException) { this.details = restException.getDetails(); } + /** + * Create V1.0 standard Rest Exception + * @return restException as RestExceptionV10 + */ + public ApiException(final RestExceptionV10 restException) { + super(restException.getMessage(), null); + this.code = restException.getCode(); + this.httpStatusCode = restException.getHttpStatusCode(); + this.userError = restException.getUserError(); + this.params = restException.getParams(); + } + public Integer getCode() { return code; } @@ -86,4 +114,16 @@ public Integer getStatusCode() { public Map getDetails() { return details; } + + public Integer getHttpStatusCode() { + return httpStatusCode; + } + + public Boolean getUserError() { + return userError; + } + + public Map getParams() { + return params; + } } diff --git a/src/main/java/com/twilio/exception/RestExceptionV10.java b/src/main/java/com/twilio/exception/RestExceptionV10.java new file mode 100644 index 0000000000..e955709e9d --- /dev/null +++ b/src/main/java/com/twilio/exception/RestExceptionV10.java @@ -0,0 +1,80 @@ +package com.twilio.exception; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.core.JsonParseException; +import com.fasterxml.jackson.databind.JsonMappingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import java.io.IOException; +import java.io.InputStream; +import java.util.Map; + +/** + * Rest Exception V1.0 for Twilio API Standards + */ +public class RestExceptionV10 { + + private final Integer code; + private final String message; + private final Integer httpStatusCode; + private final Boolean userError; + private final Map params; + + /** + * Initialize a Twilio Rest Exception. + * + * @param code Twilio-specific error code + * @param message A human readable error message + * @param httpStatusCode HTTP response status code + * @param userError whether the error is a user error (true) or a system error (false) + * @param params A map of parameters related to the error, for example, a `params.twilioErrorCodeUrl` might hold a URL or link to additional information + */ + @JsonCreator + private RestExceptionV10(@JsonProperty("code") final int code, @JsonProperty("message") final String message, + @JsonProperty("httpStatusCode") final Integer httpStatusCode, @JsonProperty("userError") final boolean userError, + @JsonProperty("params") final Map params) { + this.code = code; + this.message = message; + this.httpStatusCode = httpStatusCode; + this.userError = userError; + this.params = params; + } + + /** + * Build an exception from a JSON blob. + * + * @param json JSON blob + * @param objectMapper JSON reader + * @return Rest Exception as an object + */ + public static RestExceptionV10 fromJson(final InputStream json, final ObjectMapper objectMapper) { + // Convert all checked exception to Runtime + try { + return objectMapper.readValue(json, RestExceptionV10.class); + } catch (final JsonMappingException | JsonParseException e) { + throw new ApiException(e.getMessage(), e); + } catch (final IOException e) { + throw new ApiConnectionException(e.getMessage(), e); + } + } + + public Integer getCode() { + return code; + } + + public String getMessage() { + return message; + } + + public Integer getHttpStatusCode() { + return httpStatusCode; + } + + public Boolean getUserError() { + return userError; + } + + public Map getParams() { + return params; + } +} diff --git a/src/test/java/com/twilio/exception/ApiExceptionTest.java b/src/test/java/com/twilio/exception/ApiExceptionTest.java index 53f97dcb0a..21efdcef5b 100644 --- a/src/test/java/com/twilio/exception/ApiExceptionTest.java +++ b/src/test/java/com/twilio/exception/ApiExceptionTest.java @@ -2,6 +2,8 @@ import java.io.ByteArrayInputStream; import java.util.Collections; +import java.util.HashMap; +import java.util.Map; import com.fasterxml.jackson.databind.ObjectMapper; import org.junit.Test; @@ -9,6 +11,7 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertSame; +import static org.junit.Assert.assertTrue; @SuppressWarnings("ThrowableInstanceNeverThrown") public class ApiExceptionTest { @@ -78,4 +81,128 @@ public void getStatusCodeShouldNotThrowExceptionWhenCodeIsNull() { assertEquals(null, error.getStatusCode()); } + @Test + public void restExceptionV10ConstructorShouldPreserveValues() { + final String errorJson = "{\n" + + " \"code\": 20001,\n" + + " \"message\": \"Bad request\",\n" + + " \"httpStatusCode\": 400,\n" + + " \"userError\": true,\n" + + " \"params\": {\n" + + " \"twilioErrorCodeUrl\": \"https://www.twilio.com/docs/errors/20001\",\n" + + " \"foo\": \"bar\"\n" + + " }\n" + + "}\n"; + + final RestExceptionV10 restExceptionV10 = RestExceptionV10.fromJson(new ByteArrayInputStream(errorJson.getBytes()), + OBJECT_MAPPER); + ApiException error = new ApiException(restExceptionV10); + assertEquals("Code should match", 20001, (int) error.getCode()); + assertEquals("Message should match", "Bad request", error.getMessage()); + assertEquals("HTTP status code should match", 400, (int) error.getHttpStatusCode()); + assertTrue("User error flag should match", error.getUserError()); + + Map expectedParams = new HashMap<>(); + expectedParams.put("twilioErrorCodeUrl", "https://www.twilio.com/docs/errors/20001"); + expectedParams.put("foo", "bar"); + assertEquals("Params should match", expectedParams, error.getParams()); + } + + @Test + public void getHttpStatusCodeShouldReturnCorrectValue() { + final int expectedHttpStatus = 429; + final String errorJson = "{\n" + + " \"code\": 20001,\n" + + " \"message\": \"Rate limited\",\n" + + " \"httpStatusCode\": " + expectedHttpStatus + ",\n" + + " \"userError\": true,\n" + + " \"params\": {}\n" + + "}\n"; + + final RestExceptionV10 restExceptionV10 = RestExceptionV10.fromJson(new ByteArrayInputStream(errorJson.getBytes()), + OBJECT_MAPPER); + ApiException error = new ApiException(restExceptionV10); + assertEquals("HTTP status code should match", expectedHttpStatus, (int) error.getHttpStatusCode()); + } + + @Test + public void getUserErrorShouldReturnCorrectValue() { + // Test with userError = true + final String errorJsonTrue = "{\n" + + " \"code\": 20001,\n" + + " \"message\": \"Invalid parameter\",\n" + + " \"httpStatusCode\": 400,\n" + + " \"userError\": true,\n" + + " \"params\": {}\n" + + "}\n"; + + final RestExceptionV10 restExceptionV10True = RestExceptionV10.fromJson(new ByteArrayInputStream(errorJsonTrue.getBytes()), + OBJECT_MAPPER); + ApiException errorTrue = new ApiException(restExceptionV10True); + assertTrue("UserError should be true", errorTrue.getUserError()); + + // Test with userError = false + final String errorJsonFalse = "{\n" + + " \"code\": 50001,\n" + + " \"message\": \"System error\",\n" + + " \"httpStatusCode\": 500,\n" + + " \"userError\": false,\n" + + " \"params\": {}\n" + + "}\n"; + + final RestExceptionV10 restExceptionV10False = RestExceptionV10.fromJson(new ByteArrayInputStream(errorJsonFalse.getBytes()), + OBJECT_MAPPER); + ApiException errorFalse = new ApiException(restExceptionV10False); + assertEquals("UserError should be false", false, errorFalse.getUserError()); + } + + @Test + public void getParamsShouldReturnCorrectValues() { + final Map expectedParams = new HashMap<>(); + expectedParams.put("resource", "message"); + expectedParams.put("identifier", "SM123"); + expectedParams.put("twilioErrorCodeUrl", "https://www.twilio.com/docs/errors/20001"); + + final String errorJson = "{\n" + + " \"code\": 20001,\n" + + " \"message\": \"Resource not found\",\n" + + " \"httpStatusCode\": 404,\n" + + " \"userError\": true,\n" + + " \"params\": {\n" + + " \"resource\": \"message\",\n" + + " \"identifier\": \"SM123\",\n" + + " \"twilioErrorCodeUrl\": \"https://www.twilio.com/docs/errors/20001\"\n" + + " }\n" + + "}\n"; + + final RestExceptionV10 restExceptionV10 = RestExceptionV10.fromJson(new ByteArrayInputStream(errorJson.getBytes()), + OBJECT_MAPPER); + ApiException error = new ApiException(restExceptionV10); + assertEquals("Params should match", expectedParams, error.getParams()); + } + + @Test + public void fromJsonShouldHandleRestExceptionV10Format() { + final String errorJson = "{\n" + + " \"code\": 20001,\n" + + " \"message\": \"Bad request\",\n" + + " \"httpStatusCode\": 400,\n" + + " \"userError\": true,\n" + + " \"params\": {\n" + + " \"twilioErrorCodeUrl\": \"https://www.twilio.com/docs/errors/20001\"\n" + + " }\n" + + "}\n"; + + final RestExceptionV10 result = RestExceptionV10.fromJson(new ByteArrayInputStream(errorJson.getBytes()), + OBJECT_MAPPER); + + assertEquals("Code should match", 20001, (int) result.getCode()); + assertEquals("Message should match", "Bad request", result.getMessage()); + assertEquals("HTTP status code should match", 400, (int) result.getHttpStatusCode()); + assertTrue("User error flag should match", result.getUserError()); + + Map expectedParams = new HashMap<>(); + expectedParams.put("twilioErrorCodeUrl", "https://www.twilio.com/docs/errors/20001"); + assertEquals("Params should match", expectedParams, result.getParams()); + } }