import $assert from 'assert' import * as process from 'process' import { equals, identity } from 'remeda' import { RefinementCtx, ZodIssueCode } from 'zod' import { isTrue } from './boolean' import { Filter, FilterTwo } from './Filter' import { fetchBooleanEnvVar } from './process' import { includes } from './remeda/includes' import { nequals } from './remeda/nequals' import { toString } from './string' export type CheckUnary = (a: A, $a: string, context?: Record, $message?: string) => Out export type CheckBinary = (a: A, b: B, $a: string, $b: string, context?: Record, $message?: string) => Out export type CheckByUnary = (filter: Filter, $filter?: string) => OutBy export type CheckByBinary = (filter: FilterTwo, $filter?: string) => OutBy export type AssertUnary = CheckUnary export type AssertBinary = CheckBinary export type AssertByUnary = CheckByUnary, A> export type AssertByBinary = CheckByBinary, A, B> export type GetErrorOutput = Error | undefined export type GetErrorUnary = CheckUnary export type GetErrorBinary = CheckBinary export type GetErrorByUnary = CheckByUnary, A> export type GetErrorByBinary = CheckByBinary, A, B> export type RefineUnary = (ctx: RefinementCtx) => CheckUnary export type RefineBinary = (ctx: RefinementCtx) => CheckBinary export type RefineByUnary = CheckByUnary, A> export type RefineByBinary = CheckByBinary, A, B> const withContext = fetchBooleanEnvVar('ASSERT_WITH_CONTEXT', process.env.ASSERT_WITH_CONTEXT) export const assertOne = (filter: (a: A) => boolean, $filter = filter.name): AssertUnary => (a: A, $a = `${a}`, context?: Record, $message?: string) => { return $assert.strict( filter(a), getMessage($filter, [a], [$a], $message, context) ) } export const assertTwo = (filter: (a: A, b: B) => boolean, $filter = filter.name): AssertBinary => (a: A, b: B, $a = `${a}`, $b = `${b}`, context?: Record, $message?: string) => { return $assert.strict( filter(a, b), getMessage($filter, [a, b], [$a, $b], $message, context) ) } export const getErrorOne = (filter: (a: A) => boolean, $filter = filter.name): GetErrorUnary => (a: A, $a = `${a}`, context?: Record, $message?: string) => { return filter(a) ? undefined : new Error(getMessage($filter, [a], [$a], $message, context)) } export const getErrorTwo = (filter: (a: A, b: B) => boolean, $filter = filter.name): GetErrorBinary => (a: A, b: B, $a = `${a}`, $b = `${b}`, context?: Record, $message?: string) => { return filter(a, b) ? undefined : new Error(getMessage($filter, [a, b], [$a, $b], $message, context)) } export const refineOneR = (filter: (a: A) => boolean, $filter = filter.name): RefineUnary => (ctx: RefinementCtx) => (a: A, $a = `${a}`, context?: Record, $message?: string) => { if (!filter(a)) ctx.addIssue({ code: ZodIssueCode.custom, message: getMessage($filter, [a], [$a], $message), params: (context ?? {}) }) } export const refineTwoR = (filter: (a: A, b: B) => boolean, $filter = filter.name): RefineBinary => (ctx: RefinementCtx) => (a: A, b: B, $a = `${a}`, $b = `${b}`, context?: Record, $message?: string) => { if (!filter(a, b)) ctx.addIssue({ code: ZodIssueCode.custom, message: getMessage($filter, [a, b], [$a, $b], $message), params: (context ?? {}) }) } export const refineOne = (ctx: RefinementCtx) => (filter: (a: A) => boolean, $filter = filter.name) => refineOneR(filter, $filter)(ctx) export const refineTwo = (ctx: RefinementCtx) => (filter: (a: A, b: B) => boolean, $filter = filter.name) => refineTwoR(filter, $filter)(ctx) export function getMessage($func: string, args: unknown[], $args: string[], $message?: string, context?: Record) { return [ [ `${$func}(${$args.join(', ')})`, `${$func}(${args.map(toStringForAssert).join(', ')})`, $message, ].filter(identity).join(' ~ '), withContext ? toString(context) : '', ].filter(identity).join('\n') } export const toStringForAssert = (s: unknown) => toString(s === '' ? '' : s) export const assertEq = assertTwo(equals) export const assertNeq = assertTwo(nequals) export const assertIncludes = assertTwo(includes) export const assertTrue = assertOne(isTrue)