121 lines
3.0 KiB
TypeScript
121 lines
3.0 KiB
TypeScript
import {z, ZodType} from 'zod'
|
|
import {InjectionAware} from '../di'
|
|
import {ErrorWithContext, TypeTag} from '../util'
|
|
import {ZodifyRecipient} from './ZodifyRecipient'
|
|
import {ZodifyRegistrar} from './ZodifyRegistrar'
|
|
import {Logging} from '../service/Logging'
|
|
|
|
/** Type tag for a validated runtime type. */
|
|
export type Valid<T> = TypeTag<'@extollo/lib:Valid'> & T
|
|
|
|
export type ValidatorFactory<T> = Validator<T> | (() => Validator<T>)
|
|
|
|
/**
|
|
* Error thrown if the schema for a validator cannot be located.
|
|
*/
|
|
export class InvalidSchemaMappingError extends Error {
|
|
constructor(message = 'Unable to resolve schema for validator.') {
|
|
super(message)
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Interface defining validation error messages.
|
|
*/
|
|
export interface ValidationFormErrors<T> {
|
|
formErrors: string[],
|
|
fieldErrors: {
|
|
[k in keyof T]: string[]
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Error thrown when data validation has failed.
|
|
*/
|
|
export class ValidationError<T> extends ErrorWithContext {
|
|
constructor(
|
|
public readonly formErrors: ValidationFormErrors<T>,
|
|
message = 'Invalid form data',
|
|
) {
|
|
super(message)
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Validates input data against a schema at runtime.
|
|
*/
|
|
export class Validator<T> extends InjectionAware implements ZodifyRecipient {
|
|
public static fromSchema<T2>(type: ZodType<T2>): Validator<T2> {
|
|
const inst = new Validator<T2>()
|
|
inst.schema = type
|
|
return inst
|
|
}
|
|
|
|
__exZodifiedSchemata: number[] = []
|
|
|
|
protected schema?: ZodType<T>
|
|
|
|
/**
|
|
* Parse the input data against the schema.
|
|
* @throws ValidationError
|
|
* @param from
|
|
*/
|
|
public parse(from: unknown): Valid<T> {
|
|
try {
|
|
return this.getZod().parse(from) as Valid<T>
|
|
} catch (e: unknown) {
|
|
if ( e instanceof z.ZodError ) {
|
|
throw new ValidationError<T>(e.formErrors as ValidationFormErrors<T>)
|
|
}
|
|
|
|
throw e
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Typeguard for the input schema.
|
|
* @param what
|
|
*/
|
|
public is(what: unknown): what is Valid<T> {
|
|
try {
|
|
this.parse(what)
|
|
return true
|
|
} catch (e: unknown) {
|
|
this.log().verbose(`Error during validation: ${e}`)
|
|
|
|
if ( e instanceof ValidationError ) {
|
|
return false
|
|
}
|
|
|
|
throw e
|
|
}
|
|
}
|
|
|
|
/** Get the logging service. */
|
|
protected log(): Logging {
|
|
return this.make<Logging>(Logging)
|
|
}
|
|
|
|
/** Get the Zod schema. */
|
|
protected getZod(): z.ZodType<T> {
|
|
if ( this.schema ) {
|
|
return this.schema
|
|
}
|
|
|
|
// eslint-disable-next-line no-underscore-dangle
|
|
if ( this.__exZodifiedSchemata.length < 1 ) {
|
|
throw new InvalidSchemaMappingError()
|
|
}
|
|
|
|
// eslint-disable-next-line no-underscore-dangle
|
|
const id = this.__exZodifiedSchemata[0]
|
|
|
|
const type = this.make<ZodifyRegistrar>(ZodifyRegistrar).get(id)
|
|
if ( !type ) {
|
|
throw new InvalidSchemaMappingError()
|
|
}
|
|
|
|
return type
|
|
}
|
|
}
|