Skip to content

Commit 10983a2

Browse files
committed
feat: add validateResponse()
1 parent 958dc68 commit 10983a2

File tree

4 files changed

+84
-4
lines changed

4 files changed

+84
-4
lines changed

src/problemResponse.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import type {
1313
} from 'aws-lambda'
1414
import { aProblem } from './aProblem.js'
1515
import { ValidationFailedError } from './validateInput.js'
16+
import { ResponseValidationFailedError } from './validateResponse.js'
1617

1718
export class ProblemDetailError extends Error {
1819
public readonly problem: Static<typeof ProblemDetail>
@@ -40,6 +41,12 @@ export const problemResponse = (): MiddlewareObj<
4041
status: HttpStatusCode.BAD_REQUEST,
4142
detail: formatTypeBoxErrors(req.error.errors),
4243
})
44+
} else if (req.error instanceof ResponseValidationFailedError) {
45+
req.response = aProblem({
46+
title: 'Response validation failed',
47+
status: HttpStatusCode.INTERNAL_SERVER_ERROR,
48+
detail: formatTypeBoxErrors(req.error.errors),
49+
})
4350
} else if (req.error instanceof ProblemDetailError) {
4451
req.response = aProblem(req.error.problem)
4552
} else {

src/validateInput.ts

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
1-
import { validateWithTypeBox } from '@hello.nrfcloud.com/proto'
1+
import {
2+
formatTypeBoxErrors,
3+
validateWithTypeBox,
4+
} from '@hello.nrfcloud.com/proto'
25
import type { MiddlewareObj } from '@middy/core'
36
import type { Static, TSchema } from '@sinclair/typebox'
47
import type { ValueError } from '@sinclair/typebox/compiler'
@@ -11,8 +14,8 @@ import { tryAsJSON } from './tryAsJSON.js'
1114

1215
export class ValidationFailedError extends Error {
1316
public readonly errors: ValueError[]
14-
constructor(errors: ValueError[]) {
15-
super('Validation failed')
17+
constructor(errors: ValueError[], message = 'Validation failed') {
18+
super(message)
1619
this.errors = errors
1720
this.name = 'ValidationFailedError'
1821
}
@@ -50,7 +53,10 @@ export const validateInput = <Schema extends TSchema>(
5053
console.debug(
5154
`[validateInput]`,
5255
`Input not valid`,
53-
JSON.stringify(maybeValidInput.errors),
56+
JSON.stringify({
57+
input,
58+
errors: formatTypeBoxErrors(maybeValidInput.errors),
59+
}),
5460
)
5561
throw new ValidationFailedError(maybeValidInput.errors)
5662
}

src/validateResponse.spec.ts

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import middy from '@middy/core'
2+
import { Type } from '@sinclair/typebox'
3+
import type { Context } from 'aws-lambda'
4+
import assert from 'node:assert'
5+
import { describe, it } from 'node:test'
6+
import {
7+
ResponseValidationFailedError,
8+
validateResponse,
9+
} from './validateResponse.js'
10+
11+
void describe('validateResponse()', () => {
12+
void it('should validate the response', async () =>
13+
assert.equal(
14+
await middy()
15+
.use(validateResponse(Type.Boolean({ title: 'A boolean' })))
16+
.handler(async () => true)('Some event', {} as Context),
17+
true,
18+
))
19+
20+
void it('should throw an Error in case the response is invalid', async () =>
21+
assert.rejects(
22+
async () =>
23+
middy()
24+
.use(validateResponse(Type.Boolean()))
25+
.handler(async () => 42)('Some event', {} as Context),
26+
ResponseValidationFailedError,
27+
))
28+
})

src/validateResponse.ts

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
import {
2+
formatTypeBoxErrors,
3+
validateWithTypeBox,
4+
} from '@hello.nrfcloud.com/proto'
5+
import type middy from '@middy/core'
6+
import type { TSchema } from '@sinclair/typebox'
7+
import type { ValueError } from '@sinclair/typebox/errors'
8+
import { ValidationFailedError } from './validateInput.js'
9+
10+
export class ResponseValidationFailedError extends ValidationFailedError {
11+
constructor(errors: ValueError[]) {
12+
super(errors, 'Response validation failed')
13+
this.name = 'ResponseValidationFailedError'
14+
}
15+
}
16+
17+
export const validateResponse = <ResponseSchema extends TSchema>(
18+
schema: ResponseSchema,
19+
): middy.MiddlewareObj => {
20+
const validator = validateWithTypeBox(schema)
21+
return {
22+
after: async (req) => {
23+
const maybeValid = validator(req.response)
24+
if ('errors' in maybeValid) {
25+
console.error(
26+
`[validateResponse]`,
27+
`Response validation failed`,
28+
JSON.stringify({
29+
response: req.response,
30+
errors: formatTypeBoxErrors(maybeValid.errors),
31+
}),
32+
)
33+
throw new ResponseValidationFailedError(maybeValid.errors)
34+
}
35+
console.debug(`[validateResponse]`, `Response is`, schema.title)
36+
return undefined
37+
},
38+
}
39+
}

0 commit comments

Comments
 (0)