diff --git a/packages/validation/src/middleware.ts b/packages/validation/src/middleware.ts new file mode 100644 index 0000000000..e706d6c846 --- /dev/null +++ b/packages/validation/src/middleware.ts @@ -0,0 +1,39 @@ +import { SchemaValidationError } from './errors.js'; +import type { ValidatorOptions } from './types.js'; +import { validate } from './validate.js'; + +export function validation(options: ValidatorOptions) { + return { + before: async (handler: { event: unknown }) => { + if (options.inboundSchema) { + try { + handler.event = validate({ + payload: handler.event, + schema: options.inboundSchema, + envelope: options.envelope, + formats: options.formats, + externalRefs: options.externalRefs, + ajv: options.ajv, + }); + } catch (error) { + throw new SchemaValidationError('Inbound validation failed', error); + } + } + }, + after: async (handler: { response: unknown }) => { + if (options.outboundSchema) { + try { + handler.response = validate({ + payload: handler.response, + schema: options.outboundSchema, + formats: options.formats, + externalRefs: options.externalRefs, + ajv: options.ajv, + }); + } catch (error) { + throw new SchemaValidationError('Outbound validation failed', error); + } + } + }, + }; +} diff --git a/packages/validation/tests/unit/middleware.test.ts b/packages/validation/tests/unit/middleware.test.ts new file mode 100644 index 0000000000..204c61aadb --- /dev/null +++ b/packages/validation/tests/unit/middleware.test.ts @@ -0,0 +1,73 @@ +import middy from '@middy/core'; +import { describe, expect, it } from 'vitest'; +import { SchemaValidationError } from '../../src/errors.js'; +import { validation } from '../../src/middleware.js'; + +const inboundSchema = { + type: 'object', + properties: { + inputValue: { type: 'number' }, + }, + required: ['inputValue'], + additionalProperties: false, +}; + +const outboundSchema = { + type: 'object', + properties: { + outputValue: { type: 'number' }, + }, + required: ['outputValue'], + additionalProperties: false, +}; + +const response = { outputValue: 20 }; +const baseHandler = async (event: unknown) => { + return response; +}; + +describe('validation middleware with Middy', () => { + it('should validate inbound and outbound successfully', async () => { + // Prepare + const middleware = validation({ inboundSchema, outboundSchema }); + const wrappedHandler = middy(baseHandler).use(middleware); + const event = { inputValue: 10 }; + // Act + const result = await wrappedHandler(event); + // Assess + expect(result).toEqual(response); + }); + + it('should throw error on inbound validation failure', async () => { + // Prepare + const middleware = validation({ inboundSchema }); + const wrappedHandler = middy(baseHandler).use(middleware); + const invalidEvent = { inputValue: 'invalid' }; + // Act & Assess + await expect(wrappedHandler(invalidEvent)).rejects.toThrow( + SchemaValidationError + ); + }); + + it('should throw error on outbound validation failure', async () => { + const invalidHandler = async (_event: unknown) => { + return { outputValue: 'invalid' }; + }; + const middleware = validation({ outboundSchema }); + const wrappedHandler = middy(invalidHandler).use(middleware); + const event = { any: 'value' }; + // Act & Assess + await expect(wrappedHandler(event)).rejects.toThrow(SchemaValidationError); + }); + + it('should no-op when no schemas are provided', async () => { + // Prepare + const middleware = validation({}); + const wrappedHandler = middy(baseHandler).use(middleware); + const event = { anyKey: 'anyValue' }; + // Act + const result = await wrappedHandler(event); + // Assess + expect(result).toEqual(response); + }); +});