├── circle.yml ├── lib ├── gen-router.js ├── index.js ├── matchers.js └── checkers.js ├── .gitignore ├── .eslintrc ├── LICENSE ├── package.json ├── test ├── readme-run.js └── checkers.js └── README.md /circle.yml: -------------------------------------------------------------------------------- 1 | machine: 2 | node: 3 | version: iojs-v1.3.0 4 | test: 5 | override: 6 | - npm test 7 | - npm run-script lint 8 | -------------------------------------------------------------------------------- /lib/gen-router.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var routington = require("routington"); 4 | 5 | /** 6 | * Generates the checking router from the swagger def 7 | * @param def {Swagger} The swagger complete definition 8 | * @return {routington} A router 9 | */ 10 | module.exports = function genRouter(def) { 11 | var paths = def.paths; 12 | var base = def.basePath || ""; 13 | 14 | var router = routington(); 15 | Object.keys(paths).forEach(function eachRoute(route) { 16 | var node = router.define(base + route.replace(/{([^}]*)}/g, ":$1"))[0]; 17 | node.def = paths[route]; 18 | }); 19 | return router; 20 | }; 21 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | 5 | # Runtime data 6 | pids 7 | *.pid 8 | *.seed 9 | 10 | # Directory for instrumented libs generated by jscoverage/JSCover 11 | lib-cov 12 | 13 | # Coverage directory used by tools like istanbul 14 | coverage 15 | 16 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 17 | .grunt 18 | 19 | # node-waf configuration 20 | .lock-wscript 21 | 22 | # Compiled binary addons (http://nodejs.org/api/addons.html) 23 | build/Release 24 | 25 | # Dependency directory 26 | # https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git 27 | node_modules 28 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "node": true, 4 | "mocha": true, 5 | "es6": true 6 | }, 7 | "ecmaFeatures": { 8 | "arrowFunctions": true, 9 | "binaryLiterals": true, 10 | "blockBindings": true, 11 | "classes": true, 12 | "defaultParams": true, 13 | "destructuring": true, 14 | "forOf": true, 15 | "generators": true, 16 | "modules": true, 17 | "objectLiteralComputedProperties": true, 18 | "objectLiteralDuplicateProperties": true, 19 | "objectLiteralShorthandMethods": true, 20 | "objectLiteralShorthandProperties": true, 21 | "octalLiterals": true, 22 | "regexUFlag": true, 23 | "regexYFlag": true, 24 | "spread": true, 25 | "superInFunctions": true, 26 | "templateStrings": true, 27 | "unicodeCodePointEscapes": true 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Robin Ricard 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "koa-swagger", 3 | "version": "0.2.9", 4 | "description": "request + response sanitation/validation against swagger specs", 5 | "main": "lib/", 6 | "scripts": { 7 | "test": "mocha --require co-mocha test/", 8 | "lint": "eslint lib/ test/" 9 | }, 10 | "repository": { 11 | "type": "git", 12 | "url": "https://github.com/rricard/koa-swagger.git" 13 | }, 14 | "keywords": [ 15 | "koa", 16 | "swagger", 17 | "api" 18 | ], 19 | "author": "Robin Ricard", 20 | "license": "MIT", 21 | "bugs": { 22 | "url": "https://github.com/rricard/koa-swagger/issues" 23 | }, 24 | "homepage": "https://github.com/rricard/koa-swagger", 25 | "dependencies": { 26 | "debug": "^2.1.3", 27 | "jayschema": "^0.3.1", 28 | "json-schema-deref-sync": "^0.1.1", 29 | "koa-bodyparser": "^1.5.0", 30 | "routington": "^1.0.3" 31 | }, 32 | "devDependencies": { 33 | "bluebird": "^2.9.24", 34 | "co-mocha": "^1.1.0", 35 | "co-supertest": "0.0.8", 36 | "eslint": "^0.19.0", 37 | "koa": "^1.2.4", 38 | "koa-route": "^2.4.1", 39 | "mocha": "^2.2.4", 40 | "supertest": "^0.15.0" 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /test/readme-run.js: -------------------------------------------------------------------------------- 1 | // We need to use evil in order to parse the js in the readme 2 | // DON'T USE THAT THING ANYWHERE ELSE! 3 | /*eslint-disable no-eval */ 4 | "use strict"; 5 | 6 | var fs = require("fs"); 7 | var path = require("path"); 8 | var request = require("supertest"); 9 | var Promise = require("bluebird"); 10 | Promise.promisifyAll(fs); 11 | Promise.promisifyAll(request.Test.prototype); 12 | 13 | describe("README's code example", function() { 14 | before(function* () { 15 | var readme = yield fs.readFileAsync(path.join(__dirname, "../README.md"), 16 | "utf8"); 17 | var snippet = readme.match(/```js([\s\S]*?)```/)[1]; 18 | this.app = eval(snippet.replace("koa-swagger", "../lib") + "app"); 19 | this.request = request(this.app.listen()); 20 | this.pt = "/api/hello/bob"; 21 | }); 22 | 23 | it("should 404 or 405 when accessing the wrong endpoint", function*() { 24 | yield Promise.all([ 25 | this.request 26 | .get("/api/nope") 27 | .expect(404) 28 | .endAsync(), 29 | this.request 30 | .post("/api/hello/bob") 31 | .query({chk: true}) 32 | .expect(405) 33 | .endAsync() 34 | ]); 35 | }); 36 | 37 | it("should 400 when providing wrong parameters", function*() { 38 | yield this.request 39 | .get("/api/hello/bob") 40 | .query({chuk: true}) 41 | .expect(400) 42 | .endAsync(); 43 | }); 44 | 45 | it("should 200 when providing the right parameters", function*() { 46 | yield Promise.all([ 47 | this.request 48 | .get("/api/hello/bob") 49 | .query({punctuation: "!"}) 50 | .expect(200) 51 | .expect(/Hello bob\!/) 52 | .endAsync(), 53 | this.request 54 | .get("/api/hello/bob") 55 | .query({punctuation: "."}) 56 | .expect(200) 57 | .expect(/Hello bob\./) 58 | .endAsync() 59 | ]); 60 | }); 61 | }); 62 | -------------------------------------------------------------------------------- /lib/index.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var deref = require("json-schema-deref-sync"); 4 | var Jay = require("jayschema"); 5 | 6 | var check = require("./checkers.js"); 7 | var match = require("./matchers.js"); 8 | var genRouter = require("./gen-router.js"); 9 | 10 | /** 11 | * Creates the generator from the swagger router & validator 12 | * @param router {routington} A swagger definition 13 | * @param validator {function(object, Schema)} JSON-Schema validator function 14 | * @returns {function*} The created middleware 15 | */ 16 | function createMiddleware(router, validator) { 17 | /** 18 | * Checks request and response against a swagger spec 19 | * Uses the usual koa context attributes 20 | * Uses the koa-bodyparser context attribute 21 | * Sets a new context attribute: {object} parameter 22 | */ 23 | return function* middleware(next) { 24 | // Routing matches 25 | try { 26 | var routeMatch = match.path(router, this.path); 27 | } catch(e) { 28 | // TODO: let an option before doing that, strict mode throws the error 29 | yield next; 30 | return false; 31 | } 32 | this.pathParam = routeMatch.param; // Add the path's params to the context 33 | var methodDef = match.method(routeMatch.def, this.method); 34 | 35 | 36 | // Parameters check & assign 37 | this.parameter = check.parameters(validator, 38 | methodDef.parameters || [], this); 39 | 40 | // Let the implementation happen 41 | yield next; 42 | 43 | // Response check 44 | var statusDef = match.status(methodDef.responses || {}, this.status); 45 | check.sentHeaders(validator, statusDef.headers || {}, this.sentHeaders); 46 | if(statusDef.schema) { 47 | check.body(validator, statusDef.schema, yield this.body); 48 | } 49 | }; 50 | } 51 | 52 | /** 53 | * Middleware factory function 54 | * Creates a new koa generator enforcing swagger api spec compliance 55 | * in a new context attribute: {object} parameter 56 | * @param def {Swagger} A swagger definition 57 | * @param options {{validator: function(object, Schema)}} Optional options dict. 58 | * @returns {function*} The created middleware 59 | */ 60 | module.exports = function koaSwaggerFactory(def, options) { 61 | var flatDef = deref(def); 62 | 63 | check.swaggerVersion(flatDef); 64 | 65 | var jay = new Jay(); 66 | var validator = options && options.validator ? 67 | options.validator : 68 | jay.validate.bind(jay); 69 | var router = genRouter(flatDef); 70 | 71 | // Return the middleware 72 | return createMiddleware(router, validator); 73 | }; 74 | -------------------------------------------------------------------------------- /lib/matchers.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var debug = require("debug")("swagger:match"); 4 | 5 | /** 6 | * An error thrown when something didn't matched 7 | * @param message {string} What didn't matched ? 8 | * @param status {number} HTTP status override 9 | * @constructor 10 | */ 11 | function MatchingError(message, status) { 12 | this.name = "MatchingError"; 13 | this.message = message || ""; 14 | this.status = status || 404; 15 | } 16 | MatchingError.prototype = Error.prototype; 17 | exports.MatchingError = MatchingError; 18 | 19 | /** 20 | * Matches a registered path with its definitions and its catched params 21 | * @param router {routington} The registered router 22 | * @param path {string} The HTTP path 23 | * @return {{def: Path, param: object}} The matched path definition and the 24 | * found params 25 | * @throws {MatchingError} A 404 error 26 | */ 27 | exports.path = function(router, path) { 28 | var match = router.match(path); 29 | if(!match) { 30 | throw new MatchingError(path + " not found"); 31 | } 32 | return { 33 | def: match.node.def, 34 | param: match.param 35 | }; 36 | }; 37 | 38 | /** 39 | * Matches a registered method with it's definition 40 | * @param def {Path} The path definition to search in 41 | * @param method {string} The HTTP method 42 | * @returns {Operation} The method definition or null if it didn't matched 43 | */ 44 | exports.method = function(def, method) { 45 | var m = method.toLowerCase(); 46 | if(!def || !def[m]) { 47 | throw new MatchingError(m + " unsupported", 405); 48 | } 49 | return def[m]; 50 | }; 51 | 52 | /** 53 | * Matches an attribute from a specific location from the context and retrieve 54 | * its value 55 | * @param name {string} The attribute's name 56 | * @param from {string} The location between "query", "header", "path", 57 | * "formData" or "body". 58 | * @param context {Context} The koa context 59 | * @return {*} The matched value 60 | */ 61 | exports.fromContext = function(name, from, context) { 62 | switch(from) { 63 | case "query": 64 | return (context.query || {})[name]; 65 | case "header": 66 | return (context.header || {})[name]; 67 | case "body": 68 | return (context.body || context.request.body || {}); 69 | case "path": 70 | return (context.pathParam || {})[name]; 71 | case "formData": 72 | return (context.body || context.request.body || {})[name] || 73 | (context.request.body.fields || {})[name] || 74 | (context.request.body.files || {})[name]; 75 | default: 76 | throw new MatchingError("Unsupported parameter origin: " + from, 500); 77 | } 78 | }; 79 | 80 | /** 81 | * Matches a return code with its corresponding definition 82 | * @param def {Responses} The responses definition 83 | * @param status {number} The status 84 | * @returns {Response} 85 | */ 86 | exports.status = function(def, status) { 87 | var cast = "" + status; 88 | if(!def[cast] && !def.default) { 89 | debug("Implementation Spec Violation: Unsupported response status"); 90 | debug("Unexpected " + status); 91 | throw new MatchingError("Unmatching response format", 500); 92 | } 93 | return def[cast] || def.default; 94 | }; 95 | -------------------------------------------------------------------------------- /test/checkers.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var assert = require("assert"); 4 | var Jay = require("jayschema"); 5 | 6 | var check = require("../lib/checkers.js"); 7 | 8 | describe("check", function() { 9 | before(function* setValidator() { 10 | var jay = new Jay(); 11 | this.validator = jay.validate.bind(jay); 12 | }); 13 | 14 | describe(".swaggerVersion", function() { 15 | it("should accept 2.0", function*() { 16 | check.swaggerVersion({swagger: "2.0"}); 17 | }); 18 | 19 | it("should refuse anything else", function*() { 20 | assert.throws(function() { 21 | check.swaggerVersion({swagger: "1.0"}); 22 | check.swaggerVersion({swagger: "3.0"}); 23 | check.swaggerVersion({swagger: "2.1"}); 24 | }); 25 | }); 26 | }); 27 | 28 | describe(".parameter", function() { 29 | before(function* ctxSet() { 30 | this.ctx = { 31 | query: { 32 | thing: "hello" 33 | }, 34 | header: { 35 | "x-test": "hello" 36 | }, 37 | body: { 38 | myInt: 1, 39 | myList: [ { myStr: "hello", myDouble: 2.1 } ] 40 | }, 41 | pathParam: { 42 | id: "hello" 43 | } 44 | }; 45 | }); 46 | 47 | it("should validate querystring-parameters", function*() { 48 | assert.equal("hello", check.parameter(this.validator, { 49 | name: "thing", 50 | in: "query", 51 | required: true, 52 | type: "string" 53 | }, this.ctx)); 54 | 55 | assert.equal("default", check.parameter(this.validator, { 56 | name: "thingy", 57 | in: "query", 58 | default: "default" 59 | }, this.ctx)); 60 | 61 | assert.equal("hello", check.parameter(this.validator, { 62 | name: "thing", 63 | in: "query", 64 | default: "default" 65 | }, this.ctx)); 66 | 67 | assert.throws(function() { 68 | check.parameter(this.validator, { 69 | name: "thingy", 70 | in: "query", 71 | required: true 72 | }, this.ctx); 73 | }); 74 | }); 75 | 76 | it("should validate headers", function*() { 77 | assert.equal("hello", check.parameter(this.validator, { 78 | name: "x-test", 79 | in: "header", 80 | required: true, 81 | type: "string" 82 | }, this.ctx)); 83 | 84 | assert.equal("default", check.parameter(this.validator, { 85 | name: "x-testy", 86 | in: "header", 87 | default: "default" 88 | }, this.ctx)); 89 | 90 | assert.equal("hello", check.parameter(this.validator, { 91 | name: "x-test", 92 | in: "header", 93 | default: "default" 94 | }, this.ctx)); 95 | 96 | assert.throws(function() { 97 | check.parameter(this.validator, { 98 | name: "x-testy", 99 | in: "header", 100 | required: true 101 | }, this.ctx); 102 | }); 103 | }); 104 | it("should validate body contents"); 105 | it("should validate path-parameters"); 106 | it("should validate form data"); 107 | }); 108 | }); 109 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # koa-swagger 2 | 3 | [![Circle CI](https://circleci.com/gh/rricard/koa-swagger/tree/master.svg?style=svg)](https://circleci.com/gh/rricard/koa-swagger/tree/master) 4 | 5 | request + response sanitation/validation against swagger specs 6 | 7 | ## Introduction 8 | 9 | Creating APIs is often boring and repetitive because you need to ensure 10 | that all the data flowing respects a certain format. Often we miss 11 | something and a security flaw or a source of incoherence appears. 12 | 13 | One solution is to test your API against a complete spec: either by testing 14 | each combination of parameters & results by hand resulting in even more 15 | boring tests than the actual implementation or by using a tool like 16 | [swagger](http://swagger.io) to simplify checks. The second solution 17 | is already a good step in the right direction. 18 | 19 | But that's not enough! 20 | 21 | If the spec can be used to check and sanitize requests and tail responses 22 | to make them compliant with the spec, we can get rid of a lot of boilerplate 23 | code. **koa-swagger** does that. 24 | 25 | ## Installation 26 | 27 | ```shell 28 | $ npm install --save koa-swagger 29 | ``` 30 | 31 | ## Dependencies 32 | 33 | koa-swagger does not provide anything else than what he has been created for: 34 | check and sanitize. That's why you'll need to **provide other middleware** 35 | before injecting koa-swagger. 36 | 37 | The choice of which middleware you put before is entirely up to you but all 38 | you need should be [bodyparser](https://github.com/koajs/bodyparser) (it 39 | depends on your API's needs actually): 40 | 41 | ```shell 42 | $ npm install --save koa-bodyparser 43 | ``` 44 | 45 | You'll also need to implement the spec! For that, use what 46 | you prefer, vanilla-koa or [route](https://github.com/koajs/route) for example: 47 | 48 | ```shell 49 | $ npm install --save koa-route 50 | ``` 51 | 52 | Here's a one-liner: 53 | 54 | ```shell 55 | $ npm i --save koa koa-bodyparser koa-swagger koa-route 56 | ``` 57 | 58 | ## Usage 59 | 60 | After that, it's really simple: 61 | 62 | - Put your swagger spec in a JS object 63 | - Add bodyparser as middleware 64 | - Add koa-swagger as middleware 65 | - Implement your routes 66 | 67 | Here's an example: 68 | 69 | ```js 70 | var SPEC = { 71 | swagger: "2.0", 72 | info: { 73 | title: "Hello API", 74 | version: "1.0.0" 75 | }, 76 | basePath: "/api", 77 | paths: { 78 | "/hello/{name}": { 79 | "get": { 80 | tags: [ "Hello" ], 81 | summary: "Says hello", 82 | parameters: [ 83 | { name: "name", 84 | in: "path", 85 | type: "string", 86 | "required": true, 87 | default: "World" }, 88 | { name: "punctuation", 89 | in: "query", 90 | type: "string", 91 | required: true } 92 | ], 93 | responses: { 94 | "200": { 95 | description: "Everything went well :)", 96 | schema: { $ref: "#/definitions/Message" } 97 | }, 98 | "400": { 99 | description: "Issue with the parameters" 100 | } 101 | } 102 | } 103 | } 104 | }, 105 | definitions: { 106 | Message: { 107 | required: [ "message" ], 108 | properties: { 109 | message: { 110 | type: "string" 111 | } 112 | } 113 | } 114 | } 115 | }; 116 | 117 | var app = require("koa")(); 118 | app.use(require("koa-bodyparser")()); 119 | app.use(require("koa-swagger")(SPEC)); 120 | 121 | var _ = require("koa-route"); 122 | app.use(_.get("/api/hello/:name", function* () { 123 | this.body = { 124 | message: "Hello " + this.parameter.name + this.parameter.punctuation 125 | }; 126 | })); 127 | ``` 128 | 129 | ## Missing features/bugs 130 | 131 | Here are some things I'm planning to do when time arises: 132 | 133 | - **MORE TESTS** The code has just been refactored for writing unit tests 134 | - Consumable/Producible MIME type support 135 | - Parameter types coercion for query parameters 136 | - Input date strings conversions 137 | - Output date objects conversions 138 | - Complex parameter objects tailing (ie. remove unspecified attrs recursively) 139 | - Output objects tailing (ie. remove unspecified attrs recursively) 140 | 141 | ## Contributing 142 | 143 | You have to write PRs if you want me to merge something into master. 144 | 145 | I need to accept your feature or fix (not a problem usually!) although 146 | the tests must pass (you should test new features) and the linter must pass too. 147 | 148 | Here's a command to check everything at once (the CI will complain otherwise): 149 | 150 | ```shell 151 | $ npm test && npm run lint && echo ok 152 | ``` 153 | 154 | If it outputs `ok`, that's usually a good sign! 155 | 156 | ## Author 157 | 158 | [Robin Ricard](http://rricard.me) 159 | 160 | ## License 161 | 162 | MIT 163 | -------------------------------------------------------------------------------- /lib/checkers.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var debug = require("debug")("swagger:check"); 4 | 5 | var match = require("./matchers.js"); 6 | 7 | /** 8 | * An error thrown when validating parameters 9 | * @param message {string} What failed ? 10 | * @param status {number} HTTP status override 11 | * @constructor 12 | */ 13 | function ValidationError(message, status) { 14 | this.name = "ValidationError"; 15 | this.message = message || ""; 16 | this.status = status || 400; 17 | } 18 | ValidationError.prototype = Error.prototype; 19 | exports.ValidationError = ValidationError; 20 | 21 | /** 22 | * Defines if the spec version is supported by the middleware 23 | * @param def {Swagger} The swagger complete definition 24 | * @throws {ValidationError} If the version is not supported 25 | */ 26 | exports.swaggerVersion = function(def) { 27 | if(def.swagger !== "2.0") { 28 | throw new ValidationError("Swagger " + def.swagger + 29 | "is not supported by this middleware."); 30 | } 31 | }; 32 | 33 | /** 34 | * Check if the context carries the parameter correctly 35 | * @param validator {function(object, Schema)} JSON-Schema validator function 36 | * @param def {Parameter} The parameter's definition 37 | * @param context {Context} A koa context 38 | * @return {*} The cleaned value 39 | * @throws {ValidationError} A possible validation error 40 | */ 41 | function checkParameter(validator, def, context) { 42 | var value = match.fromContext(def.name, def.in, context); 43 | 44 | // Check requirement 45 | if(def.required && !value) { 46 | throw new ValidationError(def.name + " is required"); 47 | } else if(!value) { 48 | return def.default; 49 | } 50 | 51 | // Select the right schema according to the spec 52 | var schema; 53 | if(def.in === "body") { 54 | schema = def.schema; 55 | // TODO: clean and sanitize recursively 56 | } else { 57 | // TODO: coerce other types 58 | if(def.type === "integer") { 59 | value = parseInt(value); 60 | } else if(def.type === "number") { 61 | value = parseFloat(value); 62 | } else if(def.type === "file") { 63 | def.type = "object"; 64 | } 65 | if (def.in === "query" && def.type === "array") { 66 | if(!def.collectionFormat || def.collectionFormat === "csv") { 67 | value = value.split(","); 68 | } else if (def.collectionFormat === "tsv") { 69 | value = value.split("\t"); 70 | } else if (def.collectionFormat === "ssv") { 71 | value = value.split(" "); 72 | } else if (def.collectionFormat === "psv") { 73 | value = value.split("|"); 74 | } else if (def.collectionFormat === "multi") { 75 | throw new ValidationError("multi collectionFormat query parameters currently unsupported"); 76 | } else { 77 | throw new ValidationError("unknown collectionFormat " + def.collectionFormat); 78 | } 79 | } 80 | 81 | schema = def; 82 | } 83 | 84 | var err = validator(value, schema); 85 | if(err.length > 0) { 86 | throw new ValidationError(def.name + " has an invalid format: " + 87 | JSON.stringify(err)); 88 | } 89 | 90 | return value; 91 | } 92 | exports.parameter = checkParameter; 93 | 94 | /** 95 | * Check if the context carries the parameters correctly 96 | * @param validator {function(object, Schema)} JSON-Schema validator function 97 | * @param defs {[Parameter]} The list of parameters definitions 98 | * @param context {Context} A koa context 99 | * @return {object} The checked parameters in a dict 100 | * @throws {ValidationError} 101 | */ 102 | exports.parameters = function(validator, defs, context) { 103 | var errorMessages = []; 104 | var parameterDict = {}; 105 | defs.forEach(function eachDef(def) { 106 | try { 107 | parameterDict[def.name] = checkParameter(validator, def, context); 108 | } catch(e) { 109 | if(e.name !== "ValidationError") { 110 | throw e; 111 | } 112 | errorMessages.push(e.message); 113 | } 114 | }); 115 | if(errorMessages.length > 0) { 116 | throw new ValidationError(errorMessages.join(", ")); 117 | } 118 | return parameterDict; 119 | }; 120 | 121 | /** 122 | * Checks the response's body and logs the exact violation in swagger:check 123 | * @param validator {function(object, Schema)} JSON-Schema validator function 124 | * @param schema {Schema} The JSON-schema to test the body against 125 | * @param body {*} The body to send back 126 | * @throws {ValidationError} When the body does not respect the schema 127 | */ 128 | exports.body = function(validator, schema, body) { 129 | // TODO: clean and sanitize recursively 130 | var err = validator(body, schema); 131 | if(err.length > 0) { 132 | debug("Implementation Spec Violation: Unmatching response format"); 133 | debug(err); 134 | throw new ValidationError("Unmatching response format", 500); 135 | } 136 | }; 137 | 138 | /** 139 | * Checks a sent header and logs the exact violation in swagger:check 140 | * @param validator {function(object, Schema)} JSON-Schema validator function 141 | * @param def {Header} The header definition 142 | * @param name {string} The header's name 143 | * @param value {*} The header value 144 | * @throws {ValidationError} When the header does not respect the schema 145 | */ 146 | function checkSentHeader(validator, def, name, value) { 147 | if(!value && def.default) { 148 | return def.def.default; 149 | } 150 | var err = validator(value, def.schema); 151 | if(err) { 152 | debug("Implementation Spec Violation: Unmatching sent header format: " + 153 | name); 154 | debug(err); 155 | throw new ValidationError("Unmatching response format", 500); 156 | } 157 | } 158 | exports.sentHeader = checkSentHeader; 159 | 160 | /** 161 | * 162 | * @param validator {function(object, Schema)} JSON-Schema validator function 163 | * @param defs {Headers} The header's definitions 164 | * @param sentHeaders {object} The sent headers 165 | * @throws {ValidationError} When the headers does not respect the schema 166 | */ 167 | exports.sentHeaders = function(validator, defs, sentHeaders) { 168 | var errored = false; 169 | Object.keys(defs).forEach(function eachSentHeader(name) { 170 | try { 171 | checkSentHeader(validator, defs[name], name, sentHeaders[name]); 172 | } catch(e) { 173 | errored = true; 174 | } 175 | }); 176 | if(errored) { 177 | throw new ValidationError("Unmatching response format", 500); 178 | } 179 | }; 180 | --------------------------------------------------------------------------------