Skip to content

Commit 9fd55a8

Browse files
authored
0.5.0. (#11)
1 parent 5b14906 commit 9fd55a8

File tree

12 files changed

+312
-82
lines changed

12 files changed

+312
-82
lines changed

CHANGELOG.md

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,18 @@
1+
## 0.5.0
2+
3+
The `DefinitionValidator` class supports the validation of the whole definition. Use the `validate` method to validate a definition deeply. This method will validate all steps in the definition at once. Now you may easily validate a definition in the back-end before saving it to the storage.
4+
5+
```ts
6+
const validator = DefinitionValidator.create(definitionModel, definitionWalker);
7+
if (validator.validate(definition)) {
8+
throw new Error('Invalid definition');
9+
}
10+
```
11+
12+
**Breaking changes:**
13+
14+
* Renamed the `ModelValidator` class to `DefinitionValidator`.
15+
116
## 0.4.1
217

318
* The model validator is improved. Now the validator validates the `name` field of the step as well.

demos/webpack-app/package.json

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,11 +15,11 @@
1515
},
1616
"dependencies": {
1717
"xstate": "^4.37.2",
18-
"sequential-workflow-model": "^0.1.3",
19-
"sequential-workflow-designer": "^0.13.3",
18+
"sequential-workflow-model": "^0.1.4",
19+
"sequential-workflow-designer": "^0.13.4",
2020
"sequential-workflow-machine": "^0.2.0",
21-
"sequential-workflow-editor-model": "^0.4.1",
22-
"sequential-workflow-editor": "^0.4.1"
21+
"sequential-workflow-editor-model": "^0.5.0",
22+
"sequential-workflow-editor": "^0.5.0"
2323
},
2424
"devDependencies": {
2525
"ts-loader": "^9.4.2",

editor/package.json

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "sequential-workflow-editor",
3-
"version": "0.4.1",
3+
"version": "0.5.0",
44
"type": "module",
55
"main": "./lib/esm/index.js",
66
"types": "./lib/index.d.ts",
@@ -46,12 +46,12 @@
4646
"prettier:fix": "prettier --write ./src ./css"
4747
},
4848
"dependencies": {
49-
"sequential-workflow-editor-model": "^0.4.1",
50-
"sequential-workflow-model": "^0.1.3"
49+
"sequential-workflow-editor-model": "^0.5.0",
50+
"sequential-workflow-model": "^0.1.4"
5151
},
5252
"peerDependencies": {
53-
"sequential-workflow-editor-model": "^0.4.1",
54-
"sequential-workflow-model": "^0.1.3"
53+
"sequential-workflow-editor-model": "^0.5.0",
54+
"sequential-workflow-model": "^0.1.4"
5555
},
5656
"devDependencies": {
5757
"rollup": "^3.20.2",

editor/src/editor-provider.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { Definition, DefinitionWalker, Step } from 'sequential-workflow-model';
22
import { Editor } from './editor';
3-
import { DefinitionContext, DefinitionModel, ModelActivator, ModelValidator, Path } from 'sequential-workflow-editor-model';
3+
import { DefinitionContext, DefinitionModel, ModelActivator, DefinitionValidator, Path } from 'sequential-workflow-editor-model';
44
import { EditorServices, ValueEditorEditorFactoryResolver } from './value-editors';
55
import {
66
GlobalEditorContext,
@@ -22,7 +22,7 @@ export class EditorProvider<TDefinition extends Definition> {
2222
): EditorProvider<TDef> {
2323
const definitionWalker = configuration.definitionWalker ?? new DefinitionWalker();
2424
const activator = ModelActivator.create(definitionModel, configuration.uidGenerator);
25-
const validator = ModelValidator.create(definitionModel, definitionWalker);
25+
const validator = DefinitionValidator.create(definitionModel, definitionWalker);
2626
return new EditorProvider(activator, validator, definitionModel, definitionWalker, configuration);
2727
}
2828

@@ -33,7 +33,7 @@ export class EditorProvider<TDefinition extends Definition> {
3333

3434
private constructor(
3535
private readonly activator: ModelActivator<TDefinition>,
36-
private readonly validator: ModelValidator,
36+
private readonly validator: DefinitionValidator,
3737
private readonly definitionModel: DefinitionModel,
3838
private readonly definitionWalker: DefinitionWalker,
3939
private readonly configuration: EditorProviderConfiguration
@@ -85,13 +85,13 @@ export class EditorProvider<TDefinition extends Definition> {
8585

8686
public createStepValidator(): StepValidator {
8787
return (step: Step, _: unknown, definition: Definition): boolean => {
88-
return this.validator.validateStep(step, definition);
88+
return this.validator.validateStep(step, definition) === null;
8989
};
9090
}
9191

9292
public createRootValidator(): RootValidator {
9393
return (definition: Definition): boolean => {
94-
return this.validator.validateRoot(definition);
94+
return this.validator.validateRoot(definition) === null;
9595
};
9696
}
9797

model/package.json

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "sequential-workflow-editor-model",
3-
"version": "0.4.1",
3+
"version": "0.5.0",
44
"homepage": "https://nocode-js.com/",
55
"author": {
66
"name": "NoCode JS",
@@ -45,10 +45,10 @@
4545
"test": "jest --clearCache && jest --watchAll"
4646
},
4747
"dependencies": {
48-
"sequential-workflow-model": "^0.1.3"
48+
"sequential-workflow-model": "^0.1.4"
4949
},
5050
"peerDependencies": {
51-
"sequential-workflow-model": "^0.1.3"
51+
"sequential-workflow-model": "^0.1.4"
5252
},
5353
"devDependencies": {
5454
"typescript": "^4.9.5",

model/src/model.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -72,8 +72,8 @@ export interface CustomValidator<TValue extends PropertyValue = PropertyValue, T
7272
validate(context: CustomValidatorContext<TValue, TProperties>): string | null;
7373
}
7474

75-
export type ValidationResult = Record<string, string | null> | null;
76-
export type ValidationSingleError = Record<'$', string>;
75+
export type ValidationError = Record<string, string | null>;
76+
export type ValidationResult = ValidationError | null;
7777

7878
export function createValidationSingleError(error: string): ValidationResult {
7979
return {
Lines changed: 158 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,158 @@
1+
import { Definition, DefinitionWalker, Step } from 'sequential-workflow-model';
2+
import { createDefinitionModel, createRootModel, createStepModel } from '../builders';
3+
import { numberValueModel } from '../value-models';
4+
import { DefinitionValidator } from './definition-validator';
5+
6+
interface FooDefinition extends Definition {
7+
properties: {
8+
velocity: number;
9+
};
10+
}
11+
12+
interface FooStep extends Step {
13+
properties: {
14+
delta: number;
15+
};
16+
}
17+
18+
describe('DefinitionValidator', () => {
19+
const model = createDefinitionModel<FooDefinition>(builder => {
20+
builder.root(
21+
createRootModel(root => {
22+
root.property('velocity').value(
23+
numberValueModel({
24+
min: 0
25+
})
26+
);
27+
})
28+
);
29+
30+
builder.steps([
31+
createStepModel<FooStep>('move', 'task', step => {
32+
step.property('delta').value(
33+
numberValueModel({
34+
max: 0
35+
})
36+
);
37+
})
38+
]);
39+
});
40+
const walker = new DefinitionWalker();
41+
const validator = DefinitionValidator.create(model, walker);
42+
43+
it('returns error when root is invalid', () => {
44+
const def: FooDefinition = {
45+
sequence: [],
46+
properties: {
47+
velocity: -1 // invalid
48+
}
49+
};
50+
51+
const error = validator.validate(def);
52+
53+
expect(error?.stepId).toEqual(null);
54+
expect(error?.propertyPath.toString()).toEqual('properties/velocity');
55+
expect(error?.error.$).toEqual('The value must be at least 0.');
56+
});
57+
58+
it('returns error when step has invalid delta value', () => {
59+
const def: FooDefinition = {
60+
sequence: [
61+
{
62+
type: 'move',
63+
componentType: 'task',
64+
id: '0x000000',
65+
name: 'Correct',
66+
properties: {
67+
delta: -100
68+
}
69+
},
70+
{
71+
type: 'move',
72+
componentType: 'task',
73+
id: '0xFFFFFF',
74+
name: 'Invalid!',
75+
properties: {
76+
delta: 1 // invalid
77+
}
78+
}
79+
],
80+
properties: {
81+
velocity: 100
82+
}
83+
};
84+
85+
const error = validator.validate(def);
86+
87+
expect(error?.stepId).toEqual('0xFFFFFF');
88+
expect(error?.propertyPath.toString()).toEqual('properties/delta');
89+
expect(error?.error.$).toEqual('The value must be at most 0.');
90+
});
91+
92+
it('returns error when step has invalid name', () => {
93+
const def: FooDefinition = {
94+
sequence: [
95+
{
96+
type: 'move',
97+
componentType: 'task',
98+
id: '0xAAAAAA',
99+
name: '', // invalid
100+
properties: {
101+
delta: 1
102+
}
103+
}
104+
],
105+
properties: {
106+
velocity: 100
107+
}
108+
};
109+
110+
const error = validator.validate(def);
111+
112+
expect(error?.stepId).toEqual('0xAAAAAA');
113+
expect(error?.propertyPath.toString()).toEqual('name');
114+
expect(error?.error.$).toEqual('The value must be at least 1 characters long.');
115+
});
116+
117+
it('returns null when definition is valid', () => {
118+
const def: FooDefinition = {
119+
sequence: [
120+
{
121+
type: 'move',
122+
componentType: 'task',
123+
id: '0x000000',
124+
name: 'Right',
125+
properties: {
126+
delta: -100
127+
}
128+
}
129+
],
130+
properties: {
131+
velocity: 100
132+
}
133+
};
134+
135+
const error = validator.validate(def);
136+
137+
expect(error).toBeNull();
138+
});
139+
140+
it('throws error when step type is not supported', () => {
141+
const def: FooDefinition = {
142+
sequence: [
143+
{
144+
type: 'not_supported_type',
145+
componentType: 'task',
146+
id: '0x000000',
147+
name: 'Right',
148+
properties: {}
149+
}
150+
],
151+
properties: {
152+
velocity: 100
153+
}
154+
};
155+
156+
expect(() => validator.validate(def)).toThrowError('Cannot find model for step type: not_supported_type');
157+
});
158+
});
Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
import { Definition, DefinitionWalker, Step } from 'sequential-workflow-model';
2+
import { DefinitionModel, PropertyModel, PropertyModels, ValidationError, ValidationResult, createValidationSingleError } from '../model';
3+
import { DefinitionContext, ValueContext } from '../context';
4+
import { CustomValidatorContext } from './custom-validator-context';
5+
import { Path } from '../core';
6+
7+
export class DefinitionValidator {
8+
public static create(definitionModel: DefinitionModel, definitionWalker: DefinitionWalker): DefinitionValidator {
9+
return new DefinitionValidator(definitionModel, definitionWalker);
10+
}
11+
12+
private constructor(private readonly model: DefinitionModel, private readonly walker: DefinitionWalker) {}
13+
14+
/**
15+
* Deeply validates the given definition.
16+
* @param definition The definition to validate.
17+
* @returns `null` if the definition is valid, otherwise an object describing the validation error.
18+
*/
19+
public validate(definition: Definition): DefinitionValidationError | null {
20+
const rootError = this.validateRoot(definition);
21+
if (rootError) {
22+
return {
23+
...rootError,
24+
stepId: null
25+
};
26+
}
27+
28+
let result: DefinitionValidationError | null = null;
29+
this.walker.forEach(definition, step => {
30+
const stepError = this.validateStep(step, definition);
31+
if (stepError) {
32+
result = {
33+
...stepError,
34+
stepId: step.id
35+
};
36+
return false; // stop walking
37+
}
38+
});
39+
return result;
40+
}
41+
42+
public validateStep(step: Step, definition: Definition): PropertyValidationError | null {
43+
const definitionContext = DefinitionContext.createForStep(step, definition, this.model, this.walker);
44+
45+
const stepModel = this.model.steps[step.type];
46+
if (!stepModel) {
47+
throw new Error(`Cannot find model for step type: ${step.type}`);
48+
}
49+
50+
const nameError = this.validateProperty(stepModel.name, definitionContext);
51+
if (nameError) {
52+
return {
53+
propertyPath: stepModel.name.path,
54+
error: nameError
55+
};
56+
}
57+
return this.validateProperties(stepModel.properties, definitionContext);
58+
}
59+
60+
public validateRoot(definition: Definition): PropertyValidationError | null {
61+
const definitionContext = DefinitionContext.createForRoot(definition, this.model, this.walker);
62+
return this.validateProperties(this.model.root.properties, definitionContext);
63+
}
64+
65+
private validateProperties(properties: PropertyModels, definitionContext: DefinitionContext): PropertyValidationError | null {
66+
for (const propertyName of properties) {
67+
const error = this.validateProperty(propertyName, definitionContext);
68+
if (error) {
69+
return {
70+
propertyPath: propertyName.path,
71+
error
72+
};
73+
}
74+
}
75+
return null;
76+
}
77+
78+
private validateProperty(propertyModel: PropertyModel, definitionContext: DefinitionContext): ValidationResult {
79+
const valueContext = ValueContext.create(propertyModel.value, propertyModel, definitionContext);
80+
const valueError = propertyModel.value.validate(valueContext);
81+
if (valueError) {
82+
return valueError;
83+
}
84+
85+
if (propertyModel.customValidator) {
86+
const customContext = CustomValidatorContext.create(propertyModel, definitionContext);
87+
const customError = propertyModel.customValidator.validate(customContext);
88+
if (customError) {
89+
return createValidationSingleError(customError);
90+
}
91+
}
92+
return null;
93+
}
94+
}
95+
96+
export interface PropertyValidationError {
97+
propertyPath: Path;
98+
error: ValidationError;
99+
}
100+
101+
export interface DefinitionValidationError extends PropertyValidationError {
102+
/**
103+
* Step id. If it is `null` then the error is related to the root.
104+
*/
105+
stepId: string | null;
106+
}

0 commit comments

Comments
 (0)