Skip to content
This repository has been archived by the owner on Apr 24, 2023. It is now read-only.

Improve validation with asynchronous pipeline #175

Open
wants to merge 22 commits into
base: canary
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 19 commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
1aefc98
Fix: check whether validator is a function to avoid type error
Mosoc Apr 27, 2019
db82657
Fix: typo `ComponentWithValidation`
Mosoc Apr 27, 2019
8c03926
Only pass schema property to avj.compile()
Mosoc Apr 29, 2019
ae5d0ce
Fix some coding style
Mosoc Apr 29, 2019
991c04f
Add async keyword to validate function
Mosoc May 9, 2019
0d51466
Update validation test file because of object structure change
Mosoc May 14, 2019
ff1312c
Update validator test
Mosoc May 14, 2019
2f642e0
Implement asynchronous pipline of validation
Mosoc May 9, 2019
763fde4
Fix typo: withValidation
Mosoc May 14, 2019
dfa6ce1
Reform validation's test file with async/await, and remove unused props
Mosoc May 15, 2019
c5711d0
Change the test label of customized validator
Mosoc May 15, 2019
9e5748a
Create test cases of sync validator function
Mosoc May 15, 2019
1c731c9
Correct fault in validation HoC
Mosoc May 15, 2019
e10c57f
Create test cases with async-wait functions
Mosoc May 15, 2019
67ffc9a
Create test cases with promise operations
Mosoc May 15, 2019
a493e5a
Add try-catch in validate to handle uncaught error
Mosoc May 15, 2019
096a86a
Workaround for when validator is not a function
Mosoc May 15, 2019
91038ba
Reove `_ ` prefix of promise creator
Mosoc May 16, 2019
831fd24
Update type definition of validation
Mosoc May 16, 2019
471a875
Update the usage of customized validator in docs
Mosoc May 18, 2019
0993e84
Use keep (verb) as prefix for promise creator
Mosoc May 19, 2019
9157f2c
Change `keep`(verb) with `promise`(verb)
Mosoc May 29, 2019
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
4 changes: 2 additions & 2 deletions packages/canner/src/hocs/types.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// @flow
g// @flow
import type {Action, ActionType} from '../action/types';
import type {Query} from '../query';
import type RefId from 'canner-ref-id';
Expand All @@ -17,7 +17,7 @@ export type Subscribe = (key: string, callback: (data: any) => void) => Subscrip
export type Deploy = (key: string, id?: string) => Promise<*>;
export type OnDeploy = (key: string, callback: Function) => any;
export type RemoveOnDeploy = (key: string, callbackId: string) => void;
export type Validation = Object;
export type Validation = {schema?: Object, erorrMessage?: string, validator?: (value: any) => string | Promise<string> | Promise<void> | void}
export type UIParams = Object;
export type Relation = Object;
export type RenderChildren = any => React.Node
Expand Down
159 changes: 120 additions & 39 deletions packages/canner/src/hocs/validation.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,75 @@
import * as React from 'react';
import RefId from 'canner-ref-id';
import Ajv from 'ajv';
import {isEmpty, isArray, isPlainObject, get} from 'lodash';
import {isEmpty, isObject, isArray, isPlainObject, isFunction, toString, get} from 'lodash';
import type {HOCProps} from './types';

type State = {
error: boolean,
errorInfo: Array<any>
}


const checkValidation = (validation) => {
return (isObject(validation) && !isEmpty(validation))
}

const checkSchema = (schema) => {
return (isObject(schema) && !isEmpty(schema) )
}
const checkValidator = (validator) => {
return (isFunction(validator))
}

const isRequiredValidation = async (value) => {
const valid = Boolean(value)
return {
error: !valid,
errorInfo: !valid ? [{message: 'should be required'}] :[]
}
}

const schemaValidation = (schema, errorMessage) => {
const ajv = new Ajv();
const validate = ajv.compile(schema);
return async (value) => {
try {
const error = !validate(value);
const errorInfo = error ? [].concat( errorMessage ? {message: errorMessage} : validate.errors ) : [];
return {
error,
errorInfo
}
}
catch(err){
return {
error: true,
errorInfo: [{message: toString(err)}]
}
}

}
}
const customizedValidator = (validator) => async (value) => {
try {
const errorMessage = await validator(value);
const error = Boolean(errorMessage);
const errorInfo = error ? [{message: errorMessage}] : []
return {
error,
errorInfo
}
}
catch(err) {
return {
error: true,
errorInfo: [{message: toString(err)}]
}
}
}

export default function withValidation(Com: React.ComponentType<*>) {
return class ComponentWithValition extends React.Component<HOCProps, State> {
return class ComponentWithValidation extends React.Component<HOCProps, State> {
key: string;
id: ?string;
callbackId: ?string;
Expand All @@ -35,48 +94,70 @@ export default function withValidation(Com: React.ComponentType<*>) {
this.removeOnDeploy();
}

validate = (result: any) => {
const {refId, validation = {}, required = false} = this.props;
// required
const paths = refId.getPathArr().slice(1);
const {value} = getValueAndPaths(result.data, paths);
const isRequiredValid = required ? Boolean(value) : true;

// Ajv validation
const ajv = new Ajv();
const validate = ajv.compile(validation);

// custom validator
const {validator, errorMessage} = validation;
const reject = message => ({error: true, message});
const validatorResult = validator && validator(value, reject);

let customValid = !(validatorResult && validatorResult.error);
// if value is empty, should not validate with ajv
if (customValid && isRequiredValid && (!value || validate(value))) {
this.setState({
error: false,
errorInfo: []
});
return result;
handleValidationResult = (results: any) => {

let error = false;
let errorInfo = [];

for(let index = 0; index < results.length; index++) {
error = error || results[index].error
errorInfo = errorInfo.concat(results[index].errorInfo);
}


const errorInfo = []
.concat(isRequiredValid ? [] : {
message: 'should be required'
})
.concat(validate.errors ? (errorMessage ? {message: errorMessage} : validate.errors) : [])
.concat(customValid ? [] : validatorResult);

this.setState({
error: true,
errorInfo: errorInfo
error,
errorInfo
});

return {
...result,
error: true,
errorInfo: errorInfo
error,
errorInfo
}
}

validate = async (result: any) => {
const {refId, required = false, validation} = this.props;
const paths = refId.getPathArr().slice(1);
const {value} = getValueAndPaths(result.data, paths);
const promiseQueue = [];
try{
// check whether value is required in first step
if(required) {
promiseQueue.push(isRequiredValidation(value));
}

// skip validation if object validation is undefined or empty
if(checkValidation(validation)) {
const {schema, errorMessage, validator} = validation;
if(value && checkSchema(schema)) {
promiseQueue.push(schemaValidation(schema, errorMessage)(value));
}
if(validator) {
if(checkValidator(validator)) {
promiseQueue.push(customizedValidator(validator)(value));
} else {
throw 'Validator should be a function'
}
}
}

const ValidationResult = await Promise.all(promiseQueue);

return {
...result,
...this.handleValidationResult(ValidationResult)
}
}
catch(err){
this.setState({
error: true,
errorInfo: [].concat({message: toString(err)})
});
return {
...result,
error: true,
errorInfo: [].concat({message: toString(err)})
}
}
}

Expand Down