Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

0.5.0. #11

Merged
merged 1 commit into from
Jul 1, 2023
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
15 changes: 15 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,18 @@
## 0.5.0

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.

```ts
const validator = DefinitionValidator.create(definitionModel, definitionWalker);
if (validator.validate(definition)) {
throw new Error('Invalid definition');
}
```

**Breaking changes:**

* Renamed the `ModelValidator` class to `DefinitionValidator`.

## 0.4.1

* The model validator is improved. Now the validator validates the `name` field of the step as well.
Expand Down
8 changes: 4 additions & 4 deletions demos/webpack-app/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,11 @@
},
"dependencies": {
"xstate": "^4.37.2",
"sequential-workflow-model": "^0.1.3",
"sequential-workflow-designer": "^0.13.3",
"sequential-workflow-model": "^0.1.4",
"sequential-workflow-designer": "^0.13.4",
"sequential-workflow-machine": "^0.2.0",
"sequential-workflow-editor-model": "^0.4.1",
"sequential-workflow-editor": "^0.4.1"
"sequential-workflow-editor-model": "^0.5.0",
"sequential-workflow-editor": "^0.5.0"
},
"devDependencies": {
"ts-loader": "^9.4.2",
Expand Down
10 changes: 5 additions & 5 deletions editor/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "sequential-workflow-editor",
"version": "0.4.1",
"version": "0.5.0",
"type": "module",
"main": "./lib/esm/index.js",
"types": "./lib/index.d.ts",
Expand Down Expand Up @@ -46,12 +46,12 @@
"prettier:fix": "prettier --write ./src ./css"
},
"dependencies": {
"sequential-workflow-editor-model": "^0.4.1",
"sequential-workflow-model": "^0.1.3"
"sequential-workflow-editor-model": "^0.5.0",
"sequential-workflow-model": "^0.1.4"
},
"peerDependencies": {
"sequential-workflow-editor-model": "^0.4.1",
"sequential-workflow-model": "^0.1.3"
"sequential-workflow-editor-model": "^0.5.0",
"sequential-workflow-model": "^0.1.4"
},
"devDependencies": {
"rollup": "^3.20.2",
Expand Down
10 changes: 5 additions & 5 deletions editor/src/editor-provider.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { Definition, DefinitionWalker, Step } from 'sequential-workflow-model';
import { Editor } from './editor';
import { DefinitionContext, DefinitionModel, ModelActivator, ModelValidator, Path } from 'sequential-workflow-editor-model';
import { DefinitionContext, DefinitionModel, ModelActivator, DefinitionValidator, Path } from 'sequential-workflow-editor-model';
import { EditorServices, ValueEditorEditorFactoryResolver } from './value-editors';
import {
GlobalEditorContext,
Expand All @@ -22,7 +22,7 @@ export class EditorProvider<TDefinition extends Definition> {
): EditorProvider<TDef> {
const definitionWalker = configuration.definitionWalker ?? new DefinitionWalker();
const activator = ModelActivator.create(definitionModel, configuration.uidGenerator);
const validator = ModelValidator.create(definitionModel, definitionWalker);
const validator = DefinitionValidator.create(definitionModel, definitionWalker);
return new EditorProvider(activator, validator, definitionModel, definitionWalker, configuration);
}

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

private constructor(
private readonly activator: ModelActivator<TDefinition>,
private readonly validator: ModelValidator,
private readonly validator: DefinitionValidator,
private readonly definitionModel: DefinitionModel,
private readonly definitionWalker: DefinitionWalker,
private readonly configuration: EditorProviderConfiguration
Expand Down Expand Up @@ -85,13 +85,13 @@ export class EditorProvider<TDefinition extends Definition> {

public createStepValidator(): StepValidator {
return (step: Step, _: unknown, definition: Definition): boolean => {
return this.validator.validateStep(step, definition);
return this.validator.validateStep(step, definition) === null;
};
}

public createRootValidator(): RootValidator {
return (definition: Definition): boolean => {
return this.validator.validateRoot(definition);
return this.validator.validateRoot(definition) === null;
};
}

Expand Down
6 changes: 3 additions & 3 deletions model/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "sequential-workflow-editor-model",
"version": "0.4.1",
"version": "0.5.0",
"homepage": "https://nocode-js.com/",
"author": {
"name": "NoCode JS",
Expand Down Expand Up @@ -45,10 +45,10 @@
"test": "jest --clearCache && jest --watchAll"
},
"dependencies": {
"sequential-workflow-model": "^0.1.3"
"sequential-workflow-model": "^0.1.4"
},
"peerDependencies": {
"sequential-workflow-model": "^0.1.3"
"sequential-workflow-model": "^0.1.4"
},
"devDependencies": {
"typescript": "^4.9.5",
Expand Down
4 changes: 2 additions & 2 deletions model/src/model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -72,8 +72,8 @@ export interface CustomValidator<TValue extends PropertyValue = PropertyValue, T
validate(context: CustomValidatorContext<TValue, TProperties>): string | null;
}

export type ValidationResult = Record<string, string | null> | null;
export type ValidationSingleError = Record<'$', string>;
export type ValidationError = Record<string, string | null>;
export type ValidationResult = ValidationError | null;

export function createValidationSingleError(error: string): ValidationResult {
return {
Expand Down
158 changes: 158 additions & 0 deletions model/src/validator/definition-validator.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
import { Definition, DefinitionWalker, Step } from 'sequential-workflow-model';
import { createDefinitionModel, createRootModel, createStepModel } from '../builders';
import { numberValueModel } from '../value-models';
import { DefinitionValidator } from './definition-validator';

interface FooDefinition extends Definition {
properties: {
velocity: number;
};
}

interface FooStep extends Step {
properties: {
delta: number;
};
}

describe('DefinitionValidator', () => {
const model = createDefinitionModel<FooDefinition>(builder => {
builder.root(
createRootModel(root => {
root.property('velocity').value(
numberValueModel({
min: 0
})
);
})
);

builder.steps([
createStepModel<FooStep>('move', 'task', step => {
step.property('delta').value(
numberValueModel({
max: 0
})
);
})
]);
});
const walker = new DefinitionWalker();
const validator = DefinitionValidator.create(model, walker);

it('returns error when root is invalid', () => {
const def: FooDefinition = {
sequence: [],
properties: {
velocity: -1 // invalid
}
};

const error = validator.validate(def);

expect(error?.stepId).toEqual(null);
expect(error?.propertyPath.toString()).toEqual('properties/velocity');
expect(error?.error.$).toEqual('The value must be at least 0.');
});

it('returns error when step has invalid delta value', () => {
const def: FooDefinition = {
sequence: [
{
type: 'move',
componentType: 'task',
id: '0x000000',
name: 'Correct',
properties: {
delta: -100
}
},
{
type: 'move',
componentType: 'task',
id: '0xFFFFFF',
name: 'Invalid!',
properties: {
delta: 1 // invalid
}
}
],
properties: {
velocity: 100
}
};

const error = validator.validate(def);

expect(error?.stepId).toEqual('0xFFFFFF');
expect(error?.propertyPath.toString()).toEqual('properties/delta');
expect(error?.error.$).toEqual('The value must be at most 0.');
});

it('returns error when step has invalid name', () => {
const def: FooDefinition = {
sequence: [
{
type: 'move',
componentType: 'task',
id: '0xAAAAAA',
name: '', // invalid
properties: {
delta: 1
}
}
],
properties: {
velocity: 100
}
};

const error = validator.validate(def);

expect(error?.stepId).toEqual('0xAAAAAA');
expect(error?.propertyPath.toString()).toEqual('name');
expect(error?.error.$).toEqual('The value must be at least 1 characters long.');
});

it('returns null when definition is valid', () => {
const def: FooDefinition = {
sequence: [
{
type: 'move',
componentType: 'task',
id: '0x000000',
name: 'Right',
properties: {
delta: -100
}
}
],
properties: {
velocity: 100
}
};

const error = validator.validate(def);

expect(error).toBeNull();
});

it('throws error when step type is not supported', () => {
const def: FooDefinition = {
sequence: [
{
type: 'not_supported_type',
componentType: 'task',
id: '0x000000',
name: 'Right',
properties: {}
}
],
properties: {
velocity: 100
}
};

expect(() => validator.validate(def)).toThrowError('Cannot find model for step type: not_supported_type');
});
});
106 changes: 106 additions & 0 deletions model/src/validator/definition-validator.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
import { Definition, DefinitionWalker, Step } from 'sequential-workflow-model';
import { DefinitionModel, PropertyModel, PropertyModels, ValidationError, ValidationResult, createValidationSingleError } from '../model';
import { DefinitionContext, ValueContext } from '../context';
import { CustomValidatorContext } from './custom-validator-context';
import { Path } from '../core';

export class DefinitionValidator {
public static create(definitionModel: DefinitionModel, definitionWalker: DefinitionWalker): DefinitionValidator {
return new DefinitionValidator(definitionModel, definitionWalker);
}

private constructor(private readonly model: DefinitionModel, private readonly walker: DefinitionWalker) {}

/**
* Deeply validates the given definition.
* @param definition The definition to validate.
* @returns `null` if the definition is valid, otherwise an object describing the validation error.
*/
public validate(definition: Definition): DefinitionValidationError | null {
const rootError = this.validateRoot(definition);
if (rootError) {
return {
...rootError,
stepId: null
};
}

let result: DefinitionValidationError | null = null;
this.walker.forEach(definition, step => {
const stepError = this.validateStep(step, definition);
if (stepError) {
result = {
...stepError,
stepId: step.id
};
return false; // stop walking
}
});
return result;
}

public validateStep(step: Step, definition: Definition): PropertyValidationError | null {
const definitionContext = DefinitionContext.createForStep(step, definition, this.model, this.walker);

const stepModel = this.model.steps[step.type];
if (!stepModel) {
throw new Error(`Cannot find model for step type: ${step.type}`);
}

const nameError = this.validateProperty(stepModel.name, definitionContext);
if (nameError) {
return {
propertyPath: stepModel.name.path,
error: nameError
};
}
return this.validateProperties(stepModel.properties, definitionContext);
}

public validateRoot(definition: Definition): PropertyValidationError | null {
const definitionContext = DefinitionContext.createForRoot(definition, this.model, this.walker);
return this.validateProperties(this.model.root.properties, definitionContext);
}

private validateProperties(properties: PropertyModels, definitionContext: DefinitionContext): PropertyValidationError | null {
for (const propertyName of properties) {
const error = this.validateProperty(propertyName, definitionContext);
if (error) {
return {
propertyPath: propertyName.path,
error
};
}
}
return null;
}

private validateProperty(propertyModel: PropertyModel, definitionContext: DefinitionContext): ValidationResult {
const valueContext = ValueContext.create(propertyModel.value, propertyModel, definitionContext);
const valueError = propertyModel.value.validate(valueContext);
if (valueError) {
return valueError;
}

if (propertyModel.customValidator) {
const customContext = CustomValidatorContext.create(propertyModel, definitionContext);
const customError = propertyModel.customValidator.validate(customContext);
if (customError) {
return createValidationSingleError(customError);
}
}
return null;
}
}

export interface PropertyValidationError {
propertyPath: Path;
error: ValidationError;
}

export interface DefinitionValidationError extends PropertyValidationError {
/**
* Step id. If it is `null` then the error is related to the root.
*/
stepId: string | null;
}
Loading