Skip to content

Commit 91bbe5c

Browse files
authoredDec 2, 2024··
Merge pull request #488 from fjtirado/Fix_#468
[Fix #468] Try/raise implementation
2 parents 3bca1ee + 493980e commit 91bbe5c

File tree

18 files changed

+473
-32
lines changed

18 files changed

+473
-32
lines changed
 

‎api/src/main/resources/schema/workflow.yaml

+27-3
Original file line numberDiff line numberDiff line change
@@ -777,19 +777,22 @@ $defs:
777777
errors:
778778
type: object
779779
title: CatchErrors
780-
description: The configuration of a concept used to catch errors.
780+
properties:
781+
with:
782+
$ref: '#/$defs/errorFilter'
783+
description: static error filter
781784
as:
782785
type: string
783786
title: CatchAs
784787
description: The name of the runtime expression variable to save the error as. Defaults to 'error'.
785788
when:
786789
type: string
787790
title: CatchWhen
788-
description: A runtime expression used to determine whether or not to catch the filtered error.
791+
description: A runtime expression used to determine whether to catch the filtered error.
789792
exceptWhen:
790793
type: string
791794
title: CatchExceptWhen
792-
description: A runtime expression used to determine whether or not to catch the filtered error.
795+
description: A runtime expression used to determine whether not to catch the filtered error.
793796
retry:
794797
oneOf:
795798
- $ref: '#/$defs/retryPolicy'
@@ -1152,6 +1155,27 @@ $defs:
11521155
title: ErrorDetails
11531156
description: A human-readable explanation specific to this occurrence of the error.
11541157
required: [ type, status ]
1158+
errorFilter:
1159+
type: object
1160+
title: ErrorFilter
1161+
description: Error filtering base on static values. For error filtering on dynamic values, use catch.when property
1162+
minProperties: 1
1163+
properties:
1164+
type:
1165+
type: string
1166+
description: if present, means this value should be used for filtering
1167+
status:
1168+
type: integer
1169+
description: if present, means this value should be used for filtering
1170+
instance:
1171+
type: string
1172+
description: if present, means this value should be used for filtering
1173+
title:
1174+
type: string
1175+
description: if present, means this value should be used for filtering
1176+
details:
1177+
type: string
1178+
description: if present, means this value should be used for filtering
11551179
uriTemplate:
11561180
title: UriTemplate
11571181
anyOf:
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
/*
2+
* Copyright 2020-Present The Serverless Workflow Specification Authors
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package io.serverlessworkflow.impl;
17+
18+
import java.util.function.BiFunction;
19+
20+
@FunctionalInterface
21+
public interface StringFilter extends BiFunction<WorkflowContext, TaskContext<?>, String> {}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
/*
2+
* Copyright 2020-Present The Serverless Workflow Specification Authors
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package io.serverlessworkflow.impl;
17+
18+
public record WorkflowError(
19+
String type, int status, String instance, String title, String details) {
20+
21+
private static final String ERROR_FORMAT = "https://serverlessworkflow.io/spec/1.0.0/errors/%s";
22+
public static final String RUNTIME_TYPE = String.format(ERROR_FORMAT, "runtime");
23+
public static final String COMM_TYPE = String.format(ERROR_FORMAT, "communication");
24+
25+
public static Builder error(String type, int status) {
26+
return new Builder(type, status);
27+
}
28+
29+
public static Builder communication(int status, TaskContext<?> context, Exception ex) {
30+
return new Builder(COMM_TYPE, status)
31+
.instance(context.position().jsonPointer())
32+
.title(ex.getMessage());
33+
}
34+
35+
public static Builder runtime(int status, TaskContext<?> context, Exception ex) {
36+
return new Builder(RUNTIME_TYPE, status)
37+
.instance(context.position().jsonPointer())
38+
.title(ex.getMessage());
39+
}
40+
41+
public static class Builder {
42+
43+
private final String type;
44+
private int status;
45+
private String instance;
46+
private String title;
47+
private String details;
48+
49+
private Builder(String type, int status) {
50+
this.type = type;
51+
this.status = status;
52+
}
53+
54+
public Builder instance(String instance) {
55+
this.instance = instance;
56+
return this;
57+
}
58+
59+
public Builder title(String title) {
60+
this.title = title;
61+
return this;
62+
}
63+
64+
public Builder details(String details) {
65+
this.details = details;
66+
return this;
67+
}
68+
69+
public WorkflowError build() {
70+
return new WorkflowError(type, status, instance, title, details);
71+
}
72+
}
73+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
/*
2+
* Copyright 2020-Present The Serverless Workflow Specification Authors
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package io.serverlessworkflow.impl;
17+
18+
public class WorkflowException extends RuntimeException {
19+
20+
private static final long serialVersionUID = 1L;
21+
22+
private final WorkflowError worflowError;
23+
24+
public WorkflowException(WorkflowError error) {
25+
this(error, null);
26+
}
27+
28+
public WorkflowException(WorkflowError error, Throwable cause) {
29+
super(error.toString(), cause);
30+
this.worflowError = error;
31+
}
32+
33+
public WorkflowError getWorflowError() {
34+
return worflowError;
35+
}
36+
}

‎impl/core/src/main/java/io/serverlessworkflow/impl/WorkflowInstance.java

+1-2
Original file line numberDiff line numberDiff line change
@@ -40,8 +40,7 @@ public class WorkflowInstance {
4040
.inputFilter()
4141
.ifPresent(f -> taskContext.input(f.apply(workflowContext, taskContext, input)));
4242
state = WorkflowState.STARTED;
43-
taskContext.rawOutput(
44-
WorkflowUtils.processTaskList(definition.workflow().getDo(), workflowContext, taskContext));
43+
WorkflowUtils.processTaskList(definition.workflow().getDo(), workflowContext, taskContext);
4544
definition
4645
.outputFilter()
4746
.ifPresent(

‎impl/core/src/main/java/io/serverlessworkflow/impl/WorkflowPositionFactory.java

+1
Original file line numberDiff line numberDiff line change
@@ -17,4 +17,5 @@
1717

1818
import java.util.function.Supplier;
1919

20+
@FunctionalInterface
2021
public interface WorkflowPositionFactory extends Supplier<WorkflowPosition> {}

‎impl/core/src/main/java/io/serverlessworkflow/impl/WorkflowUtils.java

+20-3
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,23 @@ public static Optional<WorkflowFilter> buildWorkflowFilter(
9292
: Optional.empty();
9393
}
9494

95+
public static StringFilter buildStringFilter(
96+
ExpressionFactory exprFactory, String expression, String literal) {
97+
return expression != null ? from(buildWorkflowFilter(exprFactory, expression)) : from(literal);
98+
}
99+
100+
public static StringFilter buildStringFilter(ExpressionFactory exprFactory, String str) {
101+
return ExpressionUtils.isExpr(str) ? from(buildWorkflowFilter(exprFactory, str)) : from(str);
102+
}
103+
104+
public static StringFilter from(WorkflowFilter filter) {
105+
return (w, t) -> filter.apply(w, t, t.input()).asText();
106+
}
107+
108+
private static StringFilter from(String literal) {
109+
return (w, t) -> literal;
110+
}
111+
95112
private static WorkflowFilter buildWorkflowFilter(
96113
ExpressionFactory exprFactory, String str, Object object) {
97114
if (str != null) {
@@ -127,7 +144,7 @@ private static TaskItem findTaskByName(ListIterator<TaskItem> iter, String taskN
127144
throw new IllegalArgumentException("Cannot find task with name " + taskName);
128145
}
129146

130-
public static JsonNode processTaskList(
147+
public static void processTaskList(
131148
List<TaskItem> tasks, WorkflowContext context, TaskContext<?> parentTask) {
132149
parentTask.position().addProperty("do");
133150
TaskContext<? extends TaskBase> currentContext = parentTask;
@@ -136,7 +153,7 @@ public static JsonNode processTaskList(
136153
TaskItem nextTask = iter.next();
137154
while (nextTask != null) {
138155
TaskItem task = nextTask;
139-
parentTask.position().addIndex(iter.nextIndex()).addProperty(task.getName());
156+
parentTask.position().addIndex(iter.previousIndex()).addProperty(task.getName());
140157
context
141158
.definition()
142159
.listeners()
@@ -175,7 +192,7 @@ public static JsonNode processTaskList(
175192
}
176193
}
177194
parentTask.position().back();
178-
return currentContext.output();
195+
parentTask.rawOutput(currentContext.output());
179196
}
180197

181198
public static WorkflowFilter buildWorkflowFilter(ExpressionFactory exprFactory, String str) {

‎impl/core/src/main/java/io/serverlessworkflow/impl/executors/DefaultTaskExecutorFactory.java

+4
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,10 @@ public TaskExecutor<? extends TaskBase> getTaskExecutor(
6767
return new SetExecutor(task.getSetTask(), definition);
6868
} else if (task.getForTask() != null) {
6969
return new ForExecutor(task.getForTask(), definition);
70+
} else if (task.getRaiseTask() != null) {
71+
return new RaiseExecutor(task.getRaiseTask(), definition);
72+
} else if (task.getTryTask() != null) {
73+
return new TryExecutor(task.getTryTask(), definition);
7074
}
7175
throw new UnsupportedOperationException(task.get().getClass().getName() + " not supported yet");
7276
}

‎impl/core/src/main/java/io/serverlessworkflow/impl/executors/DoExecutor.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,6 @@ protected DoExecutor(DoTask task, WorkflowDefinition definition) {
2929

3030
@Override
3131
protected void internalExecute(WorkflowContext workflow, TaskContext<DoTask> taskContext) {
32-
taskContext.rawOutput(WorkflowUtils.processTaskList(task.getDo(), workflow, taskContext));
32+
WorkflowUtils.processTaskList(task.getDo(), workflow, taskContext);
3333
}
3434
}

‎impl/core/src/main/java/io/serverlessworkflow/impl/executors/ForExecutor.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ protected void internalExecute(WorkflowContext workflow, TaskContext<ForTask> ta
5252
JsonNode item = iter.next();
5353
taskContext.variables().put(task.getFor().getEach(), item);
5454
taskContext.variables().put(task.getFor().getAt(), i++);
55-
taskContext.rawOutput(WorkflowUtils.processTaskList(task.getDo(), workflow, taskContext));
55+
WorkflowUtils.processTaskList(task.getDo(), workflow, taskContext);
5656
}
5757
}
5858
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
/*
2+
* Copyright 2020-Present The Serverless Workflow Specification Authors
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package io.serverlessworkflow.impl.executors;
17+
18+
import io.serverlessworkflow.api.types.Error;
19+
import io.serverlessworkflow.api.types.ErrorInstance;
20+
import io.serverlessworkflow.api.types.ErrorType;
21+
import io.serverlessworkflow.api.types.RaiseTask;
22+
import io.serverlessworkflow.api.types.RaiseTaskError;
23+
import io.serverlessworkflow.impl.StringFilter;
24+
import io.serverlessworkflow.impl.TaskContext;
25+
import io.serverlessworkflow.impl.WorkflowContext;
26+
import io.serverlessworkflow.impl.WorkflowDefinition;
27+
import io.serverlessworkflow.impl.WorkflowError;
28+
import io.serverlessworkflow.impl.WorkflowException;
29+
import io.serverlessworkflow.impl.WorkflowUtils;
30+
import io.serverlessworkflow.impl.expressions.ExpressionFactory;
31+
import java.util.Map;
32+
import java.util.Optional;
33+
import java.util.function.BiFunction;
34+
35+
public class RaiseExecutor extends AbstractTaskExecutor<RaiseTask> {
36+
37+
private final BiFunction<WorkflowContext, TaskContext<RaiseTask>, WorkflowError> errorBuilder;
38+
39+
private final StringFilter typeFilter;
40+
private final Optional<StringFilter> instanceFilter;
41+
private final StringFilter titleFilter;
42+
private final StringFilter detailFilter;
43+
44+
protected RaiseExecutor(RaiseTask task, WorkflowDefinition definition) {
45+
super(task, definition);
46+
RaiseTaskError raiseError = task.getRaise().getError();
47+
Error error =
48+
raiseError.getRaiseErrorDefinition() != null
49+
? raiseError.getRaiseErrorDefinition()
50+
: findError(definition, raiseError.getRaiseErrorReference());
51+
this.typeFilter = getTypeFunction(definition.expressionFactory(), error.getType());
52+
this.instanceFilter = getInstanceFunction(definition.expressionFactory(), error.getInstance());
53+
this.titleFilter =
54+
WorkflowUtils.buildStringFilter(definition.expressionFactory(), error.getTitle());
55+
this.detailFilter =
56+
WorkflowUtils.buildStringFilter(definition.expressionFactory(), error.getDetail());
57+
this.errorBuilder = (w, t) -> buildError(error, w, t);
58+
}
59+
60+
private static Error findError(WorkflowDefinition definition, String raiseErrorReference) {
61+
Map<String, Error> errorsMap =
62+
definition.workflow().getUse().getErrors().getAdditionalProperties();
63+
Error error = errorsMap.get(raiseErrorReference);
64+
if (error == null) {
65+
throw new IllegalArgumentException("Error " + error + "is not defined in " + errorsMap);
66+
}
67+
return error;
68+
}
69+
70+
private WorkflowError buildError(
71+
Error error, WorkflowContext context, TaskContext<RaiseTask> taskContext) {
72+
return WorkflowError.error(typeFilter.apply(context, taskContext), error.getStatus())
73+
.instance(
74+
instanceFilter
75+
.map(f -> f.apply(context, taskContext))
76+
.orElseGet(() -> taskContext.position().jsonPointer()))
77+
.title(titleFilter.apply(context, taskContext))
78+
.details(detailFilter.apply(context, taskContext))
79+
.build();
80+
}
81+
82+
private Optional<StringFilter> getInstanceFunction(
83+
ExpressionFactory expressionFactory, ErrorInstance errorInstance) {
84+
return errorInstance != null
85+
? Optional.of(
86+
WorkflowUtils.buildStringFilter(
87+
expressionFactory,
88+
errorInstance.getExpressionErrorInstance(),
89+
errorInstance.getLiteralErrorInstance()))
90+
: Optional.empty();
91+
}
92+
93+
private StringFilter getTypeFunction(ExpressionFactory expressionFactory, ErrorType type) {
94+
return WorkflowUtils.buildStringFilter(
95+
expressionFactory,
96+
type.getExpressionErrorType(),
97+
type.getLiteralErrorType().get().toString());
98+
}
99+
100+
@Override
101+
protected void internalExecute(WorkflowContext workflow, TaskContext<RaiseTask> taskContext) {
102+
throw new WorkflowException(errorBuilder.apply(workflow, taskContext));
103+
}
104+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
/*
2+
* Copyright 2020-Present The Serverless Workflow Specification Authors
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package io.serverlessworkflow.impl.executors;
17+
18+
import io.serverlessworkflow.api.types.CatchErrors;
19+
import io.serverlessworkflow.api.types.ErrorFilter;
20+
import io.serverlessworkflow.api.types.TryTask;
21+
import io.serverlessworkflow.api.types.TryTaskCatch;
22+
import io.serverlessworkflow.impl.TaskContext;
23+
import io.serverlessworkflow.impl.WorkflowContext;
24+
import io.serverlessworkflow.impl.WorkflowDefinition;
25+
import io.serverlessworkflow.impl.WorkflowError;
26+
import io.serverlessworkflow.impl.WorkflowException;
27+
import io.serverlessworkflow.impl.WorkflowFilter;
28+
import io.serverlessworkflow.impl.WorkflowUtils;
29+
import java.util.Optional;
30+
import java.util.function.Predicate;
31+
32+
public class TryExecutor extends AbstractTaskExecutor<TryTask> {
33+
34+
private final Optional<WorkflowFilter> whenFilter;
35+
private final Optional<WorkflowFilter> exceptFilter;
36+
private final Optional<Predicate<WorkflowError>> errorFilter;
37+
38+
protected TryExecutor(TryTask task, WorkflowDefinition definition) {
39+
super(task, definition);
40+
TryTaskCatch catchInfo = task.getCatch();
41+
this.errorFilter = buildErrorFilter(catchInfo.getErrors());
42+
this.whenFilter =
43+
WorkflowUtils.optionalFilter(definition.expressionFactory(), catchInfo.getWhen());
44+
this.exceptFilter =
45+
WorkflowUtils.optionalFilter(definition.expressionFactory(), catchInfo.getExceptWhen());
46+
}
47+
48+
@Override
49+
protected void internalExecute(WorkflowContext workflow, TaskContext<TryTask> taskContext) {
50+
try {
51+
WorkflowUtils.processTaskList(task.getTry(), workflow, taskContext);
52+
} catch (WorkflowException exception) {
53+
if (errorFilter.map(f -> f.test(exception.getWorflowError())).orElse(true)
54+
&& whenFilter
55+
.map(w -> w.apply(workflow, taskContext, taskContext.input()).asBoolean())
56+
.orElse(true)
57+
&& exceptFilter
58+
.map(w -> !w.apply(workflow, taskContext, taskContext.input()).asBoolean())
59+
.orElse(true)) {
60+
if (task.getCatch().getDo() != null) {
61+
WorkflowUtils.processTaskList(task.getCatch().getDo(), workflow, taskContext);
62+
}
63+
} else {
64+
throw exception;
65+
}
66+
}
67+
}
68+
69+
private static Optional<Predicate<WorkflowError>> buildErrorFilter(CatchErrors errors) {
70+
return errors != null
71+
? Optional.of(error -> filterError(error, errors.getWith()))
72+
: Optional.empty();
73+
}
74+
75+
private static boolean filterError(WorkflowError error, ErrorFilter errorFilter) {
76+
return compareString(errorFilter.getType(), error.type())
77+
&& (errorFilter.getStatus() <= 0 || error.status() == errorFilter.getStatus())
78+
&& compareString(errorFilter.getInstance(), error.instance())
79+
&& compareString(errorFilter.getTitle(), error.title())
80+
&& compareString(errorFilter.getDetails(), errorFilter.getDetails());
81+
}
82+
83+
private static boolean compareString(String one, String other) {
84+
return one == null || one.equals(other);
85+
}
86+
}

‎impl/core/src/test/java/io/serverlessworkflow/impl/WorkflowDefinitionTest.java

+39-13
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717

1818
import static io.serverlessworkflow.api.WorkflowReader.readWorkflowFromClasspath;
1919
import static org.assertj.core.api.Assertions.assertThat;
20+
import static org.assertj.core.api.Assertions.catchThrowableOfType;
2021

2122
import java.io.IOException;
2223
import java.time.Instant;
@@ -42,10 +43,9 @@ static void init() {
4243

4344
@ParameterizedTest
4445
@MethodSource("provideParameters")
45-
void testWorkflowExecution(String fileName, Object input, Consumer<Object> assertions)
46+
void testWorkflowExecution(String fileName, Consumer<WorkflowDefinition> assertions)
4647
throws IOException {
47-
assertions.accept(
48-
appl.workflowDefinition(readWorkflowFromClasspath(fileName)).execute(input).output());
48+
assertions.accept(appl.workflowDefinition(readWorkflowFromClasspath(fileName)));
4949
}
5050

5151
private static Stream<Arguments> provideParameters() {
@@ -54,15 +54,15 @@ private static Stream<Arguments> provideParameters() {
5454
"switch-then-string.yaml",
5555
Map.of("orderType", "electronic"),
5656
o ->
57-
assertThat(o)
57+
assertThat(o.output())
5858
.isEqualTo(
5959
Map.of(
6060
"orderType", "electronic", "validate", true, "status", "fulfilled"))),
6161
args(
6262
"switch-then-string.yaml",
6363
Map.of("orderType", "physical"),
6464
o ->
65-
assertThat(o)
65+
assertThat(o.output())
6666
.isEqualTo(
6767
Map.of(
6868
"orderType",
@@ -77,7 +77,7 @@ private static Stream<Arguments> provideParameters() {
7777
"switch-then-string.yaml",
7878
Map.of("orderType", "unknown"),
7979
o ->
80-
assertThat(o)
80+
assertThat(o.output())
8181
.isEqualTo(
8282
Map.of(
8383
"orderType",
@@ -89,27 +89,53 @@ private static Stream<Arguments> provideParameters() {
8989
args(
9090
"for-sum.yaml",
9191
Map.of("input", Arrays.asList(1, 2, 3)),
92-
o -> assertThat(o).isEqualTo(6)),
92+
o -> assertThat(o.output()).isEqualTo(6)),
9393
args(
9494
"for-collect.yaml",
9595
Map.of("input", Arrays.asList(1, 2, 3)),
9696
o ->
97-
assertThat(o)
97+
assertThat(o.output())
9898
.isEqualTo(
9999
Map.of("input", Arrays.asList(1, 2, 3), "output", Arrays.asList(2, 4, 6)))),
100100
args(
101101
"simple-expression.yaml",
102102
Map.of("input", Arrays.asList(1, 2, 3)),
103-
WorkflowDefinitionTest::checkSpecialKeywords));
103+
WorkflowDefinitionTest::checkSpecialKeywords),
104+
args(
105+
"raise-inline copy.yaml",
106+
WorkflowDefinitionTest::checkWorkflowException,
107+
WorkflowException.class),
108+
args(
109+
"raise-reusable.yaml",
110+
WorkflowDefinitionTest::checkWorkflowException,
111+
WorkflowException.class));
104112
}
105113

106114
private static Arguments args(
107-
String fileName, Map<String, Object> input, Consumer<Object> object) {
108-
return Arguments.of(fileName, input, object);
115+
String fileName, Map<String, Object> input, Consumer<WorkflowInstance> instance) {
116+
return Arguments.of(
117+
fileName, (Consumer<WorkflowDefinition>) d -> instance.accept(d.execute(input)));
118+
}
119+
120+
private static <T extends Throwable> Arguments args(
121+
String fileName, Consumer<T> consumer, Class<T> clazz) {
122+
return Arguments.of(
123+
fileName,
124+
(Consumer<WorkflowDefinition>)
125+
d -> consumer.accept(catchThrowableOfType(clazz, () -> d.execute(Map.of()))));
126+
}
127+
128+
private static void checkWorkflowException(WorkflowException ex) {
129+
assertThat(ex.getWorflowError().type())
130+
.isEqualTo("https://serverlessworkflow.io/errors/not-implemented");
131+
assertThat(ex.getWorflowError().status()).isEqualTo(500);
132+
assertThat(ex.getWorflowError().title()).isEqualTo("Not Implemented");
133+
assertThat(ex.getWorflowError().details()).contains("raise-not-implemented");
134+
assertThat(ex.getWorflowError().instance()).isEqualTo("do/0/notImplemented");
109135
}
110136

111-
private static void checkSpecialKeywords(Object obj) {
112-
Map<String, Object> result = (Map<String, Object>) obj;
137+
private static void checkSpecialKeywords(WorkflowInstance obj) {
138+
Map<String, Object> result = (Map<String, Object>) obj.output();
113139
assertThat(Instant.ofEpochMilli((long) result.get("startedAt")))
114140
.isAfterOrEqualTo(before)
115141
.isBeforeOrEqualTo(Instant.now());
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
document:
2+
dsl: '1.0.0-alpha5'
3+
namespace: test
4+
name: raise-not-implemented
5+
version: '0.1.0'
6+
do:
7+
- notImplemented:
8+
raise:
9+
error:
10+
type: https://serverlessworkflow.io/errors/not-implemented
11+
status: 500
12+
title: Not Implemented
13+
detail: ${ "The workflow '\( $workflow.definition.document.name ):\( $workflow.definition.document.version )' is a work in progress and cannot be run yet" }
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
document:
2+
dsl: '1.0.0-alpha5'
3+
namespace: test
4+
name: raise-not-implemented-reusable
5+
version: '0.1.0'
6+
use:
7+
errors:
8+
notImplemented:
9+
type: https://serverlessworkflow.io/errors/not-implemented
10+
status: 500
11+
title: Not Implemented
12+
detail: ${ "The workflow '\( $workflow.definition.document.name ):\( $workflow.definition.document.version )' is a work in progress and cannot be run yet" }
13+
do:
14+
- notImplemented:
15+
raise:
16+
error: notImplemented

‎impl/http/src/main/java/io/serverlessworkflow/impl/executors/HttpExecutor.java

+10-1
Original file line numberDiff line numberDiff line change
@@ -26,11 +26,14 @@
2626
import io.serverlessworkflow.impl.TaskContext;
2727
import io.serverlessworkflow.impl.WorkflowContext;
2828
import io.serverlessworkflow.impl.WorkflowDefinition;
29+
import io.serverlessworkflow.impl.WorkflowError;
30+
import io.serverlessworkflow.impl.WorkflowException;
2931
import io.serverlessworkflow.impl.expressions.Expression;
3032
import io.serverlessworkflow.impl.expressions.ExpressionFactory;
3133
import io.serverlessworkflow.impl.expressions.ExpressionUtils;
3234
import io.serverlessworkflow.impl.json.JsonUtils;
3335
import jakarta.ws.rs.HttpMethod;
36+
import jakarta.ws.rs.WebApplicationException;
3437
import jakarta.ws.rs.client.Client;
3538
import jakarta.ws.rs.client.ClientBuilder;
3639
import jakarta.ws.rs.client.Entity;
@@ -101,7 +104,13 @@ public JsonNode apply(
101104
Builder request = target.request();
102105
ExpressionUtils.evaluateExpressionMap(headersMap, workflow, taskContext, input)
103106
.forEach(request::header);
104-
return requestFunction.apply(request, workflow, taskContext, input);
107+
try {
108+
return requestFunction.apply(request, workflow, taskContext, input);
109+
} catch (WebApplicationException exception) {
110+
throw new WorkflowException(
111+
WorkflowError.communication(exception.getResponse().getStatus(), taskContext, exception)
112+
.build());
113+
}
105114
}
106115

107116
@Override

‎impl/http/src/test/java/io/serverlessworkflow/impl/HTTPWorkflowDefinitionTest.java

+5
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,11 @@ private static Stream<Arguments> provideParameters() {
6969
o -> ((Map<String, Object>) o).containsKey("photoUrls"), "callHttpCondition");
7070
return Stream.of(
7171
Arguments.of("callGetHttp.yaml", petInput, petCondition),
72+
Arguments.of(
73+
"callGetHttp.yaml",
74+
Map.of("petId", "-1"),
75+
new Condition<>(
76+
o -> ((Map<String, Object>) o).containsKey("petId"), "notFoundCondition")),
7277
Arguments.of("call-http-endpoint-interpolation.yaml", petInput, petCondition),
7378
Arguments.of(
7479
"call-http-query-parameters.yaml",

‎impl/http/src/test/resources/callGetHttp.yaml

+15-8
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,18 @@ document:
44
name: http-call-with-response
55
version: 1.0.0
66
do:
7-
- getPet:
8-
call: http
9-
with:
10-
headers:
11-
content-type: application/json
12-
method: get
13-
endpoint:
14-
uri: https://petstore.swagger.io/v2/pet/{petId}
7+
- tryGetPet:
8+
try:
9+
- getPet:
10+
call: http
11+
with:
12+
headers:
13+
content-type: application/json
14+
method: get
15+
endpoint:
16+
uri: https://petstore.swagger.io/v2/pet/{petId}
17+
catch:
18+
errors:
19+
with:
20+
type: https://serverlessworkflow.io/spec/1.0.0/errors/communication
21+
status: 404

0 commit comments

Comments
 (0)
Please sign in to comment.