diff --git a/sdk-actors/src/test/java/io/dapr/actors/client/DaprHttpClientTest.java b/sdk-actors/src/test/java/io/dapr/actors/client/DaprHttpClientTest.java index 634780a5f..59c258c31 100644 --- a/sdk-actors/src/test/java/io/dapr/actors/client/DaprHttpClientTest.java +++ b/sdk-actors/src/test/java/io/dapr/actors/client/DaprHttpClientTest.java @@ -71,7 +71,7 @@ public class DaprHttpClientTest { assertThrowsDaprException( "ERR_SOMETHING", - "ERR_SOMETHING: error message", + "ERR_SOMETHING: error message (HTTP status code: 404)", () -> mono.block()); } diff --git a/sdk-tests/src/test/java/io/dapr/it/methodinvoke/http/MethodInvokeIT.java b/sdk-tests/src/test/java/io/dapr/it/methodinvoke/http/MethodInvokeIT.java index 0cf9e42f5..418e196ea 100644 --- a/sdk-tests/src/test/java/io/dapr/it/methodinvoke/http/MethodInvokeIT.java +++ b/sdk-tests/src/test/java/io/dapr/it/methodinvoke/http/MethodInvokeIT.java @@ -135,7 +135,7 @@ public class MethodInvokeIT extends BaseIT { // TODO(artursouza): change this to INTERNAL once runtime is fixed. assertEquals("UNKNOWN", exception.getErrorCode()); assertNotNull(exception.getMessage()); - assertTrue(exception.getMessage().contains("Internal Server Error")); + assertTrue(exception.getMessage().contains("HTTP status code: 500")); assertTrue(new String(exception.getPayload()).contains("Internal Server Error")); } } diff --git a/sdk-tests/src/test/java/io/dapr/it/pubsub/http/PubSubIT.java b/sdk-tests/src/test/java/io/dapr/it/pubsub/http/PubSubIT.java index 77b917450..494ec5bfb 100644 --- a/sdk-tests/src/test/java/io/dapr/it/pubsub/http/PubSubIT.java +++ b/sdk-tests/src/test/java/io/dapr/it/pubsub/http/PubSubIT.java @@ -122,7 +122,7 @@ public class PubSubIT extends BaseIT { } else { assertThrowsDaprExceptionWithReason( "ERR_PUBSUB_NOT_FOUND", - "ERR_PUBSUB_NOT_FOUND: pubsub unknown pubsub is not found", + "ERR_PUBSUB_NOT_FOUND: pubsub unknown pubsub is not found (HTTP status code: 404)", "DAPR_PUBSUB_NOT_FOUND", () -> client.publishEvent("unknown pubsub", "mytopic", "payload").block()); } diff --git a/sdk-tests/src/test/java/io/dapr/it/state/HttpStateClientIT.java b/sdk-tests/src/test/java/io/dapr/it/state/HttpStateClientIT.java index 9a281bd02..ed0143c8c 100644 --- a/sdk-tests/src/test/java/io/dapr/it/state/HttpStateClientIT.java +++ b/sdk-tests/src/test/java/io/dapr/it/state/HttpStateClientIT.java @@ -65,7 +65,7 @@ public class HttpStateClientIT extends AbstractStateClientIT { // DaprException is guaranteed in the Dapr SDK but getCause() is null in HTTP while present in GRPC implementation. assertThrowsDaprException( "ERR_STATE_STORE_NOT_FOUND", - "ERR_STATE_STORE_NOT_FOUND: state store unknown state store is not found", + "ERR_STATE_STORE_NOT_FOUND: state store unknown state store is not found (HTTP status code: 400)", () -> daprClient.getState("unknown state store", new State(stateKey), byte[].class).block()); } @@ -78,7 +78,7 @@ public class HttpStateClientIT extends AbstractStateClientIT { // DaprException is guaranteed in the Dapr SDK but getCause() is null in HTTP while present in GRPC implementation. assertThrowsDaprException( "ERR_STATE_STORE_NOT_FOUND", - "ERR_STATE_STORE_NOT_FOUND: state store unknown state store is not found", + "ERR_STATE_STORE_NOT_FOUND: state store unknown state store is not found (HTTP status code: 400)", () -> daprClient.getBulkState( "unknown state store", Collections.singletonList(stateKey), diff --git a/sdk/src/main/java/io/dapr/client/DaprHttp.java b/sdk/src/main/java/io/dapr/client/DaprHttp.java index ed2b7e996..05da6efd5 100644 --- a/sdk/src/main/java/io/dapr/client/DaprHttp.java +++ b/sdk/src/main/java/io/dapr/client/DaprHttp.java @@ -349,7 +349,8 @@ public class DaprHttp implements AutoCloseable { try { return DAPR_ERROR_DETAILS_OBJECT_MAPPER.readValue(json, DaprError.class); } catch (IOException e) { - throw new DaprException("UNKNOWN", new String(json, StandardCharsets.UTF_8), json); + // Could not parse DaprError. Return null. + return null; } } @@ -384,17 +385,13 @@ public class DaprHttp implements AutoCloseable { try { byte[] payload = getBodyBytesOrEmptyArray(response); DaprError error = parseDaprError(payload); - if ((error != null) && (error.getErrorCode() != null)) { - if (error.getMessage() != null) { - future.completeExceptionally(new DaprException(error, payload)); - } else { - future.completeExceptionally( - new DaprException(error.getErrorCode(), "HTTP status code: " + response.code(), payload)); - } + if (error != null) { + future.completeExceptionally(new DaprException(error, payload, response.code())); return; } - future.completeExceptionally(new DaprException("UNKNOWN", "HTTP status code: " + response.code(), payload)); + future.completeExceptionally( + new DaprException("UNKNOWN", "", payload, response.code())); return; } catch (DaprException e) { future.completeExceptionally(e); diff --git a/sdk/src/main/java/io/dapr/exceptions/DaprException.java b/sdk/src/main/java/io/dapr/exceptions/DaprException.java index 40d48530f..0801069e8 100644 --- a/sdk/src/main/java/io/dapr/exceptions/DaprException.java +++ b/sdk/src/main/java/io/dapr/exceptions/DaprException.java @@ -13,7 +13,9 @@ limitations under the License. package io.dapr.exceptions; +import com.google.rpc.Status; import io.grpc.StatusRuntimeException; +import io.grpc.protobuf.StatusProto; import reactor.core.Exceptions; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; @@ -30,26 +32,32 @@ public class DaprException extends RuntimeException { /** * Dapr's error code for this exception. */ - private String errorCode; + private final String errorCode; /** * The status details for the error. */ - private DaprErrorDetails errorDetails; + private final DaprErrorDetails errorDetails; /** * Optional payload, if the exception came from a response body. */ - private byte[] payload; + private final byte[] payload; + + /** + * Optional HTTP status code, if error happened for an HTTP call (0 if not set). + */ + private final int httpStatusCode; /** * New exception from a server-side generated error code and message. * * @param daprError Server-side error. - * @param payload Payload containing the error. + * @param payload Optional payload containing the error. + * @param httpStatusCode Optional http Status Code (0 if not set). */ - public DaprException(DaprError daprError, byte[] payload) { - this(daprError.getErrorCode(), daprError.getMessage(), daprError.getDetails(), payload); + public DaprException(DaprError daprError, byte[] payload, int httpStatusCode) { + this(daprError.getErrorCode(), daprError.getMessage(), daprError.getDetails(), payload, httpStatusCode); } /** @@ -77,10 +85,11 @@ public class DaprException extends RuntimeException { * * @param errorCode Client-side error code. * @param message Client-side error message. - * @param payload Error's raw payload. + * @param payload Optional payload containing the error. + * @param httpStatusCode Optional http Status Code (0 if not set). */ - public DaprException(String errorCode, String message, byte[] payload) { - this(errorCode, message, DaprErrorDetails.EMPTY_INSTANCE, payload); + public DaprException(String errorCode, String message, byte[] payload, int httpStatusCode) { + this(errorCode, message, DaprErrorDetails.EMPTY_INSTANCE, payload, httpStatusCode); } /** @@ -89,10 +98,12 @@ public class DaprException extends RuntimeException { * @param errorCode Client-side error code. * @param message Client-side error message. * @param errorDetails Details of the error from runtime. - * @param payload Payload containing the error. + * @param payload Optional payload containing the error. + * @param httpStatusCode Optional http Status Code (0 if not set). */ - public DaprException(String errorCode, String message, List> errorDetails, byte[] payload) { - this(errorCode, message, new DaprErrorDetails(errorDetails), payload); + public DaprException( + String errorCode, String message, List> errorDetails, byte[] payload, int httpStatusCode) { + this(errorCode, message, new DaprErrorDetails(errorDetails), payload, httpStatusCode); } /** @@ -101,10 +112,29 @@ public class DaprException extends RuntimeException { * @param errorCode Client-side error code. * @param message Client-side error message. * @param errorDetails Details of the error from runtime. - * @param payload Payload containing the error. + * @param payload Optional payload containing the error. */ public DaprException(String errorCode, String message, DaprErrorDetails errorDetails, byte[] payload) { - super(String.format("%s: %s", errorCode, message)); + this(errorCode, message, errorDetails, payload, 0); + } + + /** + * New Exception from a client-side generated error code and message. + * + * @param errorCode Client-side error code. + * @param message Client-side error message. + * @param errorDetails Details of the error from runtime. + * @param payload Optional payload containing the error. + * @param httpStatusCode Optional http Status Code (0 if not set). + */ + public DaprException( + String errorCode, + String message, + DaprErrorDetails errorDetails, + byte[] payload, + int httpStatusCode) { + super(buildErrorMessage(errorCode, httpStatusCode, message)); + this.httpStatusCode = httpStatusCode; this.errorCode = errorCode; this.errorDetails = errorDetails; this.payload = payload; @@ -120,8 +150,11 @@ public class DaprException extends RuntimeException { * unknown.) */ public DaprException(String errorCode, String message, Throwable cause) { - super(String.format("%s: %s", errorCode, emptyIfNull(message)), cause); + super(buildErrorMessage(errorCode, 0, message), cause); + this.httpStatusCode = 0; this.errorCode = errorCode; + this.errorDetails = DaprErrorDetails.EMPTY_INSTANCE; + this.payload = null; } /** @@ -137,7 +170,8 @@ public class DaprException extends RuntimeException { */ public DaprException( String errorCode, String message, Throwable cause, DaprErrorDetails errorDetails, byte[] payload) { - super(String.format("%s: %s", errorCode, emptyIfNull(message)), cause); + super(buildErrorMessage(errorCode, 0, message), cause); + this.httpStatusCode = 0; this.errorCode = errorCode; this.errorDetails = errorDetails == null ? DaprErrorDetails.EMPTY_INSTANCE : errorDetails; this.payload = payload; @@ -170,6 +204,15 @@ public class DaprException extends RuntimeException { return this.payload == null ? null : this.payload.clone(); } + /** + * Returns the exception's http status code, 0 if not applicable. + * + * @return Http status code (0 if not applicable). + */ + public int getHttpStatusCode() { + return this.httpStatusCode; + } + /** * Wraps an exception into DaprException (if not already DaprException). * @@ -266,7 +309,7 @@ public class DaprException extends RuntimeException { while (e != null) { if (e instanceof StatusRuntimeException) { StatusRuntimeException statusRuntimeException = (StatusRuntimeException) e; - com.google.rpc.Status status = io.grpc.protobuf.StatusProto.fromThrowable(statusRuntimeException); + Status status = StatusProto.fromThrowable(statusRuntimeException); DaprErrorDetails errorDetails = new DaprErrorDetails(status); @@ -289,11 +332,18 @@ public class DaprException extends RuntimeException { return new DaprException(exception); } - private static String emptyIfNull(String str) { - if (str == null) { - return ""; + private static String buildErrorMessage(String errorCode, int httpStatusCode, String message) { + String result = ((errorCode == null) || errorCode.isEmpty()) ? "UNKNOWN: " : errorCode + ": "; + if ((message == null) || message.isEmpty()) { + if (httpStatusCode > 0) { + return result + "HTTP status code: " + httpStatusCode; + } + return result; } - return str; + if (httpStatusCode > 0) { + return result + message + " (HTTP status code: " + httpStatusCode + ")"; + } + return result + message; } } diff --git a/sdk/src/test/java/io/dapr/client/DaprClientHttpTest.java b/sdk/src/test/java/io/dapr/client/DaprClientHttpTest.java index 34ec5396a..b7be8d4fc 100644 --- a/sdk/src/test/java/io/dapr/client/DaprClientHttpTest.java +++ b/sdk/src/test/java/io/dapr/client/DaprClientHttpTest.java @@ -353,7 +353,8 @@ public class DaprClientHttpTest { }); assertEquals("MYCODE", exception.getErrorCode()); - assertEquals("MYCODE: My Message", exception.getMessage()); + assertEquals("MYCODE: My Message (HTTP status code: 500)", exception.getMessage()); + assertEquals(500, exception.getHttpStatusCode()); } @Test @@ -387,7 +388,7 @@ public class DaprClientHttpTest { }); assertEquals("UNKNOWN", exception.getErrorCode()); - assertEquals("UNKNOWN: { \"anything\": 7 }", exception.getMessage()); + assertEquals("UNKNOWN: HTTP status code: 500", exception.getMessage()); assertEquals("{ \"anything\": 7 }", new String(exception.getPayload())); } @@ -1319,8 +1320,10 @@ public class DaprClientHttpTest { "{\"errorCode\":\"ERR_SECRET_STORE_NOT_FOUND\"," + "\"message\":\"error message\"}", MediaTypes.MEDIATYPE_JSON)); - assertThrowsDaprException("ERR_SECRET_STORE_NOT_FOUND", "ERR_SECRET_STORE_NOT_FOUND: error message", () -> - daprClientHttp.getSecret(SECRET_STORE_NAME, "key").block() + assertThrowsDaprException( + "ERR_SECRET_STORE_NOT_FOUND", + "ERR_SECRET_STORE_NOT_FOUND: error message (HTTP status code: 404)", + () -> daprClientHttp.getSecret(SECRET_STORE_NAME, "key").block() ); }