Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/main/java/com/apiflows/model/SourceDescription.java
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ public boolean isOpenApi() {
return "openapi".equals(this.type);
}

public boolean IsWorkflowsSpec() {
public boolean isWorkflowsSpec() {
return "workflowsSpec".equals(this.type);
}

Expand Down
106 changes: 92 additions & 14 deletions src/main/java/com/apiflows/parser/OpenAPIWorkflowValidator.java
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ public class OpenAPIWorkflowValidator {
private OpenAPIWorkflow openAPIWorkflow = null;
Set<String> workflowIds = new HashSet<>();
Map<String, Set<String>> stepIds = new HashMap<>();
Set<String> operationIds = new HashSet<>();
Set<Schema> components = new HashSet<>();

OpenAPIWorkflowValidator() {
Expand All @@ -27,8 +28,9 @@ public OpenAPIWorkflowValidatorResult validate() {
throw new RuntimeException("OpenAPIWorkflow is not provided");
}

loadWorkflowIds(this.openAPIWorkflow.getWorkflows());
loadWorkflowIds(this.openAPIWorkflow);
loadStepIds(this.openAPIWorkflow.getWorkflows());
loadOperationIds(this.openAPIWorkflow);

OpenAPIWorkflowValidatorResult result = new OpenAPIWorkflowValidatorResult();

Expand Down Expand Up @@ -346,8 +348,8 @@ List<String> validateComponents(Components components) {
if (components.getParameters() != null) {

for(String key : components.getParameters().keySet()) {
if(isValidComponentKey(key)) {
errors.add("'Component parameter " + key + " is invalid (should match regex " + getComponentKeyRegularExpression() + ")");
if(!isValidComponentKey(key)) {
errors.add("'Component parameter name " + key + " is invalid (should match regex " + getComponentKeyRegularExpression() + ")");
}
}

Expand All @@ -356,13 +358,11 @@ List<String> validateComponents(Components components) {
}
}
if (components.getInputs() != null) {

for(String key : components.getInputs().keySet()) {
if(isValidComponentKey(key)) {
if(!isValidComponentKey(key)) {
errors.add("'Component input " + key + " is invalid (should match regex " + getComponentKeyRegularExpression() + ")");
}
}

}
}

Expand Down Expand Up @@ -400,18 +400,24 @@ String getOutputsKeyRegularExpression() {
return "^[a-zA-Z0-9\\.\\-_]+$";
}

List<String> loadWorkflowIds(List<Workflow> workflows) {
List<String> loadWorkflowIds(OpenAPIWorkflow openAPIWorkflow) {
List<String> errors = new ArrayList<>();

if(workflows != null) {
for(Workflow workflow : workflows) {
if(!this.workflowIds.add(workflow.getWorkflowId())) {
// id already exists
errors.add("WorkflowId is not unique: " + workflow.getWorkflowId());
}
boolean multipleWorkflowsSpec = getNumWorkflowsSpecSourceDescriptions(openAPIWorkflow.getSourceDescriptions()) > 1 ? true : false;


if(openAPIWorkflow.getWorkflows() != null) {
validateWorkflowIdsUniqueness(openAPIWorkflow.getWorkflows());

for (Workflow workflow : openAPIWorkflow.getWorkflows()) {
errors.addAll(validateStepsWorkflowIds(workflow.getSteps(), multipleWorkflowsSpec));
}
}

for (Workflow workflow : openAPIWorkflow.getWorkflows()) {
this.workflowIds.add(workflow.getWorkflowId());
}

}
return errors;
}

Expand Down Expand Up @@ -440,10 +446,82 @@ List<String> loadStepIds(List<Workflow> workflows) {
return errors;
}

List<String> loadOperationIds(OpenAPIWorkflow openAPIWorkflow) {
List<String> errors = new ArrayList<>();

boolean multipleOpenApiFiles = getNumOpenApiSourceDescriptions(openAPIWorkflow.getSourceDescriptions()) > 1 ? true : false;

for(Workflow workflow : openAPIWorkflow.getWorkflows()) {
errors.addAll(validateStepsOperationIds(workflow.getSteps(), multipleOpenApiFiles));

for(Step step : workflow.getSteps()) {
if(step.getOperationId() != null) {
this.operationIds.add(step.getOperationId());
}
}
}

return errors;
}

public List<String> validateStepsOperationIds(List<Step> steps, boolean multipleOpenApiFiles) {
List<String> errors = new ArrayList<>();

for(Step step : steps) {
if(multipleOpenApiFiles) {
// must use runtime expression to map applicable SourceDescription
if(step.getOperationId() != null && !step.getOperationId().startsWith("$sourceDescriptions.")) {
errors.add("Operation " + step.getOperationId() + " must be specified using a runtime expression (e.g., $sourceDescriptions.<name>.<operationId>)");
}
}
}

return errors;
}

// num of SourceDescriptions with type 'openapi'
int getNumOpenApiSourceDescriptions(List<SourceDescription> sourceDescriptions) {
return (int) sourceDescriptions.stream().filter(p -> p.isOpenApi()).count();
}

// num of SourceDescriptions with type 'workflowsSpec'
int getNumWorkflowsSpecSourceDescriptions(List<SourceDescription> sourceDescriptions) {
return (int) sourceDescriptions.stream().filter(p -> p.isWorkflowsSpec()).count();
}

boolean stepExists(String workflowId, String stepId) {
return this.stepIds.get(workflowId) != null && this.stepIds.get(workflowId).contains(stepId);
}

List<String> validateWorkflowIdsUniqueness(List<Workflow> workflows) {
List<String> errors = new ArrayList<>();

Set<String> ids = new HashSet<>();
for(Workflow workflow : workflows) {
if(!ids.add(workflow.getWorkflowId())) {
// id already exists
errors.add("WorkflowId is not unique: " + workflow.getWorkflowId());
}
}
return errors;
}

List<String> validateStepsWorkflowIds(List<Step> steps, boolean multipleWorkflowsSpecFiles) {
List<String> errors = new ArrayList<>();

for(Step step : steps) {
if(multipleWorkflowsSpecFiles) {
// must use runtime expression to map applicable SourceDescription
if(step.getWorkflowId() != null && !step.getWorkflowId().startsWith("$sourceDescriptions.")) {
errors.add("Operation " + step.getWorkflowId() + " must be specified using a runtime expression (e.g., $sourceDescriptions.<name>.<workflowId>)");
}
}
}

return errors;
}


public boolean isValidJsonPointer(String jsonPointerString) {

boolean ret;
Expand Down
103 changes: 101 additions & 2 deletions src/test/java/com/apiflows/parser/OpenAPIWorkflowValidatorTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -486,14 +486,14 @@ void validateFailureActionInvalidStepId() {


@Test
void loadWorkflowIWithDuplicateIds() {
void validateWorkflowIdsUniqueness() {
List<Workflow> list = List.of(
new Workflow()
.workflowId("one"),
new Workflow()
.workflowId("one"));

assertEquals(1, validator.loadWorkflowIds(list).size());
assertEquals(1, validator.validateWorkflowIdsUniqueness(list).size());
}

@Test
Expand All @@ -510,6 +510,20 @@ void validateComponentsParameterInvalidIn() {
assertEquals(1, validator.validateParameter(parameter, worklowId).size());
}

@Test
void validateComponentsParameter() {
Parameter parameter = new Parameter()
.name("page")
.value("1")
.in("query");
String worklowId = "q1";

Components components = new Components();
components.addParameter("page", parameter);

assertEquals(0, validator.validateParameter(parameter, worklowId).size());
}

@Test
void loadStepsWithDuplicateIds() {
List<Workflow> list = List.of(
Expand All @@ -524,6 +538,91 @@ void loadStepsWithDuplicateIds() {
assertEquals(1, validator.loadStepIds(list).size());
}

@Test
public void getNumOpenApiSourceDescriptions() {
List<SourceDescription> sourceDescriptions = List.of(
new SourceDescription()
.name("openapifile-1")
.type("openapi"),
new SourceDescription()
.name("openapifile-2")
.type("openapi"),
new SourceDescription()
.name("workflowspec-1")
.type("workflowsSpec")
);

assertEquals(2, new OpenAPIWorkflowValidator().getNumOpenApiSourceDescriptions(sourceDescriptions));
}

@Test
public void getNumWorkflowsSpecSourceDescriptions() {
List<SourceDescription> sourceDescriptions = List.of(
new SourceDescription()
.name("openapifile-1")
.type("openapi"),
new SourceDescription()
.name("openapifile-2")
.type("openapi"),
new SourceDescription()
.name("workflowspec-1")
.type("workflowsSpec")
);

assertEquals(1, new OpenAPIWorkflowValidator().getNumWorkflowsSpecSourceDescriptions(sourceDescriptions));
}

@Test
public void validateStepsOperationIds() {
boolean multipleOpenApiFiles = true;

List<Step> steps = List.of(
new Step()
.stepId("step-one")
.operationId("post-operation")
);

assertEquals(1, new OpenAPIWorkflowValidator().validateStepsOperationIds(steps, multipleOpenApiFiles).size());
}

@Test
public void validateStepsOperationIdsWithoutRuntimeExpression() {
boolean multipleOpenApiFiles = false;

List<Step> steps = List.of(
new Step()
.stepId("step-one")
.operationId("post-operation")
);

assertEquals(0, new OpenAPIWorkflowValidator().validateStepsOperationIds(steps, multipleOpenApiFiles).size());
}

@Test
public void validateStepsWorkflowIds() {
boolean multipleWorkflowsSpecFiles = true;

List<Step> steps = List.of(
new Step()
.stepId("step-one")
.workflowId("w2")
);

assertEquals(1, new OpenAPIWorkflowValidator().validateStepsWorkflowIds(steps, multipleWorkflowsSpecFiles).size());
}

@Test
public void validateStepsWorkflowIdsWithoutRuntimeExpression() {
boolean multipleWorkflowsSpecFiles = false;

List<Step> steps = List.of(
new Step()
.stepId("step-one")
.workflowId("w2")
);

assertEquals(0, new OpenAPIWorkflowValidator().validateStepsWorkflowIds(steps, multipleWorkflowsSpecFiles).size());
}

@Test
void validWorkflowId() {
Expand Down