|
1 | 1 | package com.apiflows.parser; |
2 | 2 |
|
3 | | -import com.apiflows.model.Step; |
4 | | -import com.apiflows.model.Workflow; |
5 | | -import com.apiflows.model.OpenAPIWorkflow; |
6 | | -import com.apiflows.model.SourceDescription; |
| 3 | +import com.apiflows.model.*; |
| 4 | + |
| 5 | +import java.util.Arrays; |
| 6 | +import java.util.HashSet; |
| 7 | +import java.util.List; |
| 8 | +import java.util.regex.Pattern; |
7 | 9 |
|
8 | 10 | public class OpenAPIWorkflowValidator { |
9 | 11 |
|
10 | | - public void validate(OpenAPIWorkflow openAPIWorkflow) { |
| 12 | + public OpenAPIWorkflowValidatorResult validate(OpenAPIWorkflow openAPIWorkflow) { |
| 13 | + OpenAPIWorkflowValidatorResult result = new OpenAPIWorkflowValidatorResult(); |
| 14 | + |
11 | 15 | if (openAPIWorkflow.getWorkflowsSpec() == null || openAPIWorkflow.getWorkflowsSpec().isEmpty()) { |
12 | | - throw new RuntimeException("'workflowsSpec' is undefined"); |
| 16 | + result.addError("'workflowsSpec' is undefined"); |
13 | 17 | } |
14 | 18 |
|
15 | 19 | if (openAPIWorkflow.getInfo() == null) { |
16 | | - throw new RuntimeException("'Info' is undefined"); |
| 20 | + result.addError("'Info' is undefined"); |
17 | 21 | } |
18 | | - if (openAPIWorkflow.getInfo().getTitle() == null || openAPIWorkflow.getInfo().getTitle().isEmpty()) { |
19 | | - throw new RuntimeException("'Info title' is undefined"); |
| 22 | + if (openAPIWorkflow.getInfo() != null && (openAPIWorkflow.getInfo().getTitle() == null || openAPIWorkflow.getInfo().getTitle().isEmpty())) { |
| 23 | + result.addError("'Info title' is undefined"); |
20 | 24 | } |
21 | | - if (openAPIWorkflow.getInfo().getVersion() == null || openAPIWorkflow.getInfo().getVersion().isEmpty()) { |
22 | | - throw new RuntimeException("'Info version' is undefined"); |
| 25 | + if (openAPIWorkflow.getInfo() != null && (openAPIWorkflow.getInfo().getVersion() == null || openAPIWorkflow.getInfo().getVersion().isEmpty())) { |
| 26 | + result.addError("'Info version' is undefined"); |
23 | 27 | } |
24 | 28 |
|
25 | 29 | if (openAPIWorkflow.getSourceDescriptions() == null || openAPIWorkflow.getSourceDescriptions().isEmpty()) { |
26 | | - throw new RuntimeException("'SourceDescriptions' is undefined or empty"); |
| 30 | + result.addError("'SourceDescriptions' is undefined"); |
27 | 31 | } |
28 | 32 |
|
29 | | - int i = 0; |
30 | | - for (SourceDescription sourceDescription : openAPIWorkflow.getSourceDescriptions()) { |
31 | | - if (sourceDescription.getName() == null || sourceDescription.getName().isEmpty()) { |
32 | | - throw new RuntimeException("'SourceDescription[" + i + "] name' is undefined"); |
| 33 | + if (openAPIWorkflow.getSourceDescriptions() != null) { |
| 34 | + int i = 0; |
| 35 | + for (SourceDescription sourceDescription : openAPIWorkflow.getSourceDescriptions()) { |
| 36 | + if (sourceDescription.getName() == null || sourceDescription.getName().isEmpty()) { |
| 37 | + result.addError("'SourceDescription[" + i + "] name' is undefined"); |
| 38 | + } |
| 39 | + if (sourceDescription.getUrl() == null || sourceDescription.getUrl().isEmpty()) { |
| 40 | + result.addError("'SourceDescription[" + i + "] url' is undefined"); |
| 41 | + } |
| 42 | + if (sourceDescription.getType() != null) { |
| 43 | + List<String> supportedValues = Arrays.asList("openapi", "workflowsSpec"); |
| 44 | + if(!supportedValues.contains(sourceDescription.getType())) { |
| 45 | + result.addError("'SourceDescription[" + i + "] type' is invalid"); |
| 46 | + } |
| 47 | + } |
| 48 | + i++; |
33 | 49 | } |
34 | | - if (sourceDescription.getUrl() == null || sourceDescription.getUrl().isEmpty()) { |
35 | | - throw new RuntimeException("'SourceDescription[" + i + "] url' is undefined"); |
| 50 | + |
| 51 | + if(i == 0) { |
| 52 | + result.addError("'SourceDescriptions' is empty"); |
36 | 53 | } |
37 | | - i++; |
38 | 54 | } |
39 | 55 |
|
40 | 56 | if (openAPIWorkflow.getWorkflows() == null || openAPIWorkflow.getWorkflows().isEmpty()) { |
41 | | - throw new RuntimeException("'Workflows' is undefined or empty"); |
| 57 | + result.addError("'Workflows' is undefined"); |
42 | 58 | } |
43 | 59 |
|
44 | | - i = 0; |
45 | | - for(Workflow workflow : openAPIWorkflow.getWorkflows()) { |
46 | | - if (workflow.getWorkflowId() == null || workflow.getWorkflowId().isEmpty()) { |
47 | | - throw new RuntimeException("'Workflow[" + i + "] workflowId' is undefined"); |
48 | | - } |
49 | | - if (workflow.getSteps() == null) { |
50 | | - throw new RuntimeException("'Workflow Steps' is undefined"); |
51 | | - } |
52 | | - for(Step step : workflow.getSteps()) { |
53 | | - if(step.getStepId() == null || step.getStepId().isEmpty()) { |
54 | | - throw new RuntimeException("'Workflow[" + workflow.getWorkflowId() + "] stepId' is undefined"); |
| 60 | + if (openAPIWorkflow.getWorkflows() != null) { |
| 61 | + for (Workflow workflow : openAPIWorkflow.getWorkflows()) { |
| 62 | + int i = 0; |
| 63 | + |
| 64 | + if (workflow.getWorkflowId() == null || workflow.getWorkflowId().isEmpty()) { |
| 65 | + result.addError("'Workflow[" + i + "] workflowId' is undefined"); |
55 | 66 | } |
56 | | - if(step.getOperationId() == null && step.getWorkflowId() == null && step.getOperationRef() == null) { |
57 | | - throw new RuntimeException("'Workflow[" + workflow.getWorkflowId() + "]' should provide at least one of the following: [operationId, operationRef, workflowId]"); |
| 67 | + if (workflow.getSteps() == null) { |
| 68 | + result.addError("'Workflow " + workflow.getWorkflowId() + "' no Steps are undefined"); |
58 | 69 | } |
59 | 70 |
|
60 | | - int numAssignedValues = (step.getOperationId() != null ? 1 : 0) + |
61 | | - (step.getWorkflowId() != null ? 1 : 0) + |
62 | | - (step.getOperationRef() != null ? 1 : 0); |
| 71 | + int j = 0; |
| 72 | + HashSet<String> stepIds = new HashSet<>(); |
| 73 | + for (Step step : workflow.getSteps()) { |
| 74 | + if (step.getStepId() == null || step.getStepId().isEmpty()) { |
| 75 | + result.addError("'Workflow[" + workflow.getWorkflowId() + "] stepId' is undefined"); |
| 76 | + } else { |
| 77 | + if(stepIds.contains(step.getStepId())) { |
| 78 | + result.addError("'Workflow[" + workflow.getWorkflowId() + "] stepId' " + step.getStepId() + " is not unique"); |
| 79 | + } else { |
| 80 | + stepIds.add(step.getStepId()); |
| 81 | + } |
| 82 | + } |
| 83 | + if (!isValidStepId(step.getStepId())) { |
| 84 | + result.addError("'Workflow[" + workflow.getWorkflowId() + "] stepId' is invalid (should match regex " + getStepIdRegularExpression() + ")"); |
| 85 | + } |
| 86 | + if (step.getOperationId() == null && step.getWorkflowId() == null && step.getOperationRef() == null) { |
| 87 | + result.addError("'Workflow[" + workflow.getWorkflowId() + "]' should provide at least one of the following: [operationId, operationRef, workflowId]"); |
| 88 | + } |
| 89 | + |
| 90 | + int numAssignedValues = (step.getOperationId() != null ? 1 : 0) + |
| 91 | + (step.getWorkflowId() != null ? 1 : 0) + |
| 92 | + (step.getOperationRef() != null ? 1 : 0); |
| 93 | + |
| 94 | + if (numAssignedValues != 1) { |
| 95 | + result.addError("'Workflow[" + workflow.getWorkflowId() + "]' should provide only one of the following: [operationId, operationRef, workflowId]"); |
| 96 | + } |
| 97 | + |
| 98 | + if(step.getParameters() != null) { |
| 99 | + for(Parameter parameter : step.getParameters()) { |
| 100 | + if(parameter.get$ref() != null) { |
| 101 | + // Reference object |
| 102 | + // check is URI |
| 103 | + } else { |
| 104 | + // Parameter object |
| 105 | + if(parameter.getName() == null) { |
| 106 | + result.addError("'Workflow[" + workflow.getWorkflowId() + "]' parameter has no name"); |
| 107 | + } |
| 108 | + if(parameter.getIn() == null) { |
| 109 | + result.addError("'Workflow[" + workflow.getWorkflowId() + "]' parameter has no type"); |
| 110 | + } |
| 111 | + if(parameter.getIn() != null) { |
| 112 | + List<String> supportedValues = Arrays.asList("path", "query", "header", "cookie", "body", "workflow"); |
| 113 | + if(!supportedValues.contains(parameter.getIn())) { |
| 114 | + result.addError("'Workflow[" + workflow.getWorkflowId() + "]' parameter type (" + parameter.getIn() + ") is invalid"); |
| 115 | + } |
| 116 | + } |
| 117 | + if(parameter.getValue() == null) { |
| 118 | + result.addError("'Workflow[" + workflow.getWorkflowId() + "]' parameter has no value"); |
| 119 | + } |
| 120 | + } |
| 121 | + } |
| 122 | + } |
| 123 | + j++; |
| 124 | + |
| 125 | + if(step.getDependsOn() != null) { |
| 126 | + boolean matchingStep = false; |
| 127 | + for (Step s : workflow.getSteps()) { |
| 128 | + if(s.getStepId().equals(step.getDependsOn())) { |
| 129 | + matchingStep = true; |
| 130 | + break; |
| 131 | + } |
| 132 | + } |
| 133 | + if(!matchingStep) { |
| 134 | + result.addError("'Workflow[" + workflow.getWorkflowId() + "] stepId' " + step.getStepId() + " 'dependsOn' is invalid (no such a step exists)"); |
| 135 | + } |
| 136 | + } |
63 | 137 |
|
64 | | - if (numAssignedValues != 1) { |
65 | | - throw new RuntimeException("'Workflow[" + workflow.getWorkflowId() + "]' should provide only one of the following: [operationId, operationRef, workflowId]"); |
| 138 | + for(SuccessAction successAction : step.getOnSuccess()) { |
| 139 | + if(successAction.getType() == null) { |
| 140 | + result.addError("'Workflow[" + workflow.getWorkflowId() + "]' step SuccessAction has no type"); |
| 141 | + } |
| 142 | + if(successAction.getType() != null) { |
| 143 | + List<String> supportedValues = Arrays.asList("end", "goto"); |
| 144 | + if(!supportedValues.contains(successAction.getType())) { |
| 145 | + result.addError("'Workflow[" + workflow.getWorkflowId() + "]' step SuccessAction type (" + successAction.getType() + ") is invalid"); |
| 146 | + } |
| 147 | + } |
| 148 | + } |
| 149 | + } |
| 150 | + if(j == 0) { |
| 151 | + result.addError("'Workflow " + workflow.getWorkflowId() + "' no Steps are undefined"); |
| 152 | + } |
| 153 | + |
| 154 | + for (String key : workflow.getOutputs().keySet()) { |
| 155 | + if(!isValidOutputsKey(key)) { |
| 156 | + result.addError("Workflow[" + workflow.getWorkflowId() + "] Outputs key is invalid (should match regex " + getOutputsKeyRegularExpression() + ")"); |
| 157 | + } |
66 | 158 | } |
67 | 159 | } |
68 | | - i++; |
69 | 160 | } |
| 161 | + |
| 162 | + return result; |
| 163 | + } |
| 164 | + |
| 165 | + boolean isValidStepId(String stepId) { |
| 166 | + return Pattern.matches(getStepIdRegularExpression(), stepId); |
| 167 | + } |
70 | 168 |
|
| 169 | + String getStepIdRegularExpression() { |
| 170 | + return "[A-Za-z0-9_\\-]+"; |
71 | 171 | } |
| 172 | + |
| 173 | + boolean isValidOutputsKey(String key) { |
| 174 | + return Pattern.matches(getOutputsKeyRegularExpression(), key); |
| 175 | + } |
| 176 | + |
| 177 | + String getOutputsKeyRegularExpression() { |
| 178 | + return "^[a-zA-Z0-9\\.\\-_]+$"; |
| 179 | + } |
| 180 | + |
72 | 181 | } |
0 commit comments