From 8fbc3bf1dfbdd362ccf54b77b0b7f08aec29d6c9 Mon Sep 17 00:00:00 2001 From: Spolti Date: Mon, 8 May 2023 14:26:39 -0300 Subject: [PATCH] Key property is missing in the Workflow fixes https://github.com/serverlessworkflow/sdk-java/issues/227 Signed-off-by: Spolti --- .../api/serializers/WorkflowSerializer.java | 3 + api/src/main/resources/schema/workflow.json | 23 ++++--- .../api/test/MarkupToWorkflowTest.java | 43 ++++++++++++- .../resources/examples/applicantrequest.json | 1 + .../applicantrequest-with-id-and-key.json | 60 +++++++++++++++++++ .../applicantrequest-with-id-and-key.yml | 34 +++++++++++ .../features/applicantrequest-with-key.json | 59 ++++++++++++++++++ .../features/applicantrequest-with-key.yml | 33 ++++++++++ .../validation/WorkflowValidatorImpl.java | 6 +- .../test/WorkflowValidationTest.java | 27 ++++++++- 10 files changed, 276 insertions(+), 13 deletions(-) create mode 100644 api/src/test/resources/features/applicantrequest-with-id-and-key.json create mode 100644 api/src/test/resources/features/applicantrequest-with-id-and-key.yml create mode 100644 api/src/test/resources/features/applicantrequest-with-key.json create mode 100644 api/src/test/resources/features/applicantrequest-with-key.yml diff --git a/api/src/main/java/io/serverlessworkflow/api/serializers/WorkflowSerializer.java b/api/src/main/java/io/serverlessworkflow/api/serializers/WorkflowSerializer.java index 6feb22de..fa774b7c 100644 --- a/api/src/main/java/io/serverlessworkflow/api/serializers/WorkflowSerializer.java +++ b/api/src/main/java/io/serverlessworkflow/api/serializers/WorkflowSerializer.java @@ -53,6 +53,9 @@ public void serialize(Workflow workflow, JsonGenerator gen, SerializerProvider p gen.writeStringField("id", generateUniqueId()); } + if (workflow.getKey() != null) { + gen.writeStringField("key", workflow.getKey()); + } gen.writeStringField("name", workflow.getName()); if (workflow.getDescription() != null && !workflow.getDescription().isEmpty()) { diff --git a/api/src/main/resources/schema/workflow.json b/api/src/main/resources/schema/workflow.json index 7d51c57b..9ea8d6b2 100644 --- a/api/src/main/resources/schema/workflow.json +++ b/api/src/main/resources/schema/workflow.json @@ -11,8 +11,11 @@ "properties": { "id": { "type": "string", - "description": "Workflow unique identifier", - "minLength": 1 + "description": "Workflow unique identifier" + }, + "key": { + "type": "string", + "description": "Workflow Domain-specific identifier" }, "name": { "type": "string", @@ -156,10 +159,14 @@ } } }, - "required": [ - "id", - "name", - "version", - "states" - ] + "required": [ + "name", + "version", + "states" + ], + "dependencies": + { + "id": { "not": { "required": ["key"] } }, + "key": { "not": { "required": ["id"] } } + } } diff --git a/api/src/test/java/io/serverlessworkflow/api/test/MarkupToWorkflowTest.java b/api/src/test/java/io/serverlessworkflow/api/test/MarkupToWorkflowTest.java index 1faf1512..fed3dc97 100644 --- a/api/src/test/java/io/serverlessworkflow/api/test/MarkupToWorkflowTest.java +++ b/api/src/test/java/io/serverlessworkflow/api/test/MarkupToWorkflowTest.java @@ -101,10 +101,11 @@ public void testSpecExamplesParsing(String workflowLocation) { @ParameterizedTest @ValueSource(strings = {"/features/applicantrequest.json", "/features/applicantrequest.yml"}) - public void testSpecFreatureFunctionRef(String workflowLocation) { + public void testSpecFeatureFunctionRef(String workflowLocation) { Workflow workflow = Workflow.fromSource(WorkflowTestUtils.readWorkflowFile(workflowLocation)); assertNotNull(workflow); + assertNull(workflow.getKey()); assertNotNull(workflow.getId()); assertNotNull(workflow.getName()); assertNotNull(workflow.getStates()); @@ -114,6 +115,46 @@ public void testSpecFreatureFunctionRef(String workflowLocation) { assertEquals(1, workflow.getFunctions().getFunctionDefs().size()); } + @ParameterizedTest + @ValueSource( + strings = { + "/features/applicantrequest-with-key.json", + "/features/applicantrequest-with-key.yml" + }) + public void testSpecFeatureFunctionRefWithKey(String workflowLocation) { + Workflow workflow = Workflow.fromSource(WorkflowTestUtils.readWorkflowFile(workflowLocation)); + + assertNotNull(workflow); + assertEquals("applicant-key-request", workflow.getKey()); + assertNull(workflow.getId()); + assertNotNull(workflow.getName()); + assertNotNull(workflow.getStates()); + assertTrue(workflow.getStates().size() > 0); + + assertNotNull(workflow.getFunctions()); + assertEquals(1, workflow.getFunctions().getFunctionDefs().size()); + } + + @ParameterizedTest + @ValueSource( + strings = { + "/features/applicantrequest-with-id-and-key.json", + "/features/applicantrequest-with-id-and-key.yml" + }) + public void testSpecFeatureFunctionRefWithIdAndKey(String workflowLocation) { + Workflow workflow = Workflow.fromSource(WorkflowTestUtils.readWorkflowFile(workflowLocation)); + + assertNotNull(workflow); + assertEquals("applicant-key-request", workflow.getKey()); + assertEquals("applicant-with-key-and-id", workflow.getId()); + assertNotNull(workflow.getName()); + assertNotNull(workflow.getStates()); + assertTrue(workflow.getStates().size() > 0); + + assertNotNull(workflow.getFunctions()); + assertEquals(1, workflow.getFunctions().getFunctionDefs().size()); + } + @ParameterizedTest @ValueSource(strings = {"/features/vetappointment.json", "/features/vetappointment.yml"}) public void testSpecFreatureEventRef(String workflowLocation) { diff --git a/api/src/test/resources/examples/applicantrequest.json b/api/src/test/resources/examples/applicantrequest.json index 1621c2bd..652e361b 100644 --- a/api/src/test/resources/examples/applicantrequest.json +++ b/api/src/test/resources/examples/applicantrequest.json @@ -1,5 +1,6 @@ { "id": "applicantrequest", + "key": "applicant-key-request", "version": "1.0", "specVersion": "0.8", "name": "Applicant Request Decision Workflow", diff --git a/api/src/test/resources/features/applicantrequest-with-id-and-key.json b/api/src/test/resources/features/applicantrequest-with-id-and-key.json new file mode 100644 index 00000000..405d7c36 --- /dev/null +++ b/api/src/test/resources/features/applicantrequest-with-id-and-key.json @@ -0,0 +1,60 @@ +{ + "id": "applicant-with-key-and-id", + "key": "applicant-key-request", + "version": "1.0", + "specVersion": "0.8", + "name": "Applicant Request Decision Workflow", + "description": "Determine if applicant request is valid", + "start": "CheckApplication", + "functions": [ + { + "name": "sendRejectionEmailFunction", + "operation": "http://myapis.org/applicationapi.json#emailRejection" + } + ], + "states":[ + { + "name":"CheckApplication", + "type":"switch", + "dataConditions": [ + { + "condition": "${ .applicants | .age >= 18 }", + "transition": "StartApplication" + }, + { + "condition": "${ .applicants | .age < 18 }", + "transition": "RejectApplication" + } + ], + "defaultCondition": { + "transition": "RejectApplication" + } + }, + { + "name": "StartApplication", + "type": "operation", + "actions": [ + { + "subFlowRef": "startApplicationWorkflowId" + } + ], + "end": true + }, + { + "name":"RejectApplication", + "type":"operation", + "actionMode":"sequential", + "actions":[ + { + "functionRef": { + "refName": "sendRejectionEmailFunction", + "arguments": { + "applicant": "${ .applicant }" + } + } + } + ], + "end": true + } + ] +} \ No newline at end of file diff --git a/api/src/test/resources/features/applicantrequest-with-id-and-key.yml b/api/src/test/resources/features/applicantrequest-with-id-and-key.yml new file mode 100644 index 00000000..8a123663 --- /dev/null +++ b/api/src/test/resources/features/applicantrequest-with-id-and-key.yml @@ -0,0 +1,34 @@ +id: applicant-with-key-and-id +key: applicant-key-request +version: '1.0' +specVersion: '0.8' +name: Applicant Request Decision Workflow +description: Determine if applicant request is valid +start: CheckApplication +functions: + - name: sendRejectionEmailFunction + operation: http://myapis.org/applicationapi.json#emailRejection +states: + - name: CheckApplication + type: switch + dataConditions: + - condition: "${ .applicants | .age >= 18 }" + transition: StartApplication + - condition: "${ .applicants | .age < 18 }" + transition: RejectApplication + defaultCondition: + transition: RejectApplication + - name: StartApplication + type: operation + actions: + - subFlowRef: startApplicationWorkflowId + end: true + - name: RejectApplication + type: operation + actionMode: sequential + actions: + - functionRef: + refName: sendRejectionEmailFunction + arguments: + applicant: "${ .applicant }" + end: true diff --git a/api/src/test/resources/features/applicantrequest-with-key.json b/api/src/test/resources/features/applicantrequest-with-key.json new file mode 100644 index 00000000..f0481b00 --- /dev/null +++ b/api/src/test/resources/features/applicantrequest-with-key.json @@ -0,0 +1,59 @@ +{ + "key": "applicant-key-request", + "version": "1.0", + "specVersion": "0.8", + "name": "Applicant Request Decision Workflow", + "description": "Determine if applicant request is valid", + "start": "CheckApplication", + "functions": [ + { + "name": "sendRejectionEmailFunction", + "operation": "http://myapis.org/applicationapi.json#emailRejection" + } + ], + "states":[ + { + "name":"CheckApplication", + "type":"switch", + "dataConditions": [ + { + "condition": "${ .applicants | .age >= 18 }", + "transition": "StartApplication" + }, + { + "condition": "${ .applicants | .age < 18 }", + "transition": "RejectApplication" + } + ], + "defaultCondition": { + "transition": "RejectApplication" + } + }, + { + "name": "StartApplication", + "type": "operation", + "actions": [ + { + "subFlowRef": "startApplicationWorkflowId" + } + ], + "end": true + }, + { + "name":"RejectApplication", + "type":"operation", + "actionMode":"sequential", + "actions":[ + { + "functionRef": { + "refName": "sendRejectionEmailFunction", + "arguments": { + "applicant": "${ .applicant }" + } + } + } + ], + "end": true + } + ] +} \ No newline at end of file diff --git a/api/src/test/resources/features/applicantrequest-with-key.yml b/api/src/test/resources/features/applicantrequest-with-key.yml new file mode 100644 index 00000000..85beed74 --- /dev/null +++ b/api/src/test/resources/features/applicantrequest-with-key.yml @@ -0,0 +1,33 @@ +key: applicant-key-request +version: '1.0' +specVersion: '0.8' +name: Applicant Request Decision Workflow +description: Determine if applicant request is valid +start: CheckApplication +functions: + - name: sendRejectionEmailFunction + operation: http://myapis.org/applicationapi.json#emailRejection +states: + - name: CheckApplication + type: switch + dataConditions: + - condition: "${ .applicants | .age >= 18 }" + transition: StartApplication + - condition: "${ .applicants | .age < 18 }" + transition: RejectApplication + defaultCondition: + transition: RejectApplication + - name: StartApplication + type: operation + actions: + - subFlowRef: startApplicationWorkflowId + end: true + - name: RejectApplication + type: operation + actionMode: sequential + actions: + - functionRef: + refName: sendRejectionEmailFunction + arguments: + applicant: "${ .applicant }" + end: true diff --git a/validation/src/main/java/io/serverlessworkflow/validation/WorkflowValidatorImpl.java b/validation/src/main/java/io/serverlessworkflow/validation/WorkflowValidatorImpl.java index 1e0999c9..abef046b 100644 --- a/validation/src/main/java/io/serverlessworkflow/validation/WorkflowValidatorImpl.java +++ b/validation/src/main/java/io/serverlessworkflow/validation/WorkflowValidatorImpl.java @@ -112,8 +112,10 @@ public List validate() { List events = workflow.getEvents() != null ? workflow.getEvents().getEventDefs() : null; - if (workflow.getId() == null || workflow.getId().trim().isEmpty()) { - addValidationError("Workflow id should not be empty", ValidationError.WORKFLOW_VALIDATION); + if ((workflow.getId() == null || workflow.getId().trim().isEmpty()) + && (workflow.getKey() == null || workflow.getKey().trim().isEmpty())) { + addValidationError( + "Workflow id or key should not be empty", ValidationError.WORKFLOW_VALIDATION); } if (workflow.getVersion() == null || workflow.getVersion().trim().isEmpty()) { diff --git a/validation/src/test/java/io/serverlessworkflow/validation/test/WorkflowValidationTest.java b/validation/src/test/java/io/serverlessworkflow/validation/test/WorkflowValidationTest.java index 38dbe2d2..a81e14f6 100644 --- a/validation/src/test/java/io/serverlessworkflow/validation/test/WorkflowValidationTest.java +++ b/validation/src/test/java/io/serverlessworkflow/validation/test/WorkflowValidationTest.java @@ -44,8 +44,9 @@ public void testIncompleteJsonWithSchemaValidation() { public void testIncompleteYamlWithSchemaValidation() { WorkflowValidator workflowValidator = new WorkflowValidatorImpl(); List validationErrors = - workflowValidator.setSource("---\n" + "id: abc\n").validate(); + workflowValidator.setSource("---\n" + "key: abc\n").validate(); Assertions.assertNotNull(validationErrors); + System.out.println(validationErrors); Assertions.assertEquals(3, validationErrors.size()); } @@ -93,6 +94,27 @@ public void testWorkflowMissingStates() { Assertions.assertEquals("No states found", validationErrors.get(0).getMessage()); } + @Test + public void testWorkflowMissingStatesIdAndKey() { + WorkflowValidator workflowValidator = new WorkflowValidatorImpl(); + List validationErrors = + workflowValidator + .setSource( + "{\n" + + "\t\"name\": \"test workflow\",\n" + + " \"version\": \"1.0\",\n" + + " \"start\": \"SomeState\",\n" + + " \"states\": []\n" + + "}") + .validate(); + Assertions.assertNotNull(validationErrors); + Assertions.assertEquals(2, validationErrors.size()); + + Assertions.assertEquals( + "Workflow id or key should not be empty", validationErrors.get(0).getMessage()); + Assertions.assertEquals("No states found", validationErrors.get(1).getMessage()); + } + @Test public void testOperationStateNoFunctionRef() { WorkflowValidator workflowValidator = new WorkflowValidatorImpl(); @@ -101,7 +123,7 @@ public void testOperationStateNoFunctionRef() { .setSource( "{\n" + "\"id\": \"checkInbox\",\n" - + " \"name\": \"Check Inbox Workflow\",\n" + + "\"name\": \"Check Inbox Workflow\",\n" + "\"description\": \"Periodically Check Inbox\",\n" + "\"version\": \"1.0\",\n" + "\"start\": \"CheckInbox\",\n" @@ -140,6 +162,7 @@ public void testOperationStateNoFunctionRef() { Assertions.assertNotNull(validationErrors); Assertions.assertEquals(1, validationErrors.size()); + // validationErrors.stream().forEach(v -> System.out.println(v.toString())); Assertions.assertEquals( "Operation State action functionRef does not reference an existing workflow function definition", validationErrors.get(0).getMessage());