import { TypeOfRouterRequestField } from 'farrow-http/dist/router'; import { FieldDescriptors, SchemaCtorInput, toSchemaCtor, Struct, NonStrict, } from 'farrow-schema'; import { MaybeAsync } from 'farrow-pipeline'; import { createSchemaValidator, Validator, ValidationError, } from 'farrow-schema/validator'; import { RequestSchema, TypeOfRequestSchema, PureTypeOfRequestSchema, } from './request'; import { HandleResult, HandleSuccess, InputValidationError, OutputValidationError, } from './response'; export type NormalHandler = (...args: any[]) => any; export type Handler = (input: I) => MaybeAsync; export type Schema = { request: Req; response: Res; description?: string; deprecated?: string; }; const getErrorMessage = (error: ValidationError) => { let { message } = error; if (Array.isArray(error.path) && error.path.length > 0) { message = `path: ${JSON.stringify(error.path)}\n${message}`; } return message; }; const HANDLER_WITH_SCHEMA = 'HANDLER_WITH_SCHEMA'; export type BaseSchemaHandler< Req extends RequestSchema, Res extends SchemaCtorInput, > = (( input: TypeOfRequestSchema, ) => Promise>>) & { schema: Schema; [HANDLER_WITH_SCHEMA]: true; }; export type SchemaHandler< Req extends RequestSchema, Res extends SchemaCtorInput, > = (( input: TypeOfRequestSchema, ) => Promise>) & { schema: Schema; [HANDLER_WITH_SCHEMA]: true; }; export const isSchemaHandler = (input: any): input is SchemaHandler => input && input?.[HANDLER_WITH_SCHEMA] === true; export const isHandler = (input: any): input is Handler => input && typeof input === 'function'; export const baseMatch = < Req extends RequestSchema, Res extends SchemaCtorInput, >( schema: Schema, handler: Handler, TypeOfRouterRequestField>, ): BaseSchemaHandler => { const validateApiInput = createRequestSchemaValidator(schema.request); const validateApiOutput = createSchemaValidator( toSchemaCtor(schema.response), ); const handle = async ( input: TypeOfRequestSchema, ): Promise>> => { const inputResult = validateApiInput(input); if (inputResult.isErr) { return InputValidationError(getErrorMessage(inputResult.value)); } const output = await handler(input); const outputResult = validateApiOutput(output); if (outputResult.isErr) { return OutputValidationError(getErrorMessage(outputResult.value)); } return HandleSuccess(output); }; return Object.assign(handle, { schema, [HANDLER_WITH_SCHEMA]: true as const, }); }; export const match: ( schema: Schema, handler: Handler, TypeOfRouterRequestField>, ) => SchemaHandler = baseMatch as any; const createRequestSchemaValidator = (schema: T) => { const descriptors: FieldDescriptors = {}; if (schema.params) { descriptors.params = schema.params; } if (schema.query) { descriptors.query = schema.query; } if (schema.data) { descriptors.data = schema.data; } if (schema.headers) { descriptors.headers = schema.headers; } if (schema.cookies) { descriptors.cookies = schema.cookies; } const RequestStruct = Struct(descriptors); return createSchemaValidator(NonStrict(RequestStruct) as any) as Validator< TypeOfRequestSchema >; };