247 lines
6.3 KiB
TypeScript
247 lines
6.3 KiB
TypeScript
/* eslint-disable id-blacklist */
|
|
import { parseForm, parseFormData, parseQuery } from 'helpers'
|
|
import SchemaAny from 'items/any'
|
|
import SchemaDate from 'items/date'
|
|
import SchemaRecord from 'items/record'
|
|
import SchemaArray from './items/array'
|
|
import SchemaBoolean from './items/boolean'
|
|
import SchemaEnum, { EnumLike } from './items/enum'
|
|
import SchemaLiteral from './items/literal'
|
|
import SchemaNullable from './items/nullable'
|
|
import SchemaNumber from './items/number'
|
|
import SchemaObject from './items/object'
|
|
import SchemaString from './items/string'
|
|
import SchemaUnion from './items/union'
|
|
import SchemaItem from './SchemaItem'
|
|
import { SchemaJSON } from './types'
|
|
|
|
export function parceable() {
|
|
|
|
return (target: object, propertyKey: string | symbol, descriptor: TypedPropertyDescriptor<any>) => {
|
|
// make sure the target is of SchemaItem
|
|
if (!(target instanceof SchemaItem)) {
|
|
throw new Error('the decorator is only usable on Schema')
|
|
}
|
|
|
|
// make sur the property exists in the target
|
|
if (!(propertyKey in target)) {
|
|
throw new Error('property not set in object')
|
|
}
|
|
|
|
// @ts-expect-error call a function defined from calls of collable
|
|
const original = target[propertyKey] as (...args: Array<unknown>) => unknown
|
|
|
|
// replace original function with modified one
|
|
descriptor.value = function(this: SchemaItem, ...args: Array<unknown>) {
|
|
this.savedCalls.push({ name: propertyKey as string, args: args as Array<string> })
|
|
const res: unknown = original.call(this, ...args)
|
|
return res
|
|
}
|
|
|
|
// return the modified descriptor
|
|
return descriptor
|
|
}
|
|
}
|
|
|
|
type SchemaItemStatic = new (...args: Array<any>) => SchemaItem
|
|
|
|
export const Types = {
|
|
Any: SchemaAny,
|
|
Array: SchemaArray,
|
|
Boolean: SchemaBoolean,
|
|
Date: SchemaDate,
|
|
Enum: SchemaEnum,
|
|
Literal: SchemaLiteral,
|
|
Nullable: SchemaNullable,
|
|
Object: SchemaObject,
|
|
Record: SchemaRecord,
|
|
String: SchemaString,
|
|
Union: SchemaUnion
|
|
} as const
|
|
|
|
export default class Schema<T extends Record<string, SchemaItem> = Record<string, SchemaItem>> extends SchemaObject<T> {
|
|
private static registeredModules: Array<SchemaItemStatic> = [
|
|
SchemaArray,
|
|
SchemaBoolean,
|
|
SchemaDate,
|
|
SchemaEnum,
|
|
SchemaLiteral,
|
|
SchemaNullable,
|
|
SchemaObject,
|
|
SchemaRecord,
|
|
SchemaString,
|
|
SchemaUnion
|
|
]
|
|
|
|
public static register(module: SchemaItemStatic) {
|
|
this.registeredModules.push(module)
|
|
}
|
|
|
|
public static getModule(name: string) {
|
|
return this.registeredModules.find((it) => it.name === name)
|
|
}
|
|
|
|
public static any() {
|
|
return new SchemaAny()
|
|
}
|
|
|
|
public static array<Type extends SchemaItem>(
|
|
...inputs: ConstructorParameters<typeof SchemaArray<Type>>
|
|
): SchemaArray<Type> {
|
|
return new SchemaArray<Type>(...inputs)
|
|
}
|
|
|
|
public static boolean(
|
|
...inputs: ConstructorParameters<typeof SchemaBoolean>
|
|
): SchemaBoolean {
|
|
return new SchemaBoolean(...inputs)
|
|
}
|
|
|
|
public static date(
|
|
...inputs: ConstructorParameters<typeof SchemaDate>
|
|
): SchemaDate {
|
|
return new SchemaDate(...inputs)
|
|
}
|
|
|
|
public static enum<Type extends EnumLike>(
|
|
...inputs: ConstructorParameters<typeof SchemaEnum<Type>>
|
|
): SchemaEnum<Type> {
|
|
return new SchemaEnum<Type>(...inputs)
|
|
}
|
|
|
|
/**
|
|
*
|
|
* @param input the literal value (note: append `as const` else the typing won't work correctly)
|
|
* @returns
|
|
*/
|
|
public static literal<Type>(
|
|
input: Type
|
|
): SchemaLiteral<Type> {
|
|
return new SchemaLiteral<Type>(input)
|
|
}
|
|
|
|
public static nullable<Type extends SchemaItem>(
|
|
...inputs: ConstructorParameters<typeof SchemaNullable<Type>>
|
|
): SchemaNullable<Type> {
|
|
return new SchemaNullable<Type>(...inputs)
|
|
}
|
|
|
|
public static number(
|
|
...inputs: ConstructorParameters<typeof SchemaNumber>
|
|
): SchemaNumber {
|
|
return new SchemaNumber(...inputs)
|
|
}
|
|
|
|
public static object<Type extends Record<string, SchemaItem>>(
|
|
...inputs: ConstructorParameters<typeof SchemaObject<Type>>
|
|
): SchemaObject<Type> {
|
|
return new SchemaObject<Type>(...inputs)
|
|
}
|
|
|
|
public static record<Keys extends SchemaItem, Values extends SchemaItem>(
|
|
...inputs: ConstructorParameters<typeof SchemaRecord<Keys, Values>>
|
|
): SchemaRecord<Keys, Values> {
|
|
return new SchemaRecord<Keys, Values>(...inputs)
|
|
}
|
|
|
|
/**
|
|
* See {@link SchemaString}
|
|
*/
|
|
public static string(
|
|
...inputs: ConstructorParameters<typeof SchemaString>
|
|
): SchemaString {
|
|
return new SchemaString(...inputs)
|
|
}
|
|
|
|
public static union<Type extends Array<SchemaItem>>(
|
|
...inputs: ConstructorParameters<typeof SchemaUnion<Type>>
|
|
): SchemaUnion<Type> {
|
|
return new SchemaUnion<Type>(...inputs)
|
|
}
|
|
|
|
public static fromJSON(json: SchemaJSON): SchemaItem {
|
|
|
|
// get the module
|
|
const fn = this.getModule(json.i)
|
|
|
|
// handle module not detected
|
|
if (!fn) {
|
|
throw new Error(`Schema cannot parse ${json.i}`)
|
|
}
|
|
|
|
// init the module
|
|
const item = new fn(...(json.c?.map((it) => this.isSchemaJSON(it) ? Schema.fromJSON(it) : it) ?? []))
|
|
|
|
// handle validations
|
|
for (const validation of (json.f ?? [])) {
|
|
// validation not found in item :(
|
|
if (!(validation.n in item)) {
|
|
throw new Error('validation not available in Schema Item')
|
|
}
|
|
|
|
// init the validation
|
|
// @ts-expect-error call a function defined from calls of collable
|
|
(item[validation.n] as (...params: Array<unknown>) => void)(...validation.a?.map((it) => Schema.isSchemaJSON(it) ? Schema.fromJSON(it) : it) ?? [])
|
|
}
|
|
|
|
// add the attributes
|
|
item.attrs(...json.a ?? [])
|
|
|
|
|
|
return item
|
|
}
|
|
|
|
public static isSchemaJSON(data: unknown): data is SchemaJSON {
|
|
if (typeof data !== 'object' || data === null) {
|
|
return false
|
|
}
|
|
|
|
if (!('i' in data)) {
|
|
return false
|
|
}
|
|
|
|
return true
|
|
}
|
|
|
|
/**
|
|
* @deprecated use helper `parseQuery`
|
|
*/
|
|
public validateQuery(query: URLSearchParams, fast = false) {
|
|
return parseQuery(this, query, { fast })
|
|
}
|
|
|
|
/**
|
|
* @deprecated use `parse`
|
|
*/
|
|
public validate(input: unknown, fast = false) {
|
|
return this.parse(input, { fast })
|
|
}
|
|
|
|
/**
|
|
* @deprecated use helper `parseForm`
|
|
*/
|
|
public validateForm(form: HTMLFormElement, fast = false) {
|
|
return parseForm(this, form, { fast })
|
|
}
|
|
|
|
/**
|
|
* @deprecated use helper `parseFormData`
|
|
*/
|
|
public validateFormData(data: FormData, fast = false) {
|
|
return parseFormData(this, data, { fast })
|
|
}
|
|
}
|
|
|
|
export const s = Schema
|
|
|
|
export * from './helpers'
|
|
export type * from './types.d.ts'
|
|
|
|
export {
|
|
SchemaAny, SchemaArray,
|
|
SchemaBoolean, SchemaDate, SchemaEnum, SchemaItem, SchemaLiteral,
|
|
SchemaNullable,
|
|
SchemaNumber, SchemaObject, SchemaRecord, SchemaString,
|
|
SchemaUnion
|
|
}
|