From 139a23017576c4d0c82182b03ad378cf63bbc1d0 Mon Sep 17 00:00:00 2001 From: Cassandra Coyle Date: Wed, 31 Jan 2024 18:10:48 -0600 Subject: [PATCH 01/17] properly add the dapr runtime returned error details to the Java DaprException Signed-off-by: Cassandra Coyle --- .../io/dapr/examples/exception/Client.java | 35 ++++- .../java/io/dapr/examples/exception/README.md | 138 ++++++++++++++++-- .../main/java/io/dapr/client/DaprHttp.java | 5 +- .../java/io/dapr/exceptions/DaprError.java | 102 ++++++++++++- .../io/dapr/exceptions/DaprException.java | 45 +++++- .../dapr/exceptions/DetailDeserializer.java | 25 ++++ .../dapr/exceptions/DetailObjectMapper.java | 29 ++++ .../exceptions/ProtoMessageConverter.java | 81 ++++++++++ 8 files changed, 439 insertions(+), 21 deletions(-) create mode 100644 sdk/src/main/java/io/dapr/exceptions/DetailDeserializer.java create mode 100644 sdk/src/main/java/io/dapr/exceptions/DetailObjectMapper.java create mode 100644 sdk/src/main/java/io/dapr/exceptions/ProtoMessageConverter.java diff --git a/examples/src/main/java/io/dapr/examples/exception/Client.java b/examples/src/main/java/io/dapr/examples/exception/Client.java index f757a2793b..60cd3affaa 100644 --- a/examples/src/main/java/io/dapr/examples/exception/Client.java +++ b/examples/src/main/java/io/dapr/examples/exception/Client.java @@ -17,6 +17,11 @@ import io.dapr.client.DaprClientBuilder; import io.dapr.exceptions.DaprException; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + /** * 1. Build and install jars: * mvn clean install @@ -35,11 +40,39 @@ public static void main(String[] args) throws Exception { try (DaprClient client = new DaprClientBuilder().build()) { try { - client.getState("Unknown state store", "myKey", String.class).block(); + client.publishEvent("", "", "").block(); + // client.publishEvent("fake", "fake", "someData").block(); + // client.getState("Unknown state store", "myKey", String.class).block(); } catch (DaprException exception) { System.out.println("Error code: " + exception.getErrorCode()); System.out.println("Error message: " + exception.getMessage()); + try { + Map detailsMap = exception.getStatusDetails(); + if (detailsMap != null && detailsMap.containsKey("details")) { + Object detailsObject = detailsMap.get("details"); + if (detailsObject instanceof List) { + List> innerDetailsList = (List>) detailsObject; + System.out.println("Error Details: "); + + for (Map innerDetails : innerDetailsList) { + + if (innerDetails.containsKey("@type") && innerDetails.get("@type").equals("type.googleapis.com/google.rpc.ErrorInfo")) { + System.out.println("\tError Detail is of type: Error_Info"); + // Implement specific logic based on specific error type + } + + for (Map.Entry entry : innerDetails.entrySet()) { + System.out.println("\t" + entry.getKey() + ": " + entry.getValue()); + } + System.out.println(); // separate error details with newline + } + } + } + System.out.println("Error Details: " + exception.getStatusDetails()); + } catch (RuntimeException e) { + System.out.println("Error Details: NULL"); + } exception.printStackTrace(); } diff --git a/examples/src/main/java/io/dapr/examples/exception/README.md b/examples/src/main/java/io/dapr/examples/exception/README.md index 4ccfd2515c..bae6a0d7a3 100644 --- a/examples/src/main/java/io/dapr/examples/exception/README.md +++ b/examples/src/main/java/io/dapr/examples/exception/README.md @@ -23,8 +23,8 @@ cd java-sdk Then build the Maven project: ```sh -# make sure you are in the `java-sdk` directory. -mvn install +# make sure you are in the `java-sdk` (root) directory. +./mvnw clean install ``` Then get into the examples directory: @@ -32,7 +32,8 @@ Then get into the examples directory: cd examples ``` -### Running the StateClient +### Examples +#### Running the State Client This example uses the Java SDK Dapr client in order to perform an invalid operation, causing Dapr runtime to return an error. See the code snippet below: ```java @@ -60,18 +61,63 @@ The code uses the `DaprClient` created by the `DaprClientBuilder`. It tries to g The Dapr client is also within a try-with-resource block to properly close the client at the end. -### Running the example +#### Running the PubSub Client -Run this example with the following command: +##### Parsing the Error Details - +This example uses the Java SDK Dapr client in order to perform an invalid operation, causing Dapr runtime to return an error. See the code snippet below: + +```java +public class Client { + public static void main(String[] args) throws Exception { + try (DaprClient client = new DaprClientBuilder().build()) { + try { + client.publishEvent("", "", "").block(); + } catch (DaprException exception) { + System.out.println("Error code: " + exception.getErrorCode()); + System.out.println("Error message: " + exception.getMessage()); + + try { + Map detailsMap = exception.getStatusDetails(); + if (detailsMap != null && detailsMap.containsKey("details")) { + Object detailsObject = detailsMap.get("details"); + if (detailsObject instanceof List) { + List> innerDetailsList = (List>) detailsObject; + System.out.println("Error Details: "); + + for (Map innerDetails : innerDetailsList) { + + if (innerDetails.containsKey("@type") && innerDetails.get("@type").equals("type.googleapis.com/google.rpc.ErrorInfo")) { + System.out.println("\tError Detail is of type: Error_Info"); + // Implement specific logic based on specific error type + } + + for (Map.Entry entry : innerDetails.entrySet()) { + System.out.println("\t" + entry.getKey() + ": " + entry.getValue()); + } + System.out.println(); // separate error details with newline + } + } + } + System.out.println("Error Details: " + exception.getStatusDetails()); + } catch (RuntimeException e) { + System.out.println("Error Details: NULL"); + } + exception.printStackTrace(); + } + + System.out.println("Done"); + } + } +} +``` + +### Running the examples + +#### Run the State Example +1. Uncomment out this line: `client.getState("Unknown state store", "myKey", String.class).block();` +2. Comment out the publishEvent line: `client.publishEvent("", "", "").block();` +3. Run this example with the following command ```bash dapr run --app-id exception-example -- java -jar target/dapr-java-sdk-examples-exec.jar io.dapr.examples.exception.Client @@ -79,7 +125,7 @@ dapr run --app-id exception-example -- java -jar target/dapr-java-sdk-examples-e -Once running, the OutputBindingExample should print the output as follows: +Once running, the State Client Example should print the output as follows: ```txt == APP == Error code: INVALID_ARGUMENT @@ -115,6 +161,70 @@ Once running, the OutputBindingExample should print the output as follows: ``` +#### Run the PubSub Example +1. Run this example with the following command + + + +```bash +dapr run --app-id exception-example -- java -jar target/dapr-java-sdk-examples-exec.jar io.dapr.examples.exception.Client +``` + + + +Once running, the PubSub Client Example should print the output as follows: + +```txt +== APP == Error code: INVALID_ARGUMENT +== APP == Error message: INVALID_ARGUMENT: pubsub name is empty +== APP == Error Detail is of type: Error_Info +== APP == Error Details: +== APP == reason: DAPR_PUBSUB_NAME_EMPTY +== APP == metadata: null +== APP == @type: type.googleapis.com/google.rpc.ErrorInfo +== APP == domain: dapr.io +== APP == Error Details: +== APP == owner: +== APP == @type: type.googleapis.com/google.rpc.ResourceInfo +== APP == description: pubsub name is empty +== APP == resourceName: +== APP == resourceType: pubsub +== APP == Error Details: {details=[{reason=DAPR_PUBSUB_NAME_EMPTY, metadata=null, @type=type.googleapis.com/google.rpc.ErrorInfo, domain=dapr.io}, {owner=, @type=type.googleapis.com/google.rpc.ResourceInfo, description=pubsub name is empty, resourceName=, resourceType=pubsub}]} +== APP == io.dapr.exceptions.DaprException: INVALID_ARGUMENT: pubsub name is empty +... +``` + +### Debug + +If you would like to debug the Client code. Simply follow these steps: +1. Pre-req: +```sh +# make sure you are in the `java-sdk` (root) directory. +./mvnw clean install +``` +2. From the examples directory, run: `dapr run --dapr-grpc-port=50001` +3. From your IDE click the play button on the Client code and put break points where desired + ### Cleanup To stop the app run (or press `CTRL+C`): diff --git a/sdk/src/main/java/io/dapr/client/DaprHttp.java b/sdk/src/main/java/io/dapr/client/DaprHttp.java index 121c1c2641..7c02f59ede 100644 --- a/sdk/src/main/java/io/dapr/client/DaprHttp.java +++ b/sdk/src/main/java/io/dapr/client/DaprHttp.java @@ -18,6 +18,7 @@ import io.dapr.config.Properties; import io.dapr.exceptions.DaprError; import io.dapr.exceptions.DaprException; +import io.dapr.exceptions.DetailObjectMapper; import io.dapr.utils.Version; import okhttp3.Call; import okhttp3.Callback; @@ -141,6 +142,8 @@ public int getStatusCode() { */ private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper(); + + private static final DetailObjectMapper DETAIL_OBJECT_MAPPER = new DetailObjectMapper(); /** * Endpoint used to communicate to Dapr's HTTP endpoint. */ @@ -347,7 +350,7 @@ private static DaprError parseDaprError(byte[] json) { } try { - return OBJECT_MAPPER.readValue(json, DaprError.class); + return DetailObjectMapper.OBJECT_MAPPER.readValue(json, DaprError.class); } catch (IOException e) { throw new DaprException("UNKNOWN", new String(json, StandardCharsets.UTF_8)); } diff --git a/sdk/src/main/java/io/dapr/exceptions/DaprError.java b/sdk/src/main/java/io/dapr/exceptions/DaprError.java index 3400d9b579..60ad3d20ef 100644 --- a/sdk/src/main/java/io/dapr/exceptions/DaprError.java +++ b/sdk/src/main/java/io/dapr/exceptions/DaprError.java @@ -1,5 +1,5 @@ /* - * Copyright 2021 The Dapr Authors + * Copyright 2024 The Dapr Authors * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at @@ -14,7 +14,14 @@ package io.dapr.exceptions; import com.fasterxml.jackson.annotation.JsonAutoDetect; -import io.grpc.Status; +import com.google.protobuf.Any; +import com.google.protobuf.Message; +import com.google.rpc.Status; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; /** * Represents an error message from Dapr. @@ -37,6 +44,11 @@ public class DaprError { */ private Integer code; + /** + * Error status details. + */ + private List> statusDetails; + /** * Gets the error code. * @@ -44,7 +56,7 @@ public class DaprError { */ public String getErrorCode() { if ((errorCode == null) && (code != null)) { - return Status.fromCodeValue(code).getCode().name(); + return io.grpc.Status.fromCodeValue(code).getCode().name(); } return errorCode; } @@ -80,4 +92,88 @@ public DaprError setMessage(String message) { return this; } + /** + * Sets the status details for the error. + * + * @param statusDetails Status details to set. + * @return This instance. + */ + public DaprError setStatusDetails(Status statusDetails) { + if (statusDetails != null) { + this.statusDetails = parseStatusDetails(statusDetails); + } + return this; + } + + /** + * Gets the status details for the error. + * + * @return Status details. + */ + public List> getStatusDetails() { + return statusDetails; + } + + /** + * Parses status details from a gRPC Status. + * + * @param status The gRPC Status to parse details from. + * @return List containing parsed status details. + */ + public static List> parseStatusDetails(Status status) { + List> detailsList = new ArrayList<>(); + if (status == null || status.getDetailsList() == null) { + return detailsList; + } + + List grpcDetailsList = status.getDetailsList(); + for (Any detail : grpcDetailsList) { + try { + if (detail.is(com.google.rpc.ErrorInfo.class)) { + com.google.rpc.ErrorInfo errorInfo = detail.unpack(com.google.rpc.ErrorInfo.class); + detailsList.add(ProtoMessageConverter.messageToMap(errorInfo)); + } + if (detail.is(com.google.rpc.RetryInfo.class)) { + com.google.rpc.RetryInfo retryInfo = detail.unpack(com.google.rpc.RetryInfo.class); + detailsList.add(ProtoMessageConverter.messageToMap(retryInfo)); + } + if (detail.is(com.google.rpc.DebugInfo.class)) { + com.google.rpc.DebugInfo debugInfo = detail.unpack(com.google.rpc.DebugInfo.class); + detailsList.add(ProtoMessageConverter.messageToMap(debugInfo)); + } + if (detail.is(com.google.rpc.QuotaFailure.class)) { + com.google.rpc.QuotaFailure quotaFailure = detail.unpack(com.google.rpc.QuotaFailure.class); + detailsList.add(ProtoMessageConverter.messageToMap(quotaFailure)); + } + if (detail.is(com.google.rpc.PreconditionFailure.class)) { + com.google.rpc.PreconditionFailure preconditionFailure = detail.unpack( + com.google.rpc.PreconditionFailure.class); + detailsList.add(ProtoMessageConverter.messageToMap(preconditionFailure)); + } + if (detail.is(com.google.rpc.BadRequest.class)) { + com.google.rpc.BadRequest badRequest = detail.unpack(com.google.rpc.BadRequest.class); + detailsList.add(ProtoMessageConverter.messageToMap(badRequest)); + } + if (detail.is(com.google.rpc.RequestInfo.class)) { + com.google.rpc.RequestInfo requestInfo = detail.unpack(com.google.rpc.RequestInfo.class); + detailsList.add(ProtoMessageConverter.messageToMap(requestInfo)); + } + if (detail.is(com.google.rpc.ResourceInfo.class)) { + com.google.rpc.ResourceInfo resourceInfo = detail.unpack(com.google.rpc.ResourceInfo.class); + detailsList.add(ProtoMessageConverter.messageToMap(resourceInfo)); + } + if (detail.is(com.google.rpc.Help.class)) { + com.google.rpc.Help help = detail.unpack(com.google.rpc.Help.class); + detailsList.add(ProtoMessageConverter.messageToMap(help)); + } + if (detail.is(com.google.rpc.LocalizedMessage.class)) { + com.google.rpc.LocalizedMessage localizedMessage = detail.unpack(com.google.rpc.LocalizedMessage.class); + detailsList.add(ProtoMessageConverter.messageToMap(localizedMessage)); + } + } catch (Exception e) { + e.printStackTrace(); + } + } + return detailsList; + } } diff --git a/sdk/src/main/java/io/dapr/exceptions/DaprException.java b/sdk/src/main/java/io/dapr/exceptions/DaprException.java index 0771e1fac7..967a48997b 100644 --- a/sdk/src/main/java/io/dapr/exceptions/DaprException.java +++ b/sdk/src/main/java/io/dapr/exceptions/DaprException.java @@ -1,5 +1,5 @@ /* - * Copyright 2021 The Dapr Authors + * Copyright 2024 The Dapr Authors * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at @@ -13,12 +13,18 @@ package io.dapr.exceptions; +import io.dapr.config.Property; import io.grpc.StatusRuntimeException; import reactor.core.Exceptions; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; +import java.util.HashMap; +import java.util.List; +import java.util.Map; import java.util.concurrent.Callable; +import java.util.logging.Level; +import java.util.logging.Logger; /** * A Dapr's specific exception. @@ -30,6 +36,11 @@ public class DaprException extends RuntimeException { */ private String errorCode; + /** + * The status details for the error. + */ + private Map statusDetails; + /** * New exception from a server-side generated error code and message. * @@ -84,6 +95,25 @@ public DaprException(String errorCode, String message, Throwable cause) { this.errorCode = errorCode; } + + /** + * New exception from a server-side generated error code and message. + * @param errorCode Client-side error code. + * @param message Client-side error message. + * @param cause the cause (which is saved for later retrieval by the + * {@link #getCause()} method). (A {@code null} value is + * permitted, and indicates that the cause is nonexistent or + * unknown.) + * @param statusDetails the status details for the error. + */ + public DaprException(String errorCode, String message, Throwable cause, Map statusDetails) { + super(String.format("%s: %s", errorCode, emptyIfNull(message)), cause); + this.errorCode = errorCode; + if (statusDetails != null) { + this.statusDetails = statusDetails; + } + } + /** * Returns the exception's error code. * @@ -93,6 +123,10 @@ public String getErrorCode() { return this.errorCode; } + public Map getStatusDetails() { + return this.statusDetails; + } + /** * Wraps an exception into DaprException (if not already DaprException). * @@ -189,10 +223,17 @@ public static RuntimeException propagate(Throwable exception) { while (e != null) { if (e instanceof StatusRuntimeException) { StatusRuntimeException statusRuntimeException = (StatusRuntimeException) e; + com.google.rpc.Status status = io.grpc.protobuf.StatusProto.fromThrowable(statusRuntimeException); + + List> detailsList = DaprError.parseStatusDetails(status); + Map statusDetails = new HashMap<>(); + statusDetails.put("details", detailsList); + return new DaprException( statusRuntimeException.getStatus().getCode().toString(), statusRuntimeException.getStatus().getDescription(), - exception); + exception, + statusDetails); } e = e.getCause(); diff --git a/sdk/src/main/java/io/dapr/exceptions/DetailDeserializer.java b/sdk/src/main/java/io/dapr/exceptions/DetailDeserializer.java new file mode 100644 index 0000000000..5a30937763 --- /dev/null +++ b/sdk/src/main/java/io/dapr/exceptions/DetailDeserializer.java @@ -0,0 +1,25 @@ +/* + * Copyright 2024 The Dapr Authors + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and +limitations under the License. +*/ + +package io.dapr.exceptions; + +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.KeyDeserializer; + +public class DetailDeserializer extends KeyDeserializer { + + @Override + public Object deserializeKey(String key, DeserializationContext ctxt) { + return key; + } +} diff --git a/sdk/src/main/java/io/dapr/exceptions/DetailObjectMapper.java b/sdk/src/main/java/io/dapr/exceptions/DetailObjectMapper.java new file mode 100644 index 0000000000..716bb29239 --- /dev/null +++ b/sdk/src/main/java/io/dapr/exceptions/DetailObjectMapper.java @@ -0,0 +1,29 @@ +/* + * Copyright 2024 The Dapr Authors + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and +limitations under the License. +*/ + +package io.dapr.exceptions; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.module.SimpleModule; +import com.google.protobuf.Descriptors; + +public class DetailObjectMapper { + public static final ObjectMapper OBJECT_MAPPER; + + static { + SimpleModule module = new SimpleModule(); + module.addKeyDeserializer(Descriptors.FieldDescriptor.class, new DetailDeserializer()); + OBJECT_MAPPER = new ObjectMapper(); + OBJECT_MAPPER.registerModule(module); + } +} diff --git a/sdk/src/main/java/io/dapr/exceptions/ProtoMessageConverter.java b/sdk/src/main/java/io/dapr/exceptions/ProtoMessageConverter.java new file mode 100644 index 0000000000..cee1ed35dc --- /dev/null +++ b/sdk/src/main/java/io/dapr/exceptions/ProtoMessageConverter.java @@ -0,0 +1,81 @@ +/* + * Copyright 2024 The Dapr Authors + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and +limitations under the License. +*/ + +package io.dapr.exceptions; + +import com.google.protobuf.Message; + +import java.lang.reflect.Field; +import java.util.HashMap; +import java.util.Map; + +/** + * Utility class for converting Protocol Buffer (proto) messages to a Map. + */ +public class ProtoMessageConverter { + /** + * Converts a Protocol Buffer (proto) message to a Map. + * + * @param message The proto message to be converted. + * @return A Map representing the fields of the proto message. + */ + public static Map messageToMap(Message message) { + Map result = new HashMap<>(); + Field[] fields = message.getClass().getDeclaredFields(); + + // trim the 'com.' to match the kit error details returned to users + String className = message.getClass().getName(); + result.put("@type", "type.googleapis.com/" + (className.startsWith("com.") ? className.substring(4) : className)); + + for (Field field : fields) { + // The error detail fields we care about end in '_' + if (!(isSupportedField(field.getName()))) { + continue; + } + try { + field.setAccessible(true); + Object value = field.get(message); + String fieldNameMinusUnderscore = removeTrailingUnderscore(field.getName()); + result.put(fieldNameMinusUnderscore, value); + } catch (IllegalAccessException e) { + e.printStackTrace(); + } + } + + return result; + } + + /** + * Remove the trailing underscore from a string. + * + * @param input The input string. + * @return The input string without the trailing underscore. + */ + private static String removeTrailingUnderscore(String input) { + if (input.endsWith("_")) { + return input.substring(0, input.length() - 1); + } + return input; + } + + /** + * Check if the field name ends with an underscore ('_'). Those are the error detail + * fields we care about. + * + * @param fieldName The field name. + * @return True if the field name ends with an underscore, false otherwise. + */ + private static boolean isSupportedField(String fieldName) { + return fieldName.endsWith("_"); + } +} From c97dd51ce7c6c58ac77ecde30350ef666b19a723 Mon Sep 17 00:00:00 2001 From: Cassandra Coyle Date: Thu, 1 Feb 2024 10:52:13 -0600 Subject: [PATCH 02/17] add error handling to sdk docs Signed-off-by: Cassandra Coyle --- .../en/java-sdk-docs/java-client/_index.md | 45 +++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/daprdocs/content/en/java-sdk-docs/java-client/_index.md b/daprdocs/content/en/java-sdk-docs/java-client/_index.md index 74e183f354..5409a79d37 100644 --- a/daprdocs/content/en/java-sdk-docs/java-client/_index.md +++ b/daprdocs/content/en/java-sdk-docs/java-client/_index.md @@ -40,7 +40,52 @@ If your Dapr instance is configured to require the `DAPR_API_TOKEN` environment set it in the environment and the client will use it automatically. You can read more about Dapr API token authentication [here](https://docs.dapr.io/operations/security/api-token/). +#### Error Handling +Initially, errors in Dapr followed the Standard gRPC error model. However, to provide more detailed and informative error +messages, in version 1.13 an enhanced error model has been introduced which aligns with the gRPC Richer error model. In +response, the Java SDK extended the DaprException to include the error details that were added in Dapr. + +Example of handling the DaprException and consuming the error details when using the Dapr Java SDK: + +```java +... + try { + client.publishEvent("", "", "").block(); + } catch (DaprException exception) { + System.out.println("Error code: " + exception.getErrorCode()); + System.out.println("Error message: " + exception.getMessage()); + + try { + Map detailsMap = exception.getStatusDetails(); + if (detailsMap != null && detailsMap.containsKey("details")) { + Object detailsObject = detailsMap.get("details"); + if (detailsObject instanceof List) { + List> innerDetailsList = (List>) detailsObject; + System.out.println("Error Details: "); + + for (Map innerDetails : innerDetailsList) { + + if (innerDetails.containsKey("@type") && innerDetails.get("@type").equals("type.googleapis.com/google.rpc.ErrorInfo")) { + System.out.println("\tError Detail is of type: Error_Info"); + // Implement specific logic based on specific error type + } + + for (Map.Entry entry : innerDetails.entrySet()) { + System.out.println("\t" + entry.getKey() + ": " + entry.getValue()); + } + System.out.println(); // separate error details with newline + } + } + } + System.out.println("Error Details: " + exception.getStatusDetails()); + } catch (RuntimeException e) { + System.out.println("Error Details: NULL"); + } + exception.printStackTrace(); + } +... +``` ## Building blocks From 783ebf28f3774e83708368604b65c40e8e9fac25 Mon Sep 17 00:00:00 2001 From: Cassandra Coyle Date: Fri, 2 Feb 2024 13:23:05 -0600 Subject: [PATCH 03/17] add tests for the dapr exception changes Signed-off-by: Cassandra Coyle --- .../java/io/dapr/exceptions/DaprError.java | 2 - .../io/dapr/exceptions/DaprException.java | 3 - .../io/dapr/client/DaprClientGrpcTest.java | 15 ++- .../io/dapr/client/DaprExceptionTest.java | 117 ++++++++++++++++++ .../test/java/io/dapr/utils/TestUtils.java | 15 +++ 5 files changed, 145 insertions(+), 7 deletions(-) create mode 100644 sdk/src/test/java/io/dapr/client/DaprExceptionTest.java diff --git a/sdk/src/main/java/io/dapr/exceptions/DaprError.java b/sdk/src/main/java/io/dapr/exceptions/DaprError.java index 60ad3d20ef..a67f4e40c6 100644 --- a/sdk/src/main/java/io/dapr/exceptions/DaprError.java +++ b/sdk/src/main/java/io/dapr/exceptions/DaprError.java @@ -15,11 +15,9 @@ import com.fasterxml.jackson.annotation.JsonAutoDetect; import com.google.protobuf.Any; -import com.google.protobuf.Message; import com.google.rpc.Status; import java.util.ArrayList; -import java.util.HashMap; import java.util.List; import java.util.Map; diff --git a/sdk/src/main/java/io/dapr/exceptions/DaprException.java b/sdk/src/main/java/io/dapr/exceptions/DaprException.java index 967a48997b..cfd1f90823 100644 --- a/sdk/src/main/java/io/dapr/exceptions/DaprException.java +++ b/sdk/src/main/java/io/dapr/exceptions/DaprException.java @@ -13,7 +13,6 @@ package io.dapr.exceptions; -import io.dapr.config.Property; import io.grpc.StatusRuntimeException; import reactor.core.Exceptions; import reactor.core.publisher.Flux; @@ -23,8 +22,6 @@ import java.util.List; import java.util.Map; import java.util.concurrent.Callable; -import java.util.logging.Level; -import java.util.logging.Logger; /** * A Dapr's specific exception. diff --git a/sdk/src/test/java/io/dapr/client/DaprClientGrpcTest.java b/sdk/src/test/java/io/dapr/client/DaprClientGrpcTest.java index bff92e4057..be2e40ec62 100644 --- a/sdk/src/test/java/io/dapr/client/DaprClientGrpcTest.java +++ b/sdk/src/test/java/io/dapr/client/DaprClientGrpcTest.java @@ -37,6 +37,7 @@ import io.dapr.v1.DaprProtos; import io.grpc.Status; import io.grpc.StatusRuntimeException; +import io.grpc.protobuf.StatusProto; import io.grpc.stub.StreamObserver; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; @@ -2507,7 +2508,17 @@ public String toString() { } } - private static StatusRuntimeException newStatusRuntimeException(String status, String message) { - return new StatusRuntimeException(Status.fromCode(Status.Code.valueOf(status)).withDescription(message)); + public static StatusRuntimeException newStatusRuntimeException(String statusCode, String message) { + return new StatusRuntimeException(Status.fromCode(Status.Code.valueOf(statusCode)).withDescription(message)); + } + + public static StatusRuntimeException newStatusRuntimeException(String statusCode, String message, com.google.rpc.Status statusDetails) { + com.google.rpc.Status status = com.google.rpc.Status.newBuilder() + .setCode(Status.Code.valueOf(statusCode).value()) + .setMessage(message) + .addAllDetails(statusDetails.getDetailsList()) + .build(); + + return StatusProto.toStatusRuntimeException(status); } } diff --git a/sdk/src/test/java/io/dapr/client/DaprExceptionTest.java b/sdk/src/test/java/io/dapr/client/DaprExceptionTest.java new file mode 100644 index 0000000000..5ac3d0957f --- /dev/null +++ b/sdk/src/test/java/io/dapr/client/DaprExceptionTest.java @@ -0,0 +1,117 @@ +package io.dapr.client; + +import com.google.protobuf.Any; +import com.google.rpc.ErrorInfo; +import com.google.rpc.ResourceInfo; +import io.dapr.exceptions.DaprError; +import io.dapr.serializer.DefaultObjectSerializer; +import io.dapr.v1.DaprGrpc; +import io.grpc.StatusRuntimeException; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.stubbing.Answer; + +import java.io.IOException; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import static io.dapr.client.DaprClientGrpcTest.newStatusRuntimeException; +import static io.dapr.utils.TestUtils.assertThrowsDaprException; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.*; +import io.dapr.v1.DaprProtos; + +public class DaprExceptionTest { + private GrpcChannelFacade channel; + private DaprGrpc.DaprStub daprStub; + private DaprClient client; + private ObjectSerializer serializer; + + @BeforeEach + public void setup() throws IOException { + channel = mock(GrpcChannelFacade.class); + daprStub = mock(DaprGrpc.DaprStub.class); + when(daprStub.withInterceptors(any())).thenReturn(daprStub); + DaprClient grpcClient = new DaprClientGrpc( + channel, daprStub, new DefaultObjectSerializer(), new DefaultObjectSerializer()); + client = new DaprClientProxy(grpcClient); + serializer = new ObjectSerializer(); + doNothing().when(channel).close(); + } + + @AfterEach + public void tearDown() throws Exception { + client.close(); + verify(channel).close(); + } + + @Test + public void daprExceptionWithMultipleDetailsThrownTest() { + ErrorInfo errorInfo = ErrorInfo.newBuilder() + .setDomain("dapr.io") + .setReason("fake") + .build(); + + ResourceInfo resourceInfo = ResourceInfo.newBuilder() + .setResourceName("") + .setResourceType("pubsub") + .setDescription("pubsub name is empty") + .build(); + + com.google.rpc.Status status = com.google.rpc.Status.newBuilder() + .setCode(io.grpc.Status.Code.INVALID_ARGUMENT.value()) + .setMessage("bad bad argument") + .addDetails(Any.pack(errorInfo)) + .addDetails(Any.pack(resourceInfo)) + .build(); + + doAnswer((Answer) invocation -> { + throw newStatusRuntimeException("INVALID_ARGUMENT", "bad bad argument", status); + }).when(daprStub).publishEvent(any(DaprProtos.PublishEventRequest.class), any()); + + List> detailsList = DaprError.parseStatusDetails(status); + + Map expectedStatusDetails = new HashMap<>(); + expectedStatusDetails.put("details", detailsList); + + + assertThrowsDaprException( + StatusRuntimeException.class, + "INVALID_ARGUMENT", + "INVALID_ARGUMENT: bad bad argument", + expectedStatusDetails, + () -> client.publishEvent("pubsubname","topic", "object").block()); + } + + @Test + public void daprExceptionWithOneDetailThrownTest() { + ErrorInfo errorInfo = ErrorInfo.newBuilder() + .setDomain("dapr.io") + .setReason("DAPR_STATE_NOT_FOUND") + .build(); + + com.google.rpc.Status status = com.google.rpc.Status.newBuilder() + .setCode(io.grpc.Status.Code.INVALID_ARGUMENT.value()) + .setMessage("bad bad argument") + .addDetails(Any.pack(errorInfo)) + .build(); + + doAnswer((Answer) invocation -> { + throw newStatusRuntimeException("INVALID_ARGUMENT", "bad bad argument", status); + }).when(daprStub).getState(any(DaprProtos.GetStateRequest.class), any()); + + List> detailsList = DaprError.parseStatusDetails(status); + + Map expectedStatusDetails = new HashMap<>(); + expectedStatusDetails.put("details", detailsList); + + assertThrowsDaprException( + StatusRuntimeException.class, + "INVALID_ARGUMENT", + "INVALID_ARGUMENT: bad bad argument", + expectedStatusDetails, + () -> client.getState("Unknown state store", "myKey", String.class).block()); + } +} diff --git a/sdk/src/test/java/io/dapr/utils/TestUtils.java b/sdk/src/test/java/io/dapr/utils/TestUtils.java index a9c7012414..6481ac920c 100644 --- a/sdk/src/test/java/io/dapr/utils/TestUtils.java +++ b/sdk/src/test/java/io/dapr/utils/TestUtils.java @@ -19,6 +19,7 @@ import java.io.IOException; import java.net.ServerSocket; +import java.util.Map; public final class TestUtils { @@ -58,6 +59,20 @@ public static void assertThrowsDaprException( Assertions.assertEquals(expectedErrorMessage, daprException.getMessage()); } + public static void assertThrowsDaprException( + Class expectedType, + String expectedErrorCode, + String expectedErrorMessage, + Map expectedStatusDetails, + Executable executable) { + DaprException daprException = Assertions.assertThrows(DaprException.class, executable); + Assertions.assertNotNull(daprException.getCause()); + Assertions.assertEquals(expectedType, daprException.getCause().getClass()); + Assertions.assertEquals(expectedErrorCode, daprException.getErrorCode()); + Assertions.assertEquals(expectedErrorMessage, daprException.getMessage()); + Assertions.assertEquals(expectedStatusDetails, daprException.getStatusDetails()); + } + public static int findFreePort() throws IOException { try (ServerSocket socket = new ServerSocket(0)) { socket.setReuseAddress(true); From cf64bc405d0fca9a3e4b0b2afef09b1fdc47dcf4 Mon Sep 17 00:00:00 2001 From: Cassandra Coyle Date: Fri, 2 Feb 2024 14:30:05 -0600 Subject: [PATCH 04/17] try verifyNoMoreInteractions w/ channel Signed-off-by: Cassandra Coyle --- sdk/src/test/java/io/dapr/client/DaprExceptionTest.java | 1 + 1 file changed, 1 insertion(+) diff --git a/sdk/src/test/java/io/dapr/client/DaprExceptionTest.java b/sdk/src/test/java/io/dapr/client/DaprExceptionTest.java index 5ac3d0957f..8393d4f667 100644 --- a/sdk/src/test/java/io/dapr/client/DaprExceptionTest.java +++ b/sdk/src/test/java/io/dapr/client/DaprExceptionTest.java @@ -45,6 +45,7 @@ public void setup() throws IOException { public void tearDown() throws Exception { client.close(); verify(channel).close(); + verifyNoMoreInteractions(channel); } @Test From a402db2d52ce7b87a7be7ec727fedb004224def4 Mon Sep 17 00:00:00 2001 From: Cassandra Coyle Date: Fri, 2 Feb 2024 14:50:33 -0600 Subject: [PATCH 05/17] verify channel close -> channel close explicitly Signed-off-by: Cassandra Coyle --- sdk/src/test/java/io/dapr/client/DaprExceptionTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sdk/src/test/java/io/dapr/client/DaprExceptionTest.java b/sdk/src/test/java/io/dapr/client/DaprExceptionTest.java index 8393d4f667..65844463da 100644 --- a/sdk/src/test/java/io/dapr/client/DaprExceptionTest.java +++ b/sdk/src/test/java/io/dapr/client/DaprExceptionTest.java @@ -44,7 +44,7 @@ public void setup() throws IOException { @AfterEach public void tearDown() throws Exception { client.close(); - verify(channel).close(); + channel.close(); verifyNoMoreInteractions(channel); } From c2171294111f48b035d81fee77c889ecb798c8e4 Mon Sep 17 00:00:00 2001 From: Cassandra Coyle Date: Fri, 2 Feb 2024 15:04:17 -0600 Subject: [PATCH 06/17] rm verifyNoMoreInteractions Signed-off-by: Cassandra Coyle --- sdk/src/test/java/io/dapr/client/DaprExceptionTest.java | 1 - 1 file changed, 1 deletion(-) diff --git a/sdk/src/test/java/io/dapr/client/DaprExceptionTest.java b/sdk/src/test/java/io/dapr/client/DaprExceptionTest.java index 65844463da..27bef24f15 100644 --- a/sdk/src/test/java/io/dapr/client/DaprExceptionTest.java +++ b/sdk/src/test/java/io/dapr/client/DaprExceptionTest.java @@ -45,7 +45,6 @@ public void setup() throws IOException { public void tearDown() throws Exception { client.close(); channel.close(); - verifyNoMoreInteractions(channel); } @Test From f8ffb2dc004ce2cc6374a897e4bb80099454f74d Mon Sep 17 00:00:00 2001 From: Cassandra Coyle Date: Fri, 2 Feb 2024 15:16:19 -0600 Subject: [PATCH 07/17] rm test to see if that is the orphaned managed channel issue Signed-off-by: Cassandra Coyle --- .../io/dapr/client/DaprExceptionTest.java | 117 ------------------ 1 file changed, 117 deletions(-) delete mode 100644 sdk/src/test/java/io/dapr/client/DaprExceptionTest.java diff --git a/sdk/src/test/java/io/dapr/client/DaprExceptionTest.java b/sdk/src/test/java/io/dapr/client/DaprExceptionTest.java deleted file mode 100644 index 27bef24f15..0000000000 --- a/sdk/src/test/java/io/dapr/client/DaprExceptionTest.java +++ /dev/null @@ -1,117 +0,0 @@ -package io.dapr.client; - -import com.google.protobuf.Any; -import com.google.rpc.ErrorInfo; -import com.google.rpc.ResourceInfo; -import io.dapr.exceptions.DaprError; -import io.dapr.serializer.DefaultObjectSerializer; -import io.dapr.v1.DaprGrpc; -import io.grpc.StatusRuntimeException; -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.mockito.stubbing.Answer; - -import java.io.IOException; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -import static io.dapr.client.DaprClientGrpcTest.newStatusRuntimeException; -import static io.dapr.utils.TestUtils.assertThrowsDaprException; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.*; -import io.dapr.v1.DaprProtos; - -public class DaprExceptionTest { - private GrpcChannelFacade channel; - private DaprGrpc.DaprStub daprStub; - private DaprClient client; - private ObjectSerializer serializer; - - @BeforeEach - public void setup() throws IOException { - channel = mock(GrpcChannelFacade.class); - daprStub = mock(DaprGrpc.DaprStub.class); - when(daprStub.withInterceptors(any())).thenReturn(daprStub); - DaprClient grpcClient = new DaprClientGrpc( - channel, daprStub, new DefaultObjectSerializer(), new DefaultObjectSerializer()); - client = new DaprClientProxy(grpcClient); - serializer = new ObjectSerializer(); - doNothing().when(channel).close(); - } - - @AfterEach - public void tearDown() throws Exception { - client.close(); - channel.close(); - } - - @Test - public void daprExceptionWithMultipleDetailsThrownTest() { - ErrorInfo errorInfo = ErrorInfo.newBuilder() - .setDomain("dapr.io") - .setReason("fake") - .build(); - - ResourceInfo resourceInfo = ResourceInfo.newBuilder() - .setResourceName("") - .setResourceType("pubsub") - .setDescription("pubsub name is empty") - .build(); - - com.google.rpc.Status status = com.google.rpc.Status.newBuilder() - .setCode(io.grpc.Status.Code.INVALID_ARGUMENT.value()) - .setMessage("bad bad argument") - .addDetails(Any.pack(errorInfo)) - .addDetails(Any.pack(resourceInfo)) - .build(); - - doAnswer((Answer) invocation -> { - throw newStatusRuntimeException("INVALID_ARGUMENT", "bad bad argument", status); - }).when(daprStub).publishEvent(any(DaprProtos.PublishEventRequest.class), any()); - - List> detailsList = DaprError.parseStatusDetails(status); - - Map expectedStatusDetails = new HashMap<>(); - expectedStatusDetails.put("details", detailsList); - - - assertThrowsDaprException( - StatusRuntimeException.class, - "INVALID_ARGUMENT", - "INVALID_ARGUMENT: bad bad argument", - expectedStatusDetails, - () -> client.publishEvent("pubsubname","topic", "object").block()); - } - - @Test - public void daprExceptionWithOneDetailThrownTest() { - ErrorInfo errorInfo = ErrorInfo.newBuilder() - .setDomain("dapr.io") - .setReason("DAPR_STATE_NOT_FOUND") - .build(); - - com.google.rpc.Status status = com.google.rpc.Status.newBuilder() - .setCode(io.grpc.Status.Code.INVALID_ARGUMENT.value()) - .setMessage("bad bad argument") - .addDetails(Any.pack(errorInfo)) - .build(); - - doAnswer((Answer) invocation -> { - throw newStatusRuntimeException("INVALID_ARGUMENT", "bad bad argument", status); - }).when(daprStub).getState(any(DaprProtos.GetStateRequest.class), any()); - - List> detailsList = DaprError.parseStatusDetails(status); - - Map expectedStatusDetails = new HashMap<>(); - expectedStatusDetails.put("details", detailsList); - - assertThrowsDaprException( - StatusRuntimeException.class, - "INVALID_ARGUMENT", - "INVALID_ARGUMENT: bad bad argument", - expectedStatusDetails, - () -> client.getState("Unknown state store", "myKey", String.class).block()); - } -} From 967100e9950ac79f849b3dcc0ca1e0ef33469229 Mon Sep 17 00:00:00 2001 From: Cassandra Coyle Date: Fri, 2 Feb 2024 16:05:50 -0600 Subject: [PATCH 08/17] re-add test since that doesnt seem to be the issue Signed-off-by: Cassandra Coyle --- .../io/dapr/client/DaprExceptionTest.java | 117 ++++++++++++++++++ 1 file changed, 117 insertions(+) create mode 100644 sdk/src/test/java/io/dapr/client/DaprExceptionTest.java diff --git a/sdk/src/test/java/io/dapr/client/DaprExceptionTest.java b/sdk/src/test/java/io/dapr/client/DaprExceptionTest.java new file mode 100644 index 0000000000..a94f90d077 --- /dev/null +++ b/sdk/src/test/java/io/dapr/client/DaprExceptionTest.java @@ -0,0 +1,117 @@ +package io.dapr.client; + +import com.google.protobuf.Any; +import com.google.rpc.ErrorInfo; +import com.google.rpc.ResourceInfo; +import io.dapr.exceptions.DaprError; +import io.dapr.serializer.DefaultObjectSerializer; +import io.dapr.v1.DaprGrpc; +import io.grpc.StatusRuntimeException; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.stubbing.Answer; + +import java.io.IOException; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import static io.dapr.client.DaprClientGrpcTest.newStatusRuntimeException; +import static io.dapr.utils.TestUtils.assertThrowsDaprException; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.*; +import io.dapr.v1.DaprProtos; + +public class DaprExceptionTest { + private GrpcChannelFacade channel; + private DaprGrpc.DaprStub daprStub; + private DaprClient client; + private ObjectSerializer serializer; + + @BeforeEach + public void setup() throws IOException { + channel = mock(GrpcChannelFacade.class); + daprStub = mock(DaprGrpc.DaprStub.class); + when(daprStub.withInterceptors(any())).thenReturn(daprStub); + DaprClient grpcClient = new DaprClientGrpc( + channel, daprStub, new DefaultObjectSerializer(), new DefaultObjectSerializer()); + client = new DaprClientProxy(grpcClient); + serializer = new ObjectSerializer(); + doNothing().when(channel).close(); + } + + @AfterEach + public void tearDown() throws Exception { + client.close(); + channel.close(); + } + + @Test + public void daprExceptionWithMultipleDetailsThrownTest() { + ErrorInfo errorInfo = ErrorInfo.newBuilder() + .setDomain("dapr.io") + .setReason("fake") + .build(); + + ResourceInfo resourceInfo = ResourceInfo.newBuilder() + .setResourceName("") + .setResourceType("pubsub") + .setDescription("pubsub name is empty") + .build(); + + com.google.rpc.Status status = com.google.rpc.Status.newBuilder() + .setCode(io.grpc.Status.Code.INVALID_ARGUMENT.value()) + .setMessage("bad bad argument") + .addDetails(Any.pack(errorInfo)) + .addDetails(Any.pack(resourceInfo)) + .build(); + + doAnswer((Answer) invocation -> { + throw newStatusRuntimeException("INVALID_ARGUMENT", "bad bad argument", status); + }).when(daprStub).publishEvent(any(DaprProtos.PublishEventRequest.class), any()); + + List> detailsList = DaprError.parseStatusDetails(status); + + Map expectedStatusDetails = new HashMap<>(); + expectedStatusDetails.put("details", detailsList); + + + assertThrowsDaprException( + StatusRuntimeException.class, + "INVALID_ARGUMENT", + "INVALID_ARGUMENT: bad bad argument", + expectedStatusDetails, + () -> client.publishEvent("pubsubname","topic", "object").block()); + } + + @Test + public void daprExceptionWithOneDetailThrownTest() { + ErrorInfo errorInfo = ErrorInfo.newBuilder() + .setDomain("dapr.io") + .setReason("DAPR_STATE_NOT_FOUND") + .build(); + + com.google.rpc.Status status = com.google.rpc.Status.newBuilder() + .setCode(io.grpc.Status.Code.INVALID_ARGUMENT.value()) + .setMessage("bad bad argument") + .addDetails(Any.pack(errorInfo)) + .build(); + + doAnswer((Answer) invocation -> { + throw newStatusRuntimeException("INVALID_ARGUMENT", "bad bad argument", status); + }).when(daprStub).getState(any(DaprProtos.GetStateRequest.class), any()); + + List> detailsList = DaprError.parseStatusDetails(status); + + Map expectedStatusDetails = new HashMap<>(); + expectedStatusDetails.put("details", detailsList); + + assertThrowsDaprException( + StatusRuntimeException.class, + "INVALID_ARGUMENT", + "INVALID_ARGUMENT: bad bad argument", + expectedStatusDetails, + () -> client.getState("Unknown state store", "myKey", String.class).block()); + } +} \ No newline at end of file From 69b0d68c6c01812e4395798f2e6ffc84777a261f Mon Sep 17 00:00:00 2001 From: Cassandra Coyle Date: Sat, 3 Feb 2024 16:10:28 -0600 Subject: [PATCH 09/17] channel.close(); -> verify(channel).close(); Signed-off-by: Cassandra Coyle --- sdk/src/test/java/io/dapr/client/DaprExceptionTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sdk/src/test/java/io/dapr/client/DaprExceptionTest.java b/sdk/src/test/java/io/dapr/client/DaprExceptionTest.java index a94f90d077..3a772ef80e 100644 --- a/sdk/src/test/java/io/dapr/client/DaprExceptionTest.java +++ b/sdk/src/test/java/io/dapr/client/DaprExceptionTest.java @@ -44,7 +44,7 @@ public void setup() throws IOException { @AfterEach public void tearDown() throws Exception { client.close(); - channel.close(); + verify(channel).close(); } @Test From d377fadfe3df73502e4d575a1ebc08ff2c8c8afd Mon Sep 17 00:00:00 2001 From: Artur Souza Date: Mon, 12 Feb 2024 16:10:40 -0800 Subject: [PATCH 10/17] Rewrite and redesign of the DaprErrorDetail in DaprException. Signed-off-by: Artur Souza --- .../io/dapr/examples/exception/Client.java | 46 +--- .../java/io/dapr/examples/exception/README.md | 178 +++----------- .../src/test/java/io/dapr/it/TestUtils.java | 20 ++ .../java/io/dapr/it/pubsub/http/PubSubIT.java | 13 +- .../main/java/io/dapr/client/DaprHttp.java | 17 +- .../java/io/dapr/exceptions/DaprError.java | 92 +------- .../io/dapr/exceptions/DaprErrorDetails.java | 223 ++++++++++++++++++ .../io/dapr/exceptions/DaprException.java | 47 ++-- .../dapr/exceptions/DetailDeserializer.java | 25 -- .../dapr/exceptions/DetailObjectMapper.java | 29 --- .../exceptions/ProtoMessageConverter.java | 81 ------- .../io/dapr/client/DaprExceptionTest.java | 39 +-- .../java/io/dapr/client/DaprHttpTest.java | 37 ++- .../test/java/io/dapr/utils/TestUtils.java | 3 +- 14 files changed, 396 insertions(+), 454 deletions(-) create mode 100644 sdk/src/main/java/io/dapr/exceptions/DaprErrorDetails.java delete mode 100644 sdk/src/main/java/io/dapr/exceptions/DetailDeserializer.java delete mode 100644 sdk/src/main/java/io/dapr/exceptions/DetailObjectMapper.java delete mode 100644 sdk/src/main/java/io/dapr/exceptions/ProtoMessageConverter.java diff --git a/examples/src/main/java/io/dapr/examples/exception/Client.java b/examples/src/main/java/io/dapr/examples/exception/Client.java index 60cd3affaa..615a413e3c 100644 --- a/examples/src/main/java/io/dapr/examples/exception/Client.java +++ b/examples/src/main/java/io/dapr/examples/exception/Client.java @@ -15,7 +15,9 @@ import io.dapr.client.DaprClient; import io.dapr.client.DaprClientBuilder; +import io.dapr.exceptions.DaprErrorDetails; import io.dapr.exceptions.DaprException; +import io.dapr.utils.TypeRef; import java.util.ArrayList; import java.util.HashMap; @@ -38,45 +40,17 @@ public class Client { */ public static void main(String[] args) throws Exception { try (DaprClient client = new DaprClientBuilder().build()) { - try { - client.publishEvent("", "", "").block(); - // client.publishEvent("fake", "fake", "someData").block(); - // client.getState("Unknown state store", "myKey", String.class).block(); + client.publishEvent("unknown_pubsub", "mytopic", "mydata").block(); } catch (DaprException exception) { - System.out.println("Error code: " + exception.getErrorCode()); - System.out.println("Error message: " + exception.getMessage()); - - try { - Map detailsMap = exception.getStatusDetails(); - if (detailsMap != null && detailsMap.containsKey("details")) { - Object detailsObject = detailsMap.get("details"); - if (detailsObject instanceof List) { - List> innerDetailsList = (List>) detailsObject; - System.out.println("Error Details: "); - - for (Map innerDetails : innerDetailsList) { - - if (innerDetails.containsKey("@type") && innerDetails.get("@type").equals("type.googleapis.com/google.rpc.ErrorInfo")) { - System.out.println("\tError Detail is of type: Error_Info"); - // Implement specific logic based on specific error type - } - - for (Map.Entry entry : innerDetails.entrySet()) { - System.out.println("\t" + entry.getKey() + ": " + entry.getValue()); - } - System.out.println(); // separate error details with newline - } - } - } - System.out.println("Error Details: " + exception.getStatusDetails()); - } catch (RuntimeException e) { - System.out.println("Error Details: NULL"); - } - exception.printStackTrace(); + System.out.println("Dapr exception's error code: " + exception.getErrorCode()); + System.out.println("Dapr exception's message: " + exception.getMessage()); + System.out.println("Dapr exception's reason: " + exception.getStatusDetails().get( + DaprErrorDetails.ErrorDetailType.ERROR_INFO, + "reason", + TypeRef.STRING)); } - - System.out.println("Done"); } + System.out.println("Done"); } } diff --git a/examples/src/main/java/io/dapr/examples/exception/README.md b/examples/src/main/java/io/dapr/examples/exception/README.md index bae6a0d7a3..3ee57ab9d3 100644 --- a/examples/src/main/java/io/dapr/examples/exception/README.md +++ b/examples/src/main/java/io/dapr/examples/exception/README.md @@ -32,156 +32,40 @@ Then get into the examples directory: cd examples ``` -### Examples -#### Running the State Client -This example uses the Java SDK Dapr client in order to perform an invalid operation, causing Dapr runtime to return an error. See the code snippet below: +### Understanding the code + +This example uses the Java SDK Dapr client in order to perform an invalid operation, causing Dapr runtime to return an error. See the code snippet below, from `Client.java`: ```java public class Client { public static void main(String[] args) throws Exception { try (DaprClient client = new DaprClientBuilder().build()) { - try { - client.getState("Unknown state store", "myKey", String.class).block(); + client.publishEvent("unknown_pubsub", "mytopic", "mydata").block(); } catch (DaprException exception) { - System.out.println("Error code: " + exception.getErrorCode()); - System.out.println("Error message: " + exception.getMessage()); - - exception.printStackTrace(); + System.out.println("Dapr exception's error code: " + exception.getErrorCode()); + System.out.println("Dapr exception's message: " + exception.getMessage()); + System.out.println("Dapr exception's reason: " + exception.getStatusDetails().get( + DaprErrorDetails.ErrorDetailType.ERROR_INFO, + "reason", + TypeRef.STRING)); } - - System.out.println("Done"); } + System.out.println("Done"); } } -``` -The code uses the `DaprClient` created by the `DaprClientBuilder`. It tries to get a state from state store, but provides an unknown state store. It causes the Dapr sidecar to return an error, which is converted to a `DaprException` to the application. To be compatible with Project Reactor, `DaprException` extends from `RuntimeException` - making it an unchecked exception. Applications might also get an `IllegalArgumentException` when invoking methods with invalid input parameters that are validated at the client side. - -The Dapr client is also within a try-with-resource block to properly close the client at the end. - -#### Running the PubSub Client - -##### Parsing the Error Details - -This example uses the Java SDK Dapr client in order to perform an invalid operation, causing Dapr runtime to return an error. See the code snippet below: - -```java -public class Client { - public static void main(String[] args) throws Exception { - try (DaprClient client = new DaprClientBuilder().build()) { - try { - client.publishEvent("", "", "").block(); - } catch (DaprException exception) { - System.out.println("Error code: " + exception.getErrorCode()); - System.out.println("Error message: " + exception.getMessage()); - - try { - Map detailsMap = exception.getStatusDetails(); - if (detailsMap != null && detailsMap.containsKey("details")) { - Object detailsObject = detailsMap.get("details"); - if (detailsObject instanceof List) { - List> innerDetailsList = (List>) detailsObject; - System.out.println("Error Details: "); - - for (Map innerDetails : innerDetailsList) { - - if (innerDetails.containsKey("@type") && innerDetails.get("@type").equals("type.googleapis.com/google.rpc.ErrorInfo")) { - System.out.println("\tError Detail is of type: Error_Info"); - // Implement specific logic based on specific error type - } - - for (Map.Entry entry : innerDetails.entrySet()) { - System.out.println("\t" + entry.getKey() + ": " + entry.getValue()); - } - System.out.println(); // separate error details with newline - } - } - } - System.out.println("Error Details: " + exception.getStatusDetails()); - } catch (RuntimeException e) { - System.out.println("Error Details: NULL"); - } - exception.printStackTrace(); - } - - System.out.println("Done"); - } - } -} -``` - -### Running the examples - -#### Run the State Example -1. Uncomment out this line: `client.getState("Unknown state store", "myKey", String.class).block();` -2. Comment out the publishEvent line: `client.publishEvent("", "", "").block();` -3. Run this example with the following command - -```bash -dapr run --app-id exception-example -- java -jar target/dapr-java-sdk-examples-exec.jar io.dapr.examples.exception.Client -``` - - - -Once running, the State Client Example should print the output as follows: - -```txt -== APP == Error code: INVALID_ARGUMENT - -== APP == Error message: INVALID_ARGUMENT: state store Unknown state store is not found - -== APP == io.dapr.exceptions.DaprException: INVALID_ARGUMENT: state store Unknown state store is not found - -== APP == at io.dapr.exceptions.DaprException.propagate(DaprException.java:168) - -== APP == at io.dapr.client.DaprClientGrpc$2.onError(DaprClientGrpc.java:716) - -== APP == at io.grpc.stub.ClientCalls$StreamObserverToCallListenerAdapter.onClose(ClientCalls.java:478) - -== APP == at io.grpc.internal.DelayedClientCall$DelayedListener$3.run(DelayedClientCall.java:464) - -== APP == at io.grpc.internal.DelayedClientCall$DelayedListener.delayOrExecute(DelayedClientCall.java:428) - -== APP == at io.grpc.internal.DelayedClientCall$DelayedListener.onClose(DelayedClientCall.java:461) - -== APP == at io.grpc.internal.ClientCallImpl.closeObserver(ClientCallImpl.java:617) - -== APP == at io.grpc.internal.ClientCallImpl.access$300(ClientCallImpl.java:70) - -== APP == at io.grpc.internal.ClientCallImpl$ClientStreamListenerImpl$1StreamClosed.runInternal(ClientCallImpl.java:803) - -== APP == at io.grpc.internal.ClientCallImpl$ClientStreamListenerImpl$1StreamClosed.runInContext(ClientCallImpl.java:782) - -== APP == at io.grpc.internal.ContextRunnable.run(ContextRunnable.java:37) - -== APP == at io.grpc.internal.SerializingExecutor.run(SerializingExecutor.java:123) -... - ``` -#### Run the PubSub Example -1. Run this example with the following command +### Running the example @@ -192,38 +76,30 @@ dapr run --app-id exception-example -- java -jar target/dapr-java-sdk-examples-e -Once running, the PubSub Client Example should print the output as follows: +Once running, the State Client Example should print the output as follows: ```txt -== APP == Error code: INVALID_ARGUMENT -== APP == Error message: INVALID_ARGUMENT: pubsub name is empty -== APP == Error Detail is of type: Error_Info -== APP == Error Details: -== APP == reason: DAPR_PUBSUB_NAME_EMPTY -== APP == metadata: null -== APP == @type: type.googleapis.com/google.rpc.ErrorInfo -== APP == domain: dapr.io -== APP == Error Details: -== APP == owner: -== APP == @type: type.googleapis.com/google.rpc.ResourceInfo -== APP == description: pubsub name is empty -== APP == resourceName: -== APP == resourceType: pubsub -== APP == Error Details: {details=[{reason=DAPR_PUBSUB_NAME_EMPTY, metadata=null, @type=type.googleapis.com/google.rpc.ErrorInfo, domain=dapr.io}, {owner=, @type=type.googleapis.com/google.rpc.ResourceInfo, description=pubsub name is empty, resourceName=, resourceType=pubsub}]} -== APP == io.dapr.exceptions.DaprException: INVALID_ARGUMENT: pubsub name is empty +== APP == Dapr exception's error code: INVALID_ARGUMENT + +== APP == Dapr exception's error message: INVALID_ARGUMENT: state store Unknown state store is not found + +== APP == Dapr exception's error reason: DAPR_PUBSUB_NOT_FOUND ... + ``` ### Debug -If you would like to debug the Client code. Simply follow these steps: +You can further explore all the error details returned in the `DaprException` class. +Before running it in your favorite IDE (like IntelliJ), compile and run the Dapr sidecar first. + 1. Pre-req: ```sh # make sure you are in the `java-sdk` (root) directory. ./mvnw clean install ``` -2. From the examples directory, run: `dapr run --dapr-grpc-port=50001` -3. From your IDE click the play button on the Client code and put break points where desired +2. From the examples directory, run: `dapr run --app-id exception-example --dapr-grpc-port=50001 --dapr-http-port=3500` +3. From your IDE click the play button on the client code and put break points where desired. ### Cleanup diff --git a/sdk-tests/src/test/java/io/dapr/it/TestUtils.java b/sdk-tests/src/test/java/io/dapr/it/TestUtils.java index d9d74394f1..a9ef0354df 100644 --- a/sdk-tests/src/test/java/io/dapr/it/TestUtils.java +++ b/sdk-tests/src/test/java/io/dapr/it/TestUtils.java @@ -13,7 +13,9 @@ package io.dapr.it; +import io.dapr.exceptions.DaprErrorDetails; import io.dapr.exceptions.DaprException; +import io.dapr.utils.TypeRef; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.function.Executable; @@ -42,6 +44,24 @@ public static void assertThrowsDaprException( Assertions.assertEquals(expectedErrorMessage, daprException.getMessage()); } + public static void assertThrowsDaprExceptionWithReason( + String expectedErrorCode, + String expectedErrorMessage, + String expectedReason, + Executable executable) { + DaprException daprException = Assertions.assertThrows(DaprException.class, executable); + Assertions.assertEquals(expectedErrorCode, daprException.getErrorCode()); + Assertions.assertEquals(expectedErrorMessage, daprException.getMessage()); + Assertions.assertNotNull(daprException.getStatusDetails()); + Assertions.assertEquals( + expectedReason, + daprException.getStatusDetails().get( + DaprErrorDetails.ErrorDetailType.ERROR_INFO, + "reason", + TypeRef.STRING + )); + } + public static void assertThrowsDaprExceptionSubstring( String expectedErrorCode, String expectedErrorMessageSubstring, 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 4490e17158..bdde4d1124 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 @@ -32,19 +32,13 @@ import io.dapr.it.DaprRun; import io.dapr.serializer.DaprObjectSerializer; import io.dapr.utils.TypeRef; -import org.junit.After; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.Assertions; -import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.ValueSource; -import org.junit.runner.RunWith; -import org.junit.runners.Parameterized; import java.io.IOException; import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collection; import java.util.Collections; import java.util.HashSet; import java.util.Iterator; @@ -56,6 +50,7 @@ import static io.dapr.it.Retry.callWithRetry; import static io.dapr.it.TestUtils.assertThrowsDaprException; +import static io.dapr.it.TestUtils.assertThrowsDaprExceptionWithReason; import static org.junit.jupiter.api.Assertions.assertArrayEquals; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; @@ -119,14 +114,16 @@ public void publishPubSubNotFound(boolean useGrpc) throws Exception { try (DaprClient client = new DaprClientBuilder().build()) { if (useGrpc) { - assertThrowsDaprException( + assertThrowsDaprExceptionWithReason( "INVALID_ARGUMENT", "INVALID_ARGUMENT: pubsub unknown pubsub not found", + "DAPR_PUBSUB_NOT_FOUND", () -> client.publishEvent("unknown pubsub", "mytopic", "payload").block()); } else { - assertThrowsDaprException( + assertThrowsDaprExceptionWithReason( "ERR_PUBSUB_NOT_FOUND", "ERR_PUBSUB_NOT_FOUND: pubsub unknown pubsub not found", + "DAPR_PUBSUB_NOT_FOUND", () -> client.publishEvent("unknown pubsub", "mytopic", "payload").block()); } } diff --git a/sdk/src/main/java/io/dapr/client/DaprHttp.java b/sdk/src/main/java/io/dapr/client/DaprHttp.java index 7c02f59ede..2102fa84c2 100644 --- a/sdk/src/main/java/io/dapr/client/DaprHttp.java +++ b/sdk/src/main/java/io/dapr/client/DaprHttp.java @@ -17,8 +17,8 @@ import io.dapr.client.domain.Metadata; import io.dapr.config.Properties; import io.dapr.exceptions.DaprError; +import io.dapr.exceptions.DaprErrorDetails; import io.dapr.exceptions.DaprException; -import io.dapr.exceptions.DetailObjectMapper; import io.dapr.utils.Version; import okhttp3.Call; import okhttp3.Callback; @@ -74,6 +74,11 @@ public class DaprHttp implements AutoCloseable { private static final Set ALLOWED_CONTEXT_IN_HEADERS = Collections.unmodifiableSet(new HashSet<>(Arrays.asList("grpc-trace-bin", "traceparent", "tracestate"))); + /** + * Object mapper to parse DaprError with or without details. + */ + private static final ObjectMapper DAPR_ERROR_DETAILS_OBJECT_MAPPER = new ObjectMapper(); + /** * HTTP Methods supported. */ @@ -137,13 +142,6 @@ public int getStatusCode() { */ private static final byte[] EMPTY_BYTES = new byte[0]; - /** - * JSON Object Mapper. - */ - private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper(); - - - private static final DetailObjectMapper DETAIL_OBJECT_MAPPER = new DetailObjectMapper(); /** * Endpoint used to communicate to Dapr's HTTP endpoint. */ @@ -350,12 +348,13 @@ private static DaprError parseDaprError(byte[] json) { } try { - return DetailObjectMapper.OBJECT_MAPPER.readValue(json, DaprError.class); + return DAPR_ERROR_DETAILS_OBJECT_MAPPER.readValue(json, DaprError.class); } catch (IOException e) { throw new DaprException("UNKNOWN", new String(json, StandardCharsets.UTF_8)); } } + private static byte[] getBodyBytesOrEmptyArray(okhttp3.Response response) throws IOException { ResponseBody body = response.body(); if (body != null) { diff --git a/sdk/src/main/java/io/dapr/exceptions/DaprError.java b/sdk/src/main/java/io/dapr/exceptions/DaprError.java index a67f4e40c6..e6c613203e 100644 --- a/sdk/src/main/java/io/dapr/exceptions/DaprError.java +++ b/sdk/src/main/java/io/dapr/exceptions/DaprError.java @@ -14,10 +14,8 @@ package io.dapr.exceptions; import com.fasterxml.jackson.annotation.JsonAutoDetect; -import com.google.protobuf.Any; -import com.google.rpc.Status; -import java.util.ArrayList; +import java.util.Collections; import java.util.List; import java.util.Map; @@ -43,9 +41,9 @@ public class DaprError { private Integer code; /** - * Error status details. + * Details about the error. */ - private List> statusDetails; + private List> details; /** * Gets the error code. @@ -91,87 +89,23 @@ public DaprError setMessage(String message) { } /** - * Sets the status details for the error. + * Gets the error details. * - * @param statusDetails Status details to set. - * @return This instance. + * @return Error details. */ - public DaprError setStatusDetails(Status statusDetails) { - if (statusDetails != null) { - this.statusDetails = parseStatusDetails(statusDetails); - } - return this; + public List> getDetails() { + return details; } /** - * Gets the status details for the error. + * Sets the error details. * - * @return Status details. + * @param details Error details. + * @return This instance. */ - public List> getStatusDetails() { - return statusDetails; + public DaprError setDetails(List> details) { + this.details = Collections.unmodifiableList(details); + return this; } - /** - * Parses status details from a gRPC Status. - * - * @param status The gRPC Status to parse details from. - * @return List containing parsed status details. - */ - public static List> parseStatusDetails(Status status) { - List> detailsList = new ArrayList<>(); - if (status == null || status.getDetailsList() == null) { - return detailsList; - } - - List grpcDetailsList = status.getDetailsList(); - for (Any detail : grpcDetailsList) { - try { - if (detail.is(com.google.rpc.ErrorInfo.class)) { - com.google.rpc.ErrorInfo errorInfo = detail.unpack(com.google.rpc.ErrorInfo.class); - detailsList.add(ProtoMessageConverter.messageToMap(errorInfo)); - } - if (detail.is(com.google.rpc.RetryInfo.class)) { - com.google.rpc.RetryInfo retryInfo = detail.unpack(com.google.rpc.RetryInfo.class); - detailsList.add(ProtoMessageConverter.messageToMap(retryInfo)); - } - if (detail.is(com.google.rpc.DebugInfo.class)) { - com.google.rpc.DebugInfo debugInfo = detail.unpack(com.google.rpc.DebugInfo.class); - detailsList.add(ProtoMessageConverter.messageToMap(debugInfo)); - } - if (detail.is(com.google.rpc.QuotaFailure.class)) { - com.google.rpc.QuotaFailure quotaFailure = detail.unpack(com.google.rpc.QuotaFailure.class); - detailsList.add(ProtoMessageConverter.messageToMap(quotaFailure)); - } - if (detail.is(com.google.rpc.PreconditionFailure.class)) { - com.google.rpc.PreconditionFailure preconditionFailure = detail.unpack( - com.google.rpc.PreconditionFailure.class); - detailsList.add(ProtoMessageConverter.messageToMap(preconditionFailure)); - } - if (detail.is(com.google.rpc.BadRequest.class)) { - com.google.rpc.BadRequest badRequest = detail.unpack(com.google.rpc.BadRequest.class); - detailsList.add(ProtoMessageConverter.messageToMap(badRequest)); - } - if (detail.is(com.google.rpc.RequestInfo.class)) { - com.google.rpc.RequestInfo requestInfo = detail.unpack(com.google.rpc.RequestInfo.class); - detailsList.add(ProtoMessageConverter.messageToMap(requestInfo)); - } - if (detail.is(com.google.rpc.ResourceInfo.class)) { - com.google.rpc.ResourceInfo resourceInfo = detail.unpack(com.google.rpc.ResourceInfo.class); - detailsList.add(ProtoMessageConverter.messageToMap(resourceInfo)); - } - if (detail.is(com.google.rpc.Help.class)) { - com.google.rpc.Help help = detail.unpack(com.google.rpc.Help.class); - detailsList.add(ProtoMessageConverter.messageToMap(help)); - } - if (detail.is(com.google.rpc.LocalizedMessage.class)) { - com.google.rpc.LocalizedMessage localizedMessage = detail.unpack(com.google.rpc.LocalizedMessage.class); - detailsList.add(ProtoMessageConverter.messageToMap(localizedMessage)); - } - } catch (Exception e) { - e.printStackTrace(); - } - } - return detailsList; - } } diff --git a/sdk/src/main/java/io/dapr/exceptions/DaprErrorDetails.java b/sdk/src/main/java/io/dapr/exceptions/DaprErrorDetails.java new file mode 100644 index 0000000000..f78dc31e63 --- /dev/null +++ b/sdk/src/main/java/io/dapr/exceptions/DaprErrorDetails.java @@ -0,0 +1,223 @@ +/* + * Copyright 2024 The Dapr Authors + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and +limitations under the License. +*/ + +package io.dapr.exceptions; + +import com.google.protobuf.Any; +import com.google.protobuf.InvalidProtocolBufferException; +import com.google.protobuf.Message; +import com.google.rpc.Status; +import io.dapr.utils.TypeRef; + +import java.lang.reflect.Field; +import java.lang.reflect.Modifier; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.stream.Collectors; + +public class DaprErrorDetails { + + static final DaprErrorDetails EMPTY_INSTANCE = new DaprErrorDetails((Status) null); + + private static final Map, ErrorDetailType> SUPPORTED_ERROR_TYPES = + Collections.unmodifiableMap(new HashMap<>() { + { + put(com.google.rpc.ErrorInfo.class, ErrorDetailType.ERROR_INFO); + put(com.google.rpc.RetryInfo.class, ErrorDetailType.RETRY_INFO); + put(com.google.rpc.DebugInfo.class, ErrorDetailType.DEBUG_INFO); + put(com.google.rpc.QuotaFailure.class, ErrorDetailType.QUOTA_FAILURE); + put(com.google.rpc.PreconditionFailure.class, ErrorDetailType.PRECONDITION_FAILURE); + put(com.google.rpc.BadRequest.class, ErrorDetailType.BAD_REQUEST); + put(com.google.rpc.RequestInfo.class, ErrorDetailType.REQUEST_INFO); + put(com.google.rpc.ResourceInfo.class, ErrorDetailType.RESOURCE_INFO); + put(com.google.rpc.Help.class, ErrorDetailType.HELP); + put(com.google.rpc.LocalizedMessage.class, ErrorDetailType.LOCALIZED_MESSAGE); + } + }); + + private static final Map> ERROR_TYPES_FQN_REVERSE_LOOKUP = + SUPPORTED_ERROR_TYPES.keySet().stream().collect(Collectors.toMap( + item -> generateErrorTypeFqn(item), + item -> item + )); + + /** + * Error status details. + */ + private final Map> map; + + public DaprErrorDetails(Status grpcStatus) { + this.map = parse(grpcStatus); + } + + public DaprErrorDetails(List> entries) { + this.map = parse(entries); + } + + /** + * Gets an attribute of an error detail. + * @param errorDetailType Type of the error detail. + * @param errAttribute Attribute of the error detail. + * @param typeRef Type of the value expected to be returned. + * @param Type of the value to be returned. + * @return Value of the attribute or null if not found. + */ + public T get(ErrorDetailType errorDetailType, String errAttribute, TypeRef typeRef) { + Map dictionary = map.get(errorDetailType); + if (dictionary == null) { + return null; + } + + return (T) dictionary.get(errAttribute); + } + + /** + * Parses status details from a gRPC Status. + * + * @param status The gRPC Status to parse details from. + * @return List containing parsed status details. + */ + private static Map> parse(Status status) { + if (status == null || status.getDetailsList() == null) { + return Collections.emptyMap(); + } + + Map> detailsList = new HashMap<>(); + List grpcDetailsList = status.getDetailsList(); + for (Any detail : grpcDetailsList) { + for (Map.Entry, ErrorDetailType> + supportedClazzAndType : SUPPORTED_ERROR_TYPES.entrySet()) { + Class clazz = supportedClazzAndType.getKey(); + ErrorDetailType errorDetailType = supportedClazzAndType.getValue(); + if (detail.is(clazz)) { + detailsList.put(errorDetailType, parseProtoMessage(detail, clazz)); + } + } + } + return Collections.unmodifiableMap(detailsList); + } + + private static Map> parse(List> entries) { + if ((entries == null) || entries.isEmpty()) { + return Collections.emptyMap(); + } + + Map> detailsList = new HashMap<>(); + for (Map entry : entries) { + Object type = entry.getOrDefault("@type", ""); + if (type == null) { + continue; + } + + Class clazz = ERROR_TYPES_FQN_REVERSE_LOOKUP.get(type.toString()); + if (clazz == null) { + continue; + } + + ErrorDetailType errorDetailType = SUPPORTED_ERROR_TYPES.get(clazz); + if (errorDetailType == null) { + continue; + } + + detailsList.put(errorDetailType, entry); + } + return Collections.unmodifiableMap(detailsList); + } + + private static Map parseProtoMessage( + Any detail, Class clazz) { + try { + T message = detail.unpack(clazz); + return messageToMap(message); + } catch (InvalidProtocolBufferException e) { + return Collections.singletonMap(e.getClass().getSimpleName(), e.getMessage()); + } + } + + /** + * Converts a Protocol Buffer (proto) message to a Map. + * + * @param message The proto message to be converted. + * @return A Map representing the fields of the proto message. + */ + private static Map messageToMap(Message message) { + Map result = new HashMap<>(); + Field[] fields = message.getClass().getDeclaredFields(); + + result.put("@type", generateErrorTypeFqn(message.getClass())); + + for (Field field : fields) { + if (field.isSynthetic() || Modifier.isStatic(field.getModifiers())) { + continue; + } + + String normalizedFieldName = field.getName().replaceAll("_$", ""); + try { + field.setAccessible(true); + Object value = field.get(message); + result.put(normalizedFieldName, value); + } catch (IllegalAccessException e) { + // no-op, just ignore this attribute. + } + } + + return Collections.unmodifiableMap(result); + } + + private static String generateErrorTypeFqn(Class clazz) { + String className = clazz.getName(); + + // trim the 'com.' to match the kit error details returned to users + return "type.googleapis.com/" + (className.startsWith("com.") ? className.substring(4) : className); + } + + /** + * {@inheritDoc} + */ + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + DaprErrorDetails that = (DaprErrorDetails) o; + return Objects.equals(map, that.map); + } + + /** + * {@inheritDoc} + */ + @Override + public int hashCode() { + return Objects.hash(map); + } + + public enum ErrorDetailType { + ERROR_INFO, + RETRY_INFO, + DEBUG_INFO, + QUOTA_FAILURE, + PRECONDITION_FAILURE, + BAD_REQUEST, + REQUEST_INFO, + RESOURCE_INFO, + HELP, + LOCALIZED_MESSAGE, + } + +} \ No newline at end of file diff --git a/sdk/src/main/java/io/dapr/exceptions/DaprException.java b/sdk/src/main/java/io/dapr/exceptions/DaprException.java index cfd1f90823..f87eb79e7a 100644 --- a/sdk/src/main/java/io/dapr/exceptions/DaprException.java +++ b/sdk/src/main/java/io/dapr/exceptions/DaprException.java @@ -18,7 +18,6 @@ import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; -import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.concurrent.Callable; @@ -36,7 +35,7 @@ public class DaprException extends RuntimeException { /** * The status details for the error. */ - private Map statusDetails; + private DaprErrorDetails errorDetails; /** * New exception from a server-side generated error code and message. @@ -44,7 +43,7 @@ public class DaprException extends RuntimeException { * @param daprError Server-side error. */ public DaprException(DaprError daprError) { - this(daprError.getErrorCode(), daprError.getMessage()); + this(daprError.getErrorCode(), daprError.getMessage(), daprError.getDetails()); } /** @@ -74,8 +73,31 @@ public DaprException(Throwable exception) { * @param message Client-side error message. */ public DaprException(String errorCode, String message) { + this(errorCode, message, DaprErrorDetails.EMPTY_INSTANCE); + } + + /** + * 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. + */ + public DaprException(String errorCode, String message, List> errorDetails) { + this(errorCode, message, new DaprErrorDetails(errorDetails)); + } + + /** + * 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. + */ + public DaprException(String errorCode, String message, DaprErrorDetails errorDetails) { super(String.format("%s: %s", errorCode, message)); this.errorCode = errorCode; + this.errorDetails = errorDetails; } /** @@ -92,7 +114,6 @@ public DaprException(String errorCode, String message, Throwable cause) { this.errorCode = errorCode; } - /** * New exception from a server-side generated error code and message. * @param errorCode Client-side error code. @@ -101,14 +122,12 @@ public DaprException(String errorCode, String message, Throwable cause) { * {@link #getCause()} method). (A {@code null} value is * permitted, and indicates that the cause is nonexistent or * unknown.) - * @param statusDetails the status details for the error. + * @param errorDetails the status details for the error. */ - public DaprException(String errorCode, String message, Throwable cause, Map statusDetails) { + public DaprException(String errorCode, String message, Throwable cause, DaprErrorDetails errorDetails) { super(String.format("%s: %s", errorCode, emptyIfNull(message)), cause); this.errorCode = errorCode; - if (statusDetails != null) { - this.statusDetails = statusDetails; - } + this.errorDetails = errorDetails == null ? DaprErrorDetails.EMPTY_INSTANCE : errorDetails; } /** @@ -120,8 +139,8 @@ public String getErrorCode() { return this.errorCode; } - public Map getStatusDetails() { - return this.statusDetails; + public DaprErrorDetails getStatusDetails() { + return this.errorDetails; } /** @@ -222,15 +241,13 @@ public static RuntimeException propagate(Throwable exception) { StatusRuntimeException statusRuntimeException = (StatusRuntimeException) e; com.google.rpc.Status status = io.grpc.protobuf.StatusProto.fromThrowable(statusRuntimeException); - List> detailsList = DaprError.parseStatusDetails(status); - Map statusDetails = new HashMap<>(); - statusDetails.put("details", detailsList); + DaprErrorDetails errorDetails = new DaprErrorDetails(status); return new DaprException( statusRuntimeException.getStatus().getCode().toString(), statusRuntimeException.getStatus().getDescription(), exception, - statusDetails); + errorDetails); } e = e.getCause(); diff --git a/sdk/src/main/java/io/dapr/exceptions/DetailDeserializer.java b/sdk/src/main/java/io/dapr/exceptions/DetailDeserializer.java deleted file mode 100644 index 5a30937763..0000000000 --- a/sdk/src/main/java/io/dapr/exceptions/DetailDeserializer.java +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright 2024 The Dapr Authors - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * http://www.apache.org/licenses/LICENSE-2.0 - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and -limitations under the License. -*/ - -package io.dapr.exceptions; - -import com.fasterxml.jackson.databind.DeserializationContext; -import com.fasterxml.jackson.databind.KeyDeserializer; - -public class DetailDeserializer extends KeyDeserializer { - - @Override - public Object deserializeKey(String key, DeserializationContext ctxt) { - return key; - } -} diff --git a/sdk/src/main/java/io/dapr/exceptions/DetailObjectMapper.java b/sdk/src/main/java/io/dapr/exceptions/DetailObjectMapper.java deleted file mode 100644 index 716bb29239..0000000000 --- a/sdk/src/main/java/io/dapr/exceptions/DetailObjectMapper.java +++ /dev/null @@ -1,29 +0,0 @@ -/* - * Copyright 2024 The Dapr Authors - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * http://www.apache.org/licenses/LICENSE-2.0 - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and -limitations under the License. -*/ - -package io.dapr.exceptions; - -import com.fasterxml.jackson.databind.ObjectMapper; -import com.fasterxml.jackson.databind.module.SimpleModule; -import com.google.protobuf.Descriptors; - -public class DetailObjectMapper { - public static final ObjectMapper OBJECT_MAPPER; - - static { - SimpleModule module = new SimpleModule(); - module.addKeyDeserializer(Descriptors.FieldDescriptor.class, new DetailDeserializer()); - OBJECT_MAPPER = new ObjectMapper(); - OBJECT_MAPPER.registerModule(module); - } -} diff --git a/sdk/src/main/java/io/dapr/exceptions/ProtoMessageConverter.java b/sdk/src/main/java/io/dapr/exceptions/ProtoMessageConverter.java deleted file mode 100644 index cee1ed35dc..0000000000 --- a/sdk/src/main/java/io/dapr/exceptions/ProtoMessageConverter.java +++ /dev/null @@ -1,81 +0,0 @@ -/* - * Copyright 2024 The Dapr Authors - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * http://www.apache.org/licenses/LICENSE-2.0 - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and -limitations under the License. -*/ - -package io.dapr.exceptions; - -import com.google.protobuf.Message; - -import java.lang.reflect.Field; -import java.util.HashMap; -import java.util.Map; - -/** - * Utility class for converting Protocol Buffer (proto) messages to a Map. - */ -public class ProtoMessageConverter { - /** - * Converts a Protocol Buffer (proto) message to a Map. - * - * @param message The proto message to be converted. - * @return A Map representing the fields of the proto message. - */ - public static Map messageToMap(Message message) { - Map result = new HashMap<>(); - Field[] fields = message.getClass().getDeclaredFields(); - - // trim the 'com.' to match the kit error details returned to users - String className = message.getClass().getName(); - result.put("@type", "type.googleapis.com/" + (className.startsWith("com.") ? className.substring(4) : className)); - - for (Field field : fields) { - // The error detail fields we care about end in '_' - if (!(isSupportedField(field.getName()))) { - continue; - } - try { - field.setAccessible(true); - Object value = field.get(message); - String fieldNameMinusUnderscore = removeTrailingUnderscore(field.getName()); - result.put(fieldNameMinusUnderscore, value); - } catch (IllegalAccessException e) { - e.printStackTrace(); - } - } - - return result; - } - - /** - * Remove the trailing underscore from a string. - * - * @param input The input string. - * @return The input string without the trailing underscore. - */ - private static String removeTrailingUnderscore(String input) { - if (input.endsWith("_")) { - return input.substring(0, input.length() - 1); - } - return input; - } - - /** - * Check if the field name ends with an underscore ('_'). Those are the error detail - * fields we care about. - * - * @param fieldName The field name. - * @return True if the field name ends with an underscore, false otherwise. - */ - private static boolean isSupportedField(String fieldName) { - return fieldName.endsWith("_"); - } -} diff --git a/sdk/src/test/java/io/dapr/client/DaprExceptionTest.java b/sdk/src/test/java/io/dapr/client/DaprExceptionTest.java index 3a772ef80e..7310480d8b 100644 --- a/sdk/src/test/java/io/dapr/client/DaprExceptionTest.java +++ b/sdk/src/test/java/io/dapr/client/DaprExceptionTest.java @@ -1,11 +1,25 @@ +/* + * Copyright 2024 The Dapr Authors + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and +limitations under the License. +*/ + package io.dapr.client; import com.google.protobuf.Any; import com.google.rpc.ErrorInfo; import com.google.rpc.ResourceInfo; -import io.dapr.exceptions.DaprError; +import io.dapr.exceptions.DaprErrorDetails; import io.dapr.serializer.DefaultObjectSerializer; import io.dapr.v1.DaprGrpc; +import io.dapr.v1.DaprProtos; import io.grpc.StatusRuntimeException; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; @@ -13,21 +27,20 @@ import org.mockito.stubbing.Answer; import java.io.IOException; -import java.util.HashMap; -import java.util.List; -import java.util.Map; import static io.dapr.client.DaprClientGrpcTest.newStatusRuntimeException; import static io.dapr.utils.TestUtils.assertThrowsDaprException; import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.*; -import io.dapr.v1.DaprProtos; +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.doNothing; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; public class DaprExceptionTest { private GrpcChannelFacade channel; private DaprGrpc.DaprStub daprStub; private DaprClient client; - private ObjectSerializer serializer; @BeforeEach public void setup() throws IOException { @@ -37,7 +50,6 @@ public void setup() throws IOException { DaprClient grpcClient = new DaprClientGrpc( channel, daprStub, new DefaultObjectSerializer(), new DefaultObjectSerializer()); client = new DaprClientProxy(grpcClient); - serializer = new ObjectSerializer(); doNothing().when(channel).close(); } @@ -71,11 +83,7 @@ public void daprExceptionWithMultipleDetailsThrownTest() { throw newStatusRuntimeException("INVALID_ARGUMENT", "bad bad argument", status); }).when(daprStub).publishEvent(any(DaprProtos.PublishEventRequest.class), any()); - List> detailsList = DaprError.parseStatusDetails(status); - - Map expectedStatusDetails = new HashMap<>(); - expectedStatusDetails.put("details", detailsList); - + DaprErrorDetails expectedStatusDetails = new DaprErrorDetails(status); assertThrowsDaprException( StatusRuntimeException.class, @@ -102,10 +110,7 @@ public void daprExceptionWithOneDetailThrownTest() { throw newStatusRuntimeException("INVALID_ARGUMENT", "bad bad argument", status); }).when(daprStub).getState(any(DaprProtos.GetStateRequest.class), any()); - List> detailsList = DaprError.parseStatusDetails(status); - - Map expectedStatusDetails = new HashMap<>(); - expectedStatusDetails.put("details", detailsList); + DaprErrorDetails expectedStatusDetails = new DaprErrorDetails(status); assertThrowsDaprException( StatusRuntimeException.class, diff --git a/sdk/src/test/java/io/dapr/client/DaprHttpTest.java b/sdk/src/test/java/io/dapr/client/DaprHttpTest.java index a0ca430aa9..ae121e455e 100644 --- a/sdk/src/test/java/io/dapr/client/DaprHttpTest.java +++ b/sdk/src/test/java/io/dapr/client/DaprHttpTest.java @@ -13,7 +13,9 @@ package io.dapr.client; import io.dapr.config.Properties; +import io.dapr.exceptions.DaprErrorDetails; import io.dapr.exceptions.DaprException; +import io.dapr.utils.TypeRef; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.extension.ExtendWith; import reactor.test.StepVerifier; @@ -186,7 +188,7 @@ public void invokePostMethodRuntime() throws IOException { DaprHttp daprHttp = new DaprHttp(Properties.SIDECAR_IP.get(), 3500, okHttpClient); Mono mono = daprHttp.invokeApi("POST", "v1.0/state".split("/"), null, null, Context.empty()); - StepVerifier.create(mono).expectError(RuntimeException.class); + StepVerifier.create(mono).expectError(RuntimeException.class).verify(); } @Test @@ -197,7 +199,7 @@ public void invokePostDaprError() throws IOException { "{\"errorCode\":null,\"message\":null}")); DaprHttp daprHttp = new DaprHttp(Properties.SIDECAR_IP.get(), 3500, okHttpClient); Mono mono = daprHttp.invokeApi("POST", "v1.0/state".split("/"), null, null, Context.empty()); - StepVerifier.create(mono).expectError(RuntimeException.class); + StepVerifier.create(mono).expectError(RuntimeException.class).verify(); } @Test @@ -208,7 +210,36 @@ public void invokePostMethodUnknownError() throws IOException { "{\"errorCode\":\"null\",\"message\":\"null\"}")); DaprHttp daprHttp = new DaprHttp(Properties.SIDECAR_IP.get(), 3500, okHttpClient); Mono mono = daprHttp.invokeApi("POST", "v1.0/state".split("/"), null, null, Context.empty()); - StepVerifier.create(mono).expectError(RuntimeException.class); + StepVerifier.create(mono).expectError(RuntimeException.class).verify(); + } + + @Test + public void validateExceptionParsing() { + final String payload = "{" + + "\"errorCode\":\"ERR_PUBSUB_NOT_FOUND\"," + + "\"message\":\"pubsub abc is not found\"," + + "\"details\":[" + + "{" + + "\"@type\":\"type.googleapis.com/google.rpc.ErrorInfo\"," + + "\"domain\":\"dapr.io\"," + + "\"metadata\":{}," + + "\"reason\":\"DAPR_PUBSUB_NOT_FOUND\"" + + "}]}"; + mockInterceptor.addRule() + .post("http://127.0.0.1:3500/v1.0/pubsub/publish") + .respond(500, ResponseBody.create(MediaType.parse("application/json"), + payload)); + DaprHttp daprHttp = new DaprHttp(Properties.SIDECAR_IP.get(), 3500, okHttpClient); + Mono mono = daprHttp.invokeApi("POST", "v1.0/pubsub/publish".split("/"), null, null, Context.empty()); + StepVerifier.create(mono).expectErrorMatches(e -> { + assertEquals(DaprException.class, e.getClass()); + DaprException daprException = (DaprException)e; + assertEquals("ERR_PUBSUB_NOT_FOUND", daprException.getErrorCode()); + assertEquals("DAPR_PUBSUB_NOT_FOUND", + daprException.getStatusDetails() + .get(DaprErrorDetails.ErrorDetailType.ERROR_INFO, "reason", TypeRef.STRING)); + return true; + }).verify(); } /** diff --git a/sdk/src/test/java/io/dapr/utils/TestUtils.java b/sdk/src/test/java/io/dapr/utils/TestUtils.java index 6481ac920c..a53449d1a1 100644 --- a/sdk/src/test/java/io/dapr/utils/TestUtils.java +++ b/sdk/src/test/java/io/dapr/utils/TestUtils.java @@ -13,6 +13,7 @@ package io.dapr.utils; +import io.dapr.exceptions.DaprErrorDetails; import io.dapr.exceptions.DaprException; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.function.Executable; @@ -63,7 +64,7 @@ public static void assertThrowsDaprException( Class expectedType, String expectedErrorCode, String expectedErrorMessage, - Map expectedStatusDetails, + DaprErrorDetails expectedStatusDetails, Executable executable) { DaprException daprException = Assertions.assertThrows(DaprException.class, executable); Assertions.assertNotNull(daprException.getCause()); From c23c685b839403ef1c063c328515c1940273b6a8 Mon Sep 17 00:00:00 2001 From: Artur Souza Date: Mon, 12 Feb 2024 16:15:29 -0800 Subject: [PATCH 11/17] Update daprdocs too for DaprErrorDetails. Signed-off-by: Artur Souza --- .../en/java-sdk-docs/java-client/_index.md | 39 ++++--------------- 1 file changed, 8 insertions(+), 31 deletions(-) diff --git a/daprdocs/content/en/java-sdk-docs/java-client/_index.md b/daprdocs/content/en/java-sdk-docs/java-client/_index.md index 5409a79d37..dfe6c28418 100644 --- a/daprdocs/content/en/java-sdk-docs/java-client/_index.md +++ b/daprdocs/content/en/java-sdk-docs/java-client/_index.md @@ -51,38 +51,15 @@ Example of handling the DaprException and consuming the error details when using ```java ... try { - client.publishEvent("", "", "").block(); + client.publishEvent("unknown_pubsub", "mytopic", "mydata").block(); } catch (DaprException exception) { - System.out.println("Error code: " + exception.getErrorCode()); - System.out.println("Error message: " + exception.getMessage()); - - try { - Map detailsMap = exception.getStatusDetails(); - if (detailsMap != null && detailsMap.containsKey("details")) { - Object detailsObject = detailsMap.get("details"); - if (detailsObject instanceof List) { - List> innerDetailsList = (List>) detailsObject; - System.out.println("Error Details: "); - - for (Map innerDetails : innerDetailsList) { - - if (innerDetails.containsKey("@type") && innerDetails.get("@type").equals("type.googleapis.com/google.rpc.ErrorInfo")) { - System.out.println("\tError Detail is of type: Error_Info"); - // Implement specific logic based on specific error type - } - - for (Map.Entry entry : innerDetails.entrySet()) { - System.out.println("\t" + entry.getKey() + ": " + entry.getValue()); - } - System.out.println(); // separate error details with newline - } - } - } - System.out.println("Error Details: " + exception.getStatusDetails()); - } catch (RuntimeException e) { - System.out.println("Error Details: NULL"); - } - exception.printStackTrace(); + System.out.println("Dapr exception's error code: " + exception.getErrorCode()); + System.out.println("Dapr exception's message: " + exception.getMessage()); + // DaprException now contains `getStatusDetails()` to include more details about the error from Dapr runtime. + System.out.println("Dapr exception's reason: " + exception.getStatusDetails().get( + DaprErrorDetails.ErrorDetailType.ERROR_INFO, + "reason", + TypeRef.STRING)); } ... ``` From 77975d8392918fea8b48b93d9fb0f8d8564a2c32 Mon Sep 17 00:00:00 2001 From: Artur Souza Date: Mon, 12 Feb 2024 16:29:27 -0800 Subject: [PATCH 12/17] Fix README.md mm string. Signed-off-by: Artur Souza --- examples/src/main/java/io/dapr/examples/exception/README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/examples/src/main/java/io/dapr/examples/exception/README.md b/examples/src/main/java/io/dapr/examples/exception/README.md index 3ee57ab9d3..0aff60120a 100644 --- a/examples/src/main/java/io/dapr/examples/exception/README.md +++ b/examples/src/main/java/io/dapr/examples/exception/README.md @@ -63,9 +63,9 @@ public class Client { From 896e997a1232983551d8c1cd56f781b523d46834 Mon Sep 17 00:00:00 2001 From: Artur Souza Date: Mon, 12 Feb 2024 18:05:47 -0800 Subject: [PATCH 13/17] Fix exception example. Signed-off-by: Artur Souza --- .../main/java/io/dapr/examples/exception/Client.java | 6 +++--- .../main/java/io/dapr/examples/exception/README.md | 12 ++++++------ 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/examples/src/main/java/io/dapr/examples/exception/Client.java b/examples/src/main/java/io/dapr/examples/exception/Client.java index 615a413e3c..fb69a5125c 100644 --- a/examples/src/main/java/io/dapr/examples/exception/Client.java +++ b/examples/src/main/java/io/dapr/examples/exception/Client.java @@ -43,9 +43,9 @@ public static void main(String[] args) throws Exception { try { client.publishEvent("unknown_pubsub", "mytopic", "mydata").block(); } catch (DaprException exception) { - System.out.println("Dapr exception's error code: " + exception.getErrorCode()); - System.out.println("Dapr exception's message: " + exception.getMessage()); - System.out.println("Dapr exception's reason: " + exception.getStatusDetails().get( + System.out.println("Error code: " + exception.getErrorCode()); + System.out.println("Error message: " + exception.getMessage()); + System.out.println("Reason: " + exception.getStatusDetails().get( DaprErrorDetails.ErrorDetailType.ERROR_INFO, "reason", TypeRef.STRING)); diff --git a/examples/src/main/java/io/dapr/examples/exception/README.md b/examples/src/main/java/io/dapr/examples/exception/README.md index 0aff60120a..93122f5174 100644 --- a/examples/src/main/java/io/dapr/examples/exception/README.md +++ b/examples/src/main/java/io/dapr/examples/exception/README.md @@ -63,9 +63,9 @@ public class Client { @@ -79,11 +79,11 @@ dapr run --app-id exception-example -- java -jar target/dapr-java-sdk-examples-e Once running, the State Client Example should print the output as follows: ```txt -== APP == Dapr exception's error code: INVALID_ARGUMENT +== APP == Error code: ERR_PUBSUB_NOT_FOUND -== APP == Dapr exception's error message: INVALID_ARGUMENT: state store Unknown state store is not found +== APP == Error message: ERR_PUBSUB_NOT_FOUND: pubsub unknown_pubsub is not found -== APP == Dapr exception's error reason: DAPR_PUBSUB_NOT_FOUND +== APP == Reason: DAPR_PUBSUB_NOT_FOUND ... ``` From 84ebb63b7436aef60ad036d79672eb1d7d4e7bf5 Mon Sep 17 00:00:00 2001 From: Artur Souza Date: Mon, 12 Feb 2024 18:06:48 -0800 Subject: [PATCH 14/17] Use runtime 1.13.0-rc.2 Signed-off-by: Artur Souza --- .github/workflows/build.yml | 2 +- .github/workflows/validate.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index ba34ff9af1..3ce920951d 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -42,7 +42,7 @@ jobs: GOPROXY: https://proxy.golang.org JDK_VER: ${{ matrix.java }} DAPR_CLI_VER: 1.12.0 - DAPR_RUNTIME_VER: 1.12.4 + DAPR_RUNTIME_VER: 1.13.0-rc.2 DAPR_INSTALL_URL: https://raw.githubusercontent.com/dapr/cli/v1.12.0/install/install.sh DAPR_CLI_REF: DAPR_REF: diff --git a/.github/workflows/validate.yml b/.github/workflows/validate.yml index d476892c9b..d5c3816b42 100644 --- a/.github/workflows/validate.yml +++ b/.github/workflows/validate.yml @@ -38,7 +38,7 @@ jobs: GOPROXY: https://proxy.golang.org JDK_VER: ${{ matrix.java }} DAPR_CLI_VER: 1.12.0 - DAPR_RUNTIME_VER: 1.12.4 + DAPR_RUNTIME_VER: 1.13.0-rc.2 DAPR_INSTALL_URL: https://raw.githubusercontent.com/dapr/cli/v1.12.0/install/install.sh DAPR_CLI_REF: DAPR_REF: From 41a36ac14d15ad865c054863d39c099b4e6d6fe5 Mon Sep 17 00:00:00 2001 From: Artur Souza Date: Mon, 12 Feb 2024 18:27:15 -0800 Subject: [PATCH 15/17] Fix exception example to match gRPC output. Signed-off-by: Artur Souza --- examples/src/main/java/io/dapr/examples/exception/README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/src/main/java/io/dapr/examples/exception/README.md b/examples/src/main/java/io/dapr/examples/exception/README.md index 93122f5174..ff1e6182e3 100644 --- a/examples/src/main/java/io/dapr/examples/exception/README.md +++ b/examples/src/main/java/io/dapr/examples/exception/README.md @@ -63,8 +63,8 @@ public class Client {