Skip to content

Commit f574808

Browse files
authored
Merge pull request serverlessworkflow#895 from matthias-pichler-warrify/matthias-pichler/invalid-do-switch-894
Make do an array
2 parents 3a36e02 + 57cf769 commit f574808

32 files changed

+1281
-989
lines changed

.ci/validation/package-lock.json

+13-1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

.ci/validation/package.json

+2-1
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
"dependencies": {
2626
"ajv": "^8.12.0",
2727
"ajv-formats": "^2.1.1",
28-
"js-yaml": "^4.1.0"
28+
"js-yaml": "^4.1.0",
29+
"marked": "^13.0.0"
2930
}
3031
}

.ci/validation/src/dsl.test.ts

+53
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
/*
2+
* Copyright 2023-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+
17+
import { SWSchemaValidator } from "./index";
18+
import fs from "node:fs";
19+
import path from "node:path";
20+
import marked from "marked";
21+
22+
SWSchemaValidator.prepareSchemas();
23+
24+
const dslReferencePath = path.join(
25+
__dirname,
26+
"..",
27+
"..",
28+
"..",
29+
"dsl-reference.md"
30+
);
31+
32+
describe(`Verify every example in the dsl docs`, () => {
33+
const workflows = marked
34+
.lexer(fs.readFileSync(dslReferencePath, SWSchemaValidator.defaultEncoding))
35+
.filter((item): item is marked.Tokens.Code => item.type === "code")
36+
.filter((item) => item.lang === "yaml")
37+
.map((item) => item.text)
38+
.map((text) => SWSchemaValidator.yamlToJSON(text))
39+
.filter((workflow) => typeof workflow === "object")
40+
.filter((workflow) => "document" in workflow)
41+
.filter((workflow) => "dsl" in workflow.document);
42+
43+
test.each(workflows)("$document.name", (workflow) => {
44+
const results = SWSchemaValidator.validateSchema(workflow);
45+
if (results?.errors) {
46+
console.warn(
47+
`Schema validation on workflow ${workflow.document.name} failed with: `,
48+
JSON.stringify(results.errors, null, 2)
49+
);
50+
}
51+
expect(results?.valid).toBeTruthy();
52+
});
53+
});

.ci/validation/src/index.test.ts renamed to .ci/validation/src/examples.test.ts

+23-21
Original file line numberDiff line numberDiff line change
@@ -15,33 +15,35 @@
1515
*/
1616

1717
import { SWSchemaValidator } from "./index";
18-
import fs from "fs";
19-
import { join } from "path";
18+
import fs from "node:fs";
19+
import path from "node:path";
2020

2121
SWSchemaValidator.prepareSchemas();
2222

2323
const examplePath = "../../../examples";
2424

2525
describe(`Verify every example in the repository`, () => {
26-
fs.readdirSync(join(__dirname, examplePath), {
27-
encoding: SWSchemaValidator.defaultEncoding,
28-
recursive: false,
29-
withFileTypes: true,
30-
}).forEach((file) => {
31-
if (file.isFile() && file.name.endsWith(".yaml")) {
32-
test(`Example ${file.name}`, () => {
33-
const workflow = SWSchemaValidator.toJSON(
34-
join(__dirname, `${examplePath}/${file.name}`)
35-
);
36-
const results = SWSchemaValidator.validateSchema(workflow);
37-
if (results?.errors != null) {
38-
console.warn(
39-
`Schema validation on ${file.name} failed with: `,
40-
JSON.stringify(results.errors, null, 2)
41-
);
42-
}
43-
expect(results?.valid).toBeTruthy();
44-
});
26+
const examples = fs
27+
.readdirSync(path.join(__dirname, examplePath), {
28+
encoding: SWSchemaValidator.defaultEncoding,
29+
recursive: false,
30+
withFileTypes: true,
31+
})
32+
.filter((file) => file.isFile())
33+
.filter((file) => file.name.endsWith(".yaml"))
34+
.map((file) => file.name);
35+
36+
test.each(examples)("Example %s", (file) => {
37+
const workflow = SWSchemaValidator.loadAsJSON(
38+
path.join(__dirname, `${examplePath}/${file}`)
39+
);
40+
const results = SWSchemaValidator.validateSchema(workflow);
41+
if (results?.errors) {
42+
console.warn(
43+
`Schema validation on ${file} failed with: `,
44+
JSON.stringify(results.errors, null, 2)
45+
);
4546
}
47+
expect(results?.valid).toBeTruthy();
4648
});
4749
});

.ci/validation/src/index.ts

+30-22
Original file line numberDiff line numberDiff line change
@@ -14,54 +14,62 @@
1414
* limitations under the License.
1515
*/
1616

17-
import fs from "fs";
18-
import Ajv from "ajv";
17+
import fs from "node:fs";
18+
import Ajv from "ajv/dist/2020";
1919
import addFormats from "ajv-formats";
20-
import { join } from "path";
20+
import path from "node:path";
2121
import yaml = require("js-yaml");
2222

2323
export module SWSchemaValidator {
2424
const ajv = new Ajv({ strict: false, allowUnionTypes: true });
2525
addFormats(ajv);
2626

2727
const workflowSchemaId =
28-
"https://serverlessworkflow.io/schemas/1.0.0-alpha1/workflow.json";
28+
"https://serverlessworkflow.io/schemas/1.0.0-alpha1/workflow.yaml";
2929
const schemaPath = "../../../schema";
3030
export const defaultEncoding = "utf-8";
3131

3232
export function prepareSchemas() {
33-
fs.readdirSync(join(__dirname, schemaPath), {
33+
const files = fs.readdirSync(path.join(__dirname, schemaPath), {
3434
encoding: defaultEncoding,
3535
recursive: false,
3636
withFileTypes: true,
37-
}).forEach((file) => {
38-
if (file.isFile()) {
39-
ajv.addSchema(syncReadSchema(file.name));
40-
}
4137
});
38+
39+
files
40+
.filter((file) => file.isFile())
41+
.forEach((file) => {
42+
ajv.addSchema(syncReadSchema(file.name));
43+
});
4244
}
4345

44-
function syncReadSchema(filename: string) {
45-
return toJSON(join(__dirname, `${schemaPath}/${filename}`));
46+
function syncReadSchema(filename: string): any {
47+
return loadAsJSON(path.join(__dirname, `${schemaPath}/${filename}`));
4648
}
4749

48-
export function toJSON(filename: string) {
49-
const yamlObj = yaml.load(fs.readFileSync(filename, defaultEncoding), {
50+
export function loadAsJSON(filename: string): any {
51+
return yamlToJSON(fs.readFileSync(filename, defaultEncoding));
52+
}
53+
54+
export function yamlToJSON(yamlStr: string): any {
55+
const yamlObj = yaml.load(yamlStr, {
5056
json: true,
5157
});
52-
return JSON.parse(JSON.stringify(yamlObj, null, 2));
58+
return structuredClone(yamlObj);
5359
}
5460

55-
export function validateSchema(workflow: JSON) {
61+
export function validateSchema(workflow: Record<string, unknown>) {
5662
const validate = ajv.getSchema(workflowSchemaId);
57-
if (validate != undefined) {
58-
const isValid = validate(workflow);
59-
return {
60-
valid: isValid,
61-
errors: validate.errors,
62-
};
63+
64+
if (!validate) {
65+
throw new Error(`Failed to validate schema on workflow`);
6366
}
64-
throw new Error(`Failed to validate schema on workflow`);
67+
68+
const isValid = validate(workflow);
69+
return {
70+
valid: isValid,
71+
errors: validate.errors,
72+
};
6573
}
6674
}
6775

.ci/validation/src/invalid.test.ts

+43
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
/*
2+
* Copyright 2023-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+
17+
import { SWSchemaValidator } from "./index";
18+
import fs from "node:fs";
19+
import path from "node:path";
20+
21+
SWSchemaValidator.prepareSchemas();
22+
23+
const invalidPath = "../test/fixtures/invalid";
24+
25+
describe(`Check that invalid workflows are rejected`, () => {
26+
const examples = fs
27+
.readdirSync(path.join(__dirname, invalidPath), {
28+
encoding: SWSchemaValidator.defaultEncoding,
29+
recursive: false,
30+
withFileTypes: true,
31+
})
32+
.filter((file) => file.isFile())
33+
.filter((file) => file.name.endsWith(".yaml"))
34+
.map((file) => file.name);
35+
36+
test.each(examples)("Example %s", (file) => {
37+
const workflow = SWSchemaValidator.loadAsJSON(
38+
path.join(__dirname, `${invalidPath}/${file}`)
39+
);
40+
const results = SWSchemaValidator.validateSchema(workflow);
41+
expect(results?.valid).toBeFalsy();
42+
});
43+
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
document:
2+
dsl: 1.0.0-alpha1
3+
namespace: examples
4+
name: two-tasks-in-one-item
5+
version: 1.0.0-alpha1
6+
do:
7+
- getPet:
8+
call: http
9+
with:
10+
method: get
11+
endpoint: https://petstore.swagger.io/v2/pet/{petId}
12+
foo: bar
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
document:
2+
dsl: 1.0.0-alpha1
3+
namespace: examples
4+
name: two-tasks-in-one-item
5+
version: 1.0.0-alpha1
6+
do:
7+
- getPet:
8+
call: http
9+
with:
10+
method: get
11+
endpoint: https://petstore.swagger.io/v2/pet/{petId}
12+
setMessage:
13+
set:
14+
message: "Looking for {petId}"

ctk/features/branch.feature

+31
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
Feature: Composite Task
2+
As an implementer of the workflow DSL
3+
I want to ensure that composite tasks can be executed within the workflow
4+
So that my implementation conforms to the expected behavior
5+
6+
# Tests composite tasks With competing concurrent sub tasks
7+
Scenario: Fork Task With Competing Concurrent Sub Tasks
8+
Given a workflow with definition:
9+
"""yaml
10+
document:
11+
dsl: 1.0.0-alpha1
12+
namespace: default
13+
name: fork
14+
do:
15+
- branchWithCompete:
16+
fork:
17+
compete: true
18+
branches:
19+
- setRed:
20+
set:
21+
colors: ${ .colors + ["red"] }
22+
- setGreen:
23+
set:
24+
colors: ${ .colors + ["green"] }
25+
- setBlue:
26+
set:
27+
colors: ${ .colors + ["blue"] }
28+
"""
29+
When the workflow is executed
30+
Then the workflow should complete
31+
And the workflow output should have a 'colors' property containing 1 items

0 commit comments

Comments
 (0)