Skip to content

Commit 91bbe5c

Browse files
authored
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+
}

0 commit comments

Comments
 (0)