mirror of
https://gitcode.com/gh_mirrors/hy/Hydro.git
synced 2026-10-20 01:38:19 +08:00
framework: add support for passing schemastery as validator
This commit is contained in:
3 files changed
+63
-22
No files matched your search
@@ -1,4 +1,5 @@
|
|||||||
import { Context } from 'cordis';
|
import { Context } from 'cordis';
|
||||||
|
import Schema from 'schemastery';
|
||||||
import { ValidationError } from './error';
|
import { ValidationError } from './error';
|
||||||
import type { Handler } from './server';
|
import type { Handler } from './server';
|
||||||
import { Converter, Type, Validator } from './validator';
|
import { Converter, Type, Validator } from './validator';
|
||||||
@@ -14,14 +15,32 @@ export interface ParamOption<T> {
|
|||||||
validate?: Validator,
|
validate?: Validator,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const kSchema = Symbol.for('schemastery');
|
||||||
const mergeValidator = (L: Validator, R: Validator) => (v: any) => L(v) && R(v);
|
const mergeValidator = (L: Validator, R: Validator) => (v: any) => L(v) && R(v);
|
||||||
|
|
||||||
|
function isSchema(v: any): v is Schema {
|
||||||
|
return v && typeof v === 'function' && kSchema in v;
|
||||||
|
}
|
||||||
|
|
||||||
function _buildParam(name: string, source: 'get' | 'post' | 'all' | 'route', ...args: Array<Type<any> | boolean | Validator | Converter<any>>) {
|
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<any> = { 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];
|
||||||
|
if (isSchema(current)) {
|
||||||
|
v.validate = (val) => {
|
||||||
|
try {
|
||||||
|
current(val);
|
||||||
|
return true;
|
||||||
|
} catch (e) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
v.convert = (val) => current(val);
|
||||||
|
cursor++;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
if (current instanceof Array) {
|
if (current instanceof Array) {
|
||||||
const type = current;
|
const type = current;
|
||||||
if (type[0]) v.convert = type[0];
|
if (type[0]) v.convert = type[0];
|
||||||
|
|||||||
@@ -0,0 +1,20 @@
|
|||||||
|
import { expect } from 'chai';
|
||||||
|
import { describe, it } from 'node:test';
|
||||||
|
import { Types } from '../validator';
|
||||||
|
|
||||||
|
const k = Symbol.for('schemastery');
|
||||||
|
|
||||||
|
describe('validator', () => {
|
||||||
|
it('NumericArray', () => {
|
||||||
|
expect(Types.NumericArray('1,2,3')).to.deep.equal([1, 2, 3]);
|
||||||
|
expect(Types.NumericArray([1, 2, 3])).to.deep.equal([1, 2, 3]);
|
||||||
|
expect(() => Types.NumericArray('123a')).to.throw();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('CommaSeperatedArray', () => {
|
||||||
|
expect(Types.CommaSeperatedArray('1,2,3')).to.deep.equal(['1', '2', '3']);
|
||||||
|
expect(Types.CommaSeperatedArray([1, 2, 3])).to.deep.equal(['1', '2', '3']);
|
||||||
|
expect(Types.CommaSeperatedArray('123a')).to.deep.equal(['123a']);
|
||||||
|
expect(k in Types.CommaSeperatedArray).to.eq(true);
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -4,11 +4,29 @@ import { isSafeInteger } from 'lodash';
|
|||||||
import moment from 'moment-timezone';
|
import moment from 'moment-timezone';
|
||||||
import sanitize from 'sanitize-filename';
|
import sanitize from 'sanitize-filename';
|
||||||
import saslprep from 'saslprep';
|
import saslprep from 'saslprep';
|
||||||
|
import Schema from 'schemastery';
|
||||||
|
|
||||||
type InputType = string | number | Record<string, any> | any[];
|
type InputType = string | number | Record<string, any> | any[];
|
||||||
export type Converter<T> = (value: any) => T;
|
export type Converter<T> = (value: any) => T;
|
||||||
export type Validator<Loose extends boolean = true> = (value: Loose extends true ? any : InputType) => boolean;
|
export type Validator<Loose extends boolean = true> = (value: Loose extends true ? any : InputType) => boolean;
|
||||||
export type Type<T> = readonly [Converter<T>, Validator<false>?, (boolean | 'convert')?];
|
export type Type<T> = Schema<T> | readonly [Converter<T>, Validator<false>?, (boolean | 'convert')?];
|
||||||
|
|
||||||
|
const MaybeArray = <T>(inner: Schema<T>) => Schema.union([Schema.array(inner), inner]);
|
||||||
|
type CheckFunction = <IsNumber extends boolean>(v: IsNumber extends true ? number : string) => boolean;
|
||||||
|
const ArrayBase = <IsNumber extends boolean>(check: CheckFunction, number: IsNumber, doSplit: boolean = number) => Schema.transform(
|
||||||
|
MaybeArray(Schema.union([Number, String])),
|
||||||
|
(v) => {
|
||||||
|
const input = (doSplit && typeof v === 'string') ? v.split(',') : v;
|
||||||
|
const res = number
|
||||||
|
? (typeof input === 'string' ? [+input] : typeof input === 'number' ? [input] : input.map(Number))
|
||||||
|
: typeof input === 'string' ? [input] : typeof input === 'number' ? [input.toString()] : input.map(String);
|
||||||
|
const locate = number
|
||||||
|
? res.find((i) => !Number.isFinite(+i) || !check(+i as any))
|
||||||
|
: res.find((i) => !check(i as any));
|
||||||
|
if (locate !== undefined) throw new Error(`Invalid input: ${locate}`);
|
||||||
|
return res;
|
||||||
|
},
|
||||||
|
) as Schema<any, IsNumber extends true ? number[] : string[]>;
|
||||||
|
|
||||||
export interface Types {
|
export interface Types {
|
||||||
// String outputs
|
// String outputs
|
||||||
@@ -41,8 +59,6 @@ export interface Types {
|
|||||||
Date: Type<string>;
|
Date: Type<string>;
|
||||||
Time: Type<string>;
|
Time: Type<string>;
|
||||||
Range: <T extends string | number>(range: Array<T> | Record<string, any>) => Type<T>;
|
Range: <T extends string | number>(range: Array<T> | Record<string, any>) => Type<T>;
|
||||||
/** @deprecated */
|
|
||||||
Array: Type<any[]>;
|
|
||||||
NumericArray: Type<number[]>;
|
NumericArray: Type<number[]>;
|
||||||
CommaSeperatedArray: Type<string[]>;
|
CommaSeperatedArray: Type<string[]>;
|
||||||
Set: Type<Set<any>>;
|
Set: Type<Set<any>>;
|
||||||
@@ -74,7 +90,7 @@ const saslprepString = <T = string>(regex?: RegExp, cb?: (i: string) => boolean,
|
|||||||
},
|
},
|
||||||
] as [(v) => string, (v) => boolean];
|
] as [(v) => string, (v) => boolean];
|
||||||
|
|
||||||
export const Types: Types = {
|
export const Types = {
|
||||||
Content: [(v) => v.toString().trim(), (v) => v?.toString()?.trim() && v.toString().trim().length < 65536],
|
Content: [(v) => v.toString().trim(), (v) => v?.toString()?.trim() && v.toString().trim().length < 65536],
|
||||||
Key: saslprepString(/^[a-zA-Z0-9-_]{1,255}$/),
|
Key: saslprepString(/^[a-zA-Z0-9-_]{1,255}$/),
|
||||||
/** @deprecated */
|
/** @deprecated */
|
||||||
@@ -147,22 +163,8 @@ export const Types: Types = {
|
|||||||
return false;
|
return false;
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
/** @deprecated suggested to use Types.ArrayOf instead. */
|
NumericArray: ArrayBase(Number.isFinite, true),
|
||||||
Array: [(v) => {
|
CommaSeperatedArray: ArrayBase(() => true, false, true),
|
||||||
if (v instanceof Array) return v;
|
|
||||||
return v ? [v] : [];
|
|
||||||
}, null],
|
|
||||||
NumericArray: [(v) => {
|
|
||||||
if (v instanceof Array) return v.map(Number);
|
|
||||||
return v.split(',').map(Number);
|
|
||||||
}, (v) => {
|
|
||||||
if (v instanceof Array) return v.map(Number).every(Number.isSafeInteger);
|
|
||||||
return v.toString().split(',').map(Number).every(Number.isSafeInteger);
|
|
||||||
}],
|
|
||||||
CommaSeperatedArray: [
|
|
||||||
(v) => v.toString().replace(/,/g, ',').split(',').map((e) => e.trim()).filter((i) => i),
|
|
||||||
(v) => !!v.toString(),
|
|
||||||
],
|
|
||||||
Set: [(v) => {
|
Set: [(v) => {
|
||||||
if (v instanceof Array) return new Set(v);
|
if (v instanceof Array) return new Set(v);
|
||||||
return v ? new Set([v]) : new Set();
|
return v ? new Set([v]) : new Set();
|
||||||
@@ -187,11 +189,11 @@ export const Types: Types = {
|
|||||||
(v) => types.find((type) => type[1](v))[0](v),
|
(v) => types.find((type) => type[1](v))[0](v),
|
||||||
(v) => types.some((type) => type[1](v)),
|
(v) => types.some((type) => type[1](v)),
|
||||||
] as any,
|
] as any,
|
||||||
};
|
} satisfies Types;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const { ObjectId } = require('mongodb');
|
const { ObjectId } = require('mongodb');
|
||||||
Types.ObjectId = [(v) => new ObjectId(v), ObjectId.isValid];
|
Types.ObjectId = [((v) => new ObjectId(v)) as any, ObjectId.isValid];
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in new issue
Block a user