@@ -37,21 +37,56 @@ export type ValidationPath = Array<string | number>;
3737 * Runtime validator function.
3838 */
3939export type Validator < T > = ( value : unknown , path ?: ValidationPath ) => ValidationResult < T > ;
40+
4041/**
4142 * Infers the validated type from a validator.
4243 */
4344export type InferValidator < V > = V extends Validator < infer T > ? T : never ;
45+
4446/**
4547 * Generic object schema shape.
4648 */
4749export type SchemaShape = Record < string , Validator < any > > ;
50+
4851/**
4952 * Infers the validated output from an object schema shape.
5053 */
5154export type InferSchemaShape < S extends SchemaShape > = {
5255 [ K in keyof S ] : InferValidator < S [ K ] > ;
5356} ;
5457
58+ /**
59+ * Issue input shape accepted by adapter helpers.
60+ */
61+ export type ValidationIssueInput = {
62+ path ?: string | ValidationPath ;
63+ message : string ;
64+ code ?: string ;
65+ } ;
66+
67+ /**
68+ * Result shape accepted by adapter helpers.
69+ */
70+ export type ValidationAdapterResult < T > =
71+ | ValidationResult < T >
72+ | {
73+ success : true ;
74+ data : T ;
75+ }
76+ | {
77+ success : false ;
78+ errors ?: ValidationIssueInput [ ] ;
79+ issues ?: ValidationIssueInput [ ] ;
80+ error ?: unknown ;
81+ } ;
82+
83+ /**
84+ * Adapter used to bridge external schema libraries into the local validator contract.
85+ */
86+ export type ValidationAdapter < TSchema , TOutput = unknown > = {
87+ safeParse ( schema : TSchema , value : unknown ) : ValidationAdapterResult < TOutput > ;
88+ } ;
89+
5590/**
5691 * Error thrown by `validation.parse()` when validation fails.
5792 */
@@ -65,7 +100,7 @@ export class SchemaValidationError extends Error {
65100 }
66101}
67102
68- const toPathString = ( path : ValidationPath ) => {
103+ export const toPathString = ( path : ValidationPath ) => {
69104 if ( path . length === 0 ) return "$" ;
70105 return path . reduce < string > ( ( acc , part ) => {
71106 if ( typeof part === "number" ) return `${ acc } [${ part } ]` ;
@@ -74,6 +109,19 @@ const toPathString = (path: ValidationPath) => {
74109 } , "$" ) ;
75110} ;
76111
112+ const normalizePath = ( path : string | ValidationPath | undefined , fallback : ValidationPath ) : string => {
113+ if ( typeof path === "string" ) {
114+ return path . startsWith ( "$" ) ? path : toPathString ( [ ...fallback , path ] ) ;
115+ }
116+ return toPathString ( path ?? fallback ) ;
117+ } ;
118+
119+ const normalizeIssue = ( issue : ValidationIssueInput , fallback : ValidationPath ) : ValidationIssue => ( {
120+ path : normalizePath ( issue . path , fallback ) ,
121+ message : issue . message ,
122+ code : issue . code ,
123+ } ) ;
124+
77125/**
78126 * Creates a successful validation result.
79127 */
@@ -104,6 +152,47 @@ export const isFailure = <T>(result: ValidationResult<T>): result is ValidationF
104152const isPlainObject = ( value : unknown ) : value is Record < string , unknown > =>
105153 typeof value === "object" && value !== null && ! Array . isArray ( value ) ;
106154
155+ /**
156+ * Normalizes adapter output into the local validation result format.
157+ */
158+ export const normalizeAdapterResult = < T > (
159+ result : ValidationAdapterResult < T > ,
160+ path : ValidationPath = [ ]
161+ ) : ValidationResult < T > => {
162+ if ( result . success ) {
163+ return ok ( result . data ) ;
164+ }
165+
166+ if ( "errors" in result && Array . isArray ( result . errors ) && result . errors . length > 0 ) {
167+ return {
168+ success : false ,
169+ errors : result . errors . map ( ( issue ) => normalizeIssue ( issue , path ) ) ,
170+ } ;
171+ }
172+
173+ if ( "issues" in result && Array . isArray ( result . issues ) && result . issues . length > 0 ) {
174+ return {
175+ success : false ,
176+ errors : result . issues . map ( ( issue ) => normalizeIssue ( issue , path ) ) ,
177+ } ;
178+ }
179+
180+ const error = "error" in result ? result . error : undefined ;
181+ const message = error instanceof Error
182+ ? error . message
183+ : "Schema validation failed" ;
184+ return fail ( message , path , "invalid_schema" ) ;
185+ } ;
186+
187+ /**
188+ * Wraps an external schema and adapter into a local validator function.
189+ */
190+ export const adaptSchema = < TSchema , T > (
191+ schema : TSchema ,
192+ adapter : ValidationAdapter < TSchema , T >
193+ ) : Validator < T > =>
194+ ( value , path = [ ] ) => normalizeAdapterResult < T > ( adapter . safeParse ( schema , value ) , path ) ;
195+
107196/**
108197 * Runs a validator against a value.
109198 */
0 commit comments