diff --git a/impl/core/src/main/java/io/serverlessworkflow/impl/executors/RaiseExecutor.java b/impl/core/src/main/java/io/serverlessworkflow/impl/executors/RaiseExecutor.java index 0d7b44ff6..46b0cfe59 100644 --- a/impl/core/src/main/java/io/serverlessworkflow/impl/executors/RaiseExecutor.java +++ b/impl/core/src/main/java/io/serverlessworkflow/impl/executors/RaiseExecutor.java @@ -16,7 +16,9 @@ package io.serverlessworkflow.impl.executors; import io.serverlessworkflow.api.types.Error; +import io.serverlessworkflow.api.types.ErrorDetails; import io.serverlessworkflow.api.types.ErrorInstance; +import io.serverlessworkflow.api.types.ErrorTitle; import io.serverlessworkflow.api.types.ErrorType; import io.serverlessworkflow.api.types.RaiseTask; import io.serverlessworkflow.api.types.RaiseTaskError; @@ -25,6 +27,7 @@ import io.serverlessworkflow.impl.WorkflowContext; import io.serverlessworkflow.impl.WorkflowDefinition; import io.serverlessworkflow.impl.WorkflowError; +import io.serverlessworkflow.impl.WorkflowError.Builder; import io.serverlessworkflow.impl.WorkflowException; import io.serverlessworkflow.impl.WorkflowModel; import io.serverlessworkflow.impl.WorkflowMutablePosition; @@ -44,8 +47,8 @@ public static class RaiseExecutorBuilder extends RegularTaskExecutorBuilder errorBuilder; private final WorkflowValueResolver typeFilter; private final Optional> instanceFilter; - private final WorkflowValueResolver titleFilter; - private final WorkflowValueResolver detailFilter; + private final Optional> titleFilter; + private final Optional> detailFilter; protected RaiseExecutorBuilder( WorkflowMutablePosition position, RaiseTask task, WorkflowDefinition definition) { @@ -57,30 +60,38 @@ protected RaiseExecutorBuilder( : findError(raiseError.getRaiseErrorReference()); this.typeFilter = getTypeFunction(application, error.getType()); this.instanceFilter = getInstanceFunction(application, error.getInstance()); + ErrorTitle title = error.getTitle(); this.titleFilter = - WorkflowUtils.buildStringFilter( - application, - error.getTitle().getExpressionErrorTitle(), - error.getTitle().getLiteralErrorTitle()); + title == null + ? Optional.empty() + : Optional.of( + WorkflowUtils.buildStringFilter( + application, title.getExpressionErrorTitle(), title.getLiteralErrorTitle())); + ErrorDetails details = error.getDetail(); this.detailFilter = - WorkflowUtils.buildStringFilter( - application, - error.getDetail().getExpressionErrorDetails(), - error.getTitle().getExpressionErrorTitle()); + details == null + ? Optional.empty() + : Optional.of( + WorkflowUtils.buildStringFilter( + application, + details.getExpressionErrorDetails(), + details.getLiteralErrorDetails())); this.errorBuilder = (w, t) -> buildError(error, w, t); } private WorkflowError buildError( Error error, WorkflowContext context, TaskContext taskContext) { - return WorkflowError.error( - typeFilter.apply(context, taskContext, taskContext.input()), error.getStatus()) - .instance( - instanceFilter - .map(f -> f.apply(context, taskContext, taskContext.input())) - .orElseGet(() -> taskContext.position().jsonPointer())) - .title(titleFilter.apply(context, taskContext, taskContext.input())) - .details(detailFilter.apply(context, taskContext, taskContext.input())) - .build(); + Builder builder = + WorkflowError.error( + typeFilter.apply(context, taskContext, taskContext.input()), error.getStatus()) + .instance( + instanceFilter + .map(f -> f.apply(context, taskContext, taskContext.input())) + .orElseGet(() -> taskContext.position().jsonPointer())); + titleFilter.ifPresent(f -> builder.title(f.apply(context, taskContext, taskContext.input()))); + detailFilter.ifPresent( + f -> builder.details(f.apply(context, taskContext, taskContext.input()))); + return builder.build(); } private Optional> getInstanceFunction( diff --git a/impl/core/src/main/java/io/serverlessworkflow/impl/executors/TryExecutor.java b/impl/core/src/main/java/io/serverlessworkflow/impl/executors/TryExecutor.java index cb52d5b29..8bfbfdb68 100644 --- a/impl/core/src/main/java/io/serverlessworkflow/impl/executors/TryExecutor.java +++ b/impl/core/src/main/java/io/serverlessworkflow/impl/executors/TryExecutor.java @@ -52,6 +52,7 @@ public class TryExecutor extends RegularTaskExecutor { private final TaskExecutor taskExecutor; private final Optional> catchTaskExecutor; private final Optional retryIntervalExecutor; + private final String errorVariable; public static class TryExecutorBuilder extends RegularTaskExecutorBuilder { @@ -61,6 +62,7 @@ public static class TryExecutorBuilder extends RegularTaskExecutorBuilder taskExecutor; private final Optional> catchTaskExecutor; private final Optional retryIntervalExecutor; + private String errorVariable; protected TryExecutorBuilder( WorkflowMutablePosition position, TryTask task, WorkflowDefinition definition) { @@ -73,8 +75,8 @@ protected TryExecutorBuilder( TaskExecutorHelper.createExecutorList(position, task.getTry(), definition); TryTaskCatch catchTask = task.getCatch(); if (catchTask != null) { + this.errorVariable = catchTask.getAs(); List catchTaskDo = catchTask.getDo(); - this.catchTaskExecutor = catchTaskDo != null && !catchTaskDo.isEmpty() ? Optional.of( @@ -144,6 +146,7 @@ protected TryExecutor(TryExecutorBuilder builder) { this.taskExecutor = builder.taskExecutor; this.catchTaskExecutor = builder.catchTaskExecutor; this.retryIntervalExecutor = builder.retryIntervalExecutor; + this.errorVariable = builder.errorVariable; } @Override @@ -168,9 +171,17 @@ private CompletableFuture handleException( WorkflowException exception = (WorkflowException) e; CompletableFuture completable = CompletableFuture.completedFuture(taskContext.rawOutput()); - if (errorFilter.map(f -> f.test(exception.getWorkflowError())).orElse(true) + WorkflowError error = exception.getWorkflowError(); + if (errorFilter.map(f -> f.test(error)).orElse(true) && WorkflowUtils.whenExceptTest( - whenFilter, exceptFilter, workflow, taskContext, taskContext.rawOutput())) { + whenFilter, + exceptFilter, + workflow, + taskContext, + workflow.definition().application().modelFactory().fromAny(error))) { + if (errorVariable != null) { + taskContext.variables().put(errorVariable, error); + } if (catchTaskExecutor.isPresent()) { completable = completable.thenCompose( @@ -189,11 +200,10 @@ private CompletableFuture handleException( .orElse(CompletableFuture.failedFuture(e))) .thenCompose(model -> doIt(workflow, taskContext, model)); } + return completable; } - return completable; - } else { - return CompletableFuture.failedFuture(e); } + return CompletableFuture.failedFuture(e); } private static Optional> buildErrorFilter(CatchErrors errors) { @@ -207,7 +217,7 @@ private static boolean filterError(WorkflowError error, ErrorFilter errorFilter) && (errorFilter.getStatus() <= 0 || error.status() == errorFilter.getStatus()) && compareString(errorFilter.getInstance(), error.instance()) && compareString(errorFilter.getTitle(), error.title()) - && compareString(errorFilter.getDetails(), errorFilter.getDetails()); + && compareString(errorFilter.getDetails(), error.details()); } private static boolean compareString(String one, String other) { diff --git a/impl/test/src/test/java/io/serverlessworkflow/impl/test/RetryTimeoutTest.java b/impl/test/src/test/java/io/serverlessworkflow/impl/test/RetryTimeoutTest.java index 2a217fc00..6db7cffab 100644 --- a/impl/test/src/test/java/io/serverlessworkflow/impl/test/RetryTimeoutTest.java +++ b/impl/test/src/test/java/io/serverlessworkflow/impl/test/RetryTimeoutTest.java @@ -121,4 +121,54 @@ void testTimeout() throws IOException { .orElseThrow(); assertThat(result.get("message")).isEqualTo("Viva er Beti Balompie"); } + + @ParameterizedTest + @ValueSource( + strings = { + "workflows-samples/try-catch-match-when.yaml", + "workflows-samples/try-catch-match-status.yaml", + "workflows-samples/try-catch-match-details.yaml" + }) + void testDoesMatch(String path) throws IOException { + assertThat( + app.workflowDefinition(readWorkflowFromClasspath(path)) + .instance(Map.of()) + .start() + .join() + .asMap() + .map(m -> m.get("recovered")) + .orElseThrow()) + .isEqualTo(true); + } + + @ParameterizedTest + @ValueSource( + strings = { + "workflows-samples/try-catch-not-match-when.yaml", + "workflows-samples/try-catch-not-match-status.yaml", + "workflows-samples/try-catch-not-match-details.yaml" + }) + void testDoesNotMatch(String path) { + assertThatThrownBy( + () -> + app.workflowDefinition(readWorkflowFromClasspath(path)) + .instance(Map.of()) + .start() + .join()) + .hasCauseInstanceOf(WorkflowException.class); + } + + @Test + void testErrorVariable() throws IOException { + assertThat( + app.workflowDefinition( + readWorkflowFromClasspath("workflows-samples/try-catch-error-variable.yaml")) + .instance(Map.of()) + .start() + .join() + .asMap() + .map(m -> m.get("errorMessage")) + .orElseThrow()) + .isEqualTo("Javierito was here!"); + } } diff --git a/impl/test/src/test/resources/workflows-samples/try-catch-error-variable.yaml b/impl/test/src/test/resources/workflows-samples/try-catch-error-variable.yaml new file mode 100644 index 000000000..5d741ef3e --- /dev/null +++ b/impl/test/src/test/resources/workflows-samples/try-catch-error-variable.yaml @@ -0,0 +1,20 @@ +document: + dsl: '1.0.0' + namespace: test + name: try-catch-error-variable + version: '0.1.0' +do: + - attemptTask: + try: + - failingTask: + raise: + error: + type: https://example.com/errors/transient + detail: Javierito was here! + status: 503 + catch: + as: caughtError + do: + - handleError: + set: + errorMessage: ${$caughtError.details} \ No newline at end of file diff --git a/impl/test/src/test/resources/workflows-samples/try-catch-match-details.yaml b/impl/test/src/test/resources/workflows-samples/try-catch-match-details.yaml new file mode 100644 index 000000000..96dcdf662 --- /dev/null +++ b/impl/test/src/test/resources/workflows-samples/try-catch-match-details.yaml @@ -0,0 +1,24 @@ +document: + dsl: '1.0.0' + namespace: test + name: try-catch-match-details + version: '0.1.0' +do: + - attemptTask: + try: + - failingTask: + raise: + error: + type: https://example.com/errors/transient + status: 503 + detail: Enforcement Failure - invalid email + catch: + errors: + with: + type: https://example.com/errors/transient + status: 503 + details: Enforcement Failure - invalid email + do: + - handleError: + set: + recovered: true \ No newline at end of file diff --git a/impl/test/src/test/resources/workflows-samples/try-catch-match-status.yaml b/impl/test/src/test/resources/workflows-samples/try-catch-match-status.yaml new file mode 100644 index 000000000..7c397888b --- /dev/null +++ b/impl/test/src/test/resources/workflows-samples/try-catch-match-status.yaml @@ -0,0 +1,22 @@ +document: + dsl: '1.0.0' + namespace: test + name: try-catch-match-status + version: '0.1.0' +do: + - attemptTask: + try: + - failingTask: + raise: + error: + type: https://example.com/errors/transient + status: 503 + catch: + errors: + with: + type: https://example.com/errors/transient + status: 503 + do: + - handleError: + set: + recovered: true \ No newline at end of file diff --git a/impl/test/src/test/resources/workflows-samples/try-catch-match-when.yaml b/impl/test/src/test/resources/workflows-samples/try-catch-match-when.yaml new file mode 100644 index 000000000..5682164f6 --- /dev/null +++ b/impl/test/src/test/resources/workflows-samples/try-catch-match-when.yaml @@ -0,0 +1,19 @@ +document: + dsl: '1.0.0' + namespace: test + name: try-catch-match-when + version: '0.1.0' +do: + - attemptTask: + try: + - failingTask: + raise: + error: + type: https://example.com/errors/transient + status: 503 + catch: + when: ${ .status == 503 } + do: + - handleError: + set: + recovered: true \ No newline at end of file diff --git a/impl/test/src/test/resources/workflows-samples/try-catch-not-match-details.yaml b/impl/test/src/test/resources/workflows-samples/try-catch-not-match-details.yaml new file mode 100644 index 000000000..d2a2ff627 --- /dev/null +++ b/impl/test/src/test/resources/workflows-samples/try-catch-not-match-details.yaml @@ -0,0 +1,20 @@ +document: + dsl: '1.0.0' + namespace: test + name: try-catch-not-match-details + version: '0.1.0' +do: + - attemptTask: + try: + - failingTask: + raise: + error: + type: https://example.com/errors/security + status: 403 + detail: Enforcement Failure - invalid email + catch: + errors: + with: + type: https://example.com/errors/security + status: 403 + details: User not found in tenant catalog \ No newline at end of file diff --git a/impl/test/src/test/resources/workflows-samples/try-catch-not-match-status.yaml b/impl/test/src/test/resources/workflows-samples/try-catch-not-match-status.yaml new file mode 100644 index 000000000..eaf6e3ac1 --- /dev/null +++ b/impl/test/src/test/resources/workflows-samples/try-catch-not-match-status.yaml @@ -0,0 +1,18 @@ +document: + dsl: '1.0.0' + namespace: test + name: try-catch-not-match-status + version: '0.1.0' +do: + - attemptTask: + try: + - failingTask: + raise: + error: + type: https://example.com/errors/transient + status: 503 + catch: + errors: + with: + type: https://example.com/errors/transient + status: 403 \ No newline at end of file diff --git a/impl/test/src/test/resources/workflows-samples/try-catch-not-match-when.yaml b/impl/test/src/test/resources/workflows-samples/try-catch-not-match-when.yaml new file mode 100644 index 000000000..b6f061a3c --- /dev/null +++ b/impl/test/src/test/resources/workflows-samples/try-catch-not-match-when.yaml @@ -0,0 +1,19 @@ +document: + dsl: '1.0.0' + namespace: test + name: try-catch-not-match-when + version: '0.1.0' +do: + - attemptTask: + try: + - failingTask: + raise: + error: + type: https://example.com/errors/transient + status: 503 + catch: + when: ${ .status == 400 } + do: + - handleError: + set: + recovered: true \ No newline at end of file diff --git a/impl/test/src/test/resources/workflows-samples/try-catch-retry-inline.yaml b/impl/test/src/test/resources/workflows-samples/try-catch-retry-inline.yaml index 69dd3f2b0..cb541db0e 100644 --- a/impl/test/src/test/resources/workflows-samples/try-catch-retry-inline.yaml +++ b/impl/test/src/test/resources/workflows-samples/try-catch-retry-inline.yaml @@ -1,6 +1,6 @@ document: dsl: '1.0.0' - namespace: default + namespace: test name: try-catch-retry-inline version: '0.1.0' do: diff --git a/impl/test/src/test/resources/workflows-samples/try-catch-retry-reusable.yaml b/impl/test/src/test/resources/workflows-samples/try-catch-retry-reusable.yaml index 00834c6f0..fe041512f 100644 --- a/impl/test/src/test/resources/workflows-samples/try-catch-retry-reusable.yaml +++ b/impl/test/src/test/resources/workflows-samples/try-catch-retry-reusable.yaml @@ -1,6 +1,6 @@ document: dsl: '1.0.0' - namespace: default + namespace: test name: try-catch-retry-reusable version: '0.1.0' use: