@@ -48,29 +48,65 @@ export function isStringOrUndefined(
4848 */
4949export type Validator < T > = {
5050 validate : ( val : unknown ) => val is T ;
51+ check : ( val : unknown , path : string ) => CheckSchemaResult ;
5152 required : boolean ;
5253} ;
5354
55+ function defaultCheck (
56+ validate : ( val : unknown ) => val is any ,
57+ ) : ( arg : unknown ) => CheckSchemaResult {
58+ return ( arg ) => ( { unknownKeys : [ ] , valid : validate ( arg ) } ) ;
59+ }
60+
61+ function makeValidator < T > (
62+ validate : ( arg : unknown ) => arg is T ,
63+ required : boolean = true ,
64+ ) {
65+ return {
66+ validate,
67+ check : defaultCheck ( validate ) ,
68+ required,
69+ } as const satisfies Validator < T > ;
70+ }
71+
5472/** Extracts `T` from `Validator<T>`. */
5573export type UnwrapValidator < V > = V extends Validator < infer A > ? A : never ;
5674
5775/** A validator for string fields in schemas. */
58- export const string = {
59- validate : isString ,
60- required : true ,
61- } as const satisfies Validator < string > ;
76+ export const string = makeValidator ( isString ) ;
6277
6378/** A validator for number fields in schemas. */
64- export const number = {
65- validate : isNumber ,
66- required : true ,
67- } as const satisfies Validator < number > ;
79+ export const number = makeValidator ( isNumber ) ;
6880
6981/** A validator for arrays. */
7082export function array < T > ( validator : Validator < T > ) {
83+ const validate = ( val : unknown ) => {
84+ return isArray ( val ) && val . every ( ( e ) => validator . validate ( e ) ) ;
85+ } ;
7186 return {
72- validate : ( val : unknown ) => {
73- return isArray ( val ) && val . every ( ( e ) => validator . validate ( e ) ) ;
87+ validate,
88+ check : ( val : unknown , path : string ) => {
89+ const result : CheckSchemaResult = { valid : true , unknownKeys : [ ] } ;
90+
91+ if ( ! isArray ( val ) ) {
92+ result . valid = false ;
93+ return result ;
94+ }
95+
96+ let index = 0 ;
97+ for ( const e of val ) {
98+ const eResult = validator . check ( e , `${ path } [${ index } ].` ) ;
99+
100+ result . unknownKeys . push ( ...eResult . unknownKeys ) ;
101+ index ++ ;
102+
103+ if ( ! eResult . valid ) {
104+ result . valid = false ;
105+ continue ;
106+ }
107+ }
108+
109+ return result ;
74110 } ,
75111 required : true ,
76112 } as const satisfies Validator < T [ ] > ;
@@ -85,6 +121,12 @@ export function object<
85121 validate : ( val : unknown ) => {
86122 return isObject ( val ) && validateSchema < S , T > ( schema , val ) ;
87123 } ,
124+ check : ( val , path ) => {
125+ if ( ! isObject ( val ) ) {
126+ return { valid : false , unknownKeys : [ ] } ;
127+ }
128+ return checkSchema ( schema , val , { } , path ) ;
129+ } ,
88130 required : true ,
89131 } as const satisfies Validator < T > ;
90132}
@@ -98,6 +140,12 @@ export function optionalOrNull<T>(validator: Validator<T>) {
98140 validate : ( val : unknown ) => {
99141 return val === undefined || val === null || validator . validate ( val ) ;
100142 } ,
143+ check : ( val , path ) => {
144+ if ( val === undefined || val === null ) {
145+ return { valid : true , unknownKeys : [ ] } ;
146+ }
147+ return validator . check ( val , path ) ;
148+ } ,
101149 required : false ,
102150 } as const satisfies Validator < T | undefined | null > ;
103151}
@@ -111,6 +159,12 @@ export function optional<T>(validator: Validator<T>) {
111159 validate : ( val : unknown ) : val is T | undefined => {
112160 return val === undefined || validator . validate ( val ) ;
113161 } ,
162+ check : ( val , path ) => {
163+ if ( val === undefined ) {
164+ return { valid : true , unknownKeys : [ ] } ;
165+ }
166+ return validator . check ( val , path ) ;
167+ } ,
114168 required : false ,
115169 } as const satisfies Validator < T | undefined > ;
116170}
@@ -193,13 +247,19 @@ export function checkSchema<S extends Schema>(
193247 }
194248
195249 // If the property is present, validate it.
196- if ( hasKey && ! validator . validate ( obj [ key ] ) ) {
197- result . valid = false ;
250+ if ( hasKey ) {
251+ const checkResult = validator . check ( obj [ key ] , ` ${ path } ${ key } .` ) ;
198252
199- if ( options . failFast ) {
200- return result ;
253+ result . unknownKeys . push ( ...checkResult . unknownKeys ) ;
254+
255+ if ( ! checkResult . valid ) {
256+ result . valid = false ;
257+
258+ if ( options . failFast ) {
259+ return result ;
260+ }
261+ continue ;
201262 }
202- continue ;
203263 }
204264
205265 // If we reach this point, the key has been successfully validated.
0 commit comments