/*-------------------------------------------------------------------------- @sinclair/typemap The MIT License (MIT) Copyright (c) 2024 Haydn Paterson (sinclair) Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---------------------------------------------------------------------------*/ import * as t from '@sinclair/typebox' import * as z from 'zod' // ------------------------------------------------------------------ // Constraint // ------------------------------------------------------------------ type TConstraint = (input: Input) => Output // ------------------------------------------------------------------ // Any // ------------------------------------------------------------------ type TFromAny = Result function FromAny(_type: t.TAny): z.ZodTypeAny { return z.any() } // ------------------------------------------------------------------ // Array // ------------------------------------------------------------------ type TFromArray>> = Result function FromArray(type: t.TArray): z.ZodTypeAny { const constraints: TConstraint>[] = [] const { minItems, maxItems /* minContains, maxContains, contains */ } = type if (t.ValueGuard.IsNumber(minItems)) constraints.push((input) => input.min(minItems)) if (t.ValueGuard.IsNumber(maxItems)) constraints.push((input) => input.max(maxItems)) const mapped = z.array(FromType(type.items)) return constraints.reduce((type, constraint) => constraint(type), mapped) } // ------------------------------------------------------------------ // BigInt // ------------------------------------------------------------------ type TFromBigInt = Result function FromBigInt(_type: t.TBigInt): z.ZodTypeAny { return z.bigint() } // ------------------------------------------------------------------ // Boolean // ------------------------------------------------------------------ type TFromBoolean = Result function FromBoolean(_type: t.TBoolean): z.ZodTypeAny { return z.boolean() } // ------------------------------------------------------------------ // Date // ------------------------------------------------------------------ type TFromDate = Result function FromDate(_type: t.TDate): z.ZodTypeAny { return z.date() } // ------------------------------------------------------------------ // Function // ------------------------------------------------------------------ // prettier-ignore type TFromFunction > = ( MappedParameters extends [z.ZodTypeAny, ...z.ZodTypeAny[]] | [] ? z.ZodFunction, TFromType> : z.ZodNever ) function FromFunction(type: t.TFunction): z.ZodTypeAny { const mappedParameters = FromTypes(type.parameters) as [] | [z.ZodTypeAny, ...z.ZodTypeAny[]] return z.function(z.tuple(mappedParameters), FromType(type.returns)) } // ------------------------------------------------------------------ // Integer // ------------------------------------------------------------------ type TFromInteger = Result function FromInteger(type: t.TInteger): z.ZodTypeAny { const { exclusiveMaximum, exclusiveMinimum, minimum, maximum, multipleOf } = type const constraints: TConstraint[] = [(value) => value.int()] if (t.ValueGuard.IsNumber(exclusiveMinimum)) constraints.push((input) => input.min(exclusiveMinimum + 1)) if (t.ValueGuard.IsNumber(exclusiveMaximum)) constraints.push((input) => input.min(exclusiveMaximum - 1)) if (t.ValueGuard.IsNumber(maximum)) constraints.push((input) => input.max(maximum)) if (t.ValueGuard.IsNumber(minimum)) constraints.push((input) => input.min(minimum)) if (t.ValueGuard.IsNumber(multipleOf)) constraints.push((input) => input.multipleOf(multipleOf)) return constraints.reduce((input, constraint) => constraint(input), z.number()) } // ------------------------------------------------------------------ // Intersect // ------------------------------------------------------------------ type TFromIntersect = Types extends [infer Left extends t.TSchema, ...infer Right extends t.TSchema[]] ? TFromIntersect, Result>> : Result function FromIntersect(type: t.TIntersect): z.ZodTypeAny { return type.allOf.reduce((result, left) => { return z.intersection(FromType(left), result) as never }, z.unknown()) as never } // ------------------------------------------------------------------ // Literal // ------------------------------------------------------------------ type TFromLiteral> = Result function FromLiteral(type: t.TLiteral): z.ZodTypeAny { return z.literal(type.const) } // ------------------------------------------------------------------ // Object // ------------------------------------------------------------------ type TFromObject< Properties extends t.TProperties, Result = z.ZodObject<{ [Key in keyof Properties]: TFromType }>, > = Result // prettier-ignore function FromObject(type: t.TObject): z.ZodTypeAny { const constraints: TConstraint>[] = [] const { additionalProperties } = type if (additionalProperties === false) constraints.push((input) => input.strict()) if (t.KindGuard.IsSchema(additionalProperties)) constraints.push((input) => input.catchall(FromType(additionalProperties))) const properties = globalThis.Object.getOwnPropertyNames(type.properties).reduce((result, key) => ({ ...result, [key]: FromType(type.properties[key]) }), {}) return constraints.reduce((type, constraint) => constraint(type), z.object(properties)) } // ------------------------------------------------------------------ // Promise // ------------------------------------------------------------------ type TFromPromise>> = Result function FromPromise(type: t.TPromise): z.ZodTypeAny { return z.promise(FromType(type.item)) } // ------------------------------------------------------------------ // Record // ------------------------------------------------------------------ type TFromRegExp = Result function FromRegExp(type: t.TRegExp): z.ZodTypeAny { const constraints: TConstraint[] = [(input) => input.regex(new RegExp(type.source), type.flags)] const { minLength, maxLength } = type if (t.ValueGuard.IsNumber(maxLength)) constraints.push((input) => input.max(maxLength)) if (t.ValueGuard.IsNumber(minLength)) constraints.push((input) => input.min(minLength)) return constraints.reduce((type, constraint) => constraint(type), z.string()) } // ------------------------------------------------------------------ // Record // ------------------------------------------------------------------ type TFromRecord = TFromType extends infer ZodKey extends z.KeySchema ? z.ZodRecord> : z.ZodNever // prettier-ignore function FromRecord(type: t.TRecord): z.ZodTypeAny { const pattern = globalThis.Object.getOwnPropertyNames(type.patternProperties)[0] const value = FromType(type.patternProperties[pattern]) return ( pattern === t.PatternBooleanExact ? z.record(z.boolean(), value) : pattern === t.PatternNumberExact ? z.record(z.number(), value) : pattern === t.PatternStringExact ? z.record(z.string(), value) : z.record(z.string().regex(new RegExp(pattern)), value) ) } // ------------------------------------------------------------------ // Optional // ------------------------------------------------------------------ type TFromOptional>> = Result function FromOptional(type: t.TOptional): z.ZodTypeAny { return z.optional(FromType(t.Optional(type, false))) } // ------------------------------------------------------------------ // Readonly // ------------------------------------------------------------------ type TFromReadonly>> = Result function FromReadonly(type: t.TReadonly): z.ZodTypeAny { return FromType(t.Readonly(type, false)) } // ------------------------------------------------------------------ // Never // ------------------------------------------------------------------ type TFromNever = Result function FromNever(type: t.TNever): z.ZodTypeAny { return z.never() } // ------------------------------------------------------------------ // Never // ------------------------------------------------------------------ type TFromNull = Result function FromNull(_type: t.TNull): z.ZodTypeAny { return z.null() } // ------------------------------------------------------------------ // Number // ------------------------------------------------------------------ type TFromNumber = Result function FromNumber(type: t.TNumber): z.ZodTypeAny { const { exclusiveMaximum, exclusiveMinimum, minimum, maximum, multipleOf } = type const constraints: TConstraint[] = [] if (t.ValueGuard.IsNumber(exclusiveMinimum)) constraints.push((input) => input.min(exclusiveMinimum + 1)) if (t.ValueGuard.IsNumber(exclusiveMaximum)) constraints.push((input) => input.min(exclusiveMaximum - 1)) if (t.ValueGuard.IsNumber(maximum)) constraints.push((input) => input.max(maximum)) if (t.ValueGuard.IsNumber(minimum)) constraints.push((input) => input.min(minimum)) if (t.ValueGuard.IsNumber(multipleOf)) constraints.push((input) => input.multipleOf(multipleOf)) return constraints.reduce((input, constraint) => constraint(input), z.number()) } // ------------------------------------------------------------------ // String // ------------------------------------------------------------------ type TFromString = Result // prettier-ignore function FromString(type: t.TString): z.ZodTypeAny { const constraints: TConstraint[] = [] const { minLength, maxLength, pattern, format } = type if (t.ValueGuard.IsNumber(maxLength)) constraints.push((input) => input.max(maxLength)) if (t.ValueGuard.IsNumber(minLength)) constraints.push((input) => input.min(minLength)) if (t.ValueGuard.IsString(pattern)) constraints.push((input) => input.regex(new RegExp(pattern))) if (t.ValueGuard.IsString(format)) constraints.push((input) => format === 'base64' ? input.base64() : format === 'base64url' ? input.base64url() : format === 'cidrv4' ? input.cidr({ version: 'v4' }) : format === 'cidrv6' ? input.cidr({ version: 'v6' }) : format === 'cidr' ? input.cidr() : format === 'cuid' ? input.cuid() : format === 'cuid2' ? input.cuid2() : format === 'date' ? input.date() : format === 'datetime' ? input.datetime() : format === 'duration' ? input.duration() : format === 'email' ? input.email() : format === 'emoji' ? input.emoji() : format === 'ipv4' ? input.ip({ version: 'v4' }) : format === 'ipv6' ? input.ip({ version: 'v6' }) : format === 'ip' ? input.ip() : format === 'jwt' ? input.jwt() : format === 'nanoid' ? input.nanoid() : format === 'time' ? input.time() : format === 'ulid' ? input.ulid() : format === 'url' ? input.url() : format === 'uuid' ? input.uuid() : input, ) return constraints.reduce((type, constraint) => constraint(type), z.string()) } // ------------------------------------------------------------------ // Symbol // ------------------------------------------------------------------ type TFromSymbol = Result function FromSymbol(_type: t.TSymbol): z.ZodTypeAny { return z.symbol() } // ------------------------------------------------------------------ // Tuple // ------------------------------------------------------------------ // prettier-ignore type TFromTuple> = ( Mapped extends [z.ZodTypeAny, ...z.ZodTypeAny[]] | [] ? z.ZodTuple : z.ZodNever ) function FromTuple(type: t.TTuple): z.ZodTypeAny { const mapped = FromTypes(type.items || []) as [] | [z.ZodTypeAny, ...z.ZodTypeAny[]] return z.tuple(mapped) } // ------------------------------------------------------------------ // Undefined // ------------------------------------------------------------------ type TFromUndefined = Result function FromUndefined(_type: t.TUndefined): z.ZodTypeAny { return z.undefined() } // ------------------------------------------------------------------ // Union // ------------------------------------------------------------------ // prettier-ignore type TFromUnion> = ( Mapped extends z.ZodUnionOptions ? z.ZodUnion : z.ZodNever ) function FromUnion(_type: t.TUnion): z.ZodTypeAny { const mapped = FromTypes(_type.anyOf) as [z.ZodTypeAny, z.ZodTypeAny, ...z.ZodTypeAny[]] return mapped.length >= 1 ? z.union(mapped) : z.never() } // ------------------------------------------------------------------ // TUnknown // ------------------------------------------------------------------ type TFromUnknown = Result function FromUnknown(_type: t.TUnknown): z.ZodTypeAny { return z.unknown() } // ------------------------------------------------------------------ // Void // ------------------------------------------------------------------ type TFromVoid = Result function FromVoid(_type: t.TVoid): z.ZodTypeAny { return z.void() } // ------------------------------------------------------------------ // Types // ------------------------------------------------------------------ type TFromTypes = Types extends [infer Left extends t.TSchema, ...infer Right extends t.TSchema[]] ? TFromTypes]> : Result function FromTypes(types: t.TSchema[]): z.ZodTypeAny[] { return types.map((type) => FromType(type)) } // ------------------------------------------------------------------ // Type // ------------------------------------------------------------------ // prettier-ignore type TFromType = ( Type extends t.TReadonly ? TFromReadonly : Type extends t.TOptional ? TFromOptional : Type extends t.TAny ? TFromAny : Type extends t.TArray ? TFromArray : Type extends t.TBigInt ? TFromBigInt : Type extends t.TBoolean ? TFromBoolean : Type extends t.TDate ? TFromDate : Type extends t.TFunction ? TFromFunction : Type extends t.TInteger ? TFromInteger : Type extends t.TIntersect ? TFromIntersect : Type extends t.TLiteral ? TFromLiteral : Type extends t.TNever ? TFromNever : Type extends t.TNull ? TFromNull : Type extends t.TNumber ? TFromNumber : Type extends t.TObject ? TFromObject : Type extends t.TPromise ? TFromPromise : Type extends t.TRecord ? TFromRecord : Type extends t.TRegExp ? TFromRegExp : Type extends t.TString ? TFromString : Type extends t.TSymbol ? TFromSymbol : Type extends t.TTuple ? TFromTuple : Type extends t.TUndefined ? TFromUndefined : Type extends t.TUnion ? TFromUnion : Type extends t.TUnknown ? TFromUnknown : Type extends t.TVoid ? TFromVoid : z.ZodNever ) // prettier-ignore function FromType(type: t.TSchema): z.ZodTypeAny { const constraints: TConstraint[] = [] if(!t.ValueGuard.IsUndefined(type.description)) constraints.push(input => input.describe(type.description!)) if(!t.ValueGuard.IsUndefined(type.default)) constraints.push(input => input.default(type.default)) return constraints.reduce((type, constraint) => constraint(type), ( t.KindGuard.IsReadonly(type) ? FromReadonly(type) : t.KindGuard.IsOptional(type) ? FromOptional(type) : t.KindGuard.IsAny(type) ? FromAny(type) : t.KindGuard.IsArray(type) ? FromArray(type) : t.KindGuard.IsBigInt(type) ? FromBigInt(type) : t.KindGuard.IsBoolean(type) ? FromBoolean(type) : t.KindGuard.IsDate(type) ? FromDate(type) : t.KindGuard.IsFunction(type) ? FromFunction(type) : t.KindGuard.IsInteger(type) ? FromInteger(type) : t.KindGuard.IsIntersect(type) ? FromIntersect(type) : t.KindGuard.IsLiteral(type) ? FromLiteral(type) : t.KindGuard.IsNever(type) ? FromNever(type) : t.KindGuard.IsNull(type) ? FromNull(type) : t.KindGuard.IsNumber(type) ? FromNumber(type) : t.KindGuard.IsObject(type) ? FromObject(type) : t.KindGuard.IsPromise(type) ? FromPromise(type) : t.KindGuard.IsRegExp(type) ? FromRegExp(type) : t.KindGuard.IsRecord(type) ? FromRecord(type) : t.KindGuard.IsString(type) ? FromString(type) : t.KindGuard.IsSymbol(type) ? FromSymbol(type) : t.KindGuard.IsTuple(type) ? FromTuple(type) : t.KindGuard.IsUndefined(type) ? FromUndefined(type) : t.KindGuard.IsUnion(type) ? FromUnion(type) : t.KindGuard.IsUnknown(type) ? FromUnknown(type) : t.KindGuard.IsVoid(type) ? FromVoid(type) : z.never() )) } // ------------------------------------------------------------------ // ZodFromTypeBox // ------------------------------------------------------------------ // prettier-ignore export type TZodFromTypeBox = ( Type extends t.TSchema ? TFromType : z.ZodNever ) export function ZodFromTypeBox(type: Type): TZodFromTypeBox { return (t.KindGuard.IsSchema(type) ? FromType(type) : z.never()) as never }