@@ -2,8 +2,9 @@ import { app, HttpRequest, HttpResponseInit, InvocationContext, PreInvocationCon
22import { AjvOpenApiValidator } from './ajv-openapi-validator'
33import { OpenAPIV3 } from 'openapi-types'
44import { DEFAULT_AJV_SETTINGS } from './ajv-opts'
5- import { DEFAULT_VALIDATOR_OPTS } from './openapi-validator'
5+ import { DEFAULT_VALIDATOR_OPTS , ValidatorOpts } from './openapi-validator'
66import { createJsonResponse , logMessage } from './helper'
7+ import { Options } from 'ajv'
78
89export interface ValidationMode {
910 /** whether to return an error response in case of a validation error instead of the actual function result */
@@ -53,143 +54,154 @@ export const DEFAULT_HOOK_OPTIONS: ValidatorHookOptions = {
5354
5455export function setupValidation (
5556 spec : OpenAPIV3 . Document ,
56- opts = { hook : DEFAULT_HOOK_OPTIONS , validator : DEFAULT_VALIDATOR_OPTS , ajv : DEFAULT_AJV_SETTINGS }
57+ opts : { hook ?: ValidatorHookOptions ; validator ?: ValidatorOpts ; ajv ?: Options } = {
58+ hook : DEFAULT_HOOK_OPTIONS ,
59+ validator : DEFAULT_VALIDATOR_OPTS ,
60+ ajv : DEFAULT_AJV_SETTINGS ,
61+ }
5762) {
5863 const validator = new AjvOpenApiValidator ( spec , opts ?. validator , opts ?. ajv )
5964
6065 const requiredHookOpts = opts . hook ? { ...DEFAULT_HOOK_OPTIONS , ...opts . hook } : DEFAULT_HOOK_OPTIONS
6166
6267 app . hook . preInvocation ( ( preContext : PreInvocationContext ) => {
63- const originalHandler = preContext . functionHandler
64- const path = '/' + preContext . invocationContext . options . trigger . route
65-
66- preContext . functionHandler = async ( origRequest : HttpRequest , context : InvocationContext ) : Promise < HttpResponseInit > => {
67- let request = origRequest
68- const method = request . method
69- const exclusion = requiredHookOpts . exclude ?. find (
70- ( exclusion ) => exclusion . path . toLowerCase ( ) === path . toLowerCase ( ) && exclusion . method . toLowerCase ( ) === method . toLowerCase ( )
71- )
72-
73- if (
74- requiredHookOpts . queryParameterValidationMode &&
75- ( ! exclusion || ( exclusion . validation !== false && exclusion . validation . queryParmeter !== false ) )
76- ) {
77- context . debug ( `Validating query parameters '${ path } ', '${ method } '` )
78- const reqParamsValResult = validator . validateQueryParams (
79- path ,
80- method ,
81- request . query ,
82- requiredHookOpts . queryParameterValidationMode . strict
68+ if ( preContext . invocationContext . options . trigger . type === 'httpTrigger' ) {
69+ const originalHandler = preContext . functionHandler
70+ const path = '/' + preContext . invocationContext . options . trigger . route
71+
72+ preContext . functionHandler = async ( origRequest : HttpRequest , context : InvocationContext ) : Promise < HttpResponseInit > => {
73+ let request = origRequest
74+ const method = request . method
75+ const exclusion = requiredHookOpts . exclude ?. find (
76+ ( exclusion ) => exclusion . path . toLowerCase ( ) === path . toLowerCase ( ) && exclusion . method . toLowerCase ( ) === method . toLowerCase ( )
8377 )
84- if ( reqParamsValResult ) {
85- preContext . hookData . requestQueryParameterValidationError = true
86-
87- if ( requiredHookOpts . queryParameterValidationMode . logLevel !== 'off' ) {
88- logMessage (
89- `Query param validation error: ${ JSON . stringify ( reqParamsValResult ) } ` ,
90- requiredHookOpts . queryParameterValidationMode . logLevel ,
91- context
92- )
93- }
9478
95- if ( requiredHookOpts . queryParameterValidationMode . returnErrorResponse ) {
96- return Promise . resolve ( createJsonResponse ( reqParamsValResult , 400 ) )
79+ if (
80+ requiredHookOpts . queryParameterValidationMode &&
81+ ( ! exclusion || ( exclusion . validation !== false && exclusion . validation . queryParmeter !== false ) )
82+ ) {
83+ context . debug ( `Validating query parameters '${ path } ', '${ method } '` )
84+ const reqParamsValResult = validator . validateQueryParams (
85+ path ,
86+ method ,
87+ request . query ,
88+ requiredHookOpts . queryParameterValidationMode . strict
89+ )
90+ if ( reqParamsValResult ) {
91+ preContext . hookData . requestQueryParameterValidationError = true
92+
93+ if ( requiredHookOpts . queryParameterValidationMode . logLevel !== 'off' ) {
94+ logMessage (
95+ `Query param validation error: ${ JSON . stringify ( reqParamsValResult ) } ` ,
96+ requiredHookOpts . queryParameterValidationMode . logLevel ,
97+ context
98+ )
99+ }
100+
101+ if ( requiredHookOpts . queryParameterValidationMode . returnErrorResponse ) {
102+ return Promise . resolve ( createJsonResponse ( reqParamsValResult , 400 ) )
103+ }
97104 }
98105 }
99- }
100-
101- if (
102- requiredHookOpts . requestBodyValidationMode &&
103- ( ! exclusion || ( exclusion . validation !== false && exclusion . validation . requestBody !== false ) )
104- ) {
105- context . debug ( `Validating request body for '${ path } ', '${ method } '` )
106-
107- let parsedBody = undefined
108- if ( request . body ) {
109- const textBody = await origRequest . text ( )
110- parsedBody = JSON . parse ( textBody )
111-
112- const headers : Record < string , string > = { }
113- origRequest . headers ?. forEach ( ( value , key ) => {
114- headers [ key ] = value
115- } )
116-
117- const query : Record < string , string > = { }
118- origRequest . query ?. forEach ( ( value , key ) => {
119- query [ key ] = value
120- } )
121-
122- // a copy is necessary, because the request body can only be consumed once
123- // see https://github.com/Azure/azure-functions-nodejs-library/issues/79#issuecomment-1875214147
124- request = new HttpRequest ( {
125- method : origRequest . method ,
126- url : origRequest . url ,
127- body : { string : textBody } ,
128- headers,
129- query,
130- params : origRequest . params ,
131- } )
132- }
133-
134- const reqBodyValResult = validator . validateRequestBody ( path , method , parsedBody , requiredHookOpts . requestBodyValidationMode . strict )
135- if ( reqBodyValResult ) {
136- preContext . hookData . requestBodyValidationError = true
137-
138- if ( requiredHookOpts . requestBodyValidationMode . logLevel !== 'off' ) {
139- logMessage (
140- `Request body validation error: ${ JSON . stringify ( reqBodyValResult ) } ` ,
141- requiredHookOpts . requestBodyValidationMode . logLevel ,
142- context
143- )
144- }
145106
146- if ( requiredHookOpts . requestBodyValidationMode . returnErrorResponse ) {
147- return Promise . resolve ( createJsonResponse ( reqBodyValResult , 400 ) )
107+ if (
108+ requiredHookOpts . requestBodyValidationMode &&
109+ ( ! exclusion || ( exclusion . validation !== false && exclusion . validation . requestBody !== false ) )
110+ ) {
111+ context . debug ( `Validating request body for '${ path } ', '${ method } '` )
112+
113+ let parsedBody = undefined
114+ if ( request . body ) {
115+ const textBody = await origRequest . text ( )
116+ parsedBody = JSON . parse ( textBody )
117+
118+ const headers : Record < string , string > = { }
119+ origRequest . headers ?. forEach ( ( value , key ) => {
120+ headers [ key ] = value
121+ } )
122+
123+ const query : Record < string , string > = { }
124+ origRequest . query ?. forEach ( ( value , key ) => {
125+ query [ key ] = value
126+ } )
127+
128+ // a copy is necessary, because the request body can only be consumed once
129+ // see https://github.com/Azure/azure-functions-nodejs-library/issues/79#issuecomment-1875214147
130+ request = new HttpRequest ( {
131+ method : origRequest . method ,
132+ url : origRequest . url ,
133+ body : { string : textBody } ,
134+ headers,
135+ query,
136+ params : origRequest . params ,
137+ } )
148138 }
149- }
150- }
151139
152- const response : HttpResponseInit = await originalHandler ( request , context )
153-
154- if (
155- requiredHookOpts . responseBodyValidationMode &&
156- ( ! exclusion || ( exclusion . validation !== false && exclusion . validation . responseBody !== false ) )
157- ) {
158- context . debug ( `Validating response body for '${ path } ', '${ method } ', '${ response . status } '` )
159-
160- // TODO support other response body formats
161- let responseBody = response . jsonBody ? response . jsonBody : undefined
162- if ( responseBody === undefined ) {
163- if ( typeof response . body === 'string' ) {
164- responseBody = JSON . parse ( response . body )
165- } else if ( response . body !== undefined ) {
166- throw new Error ( `Response body format '${ typeof response . body } ' not supported by validator yet.` )
140+ const reqBodyValResult = validator . validateRequestBody (
141+ path ,
142+ method ,
143+ parsedBody ,
144+ requiredHookOpts . requestBodyValidationMode . strict
145+ )
146+ if ( reqBodyValResult ) {
147+ preContext . hookData . requestBodyValidationError = true
148+
149+ if ( requiredHookOpts . requestBodyValidationMode . logLevel !== 'off' ) {
150+ logMessage (
151+ `Request body validation error: ${ JSON . stringify ( reqBodyValResult ) } ` ,
152+ requiredHookOpts . requestBodyValidationMode . logLevel ,
153+ context
154+ )
155+ }
156+
157+ if ( requiredHookOpts . requestBodyValidationMode . returnErrorResponse ) {
158+ return Promise . resolve ( createJsonResponse ( reqBodyValResult , 400 ) )
159+ }
167160 }
168161 }
169162
170- const respBodyValResult = validator . validateResponseBody (
171- path ,
172- method ,
173- response . status ?? 200 ,
174- responseBody ,
175- requiredHookOpts . responseBodyValidationMode . strict
176- )
177- if ( respBodyValResult ) {
178- if ( requiredHookOpts . responseBodyValidationMode . logLevel !== 'off' ) {
179- logMessage (
180- `Response body validation error: ${ JSON . stringify ( respBodyValResult ) } ` ,
181- requiredHookOpts . responseBodyValidationMode . logLevel ,
182- context
183- )
163+ const response : HttpResponseInit = await originalHandler ( request , context )
164+
165+ if (
166+ requiredHookOpts . responseBodyValidationMode &&
167+ ( ! exclusion || ( exclusion . validation !== false && exclusion . validation . responseBody !== false ) )
168+ ) {
169+ context . debug ( `Validating response body for '${ path } ', '${ method } ', '${ response . status } '` )
170+
171+ // TODO support other response body formats
172+ let responseBody = response . jsonBody ? response . jsonBody : undefined
173+ if ( responseBody === undefined ) {
174+ if ( typeof response . body === 'string' ) {
175+ responseBody = JSON . parse ( response . body )
176+ } else if ( response . body !== undefined ) {
177+ throw new Error ( `Response body format '${ typeof response . body } ' not supported by validator yet.` )
178+ }
184179 }
185180
186- if ( requiredHookOpts . responseBodyValidationMode . returnErrorResponse ) {
187- return Promise . resolve ( createJsonResponse ( respBodyValResult , 500 ) )
181+ const respBodyValResult = validator . validateResponseBody (
182+ path ,
183+ method ,
184+ response . status ?? 200 ,
185+ responseBody ,
186+ requiredHookOpts . responseBodyValidationMode . strict
187+ )
188+ if ( respBodyValResult ) {
189+ if ( requiredHookOpts . responseBodyValidationMode . logLevel !== 'off' ) {
190+ logMessage (
191+ `Response body validation error: ${ JSON . stringify ( respBodyValResult ) } ` ,
192+ requiredHookOpts . responseBodyValidationMode . logLevel ,
193+ context
194+ )
195+ }
196+
197+ if ( requiredHookOpts . responseBodyValidationMode . returnErrorResponse ) {
198+ return Promise . resolve ( createJsonResponse ( respBodyValResult , 500 ) )
199+ }
188200 }
189201 }
190- }
191202
192- return response
203+ return response
204+ }
193205 }
194206 } )
195207}
0 commit comments