165 lines
4.0 KiB
TypeScript
165 lines
4.0 KiB
TypeScript
import Schema from "./Schema"
|
|
import { SchemaJSON, ValidationError, ValidationResult } from "./types"
|
|
|
|
export default abstract class SchemaItem<Type = any> {
|
|
|
|
private invalidError = 'the field is invalid'
|
|
|
|
/**
|
|
* list of attributes for custom works
|
|
*/
|
|
public readonly attributes: Array<string> = []
|
|
|
|
public attrs(...attributes: Array<string>) {
|
|
this.attributes.concat(attributes)
|
|
return this
|
|
}
|
|
|
|
public setInvalidError(err: string): this {
|
|
this.invalidError = err
|
|
return this
|
|
}
|
|
|
|
public clone(): this {
|
|
return Schema.fromJSON(this.toJSON()) as this
|
|
}
|
|
|
|
/**
|
|
* schemas implementing unwrap can return their child component (mostly the value)
|
|
*
|
|
* ex: s.record(s.number(), s.string()) returns s.string()
|
|
*
|
|
* es2: s.nullable(s.string()) returns s.string()
|
|
*/
|
|
public unwrap?(): SchemaItem
|
|
|
|
/**
|
|
* Pre process the variable for various reasons
|
|
*
|
|
* It will execute every pre-process sequentially in order of being added
|
|
*
|
|
* note: The type of the final Pre-Process MUST be valid
|
|
*/
|
|
// eslint-disable-next-line @typescript-eslint/no-redundant-type-constituents
|
|
public preProcess: Array<(input: unknown) => Type | unknown> = []
|
|
|
|
/**
|
|
* post process the variable after it being validated
|
|
*
|
|
* it will execute each post-processes sequentially
|
|
*
|
|
* note: the type of the final post-process MUST be valid
|
|
*/
|
|
public postProcess: Array<(input: Type) => Type> = []
|
|
|
|
/**
|
|
* keep public ?
|
|
*/
|
|
public validations: Array<{
|
|
fn: (input: Type) => boolean
|
|
error?: string | undefined
|
|
}> = []
|
|
|
|
public savedCalls: Array<{ name: string, args: Array<string> | IArguments | undefined }> = []
|
|
|
|
private readonly items?: Array<unknown>
|
|
|
|
public constructor(items?: Array<unknown> | IArguments) {
|
|
if (items && items.length > 0) {
|
|
this.items = Array.isArray(items) ? items : Array.from(items)
|
|
}
|
|
}
|
|
|
|
public parse(input: unknown, options?: { fast?: boolean }): ValidationResult<Type> {
|
|
// pre process the variable
|
|
for (const preProcess of this.preProcess) {
|
|
input = preProcess(input)
|
|
}
|
|
|
|
// validate that the pre process handled correctly the variable
|
|
if (!this.isOfType(input)) {
|
|
return {
|
|
valid: false,
|
|
errors: [{ message: 'invalid type' }]
|
|
}
|
|
}
|
|
|
|
// run validation checks
|
|
const errors: Array<ValidationError> = []
|
|
for (const validation of this.validations) {
|
|
if (!validation.fn(input)) {
|
|
|
|
// add the error to the list
|
|
errors.push({
|
|
message: validation.error ?? this.invalidError
|
|
})
|
|
|
|
// if the system should be fast, stop checking for other errors
|
|
if (options?.fast) {
|
|
return {
|
|
valid: false,
|
|
object: input as (Type extends object ? Partial<Type> : Type) | undefined,
|
|
errors: errors
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// return with errors
|
|
if (errors.length > 0) {
|
|
return {
|
|
valid: false,
|
|
object: input as (Type extends object ? Partial<Type> : Type) | undefined,
|
|
errors: errors
|
|
}
|
|
}
|
|
|
|
// post-process the variable
|
|
for (const postProcess of this.postProcess) {
|
|
input = postProcess(input as Type)
|
|
}
|
|
|
|
// validate that the pre process handled correctly the variable
|
|
if (!this.isOfType(input)) {
|
|
throw new Error('Post process error occured :(')
|
|
}
|
|
|
|
return {
|
|
valid: true,
|
|
object: input
|
|
}
|
|
}
|
|
|
|
public toJSON(): SchemaJSON {
|
|
|
|
return {
|
|
i: this.constructor.name,
|
|
a: this.attributes.length > 0 ? this.attributes : undefined,
|
|
c: this.items?.map((it) => it instanceof SchemaItem ? it.toJSON() : it),
|
|
f: this.savedCalls
|
|
.map((it) => (it.args ? { n: it.name, a: Array.from(it.args) } : { n: it.name }))
|
|
}
|
|
}
|
|
|
|
protected addValidation(fn: ((input: Type) => boolean) | { fn: (input: Type) => boolean, error?: string }, error?: string) {
|
|
|
|
this.validations.push(typeof fn === 'function' ? { fn, error } : fn)
|
|
|
|
return this
|
|
}
|
|
|
|
protected addPreProcess(fn: (input: unknown) => Type | unknown) {
|
|
this.preProcess.push(fn)
|
|
|
|
return this
|
|
}
|
|
|
|
protected addPostProcess(fn: (input: Type) => Type) {
|
|
this.postProcess.push(fn)
|
|
|
|
return this
|
|
}
|
|
|
|
public abstract isOfType(input: unknown): input is Type
|
|
}
|