You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
lib/src/validation/Validator.ts

121 lines
3.0 KiB

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
}
}