@@ -48,67 +48,133 @@ async function validateRequestData<TReq extends ReqSchema>(
4848 typedReqCtx : TypedRequestContext < TReq , HandlerResponse > ,
4949 reqSchemas : NonNullable < ValidationConfig < TReq , HandlerResponse > [ 'req' ] >
5050) : Promise < void > {
51+ const schemaEntries : [ string , StandardSchemaV1 ] [ ] = [ ] ;
52+ const dataEntries : [ string , unknown ] [ ] = [ ] ;
53+
5154 if ( reqSchemas . body ) {
5255 const bodyData = await extractBody ( typedReqCtx . req ) ;
53- typedReqCtx . valid . req . body = await validateRequest < TReq [ 'body' ] > (
54- reqSchemas . body ,
55- bodyData ,
56- 'body'
57- ) ;
56+ schemaEntries . push ( [ 'body' , reqSchemas . body ] ) ;
57+ dataEntries . push ( [ 'body' , bodyData ] ) ;
5858 }
5959
6060 if ( reqSchemas . headers ) {
6161 const headers = Object . fromEntries ( typedReqCtx . req . headers . entries ( ) ) ;
62- typedReqCtx . valid . req . headers = await validateRequest < TReq [ 'headers' ] > (
63- reqSchemas . headers ,
64- headers ,
65- 'headers'
66- ) ;
62+ schemaEntries . push ( [ 'headers' , reqSchemas . headers ] ) ;
63+ dataEntries . push ( [ 'headers' , headers ] ) ;
6764 }
6865
6966 if ( reqSchemas . path ) {
70- typedReqCtx . valid . req . path = await validateRequest < TReq [ 'path' ] > (
71- reqSchemas . path ,
72- typedReqCtx . params ,
73- 'path'
74- ) ;
67+ schemaEntries . push ( [ 'path' , reqSchemas . path ] ) ;
68+ dataEntries . push ( [ 'path' , typedReqCtx . params ] ) ;
7569 }
7670
7771 if ( reqSchemas . query ) {
7872 const query = Object . fromEntries (
7973 new URL ( typedReqCtx . req . url ) . searchParams . entries ( )
8074 ) ;
81- typedReqCtx . valid . req . query = await validateRequest < TReq [ 'query' ] > (
82- reqSchemas . query ,
83- query ,
84- 'query'
75+ schemaEntries . push ( [ 'query' , reqSchemas . query ] ) ;
76+ dataEntries . push ( [ 'query' , query ] ) ;
77+ }
78+
79+ const stitchedSchema = createObjectSchema ( schemaEntries ) ;
80+ const stitchedData = Object . fromEntries ( dataEntries ) ;
81+
82+ const result = await stitchedSchema [ '~standard' ] . validate ( stitchedData ) ;
83+
84+ if ( 'issues' in result ) {
85+ throw new RequestValidationError (
86+ 'Validation failed for request' ,
87+ result . issues
8588 ) ;
8689 }
90+
91+ const validated = result . value as Record < string , unknown > ;
92+ if ( reqSchemas . body )
93+ typedReqCtx . valid . req . body = validated . body as TReq [ 'body' ] ;
94+ if ( reqSchemas . headers )
95+ typedReqCtx . valid . req . headers = validated . headers as TReq [ 'headers' ] ;
96+ if ( reqSchemas . path )
97+ typedReqCtx . valid . req . path = validated . path as TReq [ 'path' ] ;
98+ if ( reqSchemas . query )
99+ typedReqCtx . valid . req . query = validated . query as TReq [ 'query' ] ;
87100}
88101
89102async function validateResponseData < TResBody extends HandlerResponse > (
90103 typedReqCtx : TypedRequestContext < ReqSchema , TResBody > ,
91104 resSchemas : NonNullable < ValidationConfig < ReqSchema , TResBody > [ 'res' ] >
92105) : Promise < void > {
93106 const response = typedReqCtx . res ;
107+ const schemaEntries : [ string , StandardSchemaV1 ] [ ] = [ ] ;
108+ const dataEntries : [ string , unknown ] [ ] = [ ] ;
94109
95110 if ( resSchemas . body && response . body ) {
96111 const bodyData = await extractBody ( response ) ;
97- typedReqCtx . valid . res . body = await validateResponse < TResBody > (
98- resSchemas . body ,
99- bodyData ,
100- 'body'
101- ) ;
112+ schemaEntries . push ( [ 'body' , resSchemas . body ] ) ;
113+ dataEntries . push ( [ 'body' , bodyData ] ) ;
102114 }
103115
104116 if ( resSchemas . headers ) {
105117 const headers = Object . fromEntries ( response . headers . entries ( ) ) ;
106- typedReqCtx . valid . res . headers = await validateResponse (
107- resSchemas . headers ,
108- headers ,
109- 'headers'
118+ schemaEntries . push ( [ 'headers' , resSchemas . headers ] ) ;
119+ dataEntries . push ( [ 'headers' , headers ] ) ;
120+ }
121+
122+ const stitchedSchema = createObjectSchema ( schemaEntries ) ;
123+ const stitchedData = Object . fromEntries ( dataEntries ) ;
124+
125+ const result = await stitchedSchema [ '~standard' ] . validate ( stitchedData ) ;
126+
127+ if ( 'issues' in result ) {
128+ throw new ResponseValidationError (
129+ 'Validation failed for response' ,
130+ result . issues
110131 ) ;
111132 }
133+
134+ const validated = result . value as Record < string , unknown > ;
135+ if ( resSchemas . body ) {
136+ typedReqCtx . valid . res . body = validated . body as TResBody ;
137+ }
138+ if ( resSchemas . headers ) {
139+ typedReqCtx . valid . res . headers = validated . headers as Record < string , string > ;
140+ }
141+ }
142+
143+ function createObjectSchema (
144+ entries : [ string , StandardSchemaV1 ] [ ]
145+ ) : StandardSchemaV1 {
146+ return {
147+ '~standard' : {
148+ version : 1 ,
149+ vendor : 'powertools' ,
150+ validate : async ( data ) : Promise < StandardSchemaV1 . Result < unknown > > => {
151+ const dataObj = data as Record < string , unknown > ;
152+ const validated : Record < string , unknown > = { } ;
153+ const allIssues : StandardSchemaV1 . Issue [ ] = [ ] ;
154+
155+ for ( const [ key , schema ] of entries ) {
156+ const result = await schema [ '~standard' ] . validate ( dataObj [ key ] ) ;
157+
158+ for ( const issue of result . issues ?? [ ] ) {
159+ allIssues . push ( {
160+ message : issue . message ,
161+ path : [ key , ...( issue . path || [ ] ) ] ,
162+ } ) ;
163+ }
164+
165+ if ( 'value' in result ) {
166+ validated [ key ] = result . value ;
167+ }
168+ }
169+
170+ if ( allIssues . length > 0 ) {
171+ return { issues : allIssues } ;
172+ }
173+
174+ return { value : validated } ;
175+ } ,
176+ } ,
177+ } ;
112178}
113179
114180async function extractBody ( source : Request | Response ) : Promise < unknown > {
@@ -140,33 +206,3 @@ async function extractBody(source: Request | Response): Promise<unknown> {
140206
141207 return await cloned . text ( ) ;
142208}
143-
144- async function validateRequest < T > (
145- schema : StandardSchemaV1 ,
146- data : unknown ,
147- component : 'body' | 'headers' | 'path' | 'query'
148- ) : Promise < T > {
149- const result = await schema [ '~standard' ] . validate ( data ) ;
150-
151- if ( 'issues' in result ) {
152- const message = `Validation failed for request ${ component } ` ;
153- throw new RequestValidationError ( message , result . issues ) ;
154- }
155-
156- return result . value as T ;
157- }
158-
159- async function validateResponse < T > (
160- schema : StandardSchemaV1 ,
161- data : unknown ,
162- component : 'body' | 'headers'
163- ) : Promise < T > {
164- const result = await schema [ '~standard' ] . validate ( data ) ;
165-
166- if ( 'issues' in result ) {
167- const message = `Validation failed for response ${ component } ` ;
168- throw new ResponseValidationError ( message , result . issues ) ;
169- }
170-
171- return result . value as T ;
172- }
0 commit comments