EdwardZZZ / articles

工作点滴记录
2 stars 0 forks source link

Model #80

Open EdwardZZZ opened 3 years ago

EdwardZZZ commented 3 years ago

已实现,详见 https://github.com/umajs/model

const RulesMap: Map<any, { [key: string]: Rule[] }> = new Map();
const TipsMap: Map<any, ValidateInfoList> = new Map();

type ValidateInfoList = {
    [key: string]: string[],
}

function Validate<T>(target: T, value?: T): [ValidateInfoList, T] {
    try {
        if (value !== undefined) {
            for (const key in value) {
                target[key] = value[key];
            }
        }

        let ruleInfo = TipsMap.get(target);

        TipsMap.set(target, null);

        return [ruleInfo, target];
    } catch (err) {

    } finally {
    }
}

function checker(rules: Rule[], value: any): string[] {
    const msgs = [];

    for (const rule of rules) {
        if (!rule.validate(value)) {
            msgs.push(rule.errorTemplate);
        }
    }

    return msgs;
}

class Rule {
    constructor({ validate, errorTemplate }: { validate: (value: any) => boolean, errorTemplate: string, }) {
        this.validate = validate;
        this.errorTemplate = errorTemplate ?? 'validate tips.';
    }

    validate: (value: any) => boolean;

    errorTemplate: string;

    add(): PropertyDecorator {
        if (!(this instanceof Rule)) throw new Error('....');
        const self = this;

        return function (target: Object, propertyKey: string) {
            if (!RulesMap.has(target)) RulesMap.set(target, {});

            const rules = RulesMap.get(target)[propertyKey] ?? [];

            rules.push(self);

            RulesMap.get(target)[propertyKey] = rules;

            let keyValue = target[propertyKey];

            Reflect.defineProperty(target, propertyKey, {
                set: function (v) {
                    const result = checker(RulesMap.get(target)[propertyKey], v);

                    if (result.length > 0) {
                        const tips = TipsMap.get(this) || {};

                        tips[propertyKey] = result;
                        TipsMap.set(this, tips);

                        return;
                    }

                    keyValue = v;
                },
                get: function () {
                    return keyValue;
                },
            });
        }
    }
}

const Type = (t: 'string' | 'number' | 'boolean', errorTemplate: string = 'type...'): PropertyDecorator => {
    function validate(value: any): boolean {
        return typeof value === 'number';
    }

    return new Rule({ validate, errorTemplate }).add();
}

function Min(n: number, errorTemplate: string = 'min...'): PropertyDecorator {
    const rule = new Rule({
        errorTemplate,
        validate(value: any): boolean {
            if (typeof (value) === 'undefined') return true;

            return +value > n;
        }
    });

    return rule.add();
}

function Require(errorTemplate?: string): PropertyDecorator {
    const rule = new Rule({
        errorTemplate,
        validate(value: any): boolean {
            return typeof (value) === undefined;
        }
    });

    return rule.add();
}

class UserInfo {
    constructor({ id, name, age }: UserInfo) {
        this.id = id;
        this.name = name;
        this.age = age;
    }

    @Type('number')
    id: number = 123;

    @Require()
    name?: string;

    @Min(0)
    age?: number;
}

try {
    const [info1, user] = Validate(new UserInfo({ id: 123 }));
    console.log('1>>', info1, '\n');

    // 1
    const [info2] = Validate(user, { 'id': 456, age: -1 });
    console.log('2>>', info2);

    // 2
    user.id = 456;
    user.name = undefined;
    user.age = -2;
    const [info3] = Validate(user);
    console.log('3>>', info3);
} catch (err) {
    console.log('>>>', err);
}
EdwardZZZ commented 3 years ago
import Rule from './Rule';
import { checker, hasOwnProperty, isEmpley, IS_VALID, RULES, TIPS } from './utils';

export default class Model {
    constructor(isValid: boolean = true) {
        const rulesObj: { [key: string]: Rule[] } = this.constructor[RULES];

        Object.defineProperties(this, {
            [TIPS]: {
                enumerable: false,
                writable: true,
                value: {},
            },
            [IS_VALID]: {
                enumerable: false,
                writable: true,
                value: isValid,
            },
        });

        for (const key in rulesObj) {
            if (!hasOwnProperty(rulesObj, key)) continue;

            const rules = rulesObj[key];

            Object.defineProperties(this, {
                [Symbol.for(key)]: {
                    enumerable: false,
                    writable: true,
                    value: this[key],
                },
               [key]: {
                    enumerable: true,
                    set(val) {
                        const tips = checker(rules, key, val);
                        const tipsObj: { [key: string]: string[] } = this[TIPS];

                        if (tips.length > 0) {
                            tipsObj[key] = tips;

                            if (isValid) return;
                        } else if (!isEmpley(tipsObj[key])) {
                            delete this[TIPS][key];
                        }

                        this[Symbol.for(key)] = val;
                    },
                    get() {
                        return this[Symbol.for(key)];
                    },
                },
            });
        }
    }

    static [RULES]: { [key: string]: Rule[] } = {};
}