core: enhanced validator

This commit is contained in:
undefined committed 2023-01-20 03:24:38 +08:00
1 parent feb5180476
commit 760c585ac5
1 file changed
+60 -49
+60 -49
View File
@@ -10,38 +10,41 @@ import { isContent, isTitle } from '../lib/validator';
import type { Handler } from './server'; import type { Handler } from './server';
type MethodDecorator = (target: any, name: string, obj: any) => any; type MethodDecorator = (target: any, name: string, obj: any) => any;
type Converter = (value: any) => any; type InputType = string | number | Record<string, any> | any[];
type Validator = (value: any) => boolean; type Converter<T> = (value: any) => T;
export interface ParamOption { type Validator = (value: InputType) => boolean;
export interface ParamOption<T> {
name: string, name: string,
source: 'all' | 'get' | 'post' | 'route', source: 'all' | 'get' | 'post' | 'route',
isOptional?: boolean, isOptional?: boolean,
convert?: Converter, convert?: Converter<T>,
validate?: Validator, validate?: Validator,
} }
type Type = [Converter, Validator, boolean?]; type Type<T> = [Converter<T>, Validator?, boolean?];
export interface Types { export interface Types {
Content: Type, Content: Type<string>;
Name: Type, Name: Type<string>;
Username: Type, Username: Type<string>;
Title: Type, Title: Type<string>;
String: Type, String: Type<string>;
Int: Type, Int: Type<number>;
UnsignedInt: Type, UnsignedInt: Type<number>;
PositiveInt: Type, PositiveInt: Type<number>;
Float: Type, Float: Type<number>;
ObjectID: Type, ObjectID: Type<ObjectID>;
Boolean: Type, Boolean: Type<boolean>;
Date: Type, Date: Type<string>;
Time: Type, Time: Type<string>;
Range: (range: Array<string | number> | Record<string, any>) => Type, Range: <T extends string | number>(range: Array<T> | Record<string, any>) => Type<T>;
Array: Type, Array: Type<any[]>;
NumericArray: Type, NumericArray: Type<number[]>;
CommaSeperatedArray: Type, CommaSeperatedArray: Type<string[]>;
Set: Type, Set: Type<Set<any>>;
Emoji: Type, Emoji: Type<string>;
Any: Type<any>;
ArrayOf: <T extends Type<any>>(type: T) => (T extends Type<infer R> ? Type<R[]> : never);
} }
const safeSaslprep = (v) => { const safeSaslprep = (v) => {
@@ -58,9 +61,9 @@ export const Types: Types = {
Username: [(v) => saslprep(v.toString().trim()), (v) => /^(.{3,31}|[\u4e00-\u9fa5]{2})$/.test(safeSaslprep(v))], Username: [(v) => saslprep(v.toString().trim()), (v) => /^(.{3,31}|[\u4e00-\u9fa5]{2})$/.test(safeSaslprep(v))],
Title: [(v) => v.toString().trim(), isTitle], Title: [(v) => v.toString().trim(), isTitle],
String: [(v) => v.toString(), null], String: [(v) => v.toString(), null],
Int: [(v) => parseInt(v, 10), (v) => isSafeInteger(parseInt(v, 10))], Int: [(v) => +v, (v) => /^[+-]?[0-9]+$/.test(v.toString().trim()) && isSafeInteger(+v)],
UnsignedInt: [(v) => parseInt(v, 10), (v) => parseInt(v, 10) >= 0], UnsignedInt: [(v) => +v, (v) => /^(-0|\+?[0-9]+)$/.test(v.toString().trim()) && isSafeInteger(+v)],
PositiveInt: [(v) => parseInt(v, 10), (v) => parseInt(v, 10) > 0], PositiveInt: [(v) => +v, (v) => /^\+?[1-9][0-9]*$/.test(v.toString().trim()) && isSafeInteger(+v)],
Float: [(v) => +v, (v) => Number.isFinite(+v)], Float: [(v) => +v, (v) => Number.isFinite(+v)],
// eslint-disable-next-line no-shadow // eslint-disable-next-line no-shadow
ObjectID: [(v) => new ObjectID(v), ObjectID.isValid], ObjectID: [(v) => new ObjectID(v), ObjectID.isValid],
@@ -72,7 +75,7 @@ export const Types: Types = {
return `${d[0]}-${d[1].length === 1 ? '0' : ''}${d[1]}-${d[2].length === 1 ? '0' : ''}${d[2]}`; return `${d[0]}-${d[1].length === 1 ? '0' : ''}${d[1]}-${d[2].length === 1 ? '0' : ''}${d[2]}`;
}, },
(v) => { (v) => {
const d = v.split('-'); const d = v.toString().split('-');
if (d.length !== 3) return false; if (d.length !== 3) return false;
const st = `${d[0]}-${d[1].length === 1 ? '0' : ''}${d[1]}-${d[2].length === 1 ? '0' : ''}${d[2]}`; const st = `${d[0]}-${d[1].length === 1 ? '0' : ''}${d[1]}-${d[2].length === 1 ? '0' : ''}${d[2]}`;
return moment(st).isValid(); return moment(st).isValid();
@@ -85,7 +88,7 @@ export const Types: Types = {
return `${(t[0].length === 1 ? '0' : '') + t[0]}:${t[1].length === 1 ? '0' : ''}${t[1]}`; return `${(t[0].length === 1 ? '0' : '') + t[0]}:${t[1].length === 1 ? '0' : ''}${t[1]}`;
}, },
(v) => { (v) => {
const t = v.split(':'); const t = v.toString().split(':');
if (t.length !== 2) return false; if (t.length !== 2) return false;
return moment(`2020-01-01 ${(t[0].length === 1 ? '0' : '') + t[0]}:${t[1].length === 1 ? '0' : ''}${t[1]}`).isValid(); return moment(`2020-01-01 ${(t[0].length === 1 ? '0' : '') + t[0]}:${t[1].length === 1 ? '0' : ''}${t[1]}`).isValid();
}, },
@@ -94,9 +97,8 @@ export const Types: Types = {
(v) => { (v) => {
if (range instanceof Array) { if (range instanceof Array) {
for (const item of range) { for (const item of range) {
if (typeof item === 'number') { if (typeof item === 'number' && item === +v) return +v;
if (item === parseInt(v, 10)) return parseInt(v, 10); if (item === v) return v;
} else if (item === v) return v;
} }
} }
return v; return v;
@@ -104,11 +106,8 @@ export const Types: Types = {
(v) => { (v) => {
if (range instanceof Array) { if (range instanceof Array) {
for (const item of range) { for (const item of range) {
if (typeof item === 'string') { if (typeof item === 'string' && item === v) return true;
if (item === v) return true; if (typeof item === 'number' && item === +v) return true;
} else if (typeof item === 'number') {
if (item === parseInt(v, 10)) return true;
}
} }
} else { } else {
for (const key in range) { for (const key in range) {
@@ -127,11 +126,11 @@ export const Types: Types = {
return v.split(',').map(Number); return v.split(',').map(Number);
}, (v) => { }, (v) => {
if (v instanceof Array) return v.map(Number).every(Number.isSafeInteger); if (v instanceof Array) return v.map(Number).every(Number.isSafeInteger);
return v.split(',').map(Number).every(Number.isSafeInteger); return v.toString().split(',').map(Number).every(Number.isSafeInteger);
}], }],
CommaSeperatedArray: [ CommaSeperatedArray: [
(v) => v.toString().replace(/,/g, ',').split(',').map((e) => e.trim()).filter((i) => i), (v) => v.toString().replace(/,/g, ',').split(',').map((e) => e.trim()).filter((i) => i),
(v) => v.toString(), (v) => !!v.toString(),
], ],
Set: [(v) => { Set: [(v) => {
if (v instanceof Array) return new Set(v); if (v instanceof Array) return new Set(v);
@@ -139,13 +138,25 @@ export const Types: Types = {
}, null], }, null],
Emoji: [ Emoji: [
(v: string) => v.matchAll(emojiRegex()).next().value[0], (v: string) => v.matchAll(emojiRegex()).next().value[0],
(v) => emojiRegex().test(v), (v) => emojiRegex().test(v.toString()),
], ],
Any: [(v) => v, null],
ArrayOf: (type) => [
(v) => {
const arr = v instanceof Array ? v : [v];
return arr.map((i) => type[0](i));
},
(v) => {
if (!type[1]) return true;
const arr = v instanceof Array ? v : [v];
return arr.every((i) => type[1](i));
},
] as any,
}; };
function _buildParam(name: string, source: 'get' | 'post' | 'all' | 'route', ...args: Array<Type | boolean | Validator | Converter>) { function _buildParam(name: string, source: 'get' | 'post' | 'all' | 'route', ...args: Array<Type<any> | boolean | Validator | Converter<any>>) {
let cursor = 0; let cursor = 0;
const v: ParamOption = { name, source }; const v: ParamOption<any> = { name, source };
let isValidate = true; let isValidate = true;
while (cursor < args.length) { while (cursor < args.length) {
const current = args[cursor]; const current = args[cursor];
@@ -164,7 +175,7 @@ function _buildParam(name: string, source: 'get' | 'post' | 'all' | 'route', ...
return v; return v;
} }
function _descriptor(v: ParamOption) { function _descriptor(v: ParamOption<any>) {
return function desc(this: Handler, target: any, funcName: string, obj: any) { return function desc(this: Handler, target: any, funcName: string, obj: any) {
target.__param ||= {}; target.__param ||= {};
target.__param[target.constructor.name] ||= {}; target.__param[target.constructor.name] ||= {};
@@ -174,7 +185,7 @@ function _descriptor(v: ParamOption) {
obj.value = function validate(this: Handler, rawArgs: any, ...extra: any[]) { obj.value = function validate(this: Handler, rawArgs: any, ...extra: any[]) {
if (typeof rawArgs !== 'object' || extra.length) return originalMethod.call(this, rawArgs, ...extra); if (typeof rawArgs !== 'object' || extra.length) return originalMethod.call(this, rawArgs, ...extra);
const c = []; const c = [];
const arglist: ParamOption[] = this.__param[target.constructor.name][funcName]; const arglist: ParamOption<any>[] = this.__param[target.constructor.name][funcName];
for (const item of arglist) { for (const item of arglist) {
const src = item.source === 'all' const src = item.source === 'all'
? rawArgs ? rawArgs
@@ -200,11 +211,11 @@ function _descriptor(v: ParamOption) {
} }
type DescriptorBuilder = type DescriptorBuilder =
((name: string, type: Type) => MethodDecorator) ((name: string, type: Type<any>) => MethodDecorator)
& ((name: string, type: Type, validate: null, convert: Converter) => MethodDecorator) & ((name: string, type: Type<any>, validate: null, convert: Converter<any>) => MethodDecorator)
& ((name: string, type: Type, validate?: Validator, convert?: Converter) => MethodDecorator) & ((name: string, type: Type<any>, validate?: Validator, convert?: Converter<any>) => MethodDecorator)
& ((name: string, type?: Type, isOptional?: boolean, validate?: Validator, convert?: Converter) => MethodDecorator) & ((name: string, type?: Type<any>, isOptional?: boolean, validate?: Validator, convert?: Converter<any>) => MethodDecorator)
& ((name: string, ...args: Array<Type | boolean | Validator | Converter>) => MethodDecorator); & ((name: string, ...args: Array<Type<any> | boolean | Validator | Converter<any>>) => MethodDecorator);
export const get: DescriptorBuilder = (name, ...args) => _descriptor(_buildParam(name, 'get', ...args)); export const get: DescriptorBuilder = (name, ...args) => _descriptor(_buildParam(name, 'get', ...args));
export const query: DescriptorBuilder = (name, ...args) => _descriptor(_buildParam(name, 'get', ...args)); export const query: DescriptorBuilder = (name, ...args) => _descriptor(_buildParam(name, 'get', ...args));