This is an automated email from the ASF dual-hosted git repository. sk0x50 pushed a commit to branch main in repository https://gitbox.apache.org/repos/asf/ignite-3.git
The following commit(s) were added to refs/heads/main by this push: new b2f470a3f IGNITE-17281 Added support error codes and trace ids to rest components. Fixes #926 b2f470a3f is described below commit b2f470a3f60ef0963c6773b02ebda5b34115f16e Author: Slava Koptilin <slava.kopti...@gmail.com> AuthorDate: Fri Jul 8 00:45:10 2022 +0300 IGNITE-17281 Added support error codes and trace ids to rest components. Fixes #926 --- .../apache/ignite/lang/IgniteCheckedException.java | 11 ++++ .../org/apache/ignite/lang/IgniteException.java | 11 ++++ .../IgniteInternalCheckedExceptionHandler.java | 5 +- .../handler/IgniteInternalExceptionHandler.java | 5 +- .../ConfigurationControllerBaseTest.java | 8 ++- .../java/org/apache/ignite/lang/ErrorGroup.java | 13 ++++- .../lang/IgniteInternalCheckedException.java | 11 ++++ .../ignite/lang/IgniteInternalException.java | 11 ++++ .../exception/handler/IgniteExceptionHandler.java | 18 +++--- .../handler/IgniteExceptionHandlerTest.java | 66 ++++++++++++++++------ 10 files changed, 128 insertions(+), 31 deletions(-) diff --git a/modules/api/src/main/java/org/apache/ignite/lang/IgniteCheckedException.java b/modules/api/src/main/java/org/apache/ignite/lang/IgniteCheckedException.java index 88a8eeb16..80bc7f8a3 100755 --- a/modules/api/src/main/java/org/apache/ignite/lang/IgniteCheckedException.java +++ b/modules/api/src/main/java/org/apache/ignite/lang/IgniteCheckedException.java @@ -17,6 +17,7 @@ package org.apache.ignite.lang; +import static org.apache.ignite.lang.ErrorGroup.ERR_PREFIX; import static org.apache.ignite.lang.ErrorGroup.errorGroupByCode; import static org.apache.ignite.lang.ErrorGroup.errorMessage; import static org.apache.ignite.lang.ErrorGroup.errorMessageFromCause; @@ -171,6 +172,16 @@ public class IgniteCheckedException extends Exception { return code; } + /** + * Returns a human-readable string represents a full error code. + * Returned string has the following format: IGN-XXX-nnn, where XXX is a group name and nnn is an unique error code within a group. + * + * @return Full error code in a human-readable format. + */ + public String codeAsString() { + return ERR_PREFIX + groupName() + '-' + errorCode(); + } + /** * Returns error group. * diff --git a/modules/api/src/main/java/org/apache/ignite/lang/IgniteException.java b/modules/api/src/main/java/org/apache/ignite/lang/IgniteException.java index 7fe71e413..0c482a684 100644 --- a/modules/api/src/main/java/org/apache/ignite/lang/IgniteException.java +++ b/modules/api/src/main/java/org/apache/ignite/lang/IgniteException.java @@ -17,6 +17,7 @@ package org.apache.ignite.lang; +import static org.apache.ignite.lang.ErrorGroup.ERR_PREFIX; import static org.apache.ignite.lang.ErrorGroup.errorGroupByCode; import static org.apache.ignite.lang.ErrorGroup.errorMessage; import static org.apache.ignite.lang.ErrorGroup.errorMessageFromCause; @@ -212,6 +213,16 @@ public class IgniteException extends RuntimeException { return code; } + /** + * Returns a human-readable string represents a full error code. + * Returned string has the following format: IGN-XXX-nnn, where XXX is a group name and nnn is an unique error code within a group. + * + * @return Full error code in a human-readable format. + */ + public String codeAsString() { + return ERR_PREFIX + groupName() + '-' + errorCode(); + } + /** * Returns error group. * diff --git a/modules/cluster-management/src/main/java/org/apache/ignite/internal/cluster/management/rest/exception/handler/IgniteInternalCheckedExceptionHandler.java b/modules/cluster-management/src/main/java/org/apache/ignite/internal/cluster/management/rest/exception/handler/IgniteInternalCheckedExceptionHandler.java index e512686a1..b1da2591a 100644 --- a/modules/cluster-management/src/main/java/org/apache/ignite/internal/cluster/management/rest/exception/handler/IgniteInternalCheckedExceptionHandler.java +++ b/modules/cluster-management/src/main/java/org/apache/ignite/internal/cluster/management/rest/exception/handler/IgniteInternalCheckedExceptionHandler.java @@ -38,7 +38,10 @@ public class IgniteInternalCheckedExceptionHandler @Override public HttpResponse<? extends Problem> handle(HttpRequest request, IgniteInternalCheckedException exception) { return HttpProblemResponse.from( - Problem.fromHttpCode(HttpCode.INTERNAL_ERROR).detail(exception.getMessage()) + Problem.fromHttpCode(HttpCode.INTERNAL_ERROR) + .traceId(exception.traceId()) + .code(exception.codeAsString()) + .detail(exception.getMessage()) ); } } diff --git a/modules/cluster-management/src/main/java/org/apache/ignite/internal/cluster/management/rest/exception/handler/IgniteInternalExceptionHandler.java b/modules/cluster-management/src/main/java/org/apache/ignite/internal/cluster/management/rest/exception/handler/IgniteInternalExceptionHandler.java index aa936cbee..e0d037064 100644 --- a/modules/cluster-management/src/main/java/org/apache/ignite/internal/cluster/management/rest/exception/handler/IgniteInternalExceptionHandler.java +++ b/modules/cluster-management/src/main/java/org/apache/ignite/internal/cluster/management/rest/exception/handler/IgniteInternalExceptionHandler.java @@ -37,7 +37,10 @@ public class IgniteInternalExceptionHandler implements ExceptionHandler<IgniteIn @Override public HttpResponse<? extends Problem> handle(HttpRequest request, IgniteInternalException exception) { return HttpProblemResponse.from( - Problem.fromHttpCode(HttpCode.INTERNAL_ERROR).detail(exception.getMessage()) + Problem.fromHttpCode(HttpCode.INTERNAL_ERROR) + .traceId(exception.traceId()) + .code(exception.codeAsString()) + .detail(exception.getMessage()) ); } } diff --git a/modules/configuration/src/test/java/org/apache/ignite/internal/rest/configuration/ConfigurationControllerBaseTest.java b/modules/configuration/src/test/java/org/apache/ignite/internal/rest/configuration/ConfigurationControllerBaseTest.java index 04f8d02e1..80d04b581 100644 --- a/modules/configuration/src/test/java/org/apache/ignite/internal/rest/configuration/ConfigurationControllerBaseTest.java +++ b/modules/configuration/src/test/java/org/apache/ignite/internal/rest/configuration/ConfigurationControllerBaseTest.java @@ -18,6 +18,8 @@ package org.apache.ignite.internal.rest.configuration; import static java.util.concurrent.TimeUnit.SECONDS; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.containsString; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertThrows; @@ -104,7 +106,7 @@ public abstract class ConfigurationControllerBaseTest { var problem = getProblem(thrown); assertEquals(400, problem.status()); - assertEquals("Configuration value 'no-such-root' has not been found", problem.detail()); + assertThat(problem.detail(), containsString("Configuration value 'no-such-root' has not been found")); } @Test @@ -120,7 +122,7 @@ public abstract class ConfigurationControllerBaseTest { var problem = getProblem(thrown); assertEquals(400, problem.status()); - assertEquals("'root.subCfg' configuration doesn't have the 'no-such-bar' sub-configuration", problem.detail()); + assertThat(problem.detail(), containsString("'root.subCfg' configuration doesn't have the 'no-such-bar' sub-configuration")); } @Test @@ -136,7 +138,7 @@ public abstract class ConfigurationControllerBaseTest { var problem = getValidationProblem(thrown); assertEquals(400, problem.status()); - assertEquals("Validation did not pass", problem.detail()); + assertThat(problem.detail(), containsString("ValidationIssue [key=root.foo, message=Error word]")); assertEquals("Error word", problem.invalidParams().stream().findFirst().get().reason()); // todo: check name and reason } diff --git a/modules/core/src/main/java/org/apache/ignite/lang/ErrorGroup.java b/modules/core/src/main/java/org/apache/ignite/lang/ErrorGroup.java index 454f34ca2..f2c288074 100755 --- a/modules/core/src/main/java/org/apache/ignite/lang/ErrorGroup.java +++ b/modules/core/src/main/java/org/apache/ignite/lang/ErrorGroup.java @@ -29,6 +29,9 @@ import java.util.UUID; * Each group can be identified by a name and an integer number that both must be unique across all error groups. */ public class ErrorGroup { + /** Additional prefix that is used in a human-readable format of ignite errors. */ + public static final String ERR_PREFIX = "IGN-"; + /** List of all registered error groups. */ private static final Int2ObjectMap<ErrorGroup> registeredGroups = new Int2ObjectOpenHashMap<>(); @@ -110,8 +113,13 @@ public class ErrorGroup { * @return New error group. * @throws IllegalArgumentException If the specified name or group code already registered. * or {@code groupCode} is greater than 0xFFFF or less than or equal to 0. + * Also, this exception is thrown if the given {@code groupName} is {@code null} or empty. */ public static synchronized ErrorGroup newGroup(String groupName, int groupCode) { + if (groupName == null || groupName.isEmpty()) { + throw new IllegalArgumentException("Group name is null or empty"); + } + String grpName = groupName.toUpperCase(Locale.ENGLISH); if (registeredGroups.containsKey(groupCode)) { @@ -187,7 +195,7 @@ public class ErrorGroup { * @return New error message with predefined prefix. */ public static String errorMessage(UUID traceId, String groupName, int code, String message) { - return "IGN-" + groupName + '-' + extractErrorCode(code) + " Trace ID:" + traceId + ((message != null) ? ' ' + message : ""); + return ERR_PREFIX + groupName + '-' + extractErrorCode(code) + " Trace ID:" + traceId + ((message != null) ? ' ' + message : ""); } /** @@ -214,9 +222,10 @@ public class ErrorGroup { public static String errorMessageFromCause(UUID traceId, String groupName, int code, Throwable cause) { String c = (cause != null && cause.getMessage() != null) ? cause.getMessage() : null; - return (c != null && c.startsWith("IGN-")) ? c : errorMessage(traceId, groupName, code, c); + return (c != null && c.startsWith(ERR_PREFIX)) ? c : errorMessage(traceId, groupName, code, c); } + /** {@inheritDoc} */ @Override public String toString() { return "ErrorGroup [name=" + name() + ", code=" + code() + ']'; diff --git a/modules/core/src/main/java/org/apache/ignite/lang/IgniteInternalCheckedException.java b/modules/core/src/main/java/org/apache/ignite/lang/IgniteInternalCheckedException.java index 2dda664b2..e12ad3a11 100644 --- a/modules/core/src/main/java/org/apache/ignite/lang/IgniteInternalCheckedException.java +++ b/modules/core/src/main/java/org/apache/ignite/lang/IgniteInternalCheckedException.java @@ -17,6 +17,7 @@ package org.apache.ignite.lang; +import static org.apache.ignite.lang.ErrorGroup.ERR_PREFIX; import static org.apache.ignite.lang.ErrorGroup.errorGroupByCode; import static org.apache.ignite.lang.ErrorGroup.errorMessage; import static org.apache.ignite.lang.ErrorGroup.errorMessageFromCause; @@ -247,6 +248,16 @@ public class IgniteInternalCheckedException extends Exception { return code; } + /** + * Returns a human-readable string represents a full error code. + * Returned string has the following format: IGN-XXX-nnn, where XXX is a group name and nnn is an unique error code within a group. + * + * @return Full error code in a human-readable format. + */ + public String codeAsString() { + return ERR_PREFIX + groupName() + '-' + errorCode(); + } + /** * Returns error group. * diff --git a/modules/core/src/main/java/org/apache/ignite/lang/IgniteInternalException.java b/modules/core/src/main/java/org/apache/ignite/lang/IgniteInternalException.java index 70e5e4e5c..c8e347867 100644 --- a/modules/core/src/main/java/org/apache/ignite/lang/IgniteInternalException.java +++ b/modules/core/src/main/java/org/apache/ignite/lang/IgniteInternalException.java @@ -17,6 +17,7 @@ package org.apache.ignite.lang; +import static org.apache.ignite.lang.ErrorGroup.ERR_PREFIX; import static org.apache.ignite.lang.ErrorGroup.errorGroupByCode; import static org.apache.ignite.lang.ErrorGroup.errorMessage; import static org.apache.ignite.lang.ErrorGroup.errorMessageFromCause; @@ -212,6 +213,16 @@ public class IgniteInternalException extends RuntimeException { return code; } + /** + * Returns a human-readable string represents a full error code. + * Returned string has the following format: IGN-XXX-nnn, where XXX is a group name and nnn is an unique error code within a group. + * + * @return Full error code in a human-readable format. + */ + public String codeAsString() { + return ERR_PREFIX + groupName() + '-' + errorCode(); + } + /** * Returns error group. * diff --git a/modules/rest-api/src/main/java/org/apache/ignite/internal/rest/exception/handler/IgniteExceptionHandler.java b/modules/rest-api/src/main/java/org/apache/ignite/internal/rest/exception/handler/IgniteExceptionHandler.java index b1ebb69ff..342e79a6e 100644 --- a/modules/rest-api/src/main/java/org/apache/ignite/internal/rest/exception/handler/IgniteExceptionHandler.java +++ b/modules/rest-api/src/main/java/org/apache/ignite/internal/rest/exception/handler/IgniteExceptionHandler.java @@ -38,27 +38,32 @@ import org.apache.ignite.lang.IgniteException; @Singleton @Requires(classes = {IgniteException.class, ExceptionHandler.class}) public class IgniteExceptionHandler implements ExceptionHandler<IgniteException, HttpResponse<? extends Problem>> { - @Override public HttpResponse<? extends Problem> handle(HttpRequest request, IgniteException exception) { - //TODO: set code, traceId when https://issues.apache.org/jira/browse/IGNITE-17281 is done - if (exception.getCause() instanceof IllegalArgumentException) { return HttpProblemResponse.from( - Problem.fromHttpCode(HttpCode.BAD_REQUEST).detail(exception.getCause().getMessage()) + Problem.fromHttpCode(HttpCode.BAD_REQUEST) + .detail(exception.getMessage()) + .traceId(exception.traceId()) + .code(exception.codeAsString()) ); } if (exception.getCause() instanceof ConfigurationValidationException) { return HttpProblemResponse.from( ValidationProblem.fromHttpCode(HttpCode.BAD_REQUEST) - .detail("Validation did not pass") + .detail(exception.getMessage()) .invalidParams(mapValidationIssuesToRestFormat((ConfigurationValidationException) exception.getCause())) + .traceId(exception.traceId()) + .code(exception.codeAsString()) ); } return HttpProblemResponse.from( - Problem.fromHttpCode(HttpCode.INTERNAL_ERROR).detail(exception.getMessage()) + Problem.fromHttpCode(HttpCode.INTERNAL_ERROR) + .detail(exception.getMessage()) + .traceId(exception.traceId()) + .code(exception.codeAsString()) ); } @@ -68,5 +73,4 @@ public class IgniteExceptionHandler implements ExceptionHandler<IgniteException, .map(validationIssue -> new InvalidParam(validationIssue.key(), validationIssue.message())) .collect(Collectors.toList()); } - } diff --git a/modules/rest-api/src/test/java/org/apache/ignite/internal/rest/exception/handler/IgniteExceptionHandlerTest.java b/modules/rest-api/src/test/java/org/apache/ignite/internal/rest/exception/handler/IgniteExceptionHandlerTest.java index 7998ae0d0..c666799f5 100644 --- a/modules/rest-api/src/test/java/org/apache/ignite/internal/rest/exception/handler/IgniteExceptionHandlerTest.java +++ b/modules/rest-api/src/test/java/org/apache/ignite/internal/rest/exception/handler/IgniteExceptionHandlerTest.java @@ -17,12 +17,17 @@ package org.apache.ignite.internal.rest.exception.handler; +import static org.apache.ignite.lang.ErrorGroup.errorMessage; +import static org.apache.ignite.lang.ErrorGroup.extractErrorCode; +import static org.apache.ignite.lang.ErrorGroups.Common.COMMON_ERR_GROUP; +import static org.apache.ignite.lang.ErrorGroups.Common.UNKNOWN_ERR; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.mockito.Mockito.mock; import io.micronaut.http.HttpRequest; import io.micronaut.http.HttpResponse; import java.util.List; +import java.util.UUID; import java.util.stream.Stream; import org.apache.ignite.configuration.validation.ConfigurationValidationException; import org.apache.ignite.configuration.validation.ValidationIssue; @@ -30,48 +35,76 @@ import org.apache.ignite.internal.rest.api.InvalidParam; import org.apache.ignite.internal.rest.api.Problem; import org.apache.ignite.internal.rest.api.Problem.ProblemBuilder; import org.apache.ignite.internal.rest.api.ValidationProblem; +import org.apache.ignite.lang.ErrorGroup; import org.apache.ignite.lang.IgniteException; import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Disabled; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; class IgniteExceptionHandlerTest { - private HttpRequest<?> request; private IgniteExceptionHandler exceptionHandler; static Stream<Arguments> igniteExceptions() { + UUID traceId = UUID.randomUUID(); + String errorMessage = errorMessage(traceId, UNKNOWN_ERR, null); + String humanReadableCode = ErrorGroup.ERR_PREFIX + COMMON_ERR_GROUP.name() + '-' + extractErrorCode(UNKNOWN_ERR); + + var invalidParams = List.of( + new InvalidParam("key1", "Some issue1"), + new InvalidParam("key2", "Some issue2")); + + var validationIssues = List.of( + new ValidationIssue("key1", "Some issue1"), + new ValidationIssue("key2", "Some issue2")); + return Stream.of( Arguments.of( // given - new IgniteException("Ooops"), + new IgniteException(traceId, UNKNOWN_ERR, "Ooops"), // expected - Problem.builder().status(500).title("Internal Server Error").detail("Ooops")), + Problem.builder() + .status(500) + .title("Internal Server Error") + .code(humanReadableCode) + .detail(errorMessage + " Ooops") + .traceId(traceId)), Arguments.of( // given - new IgniteException(), + new IgniteException(traceId, UNKNOWN_ERR), // expected - Problem.builder().status(500).title("Internal Server Error")), + Problem.builder() + .status(500) + .title("Internal Server Error") + .code(humanReadableCode) + .detail(errorMessage) + .traceId(traceId)), Arguments.of( // given - new IgniteException(new IllegalArgumentException("Illegal value")), + new IgniteException(traceId, UNKNOWN_ERR, new IllegalArgumentException("Illegal value")), // expected - Problem.builder().status(400).title("Bad Request").detail("Illegal value")), + Problem.builder() + .status(400) + .title("Bad Request") + .code(humanReadableCode) + .traceId(traceId) + .detail(errorMessage + " Illegal value")), Arguments.of( // given new IgniteException( - new ConfigurationValidationException( - List.of(new ValidationIssue("key1", "Some issue1"), - new ValidationIssue("key2", "Some issue2")))), + traceId, + UNKNOWN_ERR, + new ConfigurationValidationException(validationIssues)), // expected - ValidationProblem.builder().status(400).title("Bad Request").detail("Validation did not pass") - .invalidParams( - List.of( - new InvalidParam("key1", "Some issue1"), - new InvalidParam("key2", "Some issue2")))) + ValidationProblem.builder() + .status(400) + .title("Bad Request") + .detail(errorMessage + ' ' + validationIssues) + .code(humanReadableCode) + .traceId(traceId) + .invalidParams(invalidParams)) ); } @@ -83,7 +116,6 @@ class IgniteExceptionHandlerTest { @ParameterizedTest @MethodSource("igniteExceptions") - @Disabled("https://issues.apache.org/jira/browse/IGNITE-17305") void shouldHandleIgniteException(IgniteException givenIgniteException, ProblemBuilder<? extends Problem, ?> expectedProblem) { HttpResponse<? extends Problem> response = exceptionHandler.handle(request, givenIgniteException);