Skip to content

Commit

Permalink
feat: add validateResponse()
Browse files Browse the repository at this point in the history
  • Loading branch information
coderbyheart committed Mar 3, 2025
1 parent 958dc68 commit 10983a2
Show file tree
Hide file tree
Showing 4 changed files with 84 additions and 4 deletions.
7 changes: 7 additions & 0 deletions src/problemResponse.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import type {
} from 'aws-lambda'
import { aProblem } from './aProblem.js'
import { ValidationFailedError } from './validateInput.js'
import { ResponseValidationFailedError } from './validateResponse.js'

export class ProblemDetailError extends Error {
public readonly problem: Static<typeof ProblemDetail>
Expand Down Expand Up @@ -40,6 +41,12 @@ export const problemResponse = (): MiddlewareObj<
status: HttpStatusCode.BAD_REQUEST,
detail: formatTypeBoxErrors(req.error.errors),
})
} else if (req.error instanceof ResponseValidationFailedError) {
req.response = aProblem({
title: 'Response validation failed',
status: HttpStatusCode.INTERNAL_SERVER_ERROR,
detail: formatTypeBoxErrors(req.error.errors),
})
} else if (req.error instanceof ProblemDetailError) {
req.response = aProblem(req.error.problem)
} else {
Expand Down
14 changes: 10 additions & 4 deletions src/validateInput.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
import { validateWithTypeBox } from '@hello.nrfcloud.com/proto'
import {
formatTypeBoxErrors,
validateWithTypeBox,
} from '@hello.nrfcloud.com/proto'
import type { MiddlewareObj } from '@middy/core'
import type { Static, TSchema } from '@sinclair/typebox'
import type { ValueError } from '@sinclair/typebox/compiler'
Expand All @@ -11,8 +14,8 @@ import { tryAsJSON } from './tryAsJSON.js'

export class ValidationFailedError extends Error {
public readonly errors: ValueError[]
constructor(errors: ValueError[]) {
super('Validation failed')
constructor(errors: ValueError[], message = 'Validation failed') {
super(message)
this.errors = errors
this.name = 'ValidationFailedError'
}
Expand Down Expand Up @@ -50,7 +53,10 @@ export const validateInput = <Schema extends TSchema>(
console.debug(
`[validateInput]`,
`Input not valid`,
JSON.stringify(maybeValidInput.errors),
JSON.stringify({
input,
errors: formatTypeBoxErrors(maybeValidInput.errors),
}),
)
throw new ValidationFailedError(maybeValidInput.errors)
}
Expand Down
28 changes: 28 additions & 0 deletions src/validateResponse.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import middy from '@middy/core'
import { Type } from '@sinclair/typebox'
import type { Context } from 'aws-lambda'
import assert from 'node:assert'
import { describe, it } from 'node:test'
import {
ResponseValidationFailedError,
validateResponse,
} from './validateResponse.js'

void describe('validateResponse()', () => {
void it('should validate the response', async () =>
assert.equal(
await middy()
.use(validateResponse(Type.Boolean({ title: 'A boolean' })))
.handler(async () => true)('Some event', {} as Context),
true,
))

void it('should throw an Error in case the response is invalid', async () =>
assert.rejects(
async () =>
middy()
.use(validateResponse(Type.Boolean()))
.handler(async () => 42)('Some event', {} as Context),
ResponseValidationFailedError,
))
})
39 changes: 39 additions & 0 deletions src/validateResponse.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import {
formatTypeBoxErrors,
validateWithTypeBox,
} from '@hello.nrfcloud.com/proto'
import type middy from '@middy/core'
import type { TSchema } from '@sinclair/typebox'
import type { ValueError } from '@sinclair/typebox/errors'
import { ValidationFailedError } from './validateInput.js'

export class ResponseValidationFailedError extends ValidationFailedError {
constructor(errors: ValueError[]) {
super(errors, 'Response validation failed')
this.name = 'ResponseValidationFailedError'
}
}

export const validateResponse = <ResponseSchema extends TSchema>(
schema: ResponseSchema,
): middy.MiddlewareObj => {
const validator = validateWithTypeBox(schema)
return {
after: async (req) => {
const maybeValid = validator(req.response)
if ('errors' in maybeValid) {
console.error(
`[validateResponse]`,
`Response validation failed`,
JSON.stringify({
response: req.response,
errors: formatTypeBoxErrors(maybeValid.errors),
}),
)
throw new ResponseValidationFailedError(maybeValid.errors)
}
console.debug(`[validateResponse]`, `Response is`, schema.title)
return undefined
},
}
}

0 comments on commit 10983a2

Please sign in to comment.