Skip to content

Commit 2f7ffee

Browse files
restfulheadPatrick Ruhkopf
authored andcommitted
fix: only apply to http triggers
1 parent 4418be5 commit 2f7ffee

File tree

1 file changed

+129
-117
lines changed

1 file changed

+129
-117
lines changed

src/app.ts

Lines changed: 129 additions & 117 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,9 @@ import { app, HttpRequest, HttpResponseInit, InvocationContext, PreInvocationCon
22
import { AjvOpenApiValidator } from './ajv-openapi-validator'
33
import { OpenAPIV3 } from 'openapi-types'
44
import { DEFAULT_AJV_SETTINGS } from './ajv-opts'
5-
import { DEFAULT_VALIDATOR_OPTS } from './openapi-validator'
5+
import { DEFAULT_VALIDATOR_OPTS, ValidatorOpts } from './openapi-validator'
66
import { createJsonResponse, logMessage } from './helper'
7+
import { Options } from 'ajv'
78

89
export 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

5455
export 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

Comments
 (0)