diff --git a/examples/validation-example/app.js b/examples/validation-example/app.js index bb2f7744..2e291179 100644 --- a/examples/validation-example/app.js +++ b/examples/validation-example/app.js @@ -1,9 +1,9 @@ const { orka } = require('../../build'); const w = orka({ - diamorphosis: { configFolder: './examples/simple-example' }, + diamorphosis: { configFolder: './examples/validation-example' }, routesPath: './examples/validation-example/routes.js', - logoPath: './examples/simple-example/logo.txt', + logoPath: './examples/validation-example/logo.txt', beforeStart: () => { const config = require('../simple-example/config'); console.log(`Going to start env: ${config.nodeEnv}`); diff --git a/examples/validation-example/config.js b/examples/validation-example/config.js new file mode 100644 index 00000000..34ec16b9 --- /dev/null +++ b/examples/validation-example/config.js @@ -0,0 +1,4 @@ +module.exports = { + nodeEnv: 'demo', + port: 3210, +}; diff --git a/examples/validation-example/logo.txt b/examples/validation-example/logo.txt new file mode 100644 index 00000000..a79d2f55 --- /dev/null +++ b/examples/validation-example/logo.txt @@ -0,0 +1,15 @@ +| + + O . + O ' ' + o ' . + o .' + __________.-' '...___ + .-' ### '''...__ + / a### ## ''--.._ ______ + '. # ######## ' .-' + '-._ ..**********#### ___...---'''\ ' + '-._ __________...---''' \ l + \ | '._| + \__; +— \ No newline at end of file diff --git a/examples/validation-example/routes.js b/examples/validation-example/routes.js index 03c6b442..664f407c 100644 --- a/examples/validation-example/routes.js +++ b/examples/validation-example/routes.js @@ -1,5 +1,5 @@ const { - middlewares: { validateQueryString, validateBody } + middlewares: { validateQueryString, validateBody, validateParams } } = require('../../build'); const Joi = require('joi'); @@ -10,9 +10,15 @@ const schema = Joi.object().keys({ keyStringArray: Joi.array().items(Joi.string()) }); +const paramsSchema = Joi.object().keys({ + id: Joi.number().required(), + name: Joi.string() +}); + module.exports = { get: { - '/testGet': [validateQueryString(schema), async (ctx, next) => (ctx.body = ctx.request.body)] + '/testGet': [validateQueryString(schema), async (ctx, next) => (ctx.body = ctx.request.body)], + '/testParams/:id/:name': [validateParams(paramsSchema), async (ctx, next) => (ctx.body = ctx.params)] }, post: { '/testPost': [validateBody(schema), async (ctx, next) => (ctx.body = ctx.request.body)] diff --git a/src/middlewares/index.ts b/src/middlewares/index.ts index 801537e2..534d9a7b 100644 --- a/src/middlewares/index.ts +++ b/src/middlewares/index.ts @@ -1,7 +1,7 @@ import health from './health'; import metrics from './metrics'; import datadogMatchRoutes from './datadog-match-routes'; -import { validateBody, validateQueryString } from './validate-params'; +import { validateBody, validateQueryString, validateParams } from './validate-params'; import growthbook from './growthbook'; -export { health, validateBody, validateQueryString, metrics, datadogMatchRoutes, growthbook }; +export { health, validateBody, validateQueryString, validateParams, metrics, datadogMatchRoutes, growthbook }; diff --git a/src/middlewares/validate-params.ts b/src/middlewares/validate-params.ts index 812befb0..860f4a53 100644 --- a/src/middlewares/validate-params.ts +++ b/src/middlewares/validate-params.ts @@ -32,3 +32,11 @@ export function validateQueryString(schema: Joi.ObjectSchema) { await next(); }; } + +export function validateParams(schema: Joi.ObjectSchema) { + return async (ctx: Koa.Context, next: any) => { + const result = validate(ctx.params, schema); + ctx.params = result.value; + await next(); + }; +} diff --git a/test/examples/validation-examples.test.ts b/test/examples/validation-examples.test.ts index 293b119d..06ea2b83 100644 --- a/test/examples/validation-examples.test.ts +++ b/test/examples/validation-examples.test.ts @@ -15,7 +15,7 @@ describe('Validation examples', () => { }); it('/testGet returns 200', async () => { - const { text } = await supertest('localhost:3000') + const { text } = await supertest('localhost:3210') .get('/testGet?keyNumber=2') .expect(200); @@ -23,7 +23,7 @@ describe('Validation examples', () => { }); it('/testGet returns 400', async () => { - const { text } = await supertest('localhost:3000') + const { text } = await supertest('localhost:3210') .get('/testGet?keyNumber=somestring') .expect(400); @@ -31,7 +31,7 @@ describe('Validation examples', () => { }); it('/testPost returns 200', async () => { - const { text } = await supertest('localhost:3000') + const { text } = await supertest('localhost:3210') .post('/testPost') .send({ keyNumber: 2 }) .expect(200); @@ -40,11 +40,35 @@ describe('Validation examples', () => { }); it('/testPost returns 400', async () => { - const { text } = await supertest('localhost:3000') + const { text } = await supertest('localhost:3210') .post('/testPost') .send({ keyNumber: 'somestring' }) .expect(400); text.should.equal(JSON.stringify({ keyNumber: '"keyNumber" must be a number' })); }); + + it('/testParams/:id/:name returns 200 with coerced params', async () => { + const { body } = await supertest('localhost:3210') + .get('/testParams/123/john') + .expect(200); + + body.should.eql({ id: 123, name: 'john' }); + }); + + it('/testParams/:id returns 200 with optional param missing', async () => { + const { body } = await supertest('localhost:3210') + .get('/testParams/456') + .expect(200); + + body.should.eql({ id: 456 }); + }); + + it('/testParams/:id returns 400 when id is not a number', async () => { + const { text } = await supertest('localhost:3210') + .get('/testParams/notanumber') + .expect(400); + + text.should.equal(JSON.stringify({ id: '"id" must be a number' })); + }); }); diff --git a/test/initializers/koa/validate-params.test.ts b/test/initializers/koa/validate-params.test.ts index 0d171379..fb33961f 100644 --- a/test/initializers/koa/validate-params.test.ts +++ b/test/initializers/koa/validate-params.test.ts @@ -1,7 +1,7 @@ import 'should'; import * as sinon from 'sinon'; import * as Joi from 'joi'; -import { validateBody, validateQueryString } from '../../../src/middlewares/validate-params'; +import { validateBody, validateQueryString, validateParams } from '../../../src/middlewares/validate-params'; const sandbox = sinon.createSandbox(); @@ -77,4 +77,34 @@ describe('validate-params', function() { ); next.called.should.be.equal(false); }); + + it('tests validate path params', async function() { + const next = sandbox.stub(); + const ctx = { + params: { + keyString: 'somestring', + keyNumber: '2', + keyBoolean: 'true', + keyStringArray: ['a', 'b'] + } + } as any; + + await validateParams(schema)(ctx, next); + next.calledOnce.should.be.equal(true); + ctx.params.should.eql({ + keyString: 'somestring', + keyNumber: 2, + keyBoolean: true, + keyStringArray: ['a', 'b'] + }); + }); + + it('tests validate path params with error', async function() { + const next = sandbox.stub(); + const ctx = { params: { keyNumber: 'somestring' } } as any; + await validateParams(schema)(ctx, next).should.be.rejectedWith( + JSON.stringify({ keyNumber: '"keyNumber" must be a number' }) + ); + next.called.should.be.equal(false); + }); });