Skip to content

Commit 1c27dc5

Browse files
authored
Merge pull request #120 from tsurdilo/errorsupdate
Update error handling and retries
2 parents 365fcf0 + 9991ef5 commit 1c27dc5

File tree

24 files changed

+358
-67
lines changed

24 files changed

+358
-67
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
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.api.deserializers;
17+
18+
import com.fasterxml.jackson.core.JsonParser;
19+
import com.fasterxml.jackson.databind.DeserializationContext;
20+
import com.fasterxml.jackson.databind.JsonNode;
21+
import com.fasterxml.jackson.databind.ObjectMapper;
22+
import com.fasterxml.jackson.databind.deser.std.StdDeserializer;
23+
import com.fasterxml.jackson.dataformat.yaml.YAMLFactory;
24+
import io.serverlessworkflow.api.error.ErrorDefinition;
25+
import io.serverlessworkflow.api.interfaces.WorkflowPropertySource;
26+
import io.serverlessworkflow.api.utils.Utils;
27+
import io.serverlessworkflow.api.workflow.Errors;
28+
import org.json.JSONObject;
29+
import org.slf4j.Logger;
30+
import org.slf4j.LoggerFactory;
31+
32+
import java.io.IOException;
33+
import java.util.ArrayList;
34+
import java.util.List;
35+
36+
public class ErrorsDeserializer extends StdDeserializer<Errors> {
37+
38+
private static final long serialVersionUID = 510l;
39+
private static Logger logger = LoggerFactory.getLogger(ErrorsDeserializer.class);
40+
41+
@SuppressWarnings("unused")
42+
private WorkflowPropertySource context;
43+
44+
public ErrorsDeserializer() {
45+
this(Errors.class);
46+
}
47+
48+
public ErrorsDeserializer(Class<?> vc) {
49+
super(vc);
50+
}
51+
52+
public ErrorsDeserializer(WorkflowPropertySource context) {
53+
this(Errors.class);
54+
this.context = context;
55+
}
56+
57+
@Override
58+
public Errors deserialize(JsonParser jp,
59+
DeserializationContext ctxt) throws IOException {
60+
61+
ObjectMapper mapper = (ObjectMapper) jp.getCodec();
62+
JsonNode node = jp.getCodec().readTree(jp);
63+
64+
Errors errors = new Errors();
65+
List<ErrorDefinition> errorDefinitions = new ArrayList<>();
66+
67+
if (node.isArray()) {
68+
for (final JsonNode nodeEle : node) {
69+
errorDefinitions.add(mapper.treeToValue(nodeEle, ErrorDefinition.class));
70+
}
71+
} else {
72+
String errorsFileDef = node.asText();
73+
String errorsFileSrc = Utils.getResourceFileAsString(errorsFileDef);
74+
JsonNode errorsRefNode;
75+
ObjectMapper jsonWriter = new ObjectMapper();
76+
if (errorsFileSrc != null && errorsFileSrc.trim().length() > 0) {
77+
// if its a yaml def convert to json first
78+
if (!errorsFileSrc.trim().startsWith("{")) {
79+
// convert yaml to json to validate
80+
ObjectMapper yamlReader = new ObjectMapper(new YAMLFactory());
81+
Object obj = yamlReader.readValue(errorsFileSrc, Object.class);
82+
83+
errorsRefNode = jsonWriter.readTree(new JSONObject(jsonWriter.writeValueAsString(obj)).toString());
84+
} else {
85+
errorsRefNode = jsonWriter.readTree(new JSONObject(errorsFileSrc).toString());
86+
}
87+
88+
JsonNode refErrors = errorsRefNode.get("errors");
89+
if (refErrors != null) {
90+
for (final JsonNode nodeEle : refErrors) {
91+
errorDefinitions.add(mapper.treeToValue(nodeEle, ErrorDefinition.class));
92+
}
93+
} else {
94+
logger.error("Unable to find error definitions in reference file: {}", errorsFileSrc);
95+
}
96+
97+
} else {
98+
logger.error("Unable to load errors defs reference file: {}", errorsFileSrc);
99+
}
100+
101+
}
102+
errors.setErrorDefs(errorDefinitions);
103+
return errors;
104+
105+
}
106+
}
107+

api/src/main/java/io/serverlessworkflow/api/mapper/WorkflowModule.java

+1
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,7 @@ private void addDefaultDeserializers() {
112112
addDeserializer(DataInputSchema.class, new DataInputSchemaDeserializer(workflowPropertySource));
113113
addDeserializer(AuthDefinition.class, new AuthDefinitionDeserializer(workflowPropertySource));
114114
addDeserializer(StateExecTimeout.class, new StateExecTimeoutDeserializer(workflowPropertySource));
115+
addDeserializer(Errors.class, new ErrorsDeserializer(workflowPropertySource));
115116
}
116117

117118
public ExtensionSerializer getExtensionSerializer() {

api/src/main/java/io/serverlessworkflow/api/serializers/WorkflowSerializer.java

+16
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
import com.fasterxml.jackson.databind.SerializerProvider;
2121
import com.fasterxml.jackson.databind.ser.std.StdSerializer;
2222
import io.serverlessworkflow.api.Workflow;
23+
import io.serverlessworkflow.api.error.ErrorDefinition;
2324
import io.serverlessworkflow.api.events.EventDefinition;
2425
import io.serverlessworkflow.api.functions.FunctionDefinition;
2526
import io.serverlessworkflow.api.interfaces.Extension;
@@ -102,6 +103,10 @@ public void serialize(Workflow workflow,
102103
gen.writeBooleanField("keepActive", workflow.isKeepActive());
103104
}
104105

106+
if (workflow.isAutoRetries()) {
107+
gen.writeBooleanField("autoRetries", workflow.isAutoRetries());
108+
}
109+
105110
if (workflow.getMetadata() != null && !workflow.getMetadata().isEmpty()) {
106111
gen.writeObjectField("metadata",
107112
workflow.getMetadata());
@@ -140,6 +145,17 @@ public void serialize(Workflow workflow,
140145
gen.writeEndArray();
141146
}
142147

148+
if (workflow.getErrors() != null && !workflow.getErrors().getErrorDefs().isEmpty()) {
149+
gen.writeArrayFieldStart("errors");
150+
for (ErrorDefinition error : workflow.getErrors().getErrorDefs()) {
151+
gen.writeObject(error);
152+
}
153+
gen.writeEndArray();
154+
} else {
155+
gen.writeArrayFieldStart("errors");
156+
gen.writeEndArray();
157+
}
158+
143159
if (workflow.getSecrets() != null && !workflow.getSecrets().getSecretDefs().isEmpty()) {
144160
gen.writeArrayFieldStart("secrets");
145161
for (String secretDef : workflow.getSecrets().getSecretDefs()) {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
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.api.workflow;
17+
18+
import io.serverlessworkflow.api.error.ErrorDefinition;
19+
20+
import java.util.List;
21+
22+
public class Errors {
23+
private String refValue;
24+
private List<ErrorDefinition> errorDefs;
25+
26+
public Errors() {
27+
}
28+
29+
public Errors(List<ErrorDefinition> errorDefs) {
30+
this.errorDefs = errorDefs;
31+
}
32+
33+
public Errors(String refValue) {
34+
this.refValue = refValue;
35+
}
36+
37+
public String getRefValue() {
38+
return refValue;
39+
}
40+
41+
public void setRefValue(String refValue) {
42+
this.refValue = refValue;
43+
}
44+
45+
public List<ErrorDefinition> getErrorDefs() {
46+
return errorDefs;
47+
}
48+
49+
public void setErrorDefs(List<ErrorDefinition> errorDefs) {
50+
this.errorDefs = errorDefs;
51+
}
52+
}

api/src/main/resources/schema/actions/action.json

+20
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,26 @@
2222
"sleep": {
2323
"$ref": "../sleep/sleep.json"
2424
},
25+
"retryRef": {
26+
"type": "string",
27+
"description": "References a defined workflow retry definition. If not defined the default retry policy is assumed"
28+
},
29+
"nonRetryableErrors": {
30+
"type": "array",
31+
"description": "List of unique references to defined workflow errors for which the action should not be retried. Used only when `autoRetries` is set to `true`",
32+
"minItems": 1,
33+
"items": {
34+
"type": "string"
35+
}
36+
},
37+
"retryableErrors": {
38+
"type": "array",
39+
"description": "List of unique references to defined workflow errors for which the action should be retried. Used only when `autoRetries` is set to `false`",
40+
"minItems": 1,
41+
"items": {
42+
"type": "string"
43+
}
44+
},
2545
"actionDataFilter": {
2646
"$ref": "../filters/actiondatafilter.json"
2747
}

api/src/main/resources/schema/error/error.json

+9-11
Original file line numberDiff line numberDiff line change
@@ -2,20 +2,18 @@
22
"type": "object",
33
"javaType": "io.serverlessworkflow.api.error.Error",
44
"properties": {
5-
"error": {
5+
"errorRef": {
66
"type": "string",
7-
"description": "Domain-specific error name, or '*' to indicate all possible errors",
7+
"description": "Reference to a unique workflow error definition. Used of errorRefs is not used",
88
"minLength": 1
99
},
10-
"code": {
11-
"type": "string",
12-
"description": "Error code. Can be used in addition to the name to help runtimes resolve to technical errors/exceptions. Should not be defined if error is set to '*'",
13-
"minLength": 1
14-
},
15-
"retryRef": {
16-
"type": "string",
17-
"description": "References a unique name of a retry definition.",
18-
"minLength": 1
10+
"errorRefs": {
11+
"type": "array",
12+
"description": "References one or more workflow error definitions. Used if errorRef is not used",
13+
"minItems": 1,
14+
"items": {
15+
"type": "string"
16+
}
1917
},
2018
"transition": {
2119
"$ref": "../transitions/transition.json",
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
{
2+
"type": "object",
3+
"javaType": "io.serverlessworkflow.api.error.ErrorDefinition",
4+
"properties": {
5+
"name": {
6+
"type": "string",
7+
"description": "Domain-specific error name",
8+
"minLength": 1
9+
},
10+
"code": {
11+
"type": "string",
12+
"description": "Error code. Can be used in addition to the name to help runtimes resolve to technical errors/exceptions. Should not be defined if error is set to '*'",
13+
"minLength": 1
14+
},
15+
"description": {
16+
"type": "string",
17+
"description": "Error description"
18+
}
19+
},
20+
"required": [
21+
"name"
22+
]
23+
}

api/src/main/resources/schema/workflow.json

+10
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,11 @@
5050
"default": false,
5151
"description": "If 'true', workflow instances is not terminated when there are no active execution paths. Instance can be terminated via 'terminate end definition' or reaching defined 'execTimeout'"
5252
},
53+
"autoRetries": {
54+
"type": "boolean",
55+
"default": false,
56+
"description": "If set to true, actions should automatically be retried on unchecked errors. Default is false"
57+
},
5358
"metadata": {
5459
"$ref": "metadata/metadata.json"
5560
},
@@ -63,6 +68,11 @@
6368
"existingJavaType": "io.serverlessworkflow.api.workflow.Functions",
6469
"description": "Workflow function definitions"
6570
},
71+
"errors": {
72+
"type": "object",
73+
"existingJavaType": "io.serverlessworkflow.api.workflow.Errors",
74+
"description": "Workflow error definitions"
75+
},
6676
"retries": {
6777
"type": "object",
6878
"existingJavaType": "io.serverlessworkflow.api.workflow.Retries",

api/src/test/java/io/serverlessworkflow/api/test/MarkupToWorkflowTest.java

+36
Original file line numberDiff line numberDiff line change
@@ -700,4 +700,40 @@ public void testActionsSleep(String workflowLocation) {
700700
assertEquals("${ .customer }", functionRef2.getArguments().get("applicant").asText());
701701

702702
}
703+
704+
@ParameterizedTest
705+
@ValueSource(strings = {"/features/errors.json", "/features/errors.yml"})
706+
public void testErrorsParams(String workflowLocation) {
707+
Workflow workflow = Workflow.fromSource(WorkflowTestUtils.readWorkflowFile(workflowLocation));
708+
709+
assertNotNull(workflow);
710+
assertNotNull(workflow.getId());
711+
assertNotNull(workflow.getName());
712+
assertNotNull(workflow.getStates());
713+
assertTrue(workflow.isAutoRetries());
714+
715+
assertNotNull(workflow.getStates());
716+
assertEquals(1, workflow.getStates().size());
717+
718+
assertNotNull(workflow.getErrors());
719+
assertEquals(2, workflow.getErrors().getErrorDefs().size());
720+
721+
assertTrue(workflow.getStates().get(0) instanceof OperationState);
722+
723+
OperationState operationState = (OperationState) workflow.getStates().get(0);
724+
assertNotNull(operationState.getActions());
725+
assertEquals(1, operationState.getActions().size());
726+
List<Action> actions = operationState.getActions();
727+
assertNotNull(actions.get(0).getFunctionRef());
728+
assertEquals("addPet", actions.get(0).getFunctionRef().getRefName());
729+
assertNotNull(actions.get(0).getRetryRef());
730+
assertEquals("testRetry", actions.get(0).getRetryRef());
731+
assertNotNull(actions.get(0).getNonRetryableErrors());
732+
assertEquals(2, actions.get(0).getNonRetryableErrors().size());
733+
734+
assertNotNull(operationState.getOnErrors());
735+
assertEquals(1, operationState.getOnErrors().size());
736+
assertNotNull(operationState.getOnErrors().get(0).getErrorRefs());
737+
assertEquals(2, operationState.getOnErrors().get(0).getErrorRefs().size());
738+
}
703739
}

api/src/test/resources/examples/jobmonitoring.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@
4343
],
4444
"onErrors": [
4545
{
46-
"error": "*",
46+
"errorRef": "AllErrors",
4747
"transition": "SubmitError"
4848
}
4949
],

api/src/test/resources/examples/jobmonitoring.yml

+1-1
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ states:
2525
actionDataFilter:
2626
results: "${ .jobuid }"
2727
onErrors:
28-
- error: "*"
28+
- errorRef: "AllErrors"
2929
transition: SubmitError
3030
stateDataFilter:
3131
output: "${ .jobuid }"

api/src/test/resources/examples/provisionorder.json

+3-3
Original file line numberDiff line numberDiff line change
@@ -32,15 +32,15 @@
3232
"transition": "ApplyOrder",
3333
"onErrors": [
3434
{
35-
"error": "Missing order id",
35+
"errorRef": "Missing order id",
3636
"transition": "MissingId"
3737
},
3838
{
39-
"error": "Missing order item",
39+
"errorRef": "Missing order item",
4040
"transition": "MissingItem"
4141
},
4242
{
43-
"error": "Missing order quantity",
43+
"errorRef": "Missing order quantity",
4444
"transition": "MissingQuantity"
4545
}
4646
]

0 commit comments

Comments
 (0)