├── test ├── fixtures │ ├── files │ │ ├── 0MB.jpg │ │ ├── blank.yaml │ │ ├── 1MB.jpg │ │ ├── 5MB.jpg │ │ ├── 6MB.jpg │ │ ├── File.pdf │ │ ├── swagger-2 │ │ │ ├── external-refs │ │ │ │ ├── dir │ │ │ │ │ └── subdir │ │ │ │ │ │ └── text.txt │ │ │ │ ├── error.json │ │ │ │ └── external-refs.yaml │ │ │ └── pet │ │ └── index.js │ ├── config.js │ ├── specs.js │ └── helper.js └── specs │ ├── json-schema │ ├── constructor.spec.js │ └── parse │ │ ├── helper.js │ │ ├── parse-boolean.spec.js │ │ ├── parse-byte.spec.js │ │ ├── parse-object.spec.js │ │ ├── parse-number.spec.js │ │ └── parse-integer.spec.js │ ├── mock │ ├── helper.js │ └── response.spec.js │ ├── util.spec.js │ └── data-store │ └── file-data-store.spec.js ├── 404.md ├── dist ├── index.js └── package.json ├── docs ├── img │ ├── postman.png │ └── samples.png ├── middleware │ ├── README.md │ ├── files.md │ ├── metadata.md │ ├── CORS.md │ └── validateRequest.md ├── exports │ ├── README.md │ ├── MemoryDataStore.md │ ├── FileDataStore.md │ ├── Middleware.md │ ├── createMiddleware.md │ ├── DataStore.md │ └── Resource.md ├── README.md └── walkthroughs │ ├── README.md │ ├── javascript.md │ └── running.md ├── samples ├── node_modules │ └── swagger-express-middleware │ │ └── index.js ├── README.md ├── sample1.js └── sample2.js ├── .nycrc.yml ├── .mocharc.yml ├── lib ├── data-store │ ├── buffer-polyfill.js │ ├── memory-data-store.js │ └── file-data-store.js ├── context.js ├── index.js ├── mock │ ├── query-resource.js │ ├── semantic-request.js │ ├── edit-resource.js │ ├── query-collection.js │ └── semantic-response.js ├── request-parser.js ├── path-parser.js ├── file-server.js ├── param-parser.js ├── cors.js ├── middleware.js └── request-metadata.js ├── .eslintrc.yml ├── .gitignore ├── _config.yml ├── .editorconfig ├── .gitattributes ├── .vscode ├── tasks.json └── launch.json ├── LICENSE ├── package.json ├── .github └── workflows │ └── CI-CD.yaml ├── CHANGELOG.md └── README.md /test/fixtures/files/0MB.jpg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/fixtures/files/blank.yaml: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /404.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: 404 3 | --- 4 | -------------------------------------------------------------------------------- /dist/index.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | module.exports = require("@apidevtools/swagger-express-middleware"); 3 | -------------------------------------------------------------------------------- /docs/img/postman.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/APIDevTools/swagger-express-middleware/HEAD/docs/img/postman.png -------------------------------------------------------------------------------- /docs/img/samples.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/APIDevTools/swagger-express-middleware/HEAD/docs/img/samples.png -------------------------------------------------------------------------------- /samples/node_modules/swagger-express-middleware/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = require('../../../'); 4 | -------------------------------------------------------------------------------- /test/fixtures/files/1MB.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/APIDevTools/swagger-express-middleware/HEAD/test/fixtures/files/1MB.jpg -------------------------------------------------------------------------------- /test/fixtures/files/5MB.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/APIDevTools/swagger-express-middleware/HEAD/test/fixtures/files/5MB.jpg -------------------------------------------------------------------------------- /test/fixtures/files/6MB.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/APIDevTools/swagger-express-middleware/HEAD/test/fixtures/files/6MB.jpg -------------------------------------------------------------------------------- /test/fixtures/files/File.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/APIDevTools/swagger-express-middleware/HEAD/test/fixtures/files/File.pdf -------------------------------------------------------------------------------- /test/fixtures/files/swagger-2/external-refs/dir/subdir/text.txt: -------------------------------------------------------------------------------- 1 | { 2 | "This": is just: a plain text file. 3 | 4 | Not: valid 5 | - JSON 6 | - or 7 | - YAML 8 | } 9 | -------------------------------------------------------------------------------- /.nycrc.yml: -------------------------------------------------------------------------------- 1 | # NYC config 2 | # https://github.com/istanbuljs/nyc#configuration-files 3 | 4 | extension: 5 | - .js 6 | - .ts 7 | 8 | reporter: 9 | - text 10 | - lcov 11 | -------------------------------------------------------------------------------- /test/fixtures/files/swagger-2/external-refs/error.json: -------------------------------------------------------------------------------- 1 | { 2 | "properties": { 3 | "message": { 4 | "type": "string" 5 | }, 6 | "code": { 7 | "type": "integer" 8 | } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /test/fixtures/files/swagger-2/pet: -------------------------------------------------------------------------------- 1 | { 2 | "properties": { 3 | "name": { 4 | "type": "string" 5 | }, 6 | "type": { 7 | "type": "string", 8 | "enum": ["cat", "dog", "bird"] 9 | } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /docs/middleware/README.md: -------------------------------------------------------------------------------- 1 | Middleware 2 | ============================ 3 | * [Mock middleware](mock.md) 4 | * [Metadata middleware](metadata.md) 5 | * [Parse Request middleware](parseRequest.md) 6 | * [Validate Request middleware](validateRequest.md) 7 | * [CORS middleware](CORS.md) 8 | * [Files middleware](files.md) 9 | -------------------------------------------------------------------------------- /.mocharc.yml: -------------------------------------------------------------------------------- 1 | # Mocha options 2 | # https://mochajs.org/#configuring-mocha-nodejs 3 | # https://github.com/mochajs/mocha/blob/master/example/config/.mocharc.yml 4 | 5 | spec: 6 | # Test fixtures 7 | - test/fixtures/**/*.js 8 | 9 | # Test specs 10 | - test/specs/**/*.spec.js 11 | 12 | bail: true 13 | recursive: true 14 | retries: 2 15 | -------------------------------------------------------------------------------- /lib/data-store/buffer-polyfill.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | // 0.10.x versions of Node serialize Buffers as arrays instead of objects 4 | if (process.version.substring(0, 6) === "v0.10.") { 5 | Buffer.prototype.toJSON = function () { 6 | return { 7 | type: "Buffer", 8 | data: Array.prototype.slice.call(this, 0) 9 | }; 10 | }; 11 | } 12 | -------------------------------------------------------------------------------- /docs/exports/README.md: -------------------------------------------------------------------------------- 1 | The Swagger Express Middleware API 2 | ===================================== 3 | * [`createMiddleware` function](exports/createMiddleware.md) 4 | * [`Middleware` class](exports/Middleware.md) 5 | * [`DataStore` abstract class](exports/DataStore.md) 6 | * [`MemoryDataStore` class](exports/MemoryDataStore.md) 7 | * [`FileDataStore` class](exports/FileDataStore.md) 8 | * [`Resource` class](exports/Resource.md) 9 | -------------------------------------------------------------------------------- /.eslintrc.yml: -------------------------------------------------------------------------------- 1 | # ESLint config 2 | # http://eslint.org/docs/user-guide/configuring 3 | # https://jstools.dev/eslint-config/ 4 | 5 | root: true 6 | extends: "@jsdevtools" 7 | 8 | env: 9 | node: true 10 | 11 | rules: 12 | # Prevent warnings about req, res, next params in middleware 13 | no-unused-vars: off 14 | 15 | # Prevent warnings about the CORS() middleware and express.Router() 16 | new-cap: off 17 | 18 | # Allow shadowing variables like err and req, res, next 19 | no-shadow: off 20 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Git ignore 2 | # https://git-scm.com/docs/gitignore 3 | 4 | # Miscellaneous 5 | *~ 6 | *# 7 | .DS_STORE 8 | Thumbs.db 9 | .netbeans 10 | nbproject 11 | .node_history 12 | 13 | # IDEs & Text Editors 14 | .idea 15 | .sublime-* 16 | .vscode/settings.json 17 | .netbeans 18 | nbproject 19 | 20 | # Temporary files 21 | .tmp 22 | .temp 23 | .grunt 24 | .lock-wscript 25 | 26 | # Logs 27 | /logs 28 | *.log 29 | 30 | # Runtime data 31 | pids 32 | *.pid 33 | *.seed 34 | 35 | # Dependencies 36 | node_modules 37 | 38 | # Test output 39 | /.nyc_output 40 | /coverage 41 | -------------------------------------------------------------------------------- /_config.yml: -------------------------------------------------------------------------------- 1 | remote_theme: APIDevTools/gh-pages-theme 2 | 3 | title: Swagger Express Middleware 4 | logo: https://apitools.dev/img/logos/logo.png 5 | 6 | author: 7 | twitter: APIDevTools 8 | 9 | google_analytics: UA-68102273-2 10 | 11 | twitter: 12 | username: APIDevTools 13 | card: summary 14 | 15 | defaults: 16 | - scope: 17 | path: "" 18 | values: 19 | image: https://apitools.dev/img/logos/card.png 20 | 21 | - scope: 22 | path: "test/**/*" 23 | values: 24 | sitemap: false 25 | 26 | - scope: 27 | path: "samples/index.html" 28 | values: 29 | sitemap: false 30 | 31 | plugins: 32 | - jekyll-sitemap 33 | -------------------------------------------------------------------------------- /test/fixtures/config.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Test environment config 3 | */ 4 | "use strict"; 5 | 6 | // Chai plugins 7 | const chai = require("chai"); 8 | chai.use(require("chai-datetime")); 9 | 10 | // Disable warnings, which clutter the test output 11 | process.env.WARN = "off"; 12 | 13 | // Increase test timeouts. 14 | beforeEach(function () { 15 | // Some of our tests simulate uploading several large files, which takes time. 16 | // A lot of this time is garbage-collection between requests 17 | this.currentTest.timeout(15000); 18 | 19 | // Almost all of our tests take ~200 ms, since they're simulating full round-trips 20 | this.currentTest.slow(2000); 21 | }); 22 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # Editor config 2 | # http://EditorConfig.org 3 | 4 | # This EditorConfig overrides any parent EditorConfigs 5 | root = true 6 | 7 | # Default rules applied to all file types 8 | [*] 9 | 10 | # No trailing spaces, newline at EOF 11 | charset = utf-8 12 | trim_trailing_whitespace = true 13 | insert_final_newline = true 14 | end_of_line = lf 15 | 16 | # 2 space indentation 17 | indent_style = space 18 | indent_size = 2 19 | 20 | # JavaScript-specific settings 21 | [*.{js,ts}] 22 | quote_type = double 23 | continuation_indent_size = 2 24 | curly_bracket_next_line = false 25 | indent_brace_style = BSD 26 | spaces_around_operators = true 27 | spaces_around_brackets = none 28 | -------------------------------------------------------------------------------- /docs/README.md: -------------------------------------------------------------------------------- 1 | Middleware 2 | ============================ 3 | * [Mock middleware](middleware/mock.md) 4 | * [Metadata middleware](middleware/metadata.md) 5 | * [Parse Request middleware](middleware/parseRequest.md) 6 | * [Validate Request middleware](middleware/validateRequest.md) 7 | * [CORS middleware](middleware/CORS.md) 8 | * [Files middleware](middleware/files.md) 9 | 10 | 11 | API 12 | ============================ 13 | * [`createMiddleware` function](exports/createMiddleware.md) 14 | * [`Middleware` class](exports/Middleware.md) 15 | * [`DataStore` abstract class](exports/DataStore.md) 16 | * [`MemoryDataStore` class](exports/MemoryDataStore.md) 17 | * [`FileDataStore` class](exports/FileDataStore.md) 18 | * [`Resource` class](exports/Resource.md) 19 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Git attributes 2 | # https://git-scm.com/docs/gitattributes 3 | # https://git-scm.com/book/en/v2/Customizing-Git-Git-Attributes 4 | 5 | # Normalize line endings for all files that git determines to be text. 6 | # https://git-scm.com/docs/gitattributes#gitattributes-Settostringvalueauto 7 | * text=auto 8 | 9 | # Normalize line endings to LF on checkin, and do NOT convert to CRLF when checking-out on Windows. 10 | # https://git-scm.com/docs/gitattributes#gitattributes-Settostringvaluelf 11 | *.txt text eol=lf 12 | *.html text eol=lf 13 | *.md text eol=lf 14 | *.css text eol=lf 15 | *.scss text eol=lf 16 | *.map text eol=lf 17 | *.js text eol=lf 18 | *.jsx text eol=lf 19 | *.ts text eol=lf 20 | *.tsx text eol=lf 21 | *.json text eol=lf 22 | *.yml text eol=lf 23 | *.yaml text eol=lf 24 | *.xml text eol=lf 25 | *.svg text eol=lf 26 | -------------------------------------------------------------------------------- /dist/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "swagger-express-middleware", 3 | "version": "X.X.X", 4 | "description": "Swagger middleware and mocks for Express", 5 | "keywords": [ 6 | "express", 7 | "swagger", 8 | "middleware", 9 | "mock", 10 | "fake", 11 | "stub", 12 | "rest", 13 | "api", 14 | "json" 15 | ], 16 | "author": { 17 | "name": "James Messinger", 18 | "url": "https://jamesmessinger.com" 19 | }, 20 | "homepage": "https://apitools.dev/swagger-express-middleware/", 21 | "repository": { 22 | "type": "git", 23 | "url": "https://github.com/APIDevTools/swagger-express-middleware.git" 24 | }, 25 | "license": "MIT", 26 | "main": "index.js", 27 | "files": [ 28 | "index.js" 29 | ], 30 | "engines": { 31 | "node": ">=10" 32 | }, 33 | "dependencies": { 34 | "@apidevtools/swagger-express-middleware": "X.X.X" 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | // Available variables which can be used inside of strings. 2 | // ${workspaceRoot}: the root folder of the team 3 | // ${file}: the current opened file 4 | // ${fileBasename}: the current opened file's basename 5 | // ${fileDirname}: the current opened file's dirname 6 | // ${fileExtname}: the current opened file's extension 7 | // ${cwd}: the current working directory of the spawned process 8 | 9 | { 10 | "version": "0.1.0", 11 | "command": "npm", 12 | "isShellCommand": true, 13 | "suppressTaskName": true, 14 | "tasks": [ 15 | { 16 | "taskName": "build", 17 | "args": ["run", "lint"], 18 | "showOutput": "always", 19 | "problemMatcher": "$eslint-stylish", 20 | "isBuildCommand": true 21 | }, 22 | { 23 | "taskName": "test", 24 | "args": ["run", "test"], 25 | "showOutput": "always", 26 | "isTestCommand": true 27 | } 28 | ] 29 | } 30 | -------------------------------------------------------------------------------- /test/fixtures/files/swagger-2/external-refs/external-refs.yaml: -------------------------------------------------------------------------------- 1 | swagger: "2.0" 2 | info: 3 | version: 1.0.0 4 | title: external refs 5 | description: This file includes $refs to external files 6 | basePath: /api/v2 7 | paths: 8 | /pets: 9 | post: 10 | responses: 11 | 200: 12 | description: references a file with no extension (and in a parent directory) 13 | schema: 14 | $ref: ../pet 15 | 300: 16 | description: references a plain-text file 17 | schema: 18 | type: string 19 | example: 20 | $ref: dir/subdir/text.txt 21 | 400: 22 | description: references a binary file 23 | schema: 24 | type: file 25 | example: 26 | $ref: ../../1MB.jpg 27 | default: 28 | description: references the "error.json" file 29 | schema: 30 | $ref: error.json 31 | -------------------------------------------------------------------------------- /samples/README.md: -------------------------------------------------------------------------------- 1 | Samples & Walkthroughs 2 | ============================ 3 | 4 | ![Screenshot](https://apitools.dev/swagger-express-middleware/docs/img/samples.png) 5 | 6 | Sample 1 7 | -------------------------- 8 | This sample demonstrates the most simplistic usage of Swagger Express Middleware. It simply creates a new Express Application and adds all of the Swagger middleware without changing any options, and without adding any custom middleware. 9 | 10 | * [Source Code](https://github.com/APIDevTools/swagger-express-middleware/blob/master/samples/sample1.js) 11 | * [Walkthrough](https://apitools.dev/swagger-express-middleware/docs/walkthroughs/running.html) 12 | 13 | 14 | 15 | Sample 2 16 | -------------------------- 17 | This sample demonstrates a few more advanced features of Swagger Express Middleware, such as setting a few options, initializing the mock data store, and adding custom middleware logic. 18 | 19 | * [Source Code](https://github.com/APIDevTools/swagger-express-middleware/blob/master/samples/sample2.js) 20 | * [Walkthrough](https://apitools.dev/swagger-express-middleware/docs/walkthroughs/walkthrough2.html) 21 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 James Messinger 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 13 | all 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 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /lib/context.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | module.exports = MiddlewareContext; 4 | 5 | const _ = require("lodash"); 6 | const events = require("events"); 7 | 8 | // Inheritance 9 | _.extend(MiddlewareContext.prototype, events.EventEmitter.prototype); 10 | 11 | /** 12 | * A context object that is shared by all middleware functions of a {@link Middleware} instance. 13 | * 14 | * @extends EventEmitter 15 | * @constructor 16 | */ 17 | function MiddlewareContext (router) { 18 | events.EventEmitter.call(this); 19 | 20 | /** 21 | * Express routing options (e.g. `caseSensitive`, `strict`). 22 | * If set to an Express Application or Router, then its routing settings will be used. 23 | * @type {express#Router} 24 | */ 25 | this.router = router || {}; 26 | 27 | /** 28 | * The parsed Swagger API 29 | * @type {SwaggerObject} 30 | */ 31 | this.api = null; 32 | 33 | /** 34 | * The {@link SwaggerParser} instance that was used to parse the API. 35 | * @type {SwaggerParser} 36 | */ 37 | this.parser = null; 38 | 39 | /** 40 | * If the Swagger API contains errors, this will be set 41 | * @type {Error} 42 | */ 43 | this.error = null; 44 | } 45 | -------------------------------------------------------------------------------- /lib/data-store/memory-data-store.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | module.exports = MemoryDataStore; 4 | 5 | const DataStore = require("./index"); 6 | const Resource = require("./resource"); 7 | 8 | // Inheritance 9 | MemoryDataStore.prototype = Object.create(DataStore.prototype); 10 | MemoryDataStore.prototype.constructor = MemoryDataStore; 11 | 12 | /** 13 | * An in-memory data store for REST resources. 14 | * 15 | * @constructor 16 | * @extends DataStore 17 | */ 18 | function MemoryDataStore () { 19 | DataStore.call(this); 20 | 21 | /** 22 | * This implementation of DataStore uses an in-memory array. 23 | * @type {Resource[]} 24 | * @private 25 | */ 26 | this.__resourceStore = []; 27 | } 28 | 29 | /** 30 | * Overrides {@link DataStore#__openDataStore} to return data from an in-memory array. 31 | * 32 | * @protected 33 | */ 34 | MemoryDataStore.prototype.__openDataStore = function (collection, callback) { 35 | setImmediate(callback, null, this.__resourceStore); 36 | }; 37 | 38 | /** 39 | * Overrides {@link DataStore#__saveDataStore} to store data in an in-memory array. 40 | * 41 | * @protected 42 | */ 43 | MemoryDataStore.prototype.__saveDataStore = function (collection, resources, callback) { 44 | try { 45 | this.__resourceStore = Resource.parse(resources); 46 | setImmediate(callback); 47 | } 48 | catch (e) { 49 | callback(e); 50 | } 51 | }; 52 | -------------------------------------------------------------------------------- /docs/exports/MemoryDataStore.md: -------------------------------------------------------------------------------- 1 | The `MemoryDataStore` class 2 | ============================ 3 | This is the default data store that's used by the [Mock middleware](../middleware/mock.md). It simply stores data in an array in memory, which means the data only lasts as long as your app is running. When your app restarts, none of the data from the previous run will be there anymore. This may be exactly what you want if you're using the Mock middleware for a quick demo or to test-out your API as you create it. But if you need your data to stick around even after your app shuts down, then you might want to check out the [FileDataStore](FileDataStore.md) class instead. 4 | 5 | > **NOTE:** All `DataStore` classes serialize the data when saved and deserialize it when retrieved. That means that only the _actual data_ is saved/retrieved, not object references, classes, prototypes, methods, etc. So don't expect the data you retrieve to be the same object reference as the data you saved, even though the data is being kept in memory. 6 | 7 | 8 | Constructor 9 | ----------------------- 10 | ### `MemoryDataStore()` 11 | This is as simple as it gets. The constructor doesn't take any parameters. 12 | 13 | 14 | Methods 15 | ----------------------- 16 | The `MemoryDataStore` class inherits from the [DataStore](DataStore.md) class, so it has all the same methods for retrieving, saving, and deleting data. 17 | -------------------------------------------------------------------------------- /samples/sample1.js: -------------------------------------------------------------------------------- 1 | /************************************************************************************************** 2 | * This sample demonstrates the most simplistic usage of Swagger Express Middleware. 3 | * It simply creates a new Express Application and adds all of the Swagger middleware 4 | * without changing any options, and without adding any custom middleware. 5 | **************************************************************************************************/ 6 | 'use strict'; 7 | 8 | const createMiddleware = require('@apidevtools/swagger-express-middleware'); 9 | const path = require('path'); 10 | const express = require('express'); 11 | 12 | // Create an Express app 13 | const app = express(); 14 | 15 | // Initialize Swagger Express Middleware with our Swagger file 16 | let swaggerFile = path.join(__dirname, 'PetStore.yaml'); 17 | createMiddleware(swaggerFile, app, (err, middleware) => { 18 | 19 | // Add all the Swagger Express Middleware, or just the ones you need. 20 | // NOTE: Some of these accept optional options (omitted here for brevity) 21 | app.use( 22 | middleware.metadata(), 23 | middleware.CORS(), 24 | middleware.files(), 25 | middleware.parseRequest(), 26 | middleware.validateRequest(), 27 | middleware.mock() 28 | ); 29 | 30 | // Start the app 31 | app.listen(8000, () => { 32 | console.log('The Swagger Pet Store is now running at http://localhost:8000'); 33 | }); 34 | }); 35 | -------------------------------------------------------------------------------- /docs/walkthroughs/README.md: -------------------------------------------------------------------------------- 1 | Samples & Walkthroughs 2 | ============================ 3 | 4 | ![Screenshot](../img/samples.png) 5 | 6 | Sample 1 7 | -------------------------- 8 | This sample demonstrates the most simplistic usage of Swagger Express Middleware. It simply creates a new Express Application and adds all of the Swagger middleware without changing any options, and without adding any custom middleware. 9 | 10 | * [Source Code](https://github.com/APIDevTools/swagger-express-middleware/blob/master/samples/sample1.js) 11 | * [Walkthrough](running.md) 12 | * [Running the sample](running.md) 13 | * [JavaScript Walkthrough](javascript.md) 14 | * [YAML Walkthrough](yaml.md) 15 | 16 | 17 | Sample 2 18 | -------------------------- 19 | This sample demonstrates a few more advanced features of Swagger Express Middleware, such as setting a few options, initializing the mock data store, and adding custom middleware logic. 20 | 21 | * [Source Code](https://github.com/APIDevTools/swagger-express-middleware/blob/master/samples/sample2.js) 22 | * [Walkthrough](walkthrough2.md) 23 | * [Running the Sample](walkthrough2.md#running-the-sample) 24 | * [Alternate Syntax](walkthrough2.md#alternate-syntax) 25 | * [Pre-Populating Mock Data](walkthrough2.md#pre-populated-data) 26 | * [Case-Sensitive and Strict Routing](walkthrough2.md#case-sensitive-and-strict-routing) 27 | * [Setting Middleware Options](walkthrough2.md#customized-middleware-options) 28 | * [Custom Middleware](walkthrough2.md#custom-middleware) 29 | -------------------------------------------------------------------------------- /test/specs/json-schema/constructor.spec.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-new */ 2 | "use strict"; 3 | 4 | const expect = require("chai").expect; 5 | const JsonSchema = require("../../../lib/helpers/json-schema"); 6 | 7 | describe("JSON Schema constructor", () => { 8 | 9 | it("should throw an error if the schema is missing", () => { 10 | function createMissingSchema () { 11 | new JsonSchema(); 12 | } 13 | 14 | expect(createMissingSchema).to.throw("Missing JSON schema"); 15 | }); 16 | 17 | it("should throw an error if the schema is null", () => { 18 | function createNullSchema () { 19 | new JsonSchema(null); 20 | } 21 | 22 | expect(createNullSchema).to.throw("Missing JSON schema"); 23 | }); 24 | 25 | it("should not throw an error if the schema is empty", () => { 26 | function createEmptySchema () { 27 | new JsonSchema({}); 28 | } 29 | 30 | expect(createEmptySchema).not.to.throw(); 31 | }); 32 | 33 | it("should throw an error if the schema type is unsupported", () => { 34 | function unsupportedType () { 35 | new JsonSchema({ type: "foobar" }); 36 | } 37 | 38 | expect(unsupportedType).to.throw("Invalid JSON schema type: foobar"); 39 | }); 40 | 41 | it("should not throw an error if the schema type is missing", () => { 42 | function missingType () { 43 | new JsonSchema({ 44 | properties: { 45 | name: { 46 | type: "string" 47 | } 48 | } 49 | }); 50 | } 51 | 52 | expect(missingType).not.to.throw(); 53 | }); 54 | }); 55 | -------------------------------------------------------------------------------- /test/specs/json-schema/parse/helper.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const swagger = require("../../../../"); 4 | const specs = require("../../../fixtures/specs"); 5 | const helper = require("../../../fixtures/helper"); 6 | const _ = require("lodash"); 7 | 8 | _.extend(exports, helper); 9 | 10 | /** 11 | * Helper function to test {@link JsonSchema#parse}. 12 | * 13 | * @param {object} schema - The JSON Schema definition 14 | * @param {*} value - The value to pass for the parameter, or undefined to leave it unset 15 | * @param {function} done - The test's "done" callback 16 | * @returns {express} 17 | */ 18 | exports.parse = function (schema, value, done) { 19 | // Create a Swagger API that uses this schema 20 | let api = _.cloneDeep(specs.swagger2.samples.petStore); 21 | api.paths["/test"] = { 22 | post: { 23 | parameters: [_.extend(schema, { name: "Test", in: "header" })], 24 | responses: { 25 | default: { description: "Parameter parsing test" } 26 | } 27 | } 28 | }; 29 | 30 | let middleware = swagger(api, (err) => { 31 | if (err) { 32 | done(err); 33 | } 34 | 35 | // Make a request to the Swagger API, passing the test value 36 | let supertest = helper.supertest(express).post("/api/test"); 37 | if (value !== undefined) { 38 | supertest.set(schema.name, value); 39 | } 40 | supertest.end(helper.checkSpyResults(done)); 41 | }); 42 | 43 | // The parseRequest middleware will pass the JSON schema and value to JsonSchema.parse() 44 | let express = helper.express(middleware.metadata(), middleware.parseRequest()); 45 | 46 | return express; 47 | }; 48 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.2.0", 3 | "configurations": [ 4 | { 5 | "name": "Run Tests", 6 | "type": "node", 7 | "request": "launch", 8 | "program": "${workspaceRoot}/node_modules/mocha/bin/_mocha", 9 | "args": [ 10 | "--timeout=600000", 11 | "--retries=0", 12 | ], 13 | "cwd": "${workspaceRoot}", 14 | "preLaunchTask": null, 15 | "runtimeExecutable": null, 16 | "runtimeArgs": [ 17 | "--nolazy" 18 | ], 19 | "env": { 20 | "NODE_ENV": "development" 21 | }, 22 | "console": "internalConsole", 23 | "sourceMaps": false, 24 | }, 25 | { 26 | "name": "Run Sample 1", 27 | "type": "node", 28 | "request": "launch", 29 | "program": "${workspaceRoot}/samples/sample1.js", 30 | "args": [], 31 | "cwd": "${workspaceRoot}", 32 | "preLaunchTask": null, 33 | "runtimeExecutable": null, 34 | "runtimeArgs": [ 35 | "--nolazy" 36 | ], 37 | "env": { 38 | "NODE_ENV": "development" 39 | }, 40 | "console": "internalConsole", 41 | "sourceMaps": false, 42 | }, 43 | { 44 | "name": "Run Sample 2", 45 | "type": "node", 46 | "request": "launch", 47 | "program": "${workspaceRoot}/samples/sample2.js", 48 | "args": [], 49 | "cwd": "${workspaceRoot}", 50 | "preLaunchTask": null, 51 | "runtimeExecutable": null, 52 | "runtimeArgs": [ 53 | "--nolazy" 54 | ], 55 | "env": { 56 | "NODE_ENV": "development" 57 | }, 58 | "console": "internalConsole", 59 | "sourceMaps": false, 60 | }, 61 | ] 62 | } 63 | -------------------------------------------------------------------------------- /docs/exports/FileDataStore.md: -------------------------------------------------------------------------------- 1 | The `FileDataStore` class 2 | ============================ 3 | This data store persists its data to JSON files, which means the data doesn't go away when your app restarts, like it does with the [MemoryDataStore](MemoryDataStore.md). This allows you to easily create an API that acts like it has a real database behind it, so you can use the [Mock middleware](../middleware/mock.md) to create some data, and then use that data for a demos and presentations days or weeks later. 4 | 5 | > **NOTE:** The `FileDataStore` is not intended to replace a full-featured database. It does not provide features such as fault-tolerance, transactions, concurrency, etc. However, you can easily integrate with a full-featured database system (such as MySQL, SqlLite, Oracle, etc.) by creating your own class that inherits from the [DataStore](DataStore.md) base class. You simply need to override the `__openDataStore` and `__saveDataStore` methods. See the [FileDataStore source code](../../lib/data-store/file-data-store.js) for an example. 6 | 7 | 8 | Constructor 9 | ----------------------- 10 | ### `FileDataStore(baseDir)` 11 | 12 | * __baseDir__ (_optional_) - `string`
13 | The directory where the JSON files will be saved. The `FileDataStore` will create separate folders and files under this directory for each path in your Swagger API. 14 |

15 | If you don't specify this parameter, then it defaults to [`process.cwd()`](https://nodejs.org/api/process.html#process_process_cwd). 16 | 17 | 18 | Methods 19 | ----------------------- 20 | The `FileDataStore` class inherits from the [DataStore](DataStore.md) class, so it has all the same methods for retrieving, saving, and deleting data. 21 | -------------------------------------------------------------------------------- /test/fixtures/files/index.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const path = require("path"); 4 | const mkdirp = require("mkdirp"); 5 | 6 | const rootDir = path.join(__dirname, "..", "..", ".."); 7 | const filesDir = path.join(rootDir, "test", "fixtures", "files"); 8 | const swagger2Dir = path.join(filesDir, "swagger-2"); 9 | const openapi3Dir = path.join(filesDir, "openapi-3"); 10 | 11 | const files = module.exports = { 12 | tempDir: path.join(rootDir, ".tmp"), 13 | 14 | zeroMB: path.join(filesDir, "0MB.jpg"), 15 | oneMB: path.join(filesDir, "1MB.jpg"), 16 | fiveMB: path.join(filesDir, "5MB.jpg"), 17 | sixMB: path.join(filesDir, "6MB.jpg"), 18 | PDF: path.join(filesDir, "File.pdf"), 19 | blank: path.join(filesDir, "blank.yaml"), 20 | 21 | swagger2: { 22 | petStore: path.join(swagger2Dir, "petstore.yaml"), 23 | externalRefs: path.join(swagger2Dir, "external-refs", "external-refs.yaml"), 24 | error: path.join(swagger2Dir, "external-refs", "error.json"), 25 | pet: path.join(swagger2Dir, "pet"), 26 | text: path.join(swagger2Dir, "external-refs", "dir", "subdir", "text.txt"), 27 | }, 28 | 29 | openapi3: { 30 | petStore: path.join(openapi3Dir, "petstore.yaml"), 31 | externalRefs: path.join(openapi3Dir, "external-refs", "external-refs.yaml"), 32 | error: path.join(openapi3Dir, "external-refs", "error.json"), 33 | pet: path.join(openapi3Dir, "pet"), 34 | text: path.join(openapi3Dir, "external-refs", "dir", "subdir", "text.txt"), 35 | }, 36 | 37 | /** 38 | * Creates the temp directory and returns its path to the callback. 39 | */ 40 | createTempDir (done) { 41 | setTimeout(() => { 42 | let dirName = path.join(files.tempDir, new Date().toJSON().replace(/:/g, "-")); 43 | mkdirp(dirName, () => { 44 | done(dirName); 45 | }); 46 | }, 10); 47 | }, 48 | }; 49 | -------------------------------------------------------------------------------- /lib/index.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | module.exports = createMiddleware; 4 | module.exports.Middleware = require("./middleware"); 5 | module.exports.Resource = require("./data-store/resource"); 6 | module.exports.DataStore = require("./data-store"); 7 | module.exports.MemoryDataStore = require("./data-store/memory-data-store"); 8 | module.exports.FileDataStore = require("./data-store/file-data-store"); 9 | 10 | const util = require("./helpers/util"); 11 | 12 | /** 13 | * Creates Express middleware for the given Swagger API. 14 | * 15 | * @param {string|object} [swagger] 16 | * - The file path or URL of a Swagger 2.0 API spec, in YAML or JSON format. 17 | * Or a valid Swagger API object (see https://github.com/OAI/OpenAPI-Specification/blob/master/versions/2.0.md#swagger-object). 18 | * 19 | * @param {express#Router} [router] 20 | * - An Express Application or Router that will be used to determine settings (such as case-sensitivity and strict routing) 21 | * and to register path-parsing middleware. 22 | * 23 | * @param {function} [callback] 24 | * - It will be called when the API has been parsed, validated, and dereferenced, or when an error occurs. 25 | * 26 | * @returns {Middleware} 27 | * The {@link Middleware} object is returned immediately, but it isn't ready to handle requests until 28 | * the callback function is called. The same {@link Middleware} object will be passed to the callback function. 29 | */ 30 | function createMiddleware (swagger, router, callback) { 31 | // Shift args if needed 32 | if (util.isExpressRouter(swagger)) { 33 | router = swagger; 34 | swagger = callback = undefined; 35 | } 36 | else if (!util.isExpressRouter(router)) { 37 | callback = router; 38 | router = undefined; 39 | } 40 | 41 | let middleware = new module.exports.Middleware(router); 42 | 43 | if (swagger) { 44 | middleware.init(swagger, callback); 45 | } 46 | 47 | return middleware; 48 | } 49 | -------------------------------------------------------------------------------- /lib/mock/query-resource.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | module.exports = { 4 | GET: queryResource, 5 | HEAD: queryResource, 6 | OPTIONS: queryResource 7 | }; 8 | 9 | const util = require("../helpers/util"); 10 | const ono = require("@jsdevtools/ono"); 11 | const Resource = require("../data-store/resource"); 12 | 13 | /** 14 | * Returns the REST resource at the URL. 15 | * If there's no resource that matches the URL, then a 404 error is returned. 16 | * 17 | * @param {Request} req 18 | * @param {Response} res 19 | * @param {function} next 20 | * @param {DataStore} dataStore 21 | */ 22 | function queryResource (req, res, next, dataStore) { 23 | let resource = new Resource(req.path); 24 | 25 | dataStore.get(resource, (err, result) => { 26 | if (err) { 27 | next(err); 28 | } 29 | else if (!result) { 30 | let defaultValue = getDefaultValue(res); 31 | 32 | if (defaultValue === undefined) { 33 | util.debug("ERROR! 404 - %s %s does not exist", req.method, req.path); 34 | err = ono({ status: 404 }, "%s Not Found", resource.toString()); 35 | next(err); 36 | } 37 | else { 38 | // There' a default value, so use it instead of sending a 404 39 | util.debug( 40 | "%s %s does not exist, but the response schema defines a fallback value. So, using the fallback value", 41 | req.method, req.path 42 | ); 43 | res.swagger.lastModified = new Date(); 44 | res.body = defaultValue; 45 | next(); 46 | } 47 | } 48 | else { 49 | res.swagger.lastModified = result.modifiedOn; 50 | 51 | // Set the response body (unless it's already been set by other middleware) 52 | res.body = res.body || result.data; 53 | next(); 54 | } 55 | }); 56 | } 57 | 58 | /** 59 | * Returns the default/example value for this request. 60 | */ 61 | function getDefaultValue (res) { 62 | if (res.body) { 63 | return res.body; 64 | } 65 | else if (res.swagger.schema) { 66 | return res.swagger.schema.default || res.swagger.schema.example; 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@apidevtools/swagger-express-middleware", 3 | "version": "4.0.2", 4 | "description": "Swagger middleware and mocks for Express", 5 | "keywords": [ 6 | "express", 7 | "swagger", 8 | "middleware", 9 | "mock", 10 | "fake", 11 | "stub", 12 | "rest", 13 | "api", 14 | "json" 15 | ], 16 | "author": { 17 | "name": "James Messinger", 18 | "url": "https://jamesmessinger.com" 19 | }, 20 | "homepage": "https://apitools.dev/swagger-express-middleware/", 21 | "repository": { 22 | "type": "git", 23 | "url": "https://github.com/APIDevTools/swagger-express-middleware.git" 24 | }, 25 | "license": "MIT", 26 | "main": "lib/index.js", 27 | "files": [ 28 | "lib", 29 | "samples" 30 | ], 31 | "engines": { 32 | "node": ">=10" 33 | }, 34 | "scripts": { 35 | "clean": "shx rm -rf .nyc_output coverage", 36 | "lint": "eslint lib test/fixtures test/specs", 37 | "test": "mocha && npm run lint", 38 | "coverage": "nyc node_modules/mocha/bin/mocha", 39 | "upgrade": "npm-check -u && npm audit fix", 40 | "bump": "bump --tag --push --all", 41 | "release": "npm run upgrade && npm run clean && npm test && npm run bump", 42 | "start": "cd samples && node sample1.js" 43 | }, 44 | "devDependencies": { 45 | "@jsdevtools/eslint-config": "^1.1.0", 46 | "@jsdevtools/version-bump-prompt": "^6.0.6", 47 | "basic-auth": "^2.0.1", 48 | "chai": "^4.2.0", 49 | "chai-datetime": "^1.7.0", 50 | "eslint": "^7.6.0", 51 | "express": "^4.17.1", 52 | "mocha": "^8.1.0", 53 | "npm-check": "^5.9.0", 54 | "nyc": "^15.1.0", 55 | "shx": "^0.3.2", 56 | "sinon": "^9.0.2", 57 | "supertest": "^4.0.2" 58 | }, 59 | "dependencies": { 60 | "@apidevtools/swagger-methods": "^3.0.2", 61 | "@apidevtools/swagger-parser": "^10.0.1", 62 | "@jsdevtools/ono": "^7.1.3", 63 | "body-parser": "^1.19.0", 64 | "cookie-parser": "^1.4.4", 65 | "debug": "^4.1.1", 66 | "lodash": "^4.17.19", 67 | "multer": "^1.4.2", 68 | "tmp": "^0.2.1", 69 | "tv4": "^1.2.5", 70 | "type-is": "^1.6.18" 71 | }, 72 | "peerDependencies": { 73 | "express": "4.x" 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /test/specs/mock/helper.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const swagger = require("../../../"); 4 | const helper = require("../../fixtures/helper"); 5 | const util = require("../../../lib/helpers/util"); 6 | const _ = require("lodash"); 7 | 8 | _.extend(exports, helper); 9 | 10 | /** 11 | * Helper function for Mock tests. 12 | * 13 | * @param {e.app} [express] - The Express App to use for the test 14 | * @param {DataStore} [dataStore] - The DataStore to use for the test 15 | * @param {function[]} [fns] - Middleware functions to add to Express 16 | * @param {object} api - The Swagger API for the test 17 | * @param {function} test - The actual unit test 18 | */ 19 | exports.initTest = function (express, dataStore, fns, api, test) { 20 | switch (arguments.length) { 21 | case 2: 22 | test = arguments[1]; 23 | api = arguments[0]; 24 | fns = undefined; 25 | dataStore = undefined; 26 | express = undefined; 27 | break; 28 | case 3: 29 | test = arguments[2]; 30 | api = arguments[1]; 31 | if (arguments[0] instanceof swagger.DataStore) { 32 | dataStore = arguments[0]; 33 | fns = undefined; 34 | express = undefined; 35 | } 36 | else if (util.isExpressRouter(arguments[0])) { 37 | express = arguments[0]; 38 | dataStore = undefined; 39 | fns = undefined; 40 | } 41 | else { 42 | fns = arguments[0]; 43 | dataStore = undefined; 44 | express = undefined; 45 | } 46 | break; 47 | case 4: 48 | test = arguments[3]; 49 | api = arguments[2]; 50 | fns = arguments[1]; 51 | dataStore = arguments[0]; 52 | express = undefined; 53 | break; 54 | } 55 | 56 | express = express || helper.express(); 57 | let supertest = helper.supertest(express.app || express); 58 | 59 | swagger(api, express, (err, middleware) => { 60 | express.use( 61 | middleware.metadata(), 62 | middleware.CORS(), 63 | middleware.parseRequest(), 64 | middleware.validateRequest(), 65 | fns || [], 66 | middleware.mock(dataStore) 67 | ); 68 | 69 | test(supertest); 70 | }); 71 | }; 72 | -------------------------------------------------------------------------------- /test/fixtures/specs.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const _ = require("lodash"); 4 | const files = require("./files"); 5 | const swaggerMethods = require("@apidevtools/swagger-methods"); 6 | const swagger2PetStore = require("./files/swagger-2/petstore.json"); 7 | 8 | const swagger2 = { 9 | key: "swagger2", 10 | name: "Swagger 2.0", 11 | files: { 12 | ...files, 13 | ...files.swagger2, 14 | }, 15 | samples: { 16 | blank: { swagger: "2.0", info: { title: "Test Swagger", version: "1.0" }, paths: {}}, 17 | petStore: swagger2PetStore, 18 | petStoreNoBasePath: _.omit(swagger2PetStore, "basePath"), 19 | petStoreNoPaths: omitPaths(swagger2PetStore), 20 | petStoreNoOperations: omitOperations(swagger2PetStore), 21 | petStoreSecurity: swagger2PetStore.security, 22 | petsPath: swagger2PetStore.paths["/pets"], 23 | petsGetOperation: swagger2PetStore.paths["/pets"].get, 24 | petsPostOperation: swagger2PetStore.paths["/pets"].post, 25 | petsGetParams: swagger2PetStore.paths["/pets"].get.parameters, 26 | petsPostParams: swagger2PetStore.paths["/pets"].post.parameters, 27 | petsPostSecurity: swagger2PetStore.paths["/pets"].post.security, 28 | petPath: swagger2PetStore.paths["/pets/{PetName}"], 29 | petPathNoOperations: omitOperationsFromPath(swagger2PetStore.paths["/pets/{PetName}"]), 30 | petPatchOperation: swagger2PetStore.paths["/pets/{PetName}"].patch, 31 | petPatchParams: [ 32 | swagger2PetStore.paths["/pets/{PetName}"].patch.parameters[0], 33 | swagger2PetStore.paths["/pets/{PetName}"].parameters[0] 34 | ], 35 | petPatchSecurity: swagger2PetStore.paths["/pets/{PetName}"].patch.security 36 | } 37 | }; 38 | 39 | module.exports = [swagger2]; 40 | module.exports.swagger2 = swagger2; 41 | 42 | /** 43 | * Returns a copy of the API definition with all paths removed 44 | */ 45 | function omitPaths (api) { 46 | let clone = _.cloneDeep(api); 47 | clone.paths = {}; 48 | return clone; 49 | } 50 | 51 | /** 52 | * Returns a copy of the API definition with all operations removed 53 | */ 54 | function omitOperations (api) { 55 | let clone = _.cloneDeep(api); 56 | 57 | for (let path of Object.keys(clone.paths)) { 58 | clone.paths[path] = omitOperationsFromPath(clone.paths[path]); 59 | } 60 | 61 | return clone; 62 | } 63 | 64 | /** 65 | * Returns a copy of the given Path Item object with all operations removed 66 | */ 67 | function omitOperationsFromPath (pathItem) { 68 | let clone = _.omit(pathItem, swaggerMethods); 69 | return clone; 70 | } 71 | -------------------------------------------------------------------------------- /lib/mock/semantic-request.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | module.exports = SemanticRequest; 4 | 5 | const util = require("../helpers/util"); 6 | const SemanticResponse = require("./semantic-response"); 7 | 8 | /** 9 | * Describes the semantics of a Swagger request. 10 | * 11 | * @param {Request} req 12 | * @constructor 13 | */ 14 | function SemanticRequest (req) { 15 | /** 16 | * Indicates whether this is a "resource path" or a "collection path". 17 | * Resource path operate on a single REST resource, whereas collection requests operate on 18 | * a collection of resources. 19 | * @type {boolean} 20 | */ 21 | this.isCollection = isCollectionRequest(req); 22 | } 23 | 24 | /** 25 | * Determines whether the given path is a "resource path" or a "collection path". 26 | * Resource paths operate on a single REST resource, whereas collection paths operate on 27 | * a collection of resources. 28 | * 29 | * NOTE: This algorithm is subject to change. Over time, it should get smarter and better at determining request types. 30 | * 31 | * @param {Request} req 32 | * @returns {boolean} 33 | */ 34 | function isCollectionRequest (req) { 35 | let isCollection = responseIsCollection(req); 36 | 37 | if (isCollection === undefined) { 38 | isCollection = !lastPathSegmentIsAParameter(req); 39 | } 40 | 41 | return isCollection; 42 | } 43 | 44 | /** 45 | * Examines the GET or HEAD operation for the path and determines whether it is a collection response. 46 | * 47 | * @param {Request} req 48 | * 49 | * @returns {boolean|undefined} 50 | * True if the response schema is a collection. False if it's not a collection. Undefined if there is not response schema. 51 | */ 52 | function responseIsCollection (req) { 53 | let getter = req.swagger.path.get || req.swagger.path.head; 54 | if (getter) { 55 | let responses = util.getResponsesBetween(getter, 200, 299); 56 | if (responses.length > 0) { 57 | let response = new SemanticResponse(responses[0].api, req.swagger.path); 58 | if (!response.isEmpty) { 59 | return response.isCollection; 60 | } 61 | } 62 | } 63 | } 64 | 65 | /** 66 | * Determines whether the last path segment is a Swagger parameter. 67 | * 68 | * For example, the following paths are all considered to be resource requests, 69 | * because their final path segment contains a parameter: 70 | * 71 | * - /users/{username} 72 | * - /products/{productId}/reviews/review-{reviewId} 73 | * - /{country}/{state}/{city} 74 | * 75 | * Conversely, the following paths are all considered to be collection requests, 76 | * because their final path segment is NOT a parameter: 77 | * 78 | * - /users 79 | * - /products/{productId}/reviews 80 | * - /{country}/{state}/{city}/neighborhoods/streets 81 | * 82 | * @param {Request} req 83 | * @returns {boolean} 84 | */ 85 | function lastPathSegmentIsAParameter (req) { 86 | let lastSlash = req.swagger.pathName.lastIndexOf("/"); 87 | let lastParam = req.swagger.pathName.lastIndexOf("{"); 88 | return (lastParam > lastSlash); 89 | } 90 | -------------------------------------------------------------------------------- /docs/exports/Middleware.md: -------------------------------------------------------------------------------- 1 | The `Middleware` class 2 | ============================ 3 | The `Middleware` class is the main class in Swagger Express Middleware. It's role is simple: You give it a Swagger API, and it gives you Express middleware for that API. You can create multiple `Middleware` instances if you need to work with more than one Swagger API. Each `Middleware` instance is entirely isolated, so any Express middleware that is created by one instance will only know about its own Swagger API. 4 | 5 | > **TIP:** For most simple apps, you don't need to worry about the `Middleware` class. The [createMiddleware function](createMiddleware.md) — which is used in all the documentation examples — is a convenience function that automatically instantiates a `Middleware` object and calls its `init()` method for you. 6 | 7 | 8 | Constructor 9 | ----------------------- 10 | ### `Middleware(router)` 11 | This is the constructor for the Middleware class. 12 | 13 | * __router__ (_optional_) - `express.App` or `express.Router`
14 | An [Express Application](http://expressjs.com/4x/api.html#application) or [Router](http://expressjs.com/4x/api.html#router) that will be used to determine settings (such as case-sensitivity and strict routing) and to register path-parsing middleware. 15 |

16 | **NOTE:** If you don't specify this parameter, then the default Express routing settings will be used (case-insensitive, non-strict). You can override this parameter (or the defaults) for any specific middleware by passing an Express App or Router to the middleware. 17 | 18 | 19 | Methods 20 | ----------------------- 21 | ### `init(swagger, callback)` 22 | Initializes the middleware with the given Swagger API. This method can be called again to re-initialize with a new or modified API. 23 | 24 | * __swagger__ (_optional_) - `string` or `object`
25 | The file path or URL of a Swagger 2.0 API spec, in YAML or JSON format. Or a valid [Swagger object](https://github.com/OAI/OpenAPI-Specification/blob/master/versions/2.0.md#swagger-object). Any `$ref` pointers to other files/URLs will be interpreted as relative to the main Swagger file. 26 | 27 | * __callback__ (_optional_) - `function(err, middleware)`
28 | A callback function that will be called once the Swagger API is fully parsed, dereferenced, and validated. The second parameter is the same `Middleware` object. 29 | 30 | ### `files(router, options)` 31 | This method creates a new [Files middleware](../middleware/files.md) instance. 32 | 33 | ### `metadata(router)` 34 | This method creates a new [Metadata middleware](../middleware/metadata.md) instance. 35 | 36 | ### `CORS(router)` 37 | This method creates a new [CORS middleware](../middleware/CORS.md) instance. 38 | 39 | ### `parseRequest(router, options)` 40 | This method creates a new [Parse Request middleware](../middleware/parseRequest.md) instance. 41 | 42 | ### `validateRequest(router)` 43 | This method creates a new [Validate Request middleware](../middleware/validateRequest.md) instance. 44 | 45 | ### `mock(router, dataStore)` 46 | This method creates a new [Mock middleware](../middleware/mock.md) instance. 47 | -------------------------------------------------------------------------------- /.github/workflows/CI-CD.yaml: -------------------------------------------------------------------------------- 1 | # GitHub Actions workflow 2 | # https://help.github.com/en/actions/automating-your-workflow-with-github-actions 3 | # https://help.github.com/en/actions/automating-your-workflow-with-github-actions/workflow-syntax-for-github-actions 4 | # https://help.github.com/en/actions/automating-your-workflow-with-github-actions/contexts-and-expression-syntax-for-github-actions 5 | 6 | name: CI-CD 7 | 8 | on: 9 | push: 10 | branches: 11 | - "*" 12 | tags-ignore: 13 | - "*" 14 | 15 | schedule: 16 | - cron: "0 0 1 * *" 17 | 18 | jobs: 19 | test: 20 | name: Node ${{ matrix.node }} on ${{ matrix.os }} 21 | runs-on: ${{ matrix.os }} 22 | timeout-minutes: 10 23 | strategy: 24 | fail-fast: true 25 | matrix: 26 | os: 27 | - ubuntu-latest 28 | - macos-latest 29 | - windows-latest 30 | node: 31 | - 10 32 | - 12 33 | - 14 34 | 35 | steps: 36 | - name: Checkout source 37 | uses: actions/checkout@v2 38 | 39 | - name: Install Node ${{ matrix.node }} 40 | uses: actions/setup-node@v1 41 | with: 42 | node-version: ${{ matrix.node }} 43 | 44 | - name: Install dependencies 45 | run: npm ci 46 | 47 | - name: Run linter 48 | run: npm run lint 49 | 50 | - name: Run tests 51 | run: npm run coverage 52 | 53 | - name: Send code coverage results to Coveralls 54 | uses: coverallsapp/github-action@v1.1.0 55 | with: 56 | github-token: ${{ secrets.GITHUB_TOKEN }} 57 | parallel: true 58 | 59 | coverage: 60 | name: Code Coverage 61 | runs-on: ubuntu-latest 62 | timeout-minutes: 10 63 | needs: test 64 | steps: 65 | - name: Let Coveralls know that all tests have finished 66 | uses: coverallsapp/github-action@v1.1.0 67 | with: 68 | github-token: ${{ secrets.GITHUB_TOKEN }} 69 | parallel-finished: true 70 | 71 | deploy: 72 | name: Publish to NPM 73 | if: github.ref == 'refs/heads/master' 74 | runs-on: ubuntu-latest 75 | timeout-minutes: 10 76 | needs: test 77 | 78 | steps: 79 | - name: Checkout source 80 | uses: actions/checkout@v2 81 | 82 | - name: Install Node 83 | uses: actions/setup-node@v1 84 | 85 | - name: Install dependencies 86 | run: npm ci 87 | 88 | - name: Publish to NPM 89 | uses: JS-DevTools/npm-publish@v1 90 | with: 91 | token: ${{ secrets.NPM_TOKEN }} 92 | 93 | - name: Prepare the non-scoped packaged 94 | run: | 95 | cp LICENSE *.md dist 96 | VERSION=$(node -e "console.log(require('./package.json').version)") 97 | sed -i "s/X.X.X/${VERSION}/g" dist/package.json 98 | 99 | - name: Publish the non-scoped package to NPM 100 | uses: JS-DevTools/npm-publish@v1 101 | with: 102 | token: ${{ secrets.NPM_TOKEN }} 103 | package: dist/package.json 104 | -------------------------------------------------------------------------------- /docs/exports/createMiddleware.md: -------------------------------------------------------------------------------- 1 | The `createMiddleware` function 2 | ================================ 3 | Swagger Express Middleware exposes several JavaScript classes, but most of them are only needed for advanced usage scenarios. Most simple apps can just use the `createMiddleware` function, which is a convenience function that reduces the amount of code you need to write. 4 | 5 | 6 | Example 7 | -------------------------- 8 | All of the examples in these docs use the `createMiddleware` function like this: 9 | 10 | ```javascript 11 | const express = require('express'); 12 | const createMiddleware = require('@apidevtools/swagger-express-middleware'); 13 | 14 | let app = express(); 15 | 16 | // Call the createMiddleware function (aliased as "middleware") 17 | createMiddleware('PetStore.yaml', app, function(err, middleware) { 18 | ... 19 | }); 20 | ``` 21 | 22 | But any of the examples or samples could be rewritten to use the [Middleware class](Middleware.md) and the [init method](Middleware.md#initswagger-callback) instead, like this: 23 | 24 | ```javascript 25 | const express = require('express'); 26 | const swagger = require('@apidevtools/swagger-express-middleware'); 27 | 28 | let app = express(); 29 | 30 | // Create a Middleware object 31 | let middleware = new swagger.Middleware(app); 32 | 33 | // Call its init method 34 | middleware.init('PetStore.yaml', function(err) { 35 | ... 36 | }); 37 | ``` 38 | For a complete example of this second pattern, see [Sample 2](https://github.com/APIDevTools/swagger-express-middleware/blob/master/samples/sample2.js) 39 | 40 | 41 | API 42 | ---------------------- 43 | ### `createMiddleware(swagger, router, callback)` 44 | The `createMiddleware` function is the main export of Swagger Express Middleware — it's what you get when you `require('@apidevtools/swagger-express-middleware')`. It's just a convenience function that creates a [Middleware](Middleware.md) object and calls its [init method](Middleware.md#initswagger-callback). 45 | 46 | * __swagger__ (_optional_) - `string` or `object`
47 | The file path or URL of a Swagger 2.0 API spec, in YAML or JSON format. Or a valid [Swagger object](https://github.com/OAI/OpenAPI-Specification/blob/master/versions/2.0.md#swagger-object). Any `$ref` pointers to other files/URLs will be interpreted as relative to the main Swagger file. 48 | 49 | * __router__ (_optional_) - `express.App` or `express.Router`
50 | An [Express Application](http://expressjs.com/4x/api.html#application) or [Router](http://expressjs.com/4x/api.html#router) that will be used to determine settings (such as case-sensitivity and strict routing) and to register path-parsing middleware. 51 |

52 | **NOTE:** If you don't specify this parameter, then the default Express routing settings will be used (case-insensitive, non-strict). You can override this parameter (or the defaults) for any specific middleware by passing an Express App or Router to the middleware. 53 | 54 | * __callback__ (_optional_) - `function(err, middleware)`
55 | A callback function that will be called once the Swagger API is fully parsed, dereferenced, and validated. The second parameter is the [Middleware](Middleware.md) object that was created. 56 | -------------------------------------------------------------------------------- /test/specs/json-schema/parse/parse-boolean.spec.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const expect = require("chai").expect; 4 | const helper = require("./helper"); 5 | 6 | describe("JSON Schema - parse boolean params", () => { 7 | it("should parse a valid boolean param", (done) => { 8 | let schema = { 9 | type: "boolean" 10 | }; 11 | 12 | let express = helper.parse(schema, "true", done); 13 | 14 | express.post("/api/test", helper.spy((req, res, next) => { 15 | expect(req.header("Test")).to.equal(true); 16 | })); 17 | }); 18 | 19 | it("should parse an optional, unspecified boolean param", (done) => { 20 | let schema = { 21 | type: "boolean" 22 | }; 23 | 24 | let express = helper.parse(schema, undefined, done); 25 | 26 | express.post("/api/test", helper.spy((req, res, next) => { 27 | expect(req.header("Test")).to.equal(undefined); 28 | })); 29 | }); 30 | 31 | it("should parse the default value if no value is specified", (done) => { 32 | let schema = { 33 | type: "boolean", 34 | default: true 35 | }; 36 | 37 | let express = helper.parse(schema, undefined, done); 38 | 39 | express.post("/api/test", helper.spy((req, res, next) => { 40 | expect(req.header("Test")).to.equal(true); 41 | })); 42 | }); 43 | 44 | it("should parse the default value if the specified value is blank", (done) => { 45 | let schema = { 46 | type: "boolean", 47 | default: false 48 | }; 49 | 50 | let express = helper.parse(schema, "", done); 51 | 52 | express.post("/api/test", helper.spy((req, res, next) => { 53 | expect(req.header("Test")).to.equal(false); 54 | })); 55 | }); 56 | 57 | it("should throw an error if the value is blank", (done) => { 58 | let schema = { 59 | type: "boolean" 60 | }; 61 | 62 | let express = helper.parse(schema, "", done); 63 | 64 | express.use("/api/test", helper.spy((err, req, res, next) => { 65 | expect(err).to.be.an.instanceOf(Error); 66 | expect(err.status).to.equal(400); 67 | expect(err.message).to.contain("Invalid type: string (expected boolean)"); 68 | })); 69 | }); 70 | 71 | it("should throw an error if the value is not a valid boolean", (done) => { 72 | let schema = { 73 | type: "boolean" 74 | }; 75 | 76 | let express = helper.parse(schema, "hello world", done); 77 | 78 | express.use("/api/test", helper.spy((err, req, res, next) => { 79 | expect(err).to.be.an.instanceOf(Error); 80 | expect(err.status).to.equal(400); 81 | expect(err.message).to.contain("Invalid type: string (expected boolean)"); 82 | })); 83 | }); 84 | 85 | it("should throw an error if required and not specified", (done) => { 86 | let schema = { 87 | type: "boolean", 88 | required: true 89 | }; 90 | 91 | let express = helper.parse(schema, undefined, done); 92 | 93 | express.use("/api/test", helper.spy((err, req, res, next) => { 94 | expect(err).to.be.an.instanceOf(Error); 95 | expect(err.status).to.equal(400); 96 | expect(err.message).to.contain('Missing required header parameter "Test"'); 97 | })); 98 | }); 99 | }); 100 | -------------------------------------------------------------------------------- /docs/walkthroughs/javascript.md: -------------------------------------------------------------------------------- 1 | Sample 1 Walkthrough 2 | ============================ 3 | * [Running the sample](running.md) 4 | * __JavaScript Walkthrough__ 5 | * [YAML Walkthrough](yaml.md) 6 | 7 | 8 | JavaScript Walkthrough 9 | -------------------------- 10 | Now that you have the sample [running](running.md), it's time to look at the source code. Open up [sample1.js](https://github.com/APIDevTools/swagger-express-middleware/blob/master/samples/sample1.js) and let's see what it's doing. 11 | 12 | 13 | ### Creating an Express Application 14 | If you're already familiar with Express.js, then the first few lines should be pretty familiar. We `require('express')` and then create a new Express Application. If this is new to you, then you might want to take some time to read the [Getting Started guide](http://expressjs.com/starter/hello-world.html) on the Express website. 15 | 16 | 17 | ### Middleware initialization 18 | The next few lines of code are where we really start working with Swagger Express Middleware. First we use `path.join()` to get the full path of our [Swagger file](https://github.com/APIDevTools/swagger-express-middleware/blob/master/samples/PetStore.yaml), then we use it to initialize the middleware. For more details about this line, see the [createMiddleware function](../exports/createMiddleware.md) documentation. 19 | 20 | ```javascript 21 | createMiddleware(swaggerFile, app, function(err, middleware) { 22 | ``` 23 | 24 | Middleware initialization happens asynchronously. During this time, it parses and validates the Swagger file and reads/downloads any other files that are referenced by `$ref` pointers. When this process is complete, the callback function will be called. If there was an error during the initialization and parsing process, then the `err` parameter will be set; otherwise, it will be `null`. But either way, the `middleware` parameter will be initialized and you can now add Swagger middleware to your Express Application. 25 | 26 | ```javascript 27 | // Add all the Swagger Express Middleware, or just the ones you need. 28 | // NOTE: Some of these accept optional options (omitted here for brevity) 29 | app.use( 30 | middleware.metadata(), 31 | middleware.CORS(), 32 | middleware.files(), 33 | middleware.parseRequest(), 34 | middleware.validateRequest(), 35 | middleware.mock() 36 | ); 37 | ``` 38 | 39 | As the comment says, you don't have to add _all_ of the Swagger middleware modules to your app. You can choose just the ones you need, but you'll need to check the [documentation](../middleware/) because some of the middleware modules require other modules. Also, many of the middleware modules have optional settings that you can use to customize their behavior. Those are also covered in the documentation. 40 | 41 | 42 | ### Starting the server 43 | The next block of code is pretty standard for any Express app. The [listen method](http://expressjs.com/4x/api.html#app.listen) tells the server start listeing for requests on port 8000. The callback function is called once the port has been opened and is ready to begin accepting requests. 44 | 45 | ```javascript 46 | app.listen(8000, function() { 47 | ``` 48 | 49 | 50 | Sample 1 Walkthrough 51 | -------------------------- 52 | * [Running the sample](running.md) 53 | * __JavaScript Walkthrough__ 54 | * [YAML Walkthrough](yaml.md) 55 | -------------------------------------------------------------------------------- /test/specs/util.spec.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const util = require("../../lib/helpers/util"); 4 | const expect = require("chai").expect; 5 | const sinon = require("sinon"); 6 | 7 | /** 8 | * NOTE: The Util.js file isn't technically exported, but it IS used by other Swagger packages, so it needs to be tested 9 | */ 10 | describe("Util methods", () => { 11 | 12 | describe("util.warn", () => { 13 | beforeEach(() => { 14 | delete process.env.WARN; 15 | sinon.stub(console, "warn"); 16 | }); 17 | 18 | afterEach(() => { 19 | process.env.WARN = "off"; 20 | console.warn.restore(); 21 | }); 22 | 23 | it("should call console.warn if the WARN environment variable is not set", () => { 24 | util.warn("test"); 25 | sinon.assert.calledOnce(console.warn); 26 | sinon.assert.calledWithExactly(console.warn, "test"); 27 | }); 28 | 29 | it('should call console.warn if the WARN environment variable is not set to "off"', () => { 30 | process.env.WARN = "false"; 31 | util.warn("test"); 32 | sinon.assert.calledOnce(console.warn); 33 | sinon.assert.calledWithExactly(console.warn, "test"); 34 | }); 35 | 36 | it('should not call console.warn if the WARN environment variable is set to "off"', () => { 37 | process.env.WARN = "off"; 38 | util.warn("test"); 39 | sinon.assert.notCalled(console.warn); 40 | }); 41 | 42 | it("can be called with just a message", () => { 43 | util.warn("testing 1, 2, 3"); 44 | sinon.assert.calledOnce(console.warn); 45 | sinon.assert.calledWithExactly(console.warn, "testing 1, 2, 3"); 46 | }); 47 | 48 | it("can be called with a message and params", () => { 49 | util.warn("testing %s, %d, %j", 1, 2, "3"); 50 | sinon.assert.calledOnce(console.warn); 51 | sinon.assert.calledWithExactly(console.warn, 'testing 1, 2, "3"'); 52 | }); 53 | 54 | it("can be called with just an error", () => { 55 | function warnWithStackTrace () { 56 | util.warn(new RangeError("Test Error")); 57 | } 58 | 59 | warnWithStackTrace(); 60 | sinon.assert.calledOnce(console.warn); 61 | expect(console.warn.firstCall.args[0]).to.match(/^RangeError\: Test Error/); 62 | expect(console.warn.firstCall.args[0]).to.contain("at warnWithStackTrace"); 63 | }); 64 | 65 | it("can be called with an error and a message", () => { 66 | function warnWithStackTrace () { 67 | util.warn(new SyntaxError("Test Error"), "Testing 1, 2, 3"); 68 | } 69 | 70 | warnWithStackTrace(); 71 | sinon.assert.calledOnce(console.warn); 72 | expect(console.warn.firstCall.args[0]).to.match(/^Testing 1, 2, 3 \nSyntaxError\: Test Error/); 73 | expect(console.warn.firstCall.args[0]).to.contain("at warnWithStackTrace"); 74 | }); 75 | 76 | it("can be called with an error, a message, and params", () => { 77 | function warnWithStackTrace () { 78 | util.warn(new Error("Test Error"), "Testing %s, %d, %j", 1, 2, "3"); 79 | } 80 | 81 | warnWithStackTrace(); 82 | sinon.assert.calledOnce(console.warn); 83 | expect(console.warn.firstCall.args[0]).to.match(/^Testing 1, 2, "3" \nError\: Test Error/); 84 | expect(console.warn.firstCall.args[0]).to.contain("at warnWithStackTrace"); 85 | }); 86 | }); 87 | }); 88 | -------------------------------------------------------------------------------- /lib/data-store/file-data-store.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | module.exports = FileDataStore; 4 | 5 | const DataStore = require("./index"); 6 | const Resource = require("./resource"); 7 | const fs = require("fs"); 8 | const path = require("path"); 9 | const mkdirp = require("mkdirp"); 10 | 11 | // Inheritance 12 | FileDataStore.prototype = Object.create(DataStore.prototype); 13 | FileDataStore.prototype.constructor = FileDataStore; 14 | 15 | /** 16 | * A JSON file data store for REST resources. Each collection is stored as a separate file. 17 | * 18 | * @param {string} baseDir - The base directory where the JSON files will be saved. 19 | * @constructor 20 | * @extends DataStore 21 | */ 22 | function FileDataStore (baseDir) { 23 | DataStore.call(this); 24 | this.__baseDir = baseDir || process.cwd(); 25 | } 26 | 27 | /** 28 | * Overrides {@link DataStore#__openDataStore} to return data from a JSON file. 29 | * 30 | * @protected 31 | */ 32 | FileDataStore.prototype.__openDataStore = function (collection, callback) { 33 | fs.readFile(getFilePath(this.__baseDir, collection), { encoding: "utf8" }, (err, data) => { 34 | if (err) { 35 | if (err.code === "ENOENT") { 36 | // The file doesn't exist yet, so just return an empty array 37 | callback(null, []); 38 | } 39 | else { 40 | callback(err); 41 | } 42 | } 43 | else { 44 | let resources; 45 | try { 46 | // Parse the JSON data into an array of Resource objects 47 | resources = Resource.parse(data); 48 | } 49 | catch (e) { 50 | callback(e); 51 | return; 52 | } 53 | 54 | // Call the callback outside of the try..catch block, 55 | // so we don't catch any errors that happen in third-party code 56 | callback(null, resources); 57 | } 58 | }); 59 | }; 60 | 61 | /** 62 | * Overrides {@link DataStore#__saveDataStore} to store data in a a JSON file. 63 | * 64 | * @protected 65 | */ 66 | FileDataStore.prototype.__saveDataStore = function (collection, resources, callback) { 67 | let self = this; 68 | 69 | // Create the directory path 70 | mkdirp(getDirectory(this.__baseDir, collection), (err) => { 71 | if (err) { 72 | callback(err); 73 | } 74 | else { 75 | // Write the JSON data to the file 76 | fs.writeFile(getFilePath(self.__baseDir, collection), JSON.stringify(resources, null, 2), callback); 77 | } 78 | }); 79 | }; 80 | 81 | /** 82 | * Returns the directory where the given collection's JSON file is stored. 83 | * 84 | * @param {string} baseDir - (e.g. "/some/base/path") 85 | * @param {string} collection - (e.g. "/users/jdoe/orders") 86 | * @returns {string} - (e.g. "/some/base/path/users/jdoe") 87 | */ 88 | function getDirectory (baseDir, collection) { 89 | let dir = collection.substring(0, collection.lastIndexOf("/")); 90 | dir = dir.toLowerCase(); 91 | return path.normalize(path.join(baseDir, dir)); 92 | } 93 | 94 | /** 95 | * Returns the full path of the JSON file for the given collection. 96 | * 97 | * @param {string} baseDir - (e.g. "/some/base/path") 98 | * @param {string} collection - (e.g. "/users/jdoe/orders") 99 | * @returns {string} - (e.g. "/some/base/path/users/jdoe/orders.json") 100 | */ 101 | function getFilePath (baseDir, collection) { 102 | let directory = getDirectory(baseDir, collection); 103 | let fileName = collection.substring(collection.lastIndexOf("/") + 1) + ".json"; 104 | fileName = fileName.toLowerCase(); 105 | return path.join(directory, fileName); 106 | } 107 | -------------------------------------------------------------------------------- /docs/exports/DataStore.md: -------------------------------------------------------------------------------- 1 | The DataStore abstract class 2 | ============================ 3 | The [Mock middleware](../middleware/mock.md) uses `DataStore` classes to store its data, and you can use the `DataStore` API to to add/modify/remove this mock data, which is very handy for demos and POCs. Refer to the [Mock middleware documentation](../middleware/mock.md) to find out how to specify which `DataStore` class is used. Refer to the [Sample 2 walkthrough](../walkthroughs/walkthrough2.md) to see how to initialize the data store with data. 4 | 5 | > **TIP:** This is an _abstract base class_, which means you should _not_ use this class directly. Instead, you should use one of its child classes: [MemoryDataStore](MemoryDataStore.md) or [FileDataStore](FileDataStore.md). Or, if you want to store your data somewhere else — such as a SQL database, a Cloud service, etc. — then you can create your own child class that inherits from `DataStore`. 6 | 7 | 8 | Methods 9 | ----------------------- 10 | ### `get(resource, callback)` 11 | Returns the specified resource from the data store 12 | 13 | * __resource__ (_required_) - `Resource object` or `string`
14 | The resource path (such as `"/pets/Fido"`) or the [Resource](Resource.md) object to be retrieved 15 | 16 | * __callback__ (_optional_) - `function(err, resource)`
17 | An error-first callback. The second parameter is the requested [Resource](Resource.md) object, or `undefined` if no match was found. 18 | 19 | 20 | ### `save(resource1, resource2, ..., callback)` 21 | Saves the specified resource(s) to the data store. If any of the resources already exist, the new data is [merged](Resource.md#merge-resource) with the existing data. 22 | 23 | * __resources__ (_required_) - one or more `Resource objects`
24 | The resources to be saved. You can pass one or more [Resource](Resource.md) objects as separate parameters, or you can pass an array Resource objects. 25 | 26 | * __callback__ (_optional_) - `function(err, resources)`
27 | An error-first callback. The second parameter is the [Resource](Resource.md) object, or array of Resource objects that were saved. 28 | 29 | 30 | ### `delete(resource1, resource2, ..., callback)` 31 | Deletes the specified resource(s) from the data store. 32 | 33 | * __resources__ (_required_) - one or more `Resource objects`
34 | The resources to be deleted. You can pass one or more [Resource](Resource.md) objects as separate parameters, or you can pass an array Resource objects. 35 | 36 | * __callback__ (_optional_) - `function(err, resources)`
37 | An error-first callback. The second parameter is the [Resource](Resource.md) object, or array of Resource objects that were deleted. 38 |

39 | Only the resources that were actually deleted are returned. If you specify multiple resources, and none of them exist in the data store, then an empty array will be returned. If you specify a single resource to be deleted, and it doesn't exist, then `undefined` will be returned. 40 | 41 | 42 | ### `getCollection(collection, callback)` 43 | Returns all resources in the specified collection 44 | 45 | * __collection__ (_required_) - `string`
46 | The collection path (such as `"/pets"`, `"/users/jdoe/orders"`, etc.) 47 | 48 | * __callback__ (_optional_) - `function(err, resources)`
49 | An error-first callback. The second parameter is an array of all [Resource](Resource.md) objects in the collection. If there are no resources in the collection, then the array is empty. 50 | 51 | 52 | ### `deleteCollection(collection, callback)` 53 | Deletes all resources in the specified collection 54 | 55 | * __collection__ (_required_) - `string`
56 | The collection path (such as `"/pets"`, `"/users/jdoe/orders"`, etc.) 57 | 58 | * __callback__ (_optional_) - `function(err, resources)`
59 | An error-first callback. The second parameter is an array of all [Resource](Resource.md) objects that were deleted. If nothing was deleted, then the array is empty. 60 | -------------------------------------------------------------------------------- /docs/middleware/files.md: -------------------------------------------------------------------------------- 1 | Files middleware 2 | ============================ 3 | 4 | Serves your Swagger API file(s) so they can be used with front-end tools like like [Swagger UI](http://www.swagger.io), [Swagger Editor](http://editor.swagger.io), and [Postman](http://getpostman.com). 5 | 6 | 7 | Example 8 | -------------------------- 9 | This example uses the [PetStore.yaml](https://github.com/APIDevTools/swagger-express-middleware/blob/master/samples/PetStore.yaml) sample Swagger API. If you aren't familiar with using middleware in Express.js, then [read this first](http://expressjs.com/guide/using-middleware.html). 10 | 11 | ```javascript 12 | const express = require('express'); 13 | const createMiddleware = require('@apidevtools/swagger-express-middleware'); 14 | 15 | let app = express(); 16 | 17 | createMiddleware('PetStore.yaml', app, function(err, middleware) { 18 | // Add the Files middleware to the Express app 19 | app.use(middleware.files({ 20 | // Change the URL of the raw Swagger files 21 | rawFilesPath: '/my/custom/path' 22 | })); 23 | 24 | app.listen(8000, function() { 25 | console.log('Go to to http://localhost:8000/my/custom/path/PetStore.yaml'); 26 | }); 27 | }); 28 | ``` 29 | 30 | Run the above example and then browse to [http://localhost:8000/api-docs/](http://localhost:8000/api-docs/) and [http://localhost:8000/my/custom/path/PetStore.yaml](http://localhost:8000/my/custom/path/PetStore.yaml). The first URL will return the Swagger API in JSON. The second URL will return the raw [PetStore.yaml](https://github.com/APIDevTools/swagger-express-middleware/blob/master/samples/PetStore.yaml) file. Note that the second URL's path has been customized in the example code. 31 | 32 | 33 | Options 34 | -------------------------- 35 | ### `middleware.files(router, options)` 36 | This is the function you call to create the Files middleware. All of its parameters are optional. 37 | 38 | * __router__ (_optional_) - `express.App` or `express.Router`
39 | An [Express Application](http://expressjs.com/4x/api.html#application) or [Router](http://expressjs.com/4x/api.html#router) that will be used to determine settings (such as case-sensitivity and strict routing). 40 |

41 | All Swagger Express Middleware modules accept this optional first parameter. Rather than passing it to each middleware, you can just pass it to the [createMiddleware function](../exports/createMiddleware.md) (as shown in the example above) and all middleware will use it. 42 | 43 | * __options__ (_optional_) - `object`
44 | This parameter allows you to change the paths at which the files are served. It is an object with the following properties: 45 | 46 | | Property | Type | Default | Description | 47 | |:-----------------|:---------|:------------|:------------| 48 | | `useBasePath` | bool | false | If set to true, then the `apiPath` and `rawFilesPath` will be prepended with the Swagger API's `basePath`.

For example, if the `basePath` in the Swagger API is "_/api/v1_", then the Swagger JSON file would be served at "_/api/v1/api-docs/_" instead of "_/api-docs/_". 49 | | `apiPath` | string | /api-docs/ | The path that will serve the fully dereferenced Swagger API in JSON format. This file should work with any third-party tools, even if they don't support YAML, `$ref` pointers, or mutli-file Swagger APIs.

To disable serving this file, set the path to a falsy value (such as an empty string). 50 | | `rawFilesPath` | string | /api-docs/ | The path that will serve the raw Swagger API file(s).

For example, assume that your API consists of the following files:
- Main.yaml
- Users.json
- Products/Get-Products.yml
- Products/Post-Products.yaml

By default, each of these files would be served at:
- /api-docs/Main.yaml
- /api-docs/Users.json
- /api-docs/Products/Get-Products.yml
- /api-docs/Products/Post-Products.yaml

To disable serving raw Swagger files, set the path to a falsy value (such as an empty string). 51 | -------------------------------------------------------------------------------- /lib/mock/edit-resource.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | module.exports = { 4 | POST: mergeResource, 5 | PATCH: mergeResource, 6 | PUT: overwriteResource, 7 | DELETE: deleteResource 8 | }; 9 | 10 | const _ = require("lodash"); 11 | const Resource = require("../data-store/resource"); 12 | const util = require("../helpers/util"); 13 | 14 | /** 15 | * Creates or updates the REST resource at the URL. 16 | * 17 | * If the resource already exists, then the new data is merged with the existing data. 18 | * To completely overwrite the existing data, use PUT instead of POST or PATCH. 19 | * 20 | * @param {Request} req 21 | * @param {Response} res 22 | * @param {function} next 23 | * @param {DataStore} dataStore 24 | */ 25 | function mergeResource (req, res, next, dataStore) { 26 | let resource = createResource(req); 27 | 28 | // Save/Update the resource 29 | util.debug("Saving data at %s", resource.toString()); 30 | dataStore.save(resource, sendResponse(req, res, next, dataStore)); 31 | } 32 | 33 | /** 34 | * Creates or overwrites the REST resource at the URL. 35 | * 36 | * If the resource already exists, it is overwritten. 37 | * To merge with the existing data, use POST or PATCH instead of PUT. 38 | * 39 | * @param {Request} req 40 | * @param {Response} res 41 | * @param {function} next 42 | * @param {DataStore} dataStore 43 | */ 44 | function overwriteResource (req, res, next, dataStore) { 45 | let resource = createResource(req); 46 | 47 | // Delete the existing resource, if any 48 | dataStore.delete(resource, (err) => { 49 | if (err) { 50 | next(err); 51 | } 52 | else { 53 | // Save the new resource 54 | util.debug("Saving data at %s", resource.toString()); 55 | dataStore.save(resource, sendResponse(req, res, next, dataStore)); 56 | } 57 | }); 58 | } 59 | 60 | /** 61 | * Deletes the REST resource at the URL. 62 | * If the resource does not exist, then nothing happens. No error is thrown. 63 | * 64 | * @param {Request} req 65 | * @param {Response} res 66 | * @param {function} next 67 | * @param {DataStore} dataStore 68 | */ 69 | function deleteResource (req, res, next, dataStore) { // jshint ignore:line 70 | let resource = createResource(req); 71 | 72 | // Delete the resource 73 | dataStore.delete(resource, (err, deletedResource) => { 74 | // Respond with the deleted resource, if possible; otherwise, use the empty resource we just created. 75 | sendResponse(req, res, next, dataStore)(err, deletedResource || resource); 76 | }); 77 | } 78 | 79 | /** 80 | * Creates a {@link Resource} objects from the request's data. 81 | * 82 | * @param {Request} req 83 | * @returns {Resource} 84 | */ 85 | function createResource (req) { 86 | let resource = new Resource(req.path); 87 | 88 | if (!_.isEmpty(req.files)) { 89 | // Save file data too 90 | resource.data = _.extend({}, req.body, req.files); 91 | } 92 | else { 93 | resource.data = req.body; 94 | } 95 | 96 | return resource; 97 | } 98 | 99 | /** 100 | * Returns a function that sends the correct response for the operation. 101 | * 102 | * @param {Request} req 103 | * @param {Response} res 104 | * @param {function} next 105 | * @param {DataStore} dataStore 106 | */ 107 | function sendResponse (req, res, next, dataStore) { 108 | return function (err, resource) { 109 | if (!err) { 110 | util.debug("%s successfully created/edited/deleted", resource.toString()); 111 | res.swagger.lastModified = resource.modifiedOn; 112 | } 113 | 114 | // Set the response body (unless it's already been set by other middleware) 115 | if (err || res.body) { 116 | next(err); 117 | } 118 | else if (res.swagger.isCollection) { 119 | // Response body is the entire collection 120 | dataStore.getCollection(resource.collection, (err, collection) => { 121 | res.body = _.map(collection, "data"); 122 | next(err); 123 | }); 124 | } 125 | else { 126 | // Response body is the resource that was created/update/deleted 127 | res.body = resource.data; 128 | next(); 129 | } 130 | }; 131 | } 132 | -------------------------------------------------------------------------------- /samples/sample2.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | /************************************************************************************************** 3 | * This sample demonstrates a few more advanced features of Swagger Express Middleware, 4 | * such as setting a few options, initializing the mock data store, and adding custom middleware logic. 5 | **************************************************************************************************/ 6 | 7 | const util = require('util'); 8 | const path = require('path'); 9 | const express = require('express'); 10 | const swagger = require('@apidevtools/swagger-express-middleware'); 11 | const Middleware = swagger.Middleware; 12 | const MemoryDataStore = swagger.MemoryDataStore; 13 | const Resource = swagger.Resource; 14 | 15 | let app = express(); 16 | let middleware = new Middleware(app); 17 | 18 | // Initialize Swagger Express Middleware with our Swagger file 19 | let swaggerFile = path.join(__dirname, 'PetStore.yaml'); 20 | middleware.init(swaggerFile, (err) => { 21 | 22 | // Create a custom data store with some initial mock data 23 | let myDB = new MemoryDataStore(); 24 | myDB.save( 25 | new Resource('/pets/Lassie', { name: 'Lassie', type: 'dog', tags: ['brown', 'white']}), 26 | new Resource('/pets/Clifford', { name: 'Clifford', type: 'dog', tags: ['red', 'big']}), 27 | new Resource('/pets/Garfield', { name: 'Garfield', type: 'cat', tags: ['orange']}), 28 | new Resource('/pets/Snoopy', { name: 'Snoopy', type: 'dog', tags: ['black', 'white']}), 29 | new Resource('/pets/Hello%20Kitty', { name: 'Hello Kitty', type: 'cat', tags: ['white']}) 30 | ); 31 | 32 | // Enable Express' case-sensitive and strict options 33 | // (so "/pets/Fido", "/pets/fido", and "/pets/fido/" are all different) 34 | app.enable('case sensitive routing'); 35 | app.enable('strict routing'); 36 | 37 | app.use(middleware.metadata()); 38 | app.use(middleware.files( 39 | { 40 | // Override the Express App's case-sensitive and strict-routing settings 41 | // for the Files middleware. 42 | caseSensitive: false, 43 | strict: false 44 | }, 45 | { 46 | // Serve the Swagger API from "/swagger/api" instead of "/api-docs" 47 | apiPath: '/swagger/api', 48 | 49 | // Disable serving the "PetStore.yaml" file 50 | rawFilesPath: false 51 | } 52 | )); 53 | 54 | app.use(middleware.parseRequest( 55 | { 56 | // Configure the cookie parser to use secure cookies 57 | cookie: { 58 | secret: 'MySuperSecureSecretKey' 59 | }, 60 | 61 | // Don't allow JSON content over 100kb (default is 1mb) 62 | json: { 63 | limit: '100kb' 64 | }, 65 | 66 | // Change the location for uploaded pet photos (default is the system's temp directory) 67 | multipart: { 68 | dest: path.join(__dirname, 'photos') 69 | } 70 | } 71 | )); 72 | 73 | // These two middleware don't have any options (yet) 74 | app.use( 75 | middleware.CORS(), 76 | middleware.validateRequest() 77 | ); 78 | 79 | // Add custom middleware 80 | app.patch('/pets/:petName', (req, res, next) => { 81 | if (req.body.name !== req.params.petName) { 82 | // The pet's name has changed, so change its URL. 83 | // Start by deleting the old resource 84 | myDB.delete(new Resource(req.path), (err, pet) => { 85 | if (pet) { 86 | // Merge the new data with the old data 87 | pet.merge(req.body); 88 | } 89 | else { 90 | pet = req.body; 91 | } 92 | 93 | // Save the pet with the new URL 94 | myDB.save(new Resource('/pets', req.body.name, pet), (err, pet) => { 95 | // Send the response 96 | res.json(pet.data); 97 | }); 98 | }); 99 | } 100 | else { 101 | next(); 102 | } 103 | }); 104 | 105 | // The mock middleware will use our custom data store, 106 | // which we already pre-populated with mock data 107 | app.use(middleware.mock(myDB)); 108 | 109 | // Add a custom error handler that returns errors as HTML 110 | app.use((err, req, res, next) => { 111 | res.status(err.status); 112 | res.type('html'); 113 | res.send(util.format('

%d Error!

%s

', err.status, err.message)); 114 | }); 115 | 116 | app.listen(8000, () => { 117 | console.log('The Swagger Pet Store is now running at http://localhost:8000'); 118 | }); 119 | }); 120 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | Change Log 2 | ==================================================================================================== 3 | All notable changes will be documented in this file. 4 | Swagger Express Middleware adheres to [Semantic Versioning](http://semver.org/). 5 | 6 | 7 | 8 | [v4.0.0](https://github.com/APIDevTools/swagger-express-middleware/tree/v4.0.0) (2020-03-24) 9 | ---------------------------------------------------------------------------------------------------- 10 | 11 | - Moved from a [high security risk version](https://www.npmjs.com/package/multer/v/0.1.8) of multer 12 | to a more [up-to-date version](https://www.npmjs.com/package/multer/v/1.4.2) to remove a 13 | [high-security risk dependency](https://www.npmjs.com/advisories/1469) 14 | - This library change resulted in some API breaking changes to the library: 15 | * [CHANGE](https://github.com/APIDevTools/swagger-express-middleware/pull/165#discussion_r396014909): 16 | Files downloaded to disk are not saved with the extension in the name so Content-Type may need to 17 | be set manually or based on the File object since it can not be inferred from the downloaded filename 18 | * The File object created from multer is a little different: 19 | - For compatibility `extension` 20 | [is backfilled](https://github.com/APIDevTools/swagger-express-middleware/pull/165/files#diff-0819ff236dc445648af37b543cd2b958R63). 21 | - the `name` property on File is now `filename` and doesn't include a file extension 22 | - the `buffer` property on File is only present if `inMemory` or `storage: memoryStorage` is used. 23 | - the `truncated` property on File is no longer present. Instead, 24 | [an error is sent](https://github.com/expressjs/multer/blob/805170c61530e1f1cafd818c9b63d16a9dd46c36/lib/make-middleware.js#L84-L85) 25 | through the `next` function of middleware 26 | * multipart opts have changed significantly 27 | - [Old](https://github.com/expressjs/multer/tree/b3c444728277202d1f5f720cc7269883ff888386#options) 28 | vs [New](https://github.com/expressjs/multer/tree/v1.4.2#multeropts) 29 | - See [MemoryStorage](https://github.com/expressjs/multer/tree/v1.4.2#memorystorage) if you were previously using 30 | `inMemory: true`, though `inMemory` option [has been recreated](https://github.com/APIDevTools/swagger-express-middleware/pull/165#discussion_r396015204), 31 | it may be removed in the future. 32 | - See [Error handling](https://github.com/expressjs/multer/tree/v1.4.2#error-handling) for more info on how to 33 | recreate certain functionality. 34 | * As with previous versions extra files provided to swagger routes will 413 and any files coming 35 | in outside of the swagger routes will be passed through multer. The 413 functionality was recreated 36 | [like so](https://github.com/APIDevTools/swagger-express-middleware/pull/165#discussion_r396015249). 37 | * Indexed params are placed in exactly the index specified so `foo[0]=A&foo[2]=B` results in a param 38 | like `foo: ["A", undefined, "B"]` whereas previously it would have been `["A", "B"]`, 39 | [example here](https://github.com/APIDevTools/swagger-express-middleware/blob/244a1aa05e4bc21ee96b8a7973f98c76406ea4c5/test/specs/request-parser.spec.js#L668-L679) 40 | 41 | [Full Changelog](https://github.com/APIDevTools/swagger-express-middleware/compare/v3.0.1...v4.0.0) 42 | 43 | [v3.0.0](https://github.com/APIDevTools/swagger-express-middleware/tree/v3.0.0) (2020-03-15) 44 | ---------------------------------------------------------------------------------------------------- 45 | 46 | - Moved Swagger Express Middleware to the [@APIDevTools scope](https://www.npmjs.com/org/apidevtools) on NPM 47 | 48 | - The "swagger-express-middleware" NPM package is now just a wrapper around the scoped "@apidevtools/swagger-express-middleware" package 49 | 50 | [Full Changelog](https://github.com/APIDevTools/swagger-express-middleware/compare/v2.0.5...v3.0.0) 51 | 52 | 53 | 54 | [v2.0.0](https://github.com/APIDevTools/swagger-express-middleware/tree/v2.0.0) (2018-12-13) 55 | ---------------------------------------------------------------------------------------------------- 56 | 57 | ### Breaking Changes 58 | 59 | - Dropped support for Node 0.10 through 8.0.0 60 | 61 | [Full Changelog](https://github.com/APIDevTools/swagger-express-middleware/compare/v1.2.0...v2.0.0) 62 | 63 | 64 | 65 | [v1.0.0](https://github.com/APIDevTools/swagger-express-middleware/tree/v1.0.0) (2018-10-06) 66 | ---------------------------------------------------------------------------------------------------- 67 | 68 | Initial release 🎉 69 | -------------------------------------------------------------------------------- /docs/middleware/metadata.md: -------------------------------------------------------------------------------- 1 | Metadata middleware 2 | ============================ 3 | Annotates each request with all the relevant information from the Swagger definition. The path, the operation, the parameters, the security requirements - they're all easily accessible at `req.swagger`. 4 | 5 | 6 | Example 7 | -------------------------- 8 | This example uses the [PetStore.yaml](https://github.com/APIDevTools/swagger-express-middleware/blob/master/samples/PetStore.yaml) sample Swagger API. If you aren't familiar with using middleware in Express.js, then [read this first](http://expressjs.com/guide/using-middleware.html). 9 | 10 | ```javascript 11 | const util = require('util'); 12 | const express = require('express'); 13 | const createMiddleware = require('@apidevtools/swagger-express-middleware'); 14 | 15 | let app = express(); 16 | 17 | createMiddleware('PetStore.yaml', app, function(err, middleware) { 18 | // Add the Metadata middleware to the Express app 19 | app.use(middleware.metadata()); 20 | 21 | // Add middleware to display the Swagger metadata as HTML 22 | app.use(function(req, res, next) { 23 | res.type('html'); 24 | res.send(util.format('

%s has %d parameters

%s
', 25 | req.swagger.pathName, 26 | req.swagger.params.length, 27 | util.inspect(req.swagger.params) 28 | )); 29 | }); 30 | 31 | app.listen(8000, function() { 32 | console.log('Go to http://localhost:8000/pets/Fido/photos/12345'); 33 | }); 34 | }); 35 | ``` 36 | 37 | Run the above example and then try browsing to [http://localhost:8000/pets/Fido](http://localhost:8000/pets/Fido) and [http://localhost:8000/pets/Fido/photos/12345](http://localhost:8000/pets/Fido/photos/12345). You will see different metadata for each path. 38 | 39 | 40 | Options 41 | -------------------------- 42 | ### `middleware.metadata(router)` 43 | This is the function you call to create the metadata middleware. 44 | 45 | * __router__ (_optional_) - `express.App` or `express.Router`
46 | An [Express Application](http://expressjs.com/4x/api.html#application) or [Router](http://expressjs.com/4x/api.html#router) that will be used to determine settings (such as case-sensitivity and strict routing). 47 |

48 | All Swagger Express Middleware modules accept this optional first parameter. Rather than passing it to each middleware, you can just pass it to the [createMiddleware function](../exports/createMiddleware.md) (as shown in the example above) and all middleware will use it. 49 | 50 | 51 | API 52 | -------------------------- 53 | ### `req.swagger` 54 | A `swagger` property is added to the [Request object](http://expressjs.com/4x/api.html#request). It's an object with the following properties: 55 | 56 | | Property | Type | Description | 57 | |:-----------------|:-----------------|:------------| 58 | | `api` | [Swagger Object](https://github.com/OAI/OpenAPI-Specification/blob/master/versions/2.0.md#swagger-object) | The complete Swagger API object. If the Swagger API has a `basePath` and the current request is not within that path, then this property is `null`. 59 | | `pathName` | string | The Swagger path that corresponds to the current HTTP request. If the current request does not match any paths in the Swagger API, then this property is an empty string.

For example, the "_/pets/{petName}/photos/{id}_" Swagger path would match a request to "_/pets/Fido/photos/123_". 60 | | `path` | [Path Object](https://github.com/OAI/OpenAPI-Specification/blob/master/versions/2.0.md#path-item-object) | The Swagger path object that corresponds to the current HTTP request, or `null` if the request does not match any path in the Swagger API. 61 | | `operation` | [Operation Object](https://github.com/OAI/OpenAPI-Specification/blob/master/versions/2.0.md#operation-object) | The Swagger operation object that corresponds to the current HTTP request, or `null` if the request does not match any operation in the Swagger API. 62 | | `params` | array of [Parameter Objects](https://github.com/OAI/OpenAPI-Specification/blob/master/versions/2.0.md#parameter-object) | The Swagger parameter objects that correspond to the current HTTP request. The array is empty if there are no parameters. These are just the parameter _definitions_ from the API, not the _values_ for the current request. See the [Parse Request middleware](parseRequest.md) for parameter values. 63 | | `security` | array of [Security Requirement Objects](https://github.com/OAI/OpenAPI-Specification/blob/master/versions/2.0.md#securityRequirementObject) | The security requirement objects that correspond to the current HTTP request. The array is empty if there are no security requirements. These are just the security _definitions_ from the API, not any validated or authenticated values. See the [Validate Request middleware](validateRequest.md) for security validation. 64 | -------------------------------------------------------------------------------- /test/fixtures/helper.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Helper methods for working with Express & Supertest 3 | */ 4 | "use strict"; 5 | 6 | const _ = require("lodash"); 7 | const express = require("express"); 8 | const supertest = require("supertest"); 9 | const sinon = require("sinon"); 10 | 11 | /** 12 | * Creates and configures an Express application. 13 | */ 14 | exports.express = function (middleware) { 15 | let app = express(); 16 | app.set("env", "test"); // Turns on enhanced debug/error info 17 | 18 | _.each(arguments, (middleware) => { 19 | app.use(middleware); 20 | }); 21 | 22 | return app; 23 | }; 24 | 25 | /** 26 | * Creates and configures an Express Router. 27 | */ 28 | exports.router = function (middleware) { 29 | let args, opts; 30 | if (_.isObject(middleware)) { 31 | opts = middleware; 32 | args = []; 33 | } 34 | else { 35 | opts = undefined; 36 | args = arguments; 37 | } 38 | 39 | let router = express.Router(opts); 40 | 41 | _.each(args, (middleware) => { 42 | router.use(middleware); 43 | }); 44 | 45 | return router; 46 | }; 47 | 48 | /** 49 | * Creates and configures a Supertest instance. 50 | */ 51 | exports.supertest = function (middleware) { 52 | return supertest(middleware); 53 | }; 54 | 55 | /** 56 | * HTTP response code for successful tests 57 | */ 58 | exports.testPassed = 299; 59 | 60 | /** 61 | * HTTP response code for failed tests 62 | */ 63 | exports.testFailed = 598; 64 | 65 | /** 66 | * Spies on the given middleware function and captures any errors. 67 | * If the middleware doesn't call `next()`, then a successful response is sent. 68 | */ 69 | exports.spy = function (fn) { 70 | exports.spy.error = null; 71 | 72 | if (fn.length === 4) { 73 | return function (err, req, res, next) { 74 | tryCatch(err, req, res, next); 75 | }; 76 | } 77 | else { 78 | return function (req, res, next) { 79 | tryCatch(null, req, res, next); 80 | }; 81 | } 82 | 83 | function tryCatch (err, req, res, next) { 84 | try { 85 | let spy = sinon.spy(); 86 | if (err) { 87 | fn(err, req, res, spy); 88 | } 89 | else { 90 | fn(req, res, spy); 91 | } 92 | 93 | if (spy.called) { 94 | next(spy.firstCall.args[0]); 95 | } 96 | else { 97 | res.sendStatus(exports.testPassed); 98 | } 99 | } 100 | catch (e) { 101 | exports.spy.error = e; 102 | res.sendStatus(exports.testFailed); 103 | } 104 | } 105 | }; 106 | 107 | /** 108 | * Checks the results of any {@link env#spy} middleware, and fails the test if an error occurred. 109 | */ 110 | exports.checkSpyResults = function (done) { 111 | return function (err, res) { 112 | if (err) { 113 | done(err); 114 | } 115 | else if (exports.spy.error) { 116 | done(exports.spy.error); 117 | } 118 | else if (res.status !== exports.testPassed) { 119 | let serverError; 120 | if (res.error) { 121 | serverError = new Error(res.error.message + 122 | _.unescape(res.error.text).replace(/
/g, "\n").replace(/ /g, " ")); 123 | } 124 | else { 125 | serverError = new Error("The test failed, but no server error was returned"); 126 | } 127 | done(serverError); 128 | } 129 | else { 130 | done(); 131 | } 132 | }; 133 | }; 134 | 135 | /** 136 | * Checks the results of a Supertest request, and fails the test if an error occurred. 137 | * 138 | * @param {function} done - The `done` callback for the test. This will be called if an error occurs. 139 | * @param {function} next - The next code to run after checking the results (if no error occurs) 140 | */ 141 | exports.checkResults = function (done, next) { 142 | return function (err, res) { 143 | if (res && res.status >= 400) { 144 | let serverError; 145 | if (res.error) { 146 | serverError = new Error(res.error.message + 147 | _.unescape(res.error.text).replace(/
/g, "\n").replace(/ /g, " ")); 148 | } 149 | else { 150 | serverError = new Error("The test failed, but no server error was returned"); 151 | } 152 | done(serverError); 153 | } 154 | else if (err) { 155 | done(err); 156 | } 157 | else { 158 | if (_.isFunction(next)) { 159 | next(res); 160 | } 161 | else { 162 | done(); 163 | } 164 | } 165 | }; 166 | }; 167 | 168 | /** 169 | * The new superagent library handles the HTTP body property differently depending on the 170 | * HTTP verb that is used. 171 | */ 172 | exports.processMethod = function (request, method, expectedResult) { 173 | if (method === "head") { 174 | request.expect(200, undefined); 175 | } 176 | else if (method === "options") { 177 | request.expect(200, ""); 178 | } 179 | else { 180 | request.expect(200, expectedResult); 181 | } 182 | }; 183 | -------------------------------------------------------------------------------- /lib/request-parser.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | module.exports = requestParser; 4 | 5 | const _ = require("lodash"); 6 | const bodyParser = require("body-parser"); 7 | const cookieParser = require("cookie-parser"); 8 | const multer = require("multer"); 9 | const tmp = require("tmp"); 10 | const path = require("path"); 11 | const util = require("./helpers/util"); 12 | 13 | // Clean-up the temp directory, even if the app crashes 14 | tmp.setGracefulCleanup(); 15 | 16 | /** 17 | * Generates a middleware that parses multipart/form-data 18 | */ 19 | function generateMultipartFormDataMiddleware (options) { 20 | options.allowAll = options.allowAll === undefined ? true : options.allowAll; 21 | 22 | if (options.inMemory && options.storage === undefined) { 23 | options.storage = multer.memoryStorage(); 24 | } 25 | 26 | const uploader = multer(options); 27 | return function multipartFormData (req, res, next) { 28 | // Compatibility with old multer 29 | req.body = req.body || {}; 30 | req.files = req.files || {}; 31 | 32 | // Get all the "file" params 33 | if (util.isSwaggerRequest(req) && req.swagger.params.length > 0) { 34 | const fileFields = []; 35 | req.swagger.params.forEach((param) => { 36 | if (param.in === "formData" && param.type === "file") { 37 | fileFields.push({ 38 | name: param.name, 39 | maxCount: 1 40 | }); 41 | } 42 | }); 43 | 44 | uploader.fields(fileFields)(req, res, (err) => { 45 | if (err && err.code === "LIMIT_UNEXPECTED_FILE") { 46 | next(); // let request-validator handle 47 | } 48 | else { 49 | next(err); 50 | } 51 | }); 52 | } 53 | else { 54 | // Handle the multipart/form-data (even if it doesn't have any file fields) 55 | uploader.any()(req, res, next); 56 | } 57 | }; 58 | } 59 | 60 | function multerCompatability (req, res, next) { 61 | function standardize (file) { 62 | if (file.originalname) { 63 | file.extension = path.extname(file.originalname).slice(1); 64 | } 65 | } 66 | 67 | if (req.files) { 68 | if (req.files.length > 0) { 69 | req.files.forEach((file) => standardize(file)); 70 | } 71 | if (Object.keys(req.files).length > 0) { 72 | Object.keys(req.files).forEach((filekey) => { 73 | const filearray = Array.from(req.files[filekey]); 74 | filearray.forEach((fileRecord) => standardize(fileRecord)); 75 | }); 76 | } 77 | } 78 | next(); 79 | } 80 | 81 | /** 82 | * Parses the HTTP request into useful objects. 83 | * This middleware populates {@link Request#params}, {@link Request#headers}, {@link Request#cookies}, 84 | * {@link Request#signedCookies}, {@link Request#query}, {@link Request#body}, and {@link Request#files}. 85 | * 86 | * @param {requestParser.defaultOptions} [options] 87 | * @returns {function[]} 88 | */ 89 | function requestParser (options) { 90 | // Override default options 91 | options = _.merge({}, requestParser.defaultOptions, options); 92 | 93 | return [ 94 | cookieParser(options.cookie.secret, options.cookie), 95 | bodyParser.json(options.json), 96 | bodyParser.text(options.text), 97 | bodyParser.urlencoded(options.urlencoded), 98 | bodyParser.raw(options.raw), 99 | generateMultipartFormDataMiddleware(options.multipart), 100 | multerCompatability 101 | ]; 102 | } 103 | 104 | requestParser.defaultOptions = { 105 | /** 106 | * Cookie parser options 107 | * (see https://github.com/expressjs/cookie-parser#cookieparsersecret-options) 108 | */ 109 | cookie: { 110 | secret: undefined 111 | }, 112 | 113 | /** 114 | * JSON body parser options 115 | * (see https://github.com/expressjs/body-parser#bodyparserjsonoptions) 116 | */ 117 | json: { 118 | limit: "1mb", 119 | type: ["json", "*/json", "+json"] 120 | }, 121 | 122 | /** 123 | * Plain-text body parser options 124 | * (see https://github.com/expressjs/body-parser#bodyparsertextoptions) 125 | */ 126 | text: { 127 | limit: "1mb", 128 | type: ["text/*"] 129 | }, 130 | 131 | /** 132 | * URL-encoded body parser options 133 | * (see https://github.com/expressjs/body-parser#bodyparserurlencodedoptions) 134 | */ 135 | urlencoded: { 136 | extended: true, 137 | limit: "1mb" 138 | }, 139 | 140 | /** 141 | * Raw body parser options 142 | * (see https://github.com/expressjs/body-parser#bodyparserrawoptions) 143 | */ 144 | raw: { 145 | limit: "5mb", 146 | type: "application/*" 147 | }, 148 | 149 | /** 150 | * Multipart form data parser options 151 | * (see https://github.com/expressjs/multer#options) 152 | */ 153 | multipart: { 154 | // By default, use the system's temp directory, and clean-up when the app exits 155 | dest: tmp.dirSync({ prefix: "swagger-express-middleware-", unsafeCleanup: true }).name, 156 | 157 | // the Swagger spec does not allow multiple file params with same name 158 | putSingleFilesInArray: false 159 | } 160 | }; 161 | -------------------------------------------------------------------------------- /test/specs/json-schema/parse/parse-byte.spec.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const expect = require("chai").expect; 4 | const helper = require("./helper"); 5 | 6 | describe("JSON Schema - parse byte params", () => { 7 | 8 | it("should parse a valid byte param", (done) => { 9 | let schema = { 10 | type: "string", 11 | format: "byte", 12 | multipleOf: 5, 13 | minimum: 40, 14 | exclusiveMinimum: true, 15 | maximum: 45, 16 | exclusiveMaximum: false 17 | }; 18 | 19 | let express = helper.parse(schema, 45, done); 20 | 21 | express.post("/api/test", helper.spy((req) => { 22 | expect(req.header("Test")).to.equal(45); 23 | })); 24 | }); 25 | 26 | it("should parse an optional, unspecified byte param", (done) => { 27 | let schema = { 28 | type: "string", 29 | format: "byte" 30 | }; 31 | 32 | let express = helper.parse(schema, undefined, done); 33 | 34 | express.post("/api/test", helper.spy((req) => { 35 | expect(req.header("Test")).to.equal(undefined); 36 | })); 37 | }); 38 | 39 | it("should parse the default value if no value is specified", (done) => { 40 | let schema = { 41 | type: "string", 42 | format: "byte", 43 | default: 255 44 | }; 45 | 46 | let express = helper.parse(schema, undefined, done); 47 | 48 | express.post("/api/test", helper.spy((req) => { 49 | expect(req.header("Test")).to.equal(255); 50 | })); 51 | }); 52 | 53 | it("should parse the default value if the specified value is blank", (done) => { 54 | let schema = { 55 | type: "string", 56 | format: "byte", 57 | default: 1 58 | }; 59 | 60 | let express = helper.parse(schema, "", done); 61 | 62 | express.post("/api/test", helper.spy((req) => { 63 | expect(req.header("Test")).to.equal(1); 64 | })); 65 | }); 66 | 67 | it("should throw an error if the value is blank", (done) => { 68 | let schema = { 69 | type: "string", 70 | format: "byte" 71 | }; 72 | 73 | let express = helper.parse(schema, "", done); 74 | 75 | express.use("/api/test", helper.spy((err, req, res, next) => { 76 | expect(err).to.be.an.instanceOf(Error); 77 | expect(err.status).to.equal(400); 78 | expect(err.message).to.contain('"" is not a properly-formatted whole number'); 79 | })); 80 | }); 81 | 82 | it("should throw an error if the value is not a valid byte", (done) => { 83 | let schema = { 84 | type: "string", 85 | format: "byte" 86 | }; 87 | 88 | let express = helper.parse(schema, "hello world", done); 89 | 90 | express.use("/api/test", helper.spy((err, req, res, next) => { 91 | expect(err).to.be.an.instanceOf(Error); 92 | expect(err.status).to.equal(400); 93 | expect(err.message).to.contain('"hello world" is not a properly-formatted whole number'); 94 | })); 95 | }); 96 | 97 | it("should throw an error if the value fails schema validation", (done) => { 98 | let schema = { 99 | type: "string", 100 | format: "byte", 101 | multipleOf: 3 102 | }; 103 | 104 | let express = helper.parse(schema, "14", done); 105 | 106 | express.use("/api/test", helper.spy((err, req, res, next) => { 107 | expect(err).to.be.an.instanceOf(Error); 108 | expect(err.status).to.equal(400); 109 | expect(err.message).to.contain("Value 14 is not a multiple of 3"); 110 | })); 111 | }); 112 | 113 | it("should throw an error if the value is above the byte maximum", (done) => { 114 | let schema = { 115 | type: "string", 116 | format: "byte" 117 | }; 118 | 119 | let express = helper.parse(schema, "256", done); 120 | 121 | express.use("/api/test", helper.spy((err, req, res, next) => { 122 | expect(err).to.be.an.instanceOf(Error); 123 | expect(err.status).to.equal(400); 124 | expect(err.message).to.contain('"256" is not a valid byte. Must be between 0 and 255'); 125 | })); 126 | }); 127 | 128 | it("should throw an error if the value is below the byte minimum", (done) => { 129 | let schema = { 130 | type: "string", 131 | format: "byte" 132 | }; 133 | 134 | let express = helper.parse(schema, "-5", done); 135 | 136 | express.use("/api/test", helper.spy((err, req, res, next) => { 137 | expect(err).to.be.an.instanceOf(Error); 138 | expect(err.status).to.equal(400); 139 | expect(err.message).to.contain('"-5" is not a valid byte. Must be between 0 and 255'); 140 | })); 141 | }); 142 | 143 | it("should throw an error if required and not specified", (done) => { 144 | let schema = { 145 | type: "string", 146 | format: "byte", 147 | required: true 148 | }; 149 | 150 | let express = helper.parse(schema, undefined, done); 151 | 152 | express.use("/api/test", helper.spy((err, req, res, next) => { 153 | expect(err).to.be.an.instanceOf(Error); 154 | expect(err.status).to.equal(400); 155 | expect(err.message).to.contain('Missing required header parameter "Test"'); 156 | })); 157 | }); 158 | }); 159 | -------------------------------------------------------------------------------- /lib/path-parser.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | module.exports = pathParser; 4 | 5 | const _ = require("lodash"); 6 | const util = require("./helpers/util"); 7 | const paramParser = require("./param-parser"); 8 | 9 | /** 10 | * Parses Swagger path parameters in the HTTP request. 11 | * This middleware populates {@link Request#params} and {@link Request#pathParams}. 12 | * 13 | * NOTE: Express uses a special type of middleware for parsing path parameters. 14 | * This middleware must be registered using {@link Router#param} rather than {@link Router#use}, {@link Router#get}, etc. 15 | * In addition, path-parsing middleware must be registered for EACH path parameter in the Swagger API. 16 | * See http://expressjs.com/4x/api.html#router.param for more info. 17 | * 18 | * @param {MiddlewareContext} context 19 | * @param {express#Router} [router] 20 | * @returns {function[]} 21 | */ 22 | function pathParser (context, router) { 23 | router = util.isExpressRouter(router) ? router : context.router; 24 | 25 | if (util.isExpressRouter(router)) { 26 | // This is special path-param middleware, which sets `req.params` 27 | registerPathParamMiddleware(); 28 | 29 | // If the API changes, register any new path-params 30 | context.on("change", registerPathParamMiddleware); 31 | } 32 | else { 33 | util.debug( 34 | "WARNING! An Express Router/Application was not passed to the requestParser middleware. " + 35 | "req.params will not be parsed. Use req.pathParams instead." 36 | ); 37 | } 38 | 39 | // This is normal middleware, which sets `req.pathParams` 40 | return [parsePathParams]; 41 | 42 | /** 43 | * Registers middleware to parse path parameters. 44 | */ 45 | function registerPathParamMiddleware () { 46 | let pathParams = getAllPathParamNames(); 47 | 48 | pathParams.forEach((param) => { 49 | if (!alreadyRegistered(param)) { 50 | router.param(param, pathParamMiddleware); 51 | } 52 | }); 53 | } 54 | 55 | /** 56 | * Returns the unique names of all path params in the Swagger API. 57 | * 58 | * @returns {string[]} 59 | */ 60 | function getAllPathParamNames () { 61 | let params = []; 62 | 63 | function addParam (param) { 64 | if (param.in === "path") { 65 | params.push(param.name); 66 | } 67 | } 68 | 69 | if (context.api) { 70 | _.each(context.api.paths, (path) => { 71 | // Add each path parameter 72 | _.each(path.parameters, addParam); 73 | 74 | // Add each operation parameter 75 | _.each(path, (operation) => { 76 | _.each(operation.parameters, addParam); 77 | }); 78 | }); 79 | } 80 | 81 | return _.uniq(params); 82 | } 83 | 84 | /** 85 | * Determines whether we've already registered path-param middleware for the given parameter. 86 | * 87 | * @param {string} paramName 88 | * @returns {boolean} 89 | */ 90 | function alreadyRegistered (paramName) { 91 | let params = router.params; 92 | if (!params && router._router) { 93 | params = router._router.params; 94 | } 95 | 96 | return params && params[paramName] && 97 | (params[paramName].indexOf(pathParamMiddleware) >= 0); 98 | } 99 | 100 | /** 101 | * This is a special type of Express middleware that specifically parses path parameters and sets `req.params`. 102 | * See http://expressjs.com/4x/api.html#router.param 103 | */ 104 | function pathParamMiddleware (req, res, next, value, name) { 105 | if (req.pathParams) { 106 | // Path parameters have already been parsed by 107 | req.params[name] = req.pathParams[name] || req.params[name]; 108 | } 109 | 110 | next(); 111 | } 112 | 113 | /** 114 | * Parses all Swagger path parameters and sets `req.pathParams`. 115 | * NOTE: This middleware cannot set `req.params`. That requires special path-param middleware (see above) 116 | */ 117 | function parsePathParams (req, res, next) { 118 | if (util.isSwaggerRequest(req)) { 119 | req.pathParams = {}; 120 | 121 | if (req.swagger.pathName.indexOf("{") >= 0) { 122 | // Convert the Swagger path to a RegExp 123 | let paramNames = []; 124 | let pathPattern = req.swagger.pathName.replace(util.swaggerParamRegExp, (match, paramName) => { 125 | paramNames.push(paramName); 126 | return "([^\/]+)"; 127 | }); 128 | 129 | // Exec the RegExp to get the path param values from the URL 130 | let values = new RegExp(pathPattern + "\/?$", "i").exec(req.path); 131 | 132 | // Parse each path param 133 | for (let i = 1; i < values.length; i++) { 134 | let paramName = paramNames[i - 1]; 135 | let paramValue = decodeURIComponent(values[i]); 136 | let param = _.find(req.swagger.params, { in: "path", name: paramName }); 137 | 138 | util.debug(' Parsing the "%s" path parameter', paramName); 139 | req.pathParams[paramName] = paramParser.parseParameter(param, paramValue, param); 140 | } 141 | } 142 | } 143 | 144 | next(); 145 | } 146 | } 147 | -------------------------------------------------------------------------------- /docs/walkthroughs/running.md: -------------------------------------------------------------------------------- 1 | Sample 1 Walkthrough 2 | ============================ 3 | * __Running the sample__ 4 | * [JavaScript Walkthrough](javascript.md) 5 | * [YAML Walkthrough](yaml.md) 6 | 7 | 8 | Overview 9 | -------------------------- 10 | This sample demonstrates the most simplistic usage of Swagger Express Middleware. It simply creates a new Express Application and adds all of the Swagger middleware without changing any options, and without adding any custom middleware. 11 | 12 | This sample also demonstrates a __fully-functional mock__ without any custom middleware or logic. 13 | 14 | 15 | Run the Sample 16 | -------------------------- 17 | To run the sample, you'll need to `cd` to the `samples` directory and then run [sample1.js](https://github.com/APIDevTools/swagger-express-middleware/blob/master/samples/sample1.js) in Node. Here's how to do that: 18 | 19 | ```bash 20 | cd node_modules/@apidevtools/swagger-express-middleware/samples 21 | node sample1.js 22 | ``` 23 | 24 | 25 | ### Open Your Browser 26 | You should see a message telling you that the Swagger Pet Store is now running at [http://localhost:8000](http://localhost:8000). Open that link in your web browser, and you should see the following page: 27 | 28 | ![Screenshot](../img/samples.png) 29 | 30 | This page is just a simple GUI that lets you perform _some_ of the operations in the [Swagger Pet Store API](https://github.com/APIDevTools/swagger-express-middleware/blob/master/samples/PetStore.yaml). You can add, edit, and delete pets, as well as add pet photos. There's also helpful links to the raw YAML and JSON data. 31 | 32 | > **TIP:** Browsers differ in how they handle .yaml and .json URLs. Some browsers open the files as plain-text in the browser window. Others prompt you to download the file or open it in your default text editor. 33 | 34 | 35 | ### Adding/Editing a Pet 36 | The big text box in the middle of the page lets you enter the raw JSON data for a pet. The `name` and `type` properties are required. Everything else is optional. Try adding a few pets, and then click the "_All Pets_" button to see all the persisted data. Also try deleting some pets, and then click "_All Pets_" again to confirm that the pets were removed. 37 | 38 | Each time you save or delete a pet, you'll see a pop-up window showing you the response from the server. If there's something wrong with your JSON data, then you'll see an error message explaining the problem. If all goes well, then the server will send the back the JSON data for the pet that was created, updated, or deleted. This is _not necessarily_ the same as the JSON data you sent to the server, especially in the case of an update or delete. 39 | 40 | > **TIP:** Swagger Express Middleware will automatically determine that the `name` property is the key field for a pet. That's how it determines whether the "_Save_" button causes an insert or an update. It's also how it determines which pet to delete when you click the "_Delete_" button. How does this work? Read the [Mock middleware documentation](../middleware/mock.md). 41 | 42 | 43 | ### Adding/Viewing Photos 44 | The form at the bottom of the page lets you add pet photos. Use the file-picker to select an image file on your computer, then add a friendly name and description of the image. When you click "_Add Photo_", the photo will be saved for the current pet (based on the `name` property in the big text box above), and you will see a pop-up window showing you the response from the server. Just like before, if there's something wrong with your data, then you'll see an error message; otherwise, you'll see the JSON data for your newly-saved photo. 45 | 46 | You can click the "_Photos_" button to view all the photo data for the current pet. This is _not_ the actual images, just the JSON data, such as file names, sizes, MIME types, etc. However, you'll see that each image is assigned a unique `id` property. You can use this ID to view an individual photo (e.g. [http://localhost:8000/pets/Fido/photos/123456789](http://localhost:8000/pets/Fido/photos/123456789)) 47 | 48 | > **TIP:** So how does each photo automatically get a unique ID assigned? The [Swagger Pet Store API](https://github.com/APIDevTools/swagger-express-middleware/blob/master/samples/PetStore.yaml) defines an optional, numeric `id` parameter for the `POST /pets/{petName}/photos` operation, and our HTML form didn't supply a value for that parameter. The mock middleware determines that the `id` property is the key property, so it generates a unique value for it. For more information, read the [Mock middleware documentation](../middleware/mock.md#how-primary-keys-are-determined). 49 | 50 | 51 | ### More Advanced Stuff 52 | This HTML page is just a simple interface on top of the [Swagger Pet Store API](https://github.com/APIDevTools/swagger-express-middleware/blob/master/samples/PetStore.yaml), and there are several other operations in the API that aren't exposed on this page. We encourage you to use a tool such as [Postman](http://www.getpostman.com) or [curl](http://curl.haxx.se) to further experiment with the API and try a few more advanced REST operations. 53 | 54 | [![Postman screenshot](../img/postman.png)](http://www.getpostman.com) 55 | 56 | 57 | Sample 1 Walkthrough 58 | -------------------------- 59 | * __Running the sample__ 60 | * [JavaScript Walkthrough](javascript.md) 61 | * [YAML Walkthrough](yaml.md) 62 | -------------------------------------------------------------------------------- /docs/middleware/CORS.md: -------------------------------------------------------------------------------- 1 | CORS middleware 2 | ============================ 3 | Adds the appropriate CORS headers to each request and automatically responds to CORS preflight requests, all in compliance with your Swagger API definition. 4 | 5 | If you aren't familiar with how CORS works, then [here's a good explanation](https://developer.mozilla.org/en-US/docs/Web/HTTP/Access_control_CORS). Don't worry if it seems really complicated — the CORS middleware handles it all for you. 6 | 7 | 8 | Example 9 | -------------------------- 10 | This example uses the [PetStore.yaml](https://github.com/APIDevTools/swagger-express-middleware/blob/master/samples/PetStore.yaml) sample Swagger API. If you aren't familiar with using middleware in Express.js, then [read this first](http://expressjs.com/guide/using-middleware.html). 11 | 12 | ```javascript 13 | const util = require('util'); 14 | const express = require('express'); 15 | const createMiddleware = require('@apidevtools/swagger-express-middleware'); 16 | 17 | let app = express(); 18 | 19 | createMiddleware('PetStore.yaml', app, function(err, middleware) { 20 | app.use(middleware.metadata()); 21 | app.use(middleware.CORS()); 22 | 23 | // Show the CORS headers as HTML 24 | app.use(function(req, res, next) { 25 | res.send('
' + util.inspect(res._headers) + '
'); 26 | }); 27 | 28 | app.listen(8000, function() { 29 | console.log('Go to http://localhost:8000/pets'); 30 | }); 31 | }); 32 | ``` 33 | 34 | Run the above example and then browse to [http://localhost:8000/pets](http://localhost:8000/pets) and [http://localhost:8000/pets/Fido](http://localhost:8000/pets/Fido) and [http://localhost:8000/pets/Fido/photos](http://localhost:8000/pets/Fido/photos). You will see that the HTTP headers are set differently for each URL, based on the Swagger API. 35 | 36 | If you use a tool such as [Postman](http://getpostman.com) or [curl](http://curl.haxx.se/) to send CORS request headers (e.g. `Origin`, `Access-Control-Request-Method`, `Access-Control-Request-Headers`, etc.), then you'll notice that the CORS middleware will adjust the corresponding HTTP response headers (e.g. `Access-Control-Allow-Origin`, `Access-Control-Allow-Credentials`, `Access-Control-Allow-Headers`, etc.). 37 | 38 | 39 | Dependencies 40 | -------------------------- 41 | The CORS middleware requires the [Metadata middleware](metadata.md) to come before it in the middleware pipeline (as shown in the example above). 42 | 43 | 44 | How CORS headers are set 45 | -------------------------- 46 | The CORS middleware automatically sets the following HTTP headers on _every_ request: 47 | 48 | | Header Name | Value Assigned 49 | |:-----------------------------------|:----------------- 50 | | `Access-Control-Allow-Origin` | If the HTTP request includes an `Origin` header, then that value is echoed back; otherwise, a wildcard (`*`) is sent. 51 | | `Access-Control-Allow-Methods` | If the HTTP request matches a path in your Swagger API, then the methods defined for that path are returned. If the request _doesn't_ match a Swagger path, then the `Access-Control-Request-Method` header is echoed back. If that header is not set, then _all_ HTTP methods are sent. 52 | | `Access-Control-Allow-Headers` | If the HTTP request includes an `Access-Control-Request-Headers` header, then that value is echoed back; otherwise, an empty value is returned. 53 | | `Access-Control-Allow-Max-Age` | This header is always set to zero, which means CORS preflight requests will not be cached. This is especially useful for development/debugging, but you may want to set it to a higher value for production. 54 | | `Access-Control-Allow-Credentials` | If the `Access-Control-Allow-Origin` is a wildcard (`*`), then `false` is sent; otherwise, `true` is sent.

**NOTE:** This behavior is required by the CORS spec. Wildcarded origins cannot allow credentials. 55 | | `Vary` | If the `Access-Control-Allow-Origin` is _not_ a wildcard, then `Origin` is added to the `Vary` response header.

**NOTE:** This behavior is required by the CORS spec. It indicates to clients that server responses will differ based on the value of the `Origin` request header. 56 | 57 | 58 | ### Customizing CORS headers 59 | As shown above, the CORS middleware tries to determine the best value for each CORS header based on the HTTP request from the client and the structure of your Swagger API, but you can override the value for any header if you want. 60 | 61 | To override a header's value, just specify a `default` value in your Swagger API. You can do this for a specific operation, or for an entire path by using the `options` operation. For example, in the following Swagger API, the `Access-Control-Allow-Headers` and `Access-Control-Allow-Origin` headers have been customized for all operations on the "_/pets/{petName}_" path, and the `Access-Control-Max-Age` header has been customized only for the `get` operation. 62 | 63 | ```yaml 64 | /pets/{petName}: 65 | options: 66 | responses: 67 | default: 68 | description: CORS headers for all operations 69 | headers: 70 | Access-Control-Allow-Origin: 71 | type: string 72 | default: http://www.company.com 73 | Access-Control-Allow-Headers: 74 | type: string 75 | default: X-UA-Compatible, X-XSS-Protection 76 | 77 | get: 78 | responses: 79 | default: 80 | description: CORS header for this operation 81 | headers: 82 | Access-Control-Max-Age: 83 | type: number 84 | default: 60 85 | ``` 86 | -------------------------------------------------------------------------------- /lib/file-server.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | module.exports = fileServer; 4 | 5 | const _ = require("lodash"); 6 | const path = require("path"); 7 | const util = require("./helpers/util"); 8 | 9 | /** 10 | * Serves the Swagger API file(s) in JSON and YAML formats, 11 | * so they can be used with third-party front-end tools like Swagger UI and Swagger Editor. 12 | * 13 | * @param {MiddlewareContext} context 14 | * @param {express#Router} [router] 15 | * @param {fileServer.defaultOptions} [options] 16 | * @returns {function[]} 17 | */ 18 | function fileServer (context, router, options) { 19 | router = router || context.router; 20 | 21 | // Override default options 22 | options = _.merge({}, fileServer.defaultOptions, options); 23 | 24 | // Get the directory of the main Swagger file 25 | let baseDir = path.dirname(decodeURI(context.parser.$refs.paths()[0])); 26 | 27 | // Only return the middleware that's allowed 28 | let middleware = []; 29 | options.apiPath && middleware.push(serveDereferencedSwaggerFile); 30 | options.rawFilesPath && middleware.push(serveRawSwaggerFiles); 31 | return middleware; 32 | 33 | /** 34 | * Serves the fully-dereferenced Swagger API in JSON format. 35 | */ 36 | function serveDereferencedSwaggerFile (req, res, next) { 37 | if (req.method === "GET" || req.method === "HEAD") { 38 | let configPath = getConfiguredPath(options.apiPath); 39 | configPath = util.normalizePath(configPath, router); 40 | let reqPath = util.normalizePath(req.path, router); 41 | 42 | if (reqPath === configPath) { 43 | if (context.api) { 44 | util.debug("%s %s => Sending the Swagger API as JSON", req.method, req.path); 45 | res.json(context.api); 46 | } 47 | else { 48 | util.warn("WARNING! The Swagger API is empty. Sending an HTTP 500 response to %s %s", req.method, req.path); 49 | res.status(500).json({}); 50 | } 51 | return; 52 | } 53 | } 54 | 55 | next(); 56 | } 57 | 58 | /** 59 | * Serves the raw Swagger file(s). 60 | */ 61 | function serveRawSwaggerFiles (req, res, next) { 62 | if (req.method === "GET" || req.method === "HEAD") { 63 | let configPath = getConfiguredPath(options.rawFilesPath); 64 | configPath = util.normalizePath(configPath, router); 65 | let reqPath = util.normalizePath(req.path, router); 66 | 67 | if (_.startsWith(reqPath, configPath) && context.parser && context.parser.$refs) { 68 | // Get the normalized path of the requested file, relative to the baseDir 69 | let relativePath = req.path.substring(configPath.length); 70 | if (_.startsWith(relativePath, "/")) { 71 | relativePath = relativePath.substring(1); 72 | } 73 | relativePath = path.normalize(util.normalizePath(relativePath, router)); 74 | 75 | // See if any of the Swagger files match this path 76 | let filePath = _.find(context.parser.$refs.paths(), (file) => { 77 | let relativeFile = path.relative(baseDir, file); 78 | relativeFile = util.normalizePath(relativeFile, router); 79 | return relativeFile === relativePath; 80 | }); 81 | 82 | if (filePath) { 83 | // Normalize the file path (required for Windows) 84 | filePath = path.normalize(filePath); 85 | 86 | util.debug("%s %s => Sending file %s", req.method, req.path, filePath); 87 | res.sendFile(filePath); 88 | return; 89 | } 90 | else { 91 | util.debug("%s %s does not match any files in the Swagger API", req.method, req.path); 92 | } 93 | } 94 | } 95 | 96 | next(); 97 | } 98 | 99 | /** 100 | * Prefixes the given path with the API's basePath, if that option is enabled and the API has a basePath. 101 | * 102 | * @param {string} path 103 | * @returns {string} 104 | */ 105 | function getConfiguredPath (path) { 106 | if (options.useBasePath && context.api && context.api.basePath) { 107 | return context.api.basePath + path; 108 | } 109 | else { 110 | return path; 111 | } 112 | } 113 | } 114 | 115 | fileServer.defaultOptions = { 116 | /** 117 | * Determines whether the file paths are prefixed with the Swagger API's `basePath` value. 118 | * For example, if the `basePath` in the Swagger API is "/api/v1", then the Swagger JSON file 119 | * would be served at "/api/v1/api-docs/" instead of "/api-docs/". 120 | * 121 | * @type {boolean} 122 | */ 123 | useBasePath: false, 124 | 125 | /** 126 | * The path that will serve the fully dereferenced Swagger API in JSON format. 127 | * This file should work with any third-party tools, even if they don't support YAML, 128 | * `$ref` pointers, or mutli-file Swagger APIs. 129 | * 130 | * To disable serving this file, set the path to a falsy value (such as an empty string). 131 | * 132 | * @type {string} 133 | */ 134 | apiPath: "/api-docs/", 135 | 136 | /** 137 | * The path that will serve the raw Swagger API file(s). 138 | * For example, assume that your API consists of the following files: 139 | * 140 | * - Main.yaml 141 | * - Users.json 142 | * - Products/Get-Products.yml 143 | * - Products/Post-Products.yaml 144 | * 145 | * By default, each of these files would be served at: 146 | * 147 | * - /api-docs/Main.yaml 148 | * - /api-docs/Users.json 149 | * - /api-docs/Products/Get-Products.yml 150 | * - /api-docs/Products/Post-Products.yaml 151 | * 152 | * To disable serving raw Swagger files, set the path to a falsy value (such as an empty string). 153 | * 154 | * @type {string} 155 | */ 156 | rawFilesPath: "/api-docs/" 157 | }; 158 | -------------------------------------------------------------------------------- /lib/param-parser.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | module.exports = paramParser; 4 | module.exports.parseParameter = parseParameter; 5 | 6 | const _ = require("lodash"); 7 | const ono = require("@jsdevtools/ono"); 8 | const util = require("./helpers/util"); 9 | const JsonSchema = require("./helpers/json-schema"); 10 | 11 | /** 12 | * Parses all Swagger parameters in the HTTP request. 13 | * This middleware populates {@link Request#headers}, {@link Request#query}, and {@link Request#body}. 14 | * 15 | * @returns {function[]} 16 | */ 17 | function paramParser () { 18 | return [parseSimpleParams, parseFormDataParams, parseBodyParam]; 19 | } 20 | 21 | /** 22 | * Parses all Swagger parameters in the query string and headers 23 | */ 24 | function parseSimpleParams (req, res, next) { 25 | let params = getParams(req); 26 | 27 | if (params.length > 0) { 28 | util.debug("Parsing %d request parameters...", params.length); 29 | 30 | params.forEach((param) => { 31 | // Get the raw value of the parameter 32 | switch (param.in) { 33 | case "query": 34 | util.debug(' Parsing the "%s" query parameter', param.name); 35 | req.query[param.name] = parseParameter(param, req.query[param.name], param); 36 | break; 37 | case "header": 38 | // NOTE: req.headers properties are always lower-cased 39 | util.debug(' Parsing the "%s" header parameter', param.name); 40 | req.headers[param.name.toLowerCase()] = parseParameter(param, req.header(param.name), param); 41 | break; 42 | } 43 | }); 44 | } 45 | 46 | next(); 47 | } 48 | 49 | /** 50 | * Parses all Swagger parameters in the form data. 51 | */ 52 | function parseFormDataParams (req, res, next) { 53 | getParams(req).forEach((param) => { 54 | if (param.in === "formData") { 55 | util.debug(' Parsing the "%s" form-data parameter', param.name); 56 | 57 | if (param.type === "file") { 58 | // Validate the file (min/max size, etc.) 59 | let file; 60 | if (req.files[param.name] && req.files[param.name][0]) { 61 | file = req.files[param.name][0]; 62 | } 63 | req.files[param.name] = parseParameter(param, file, param); 64 | } 65 | else { 66 | // Parse the body parameter 67 | req.body[param.name] = parseParameter(param, req.body[param.name], param); 68 | } 69 | } 70 | }); 71 | 72 | next(); 73 | } 74 | 75 | /** 76 | * Parses the Swagger "body" parameter. 77 | */ 78 | function parseBodyParam (req, res, next) { 79 | let params = getParams(req); 80 | 81 | params.some((param) => { 82 | if (param.in === "body") { 83 | util.debug(' Parsing the "%s" body parameter', param.name); 84 | 85 | if (_.isPlainObject(req.body) && _.isEmpty(req.body)) { 86 | if (param.type === "string" || (param.schema && param.schema.type === "string")) { 87 | // Convert {} to "" 88 | req.body = ""; 89 | } 90 | else { 91 | // Convert {} to undefined 92 | req.body = undefined; 93 | } 94 | } 95 | 96 | // Parse the body 97 | req.body = parseParameter(param, req.body, param.schema); 98 | 99 | // There can only be one "body" parameter, so skip the rest 100 | return true; 101 | } 102 | }); 103 | 104 | // If there are no body/formData parameters, then reset `req.body` to undefined 105 | if (params.length > 0) { 106 | let hasBody = params.some((param) => { 107 | return param.in === "body" || param.in === "formData"; 108 | }); 109 | 110 | if (!hasBody) { 111 | req.body = undefined; 112 | } 113 | } 114 | 115 | next(); 116 | } 117 | 118 | /** 119 | * Parses the given parameter, using the given JSON schema definition. 120 | * 121 | * @param {object} param - The Parameter API object 122 | * @param {*} value - The value to be parsed (it will be coerced if needed) 123 | * @param {object} schema - The JSON schema definition for the parameter 124 | * @returns {*} - The parsed value 125 | */ 126 | function parseParameter (param, value, schema) { 127 | if (value === undefined) { 128 | if (param.required) { 129 | // The parameter is required, but was not provided, so throw a 400 error 130 | let errCode = 400; 131 | 132 | if (param.in === "header" && param.name.toLowerCase() === "content-length") { 133 | // Special case for the Content-Length header. It has it's own HTTP error code. 134 | errCode = 411; // (Length Required) 135 | } 136 | 137 | // noinspection ExceptionCaughtLocallyJS 138 | throw ono({ status: errCode }, 'Missing required %s parameter "%s"', param.in, param.name); 139 | } 140 | else if (schema.default === undefined) { 141 | // The parameter is optional, and there's no default value 142 | return undefined; 143 | } 144 | } 145 | 146 | // Special case for the Content-Length header. It has it's own HTTP error code (411 Length Required) 147 | if (value === "" && param.in === "header" && param.name.toLowerCase() === "content-length") { 148 | throw ono({ status: 411 }, 'Missing required header parameter "%s"', param.name); 149 | } 150 | 151 | try { 152 | return new JsonSchema(schema).parse(value); 153 | } 154 | catch (e) { 155 | throw ono(e, { status: e.status }, 'The "%s" %s parameter is invalid (%j)', 156 | param.name, param.in, value === undefined ? param.default : value); 157 | } 158 | } 159 | 160 | /** 161 | * Returns an array of the `req.swagger.params` properties. 162 | * 163 | * @returns {object[]} 164 | */ 165 | function getParams (req) { 166 | if (req.swagger && req.swagger.params) { 167 | return req.swagger.params; 168 | } 169 | return []; 170 | } 171 | -------------------------------------------------------------------------------- /lib/cors.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | module.exports = CORS; 4 | 5 | const util = require("./helpers/util"); 6 | const swaggerMethods = require("@apidevtools/swagger-methods"); 7 | const _ = require("lodash"); 8 | 9 | // The CORS headers 10 | let accessControl = { 11 | allowOrigin: "access-control-allow-origin", 12 | allowMethods: "access-control-allow-methods", 13 | allowHeaders: "access-control-allow-headers", 14 | allowCredentials: "access-control-allow-credentials", 15 | maxAge: "access-control-max-age" 16 | }; 17 | 18 | /** 19 | * Handles CORS preflight requests and sets CORS headers for all requests according the Swagger API definition. 20 | * 21 | * @returns {function[]} 22 | */ 23 | function CORS () { 24 | return [corsHeaders, corsPreflight]; 25 | } 26 | 27 | /** 28 | * Sets the CORS headers. If default values are specified in the Swagger API, then those values are used. 29 | * Otherwise, sensible defaults are used. 30 | */ 31 | function corsHeaders (req, res, next) { 32 | // Get the default CORS response headers as specified in the Swagger API 33 | let responseHeaders = getResponseHeaders(req); 34 | 35 | // Set each CORS header 36 | _.each(accessControl, (header) => { 37 | if (responseHeaders[header] !== undefined) { 38 | // Set the header to the default value from the Swagger API 39 | res.set(header, responseHeaders[header]); 40 | } 41 | else { 42 | // Set the header to a sensible default 43 | switch (header) { 44 | case accessControl.allowOrigin: 45 | // By default, allow the origin host. Fallback to wild-card. 46 | res.set(header, req.get("Origin") || "*"); 47 | break; 48 | 49 | case accessControl.allowMethods: 50 | if (req.swagger && req.swagger.path) { 51 | // Return the allowed methods for this Swagger path 52 | res.set(header, util.getAllowedMethods(req.swagger.path)); 53 | } 54 | else { 55 | // By default, allow all of the requested methods. Fallback to ALL methods. 56 | res.set(header, req.get("Access-Control-Request-Method") || swaggerMethods.join(", ").toUpperCase()); 57 | } 58 | break; 59 | 60 | case accessControl.allowHeaders: 61 | // By default, allow all of the requested headers 62 | res.set(header, req.get("Access-Control-Request-Headers") || ""); 63 | break; 64 | 65 | case accessControl.allowCredentials: 66 | // By default, allow credentials 67 | res.set(header, true); 68 | break; 69 | 70 | case accessControl.maxAge: 71 | // By default, access-control expires immediately. 72 | res.set(header, 0); 73 | break; 74 | } 75 | } 76 | }); 77 | 78 | if (res.get(accessControl.allowOrigin) === "*") { 79 | // If Access-Control-Allow-Origin is wild-carded, then Access-Control-Allow-Credentials must be false 80 | res.set("Access-Control-Allow-Credentials", "false"); 81 | } 82 | else { 83 | // If Access-Control-Allow-Origin is set (not wild-carded), then "Vary: Origin" must be set 84 | res.vary("Origin"); 85 | } 86 | 87 | next(); 88 | } 89 | 90 | /** 91 | * Handles CORS preflight requests. 92 | */ 93 | function corsPreflight (req, res, next) { 94 | if (req.method === "OPTIONS") { 95 | util.debug("OPTIONS %s is a CORS preflight request. Sending HTTP 200 response.", req.path); 96 | res.send(); 97 | } 98 | else { 99 | next(); 100 | } 101 | } 102 | 103 | /** 104 | * Returns an object containing the CORS response headers that are defined in the Swagger API. 105 | * If the same CORS header is defined for multiple responses, then the first one wins. 106 | * 107 | * @param {Request} req 108 | * @returns {object} 109 | */ 110 | function getResponseHeaders (req) { 111 | let corsHeaders = {}; 112 | if (req.swagger) { 113 | let headers = []; 114 | 115 | if (req.method !== "OPTIONS") { 116 | // This isn't a preflight request, so the operation's response headers take precedence over the OPTIONS headers 117 | headers = getOperationResponseHeaders(req.swagger.operation); 118 | } 119 | 120 | if (req.swagger.path) { 121 | // Regardless of whether this is a preflight request, append the OPTIONS response headers 122 | headers = headers.concat(getOperationResponseHeaders(req.swagger.path.options)); 123 | } 124 | 125 | // Add the headers to the `corsHeaders` object. First one wins. 126 | headers.forEach((header) => { 127 | if (corsHeaders[header.name] === undefined) { 128 | corsHeaders[header.name] = header.value; 129 | } 130 | }); 131 | } 132 | 133 | return corsHeaders; 134 | } 135 | 136 | /** 137 | * Returns all response headers for the given Swagger operation, sorted by HTTP response code. 138 | * 139 | * @param {object} operation - The Operation object from the Swagger API 140 | * @returns {{responseCode: integer, name: string, value: string}[]} 141 | */ 142 | function getOperationResponseHeaders (operation) { 143 | let headers = []; 144 | 145 | if (operation) { 146 | _.each(operation.responses, (response, responseCode) => { 147 | // Convert responseCode to a numeric value for sorting ("default" comes last) 148 | responseCode = parseInt(responseCode) || 999; 149 | 150 | _.each(response.headers, (header, name) => { 151 | // We only care about headers that have a default value defined 152 | if (header.default !== undefined) { 153 | headers.push({ 154 | order: responseCode, 155 | name: name.toLowerCase(), 156 | value: header.default 157 | }); 158 | } 159 | }); 160 | }); 161 | } 162 | 163 | return _.sortBy(headers, "order"); 164 | } 165 | -------------------------------------------------------------------------------- /lib/mock/query-collection.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | module.exports = { 4 | GET: queryCollection, 5 | HEAD: queryCollection, 6 | OPTIONS: queryCollection, 7 | DELETE: deleteCollection 8 | }; 9 | 10 | const _ = require("lodash"); 11 | const util = require("../helpers/util"); 12 | 13 | /** 14 | * Returns an array of all resources in the collection. 15 | * If there are no resources, then an empty array is returned. No 404 error is thrown. 16 | * 17 | * If the Swagger API includes "query" parameters, they can be used to filter the results. 18 | * Deep property names are allowed (e.g. "?address.city=New+York"). 19 | * Query string params that are not defined in the Swagger API are ignored. 20 | * 21 | * @param {Request} req 22 | * @param {Response} res 23 | * @param {function} next 24 | * @param {DataStore} dataStore 25 | */ 26 | function queryCollection (req, res, next, dataStore) { 27 | dataStore.getCollection(req.path, (err, resources) => { 28 | if (!err) { 29 | resources = filter(resources, req); 30 | 31 | if (resources.length === 0) { 32 | // There is no data, so use the current date/time as the "last-modified" header 33 | res.swagger.lastModified = new Date(); 34 | 35 | // Use the default/example value, if there is one 36 | if (res.swagger.schema) { 37 | resources = res.swagger.schema.default || res.swagger.schema.example || []; 38 | } 39 | } 40 | else { 41 | // Determine the max "modifiedOn" date of the remaining items 42 | res.swagger.lastModified = _.maxBy(resources, "modifiedOn").modifiedOn; 43 | 44 | // Extract the "data" of each Resource 45 | resources = _.map(resources, "data"); 46 | } 47 | 48 | // Set the response body (unless it's already been set by other middleware) 49 | res.body = res.body || resources; 50 | } 51 | 52 | next(err); 53 | }); 54 | } 55 | 56 | /** 57 | * Deletes all resources in the collection. 58 | * If there are no resources, then nothing happens. No error is thrown. 59 | * 60 | * If the Swagger API includes "query" parameters, they can be used to filter what gets deleted. 61 | * Deep property names are allowed (e.g. "?address.city=New+York"). 62 | * Query string params that are not defined in the Swagger API are ignored. 63 | * 64 | * @param {Request} req 65 | * @param {Response} res 66 | * @param {function} next 67 | * @param {DataStore} dataStore 68 | */ 69 | function deleteCollection (req, res, next, dataStore) { 70 | dataStore.getCollection(req.path, (err, resources) => { 71 | if (err) { 72 | next(err); 73 | } 74 | else { 75 | // Determine which resources to delete, based on query params 76 | let resourcesToDelete = filter(resources, req); 77 | 78 | if (resourcesToDelete.length === 0) { 79 | sendResponse(null, []); 80 | } 81 | else if (resourcesToDelete.length === resources.length) { 82 | // Delete the whole collection 83 | dataStore.deleteCollection(req.path, sendResponse); 84 | } 85 | else { 86 | // Only delete the matching resources 87 | dataStore.delete(resourcesToDelete, sendResponse); 88 | } 89 | } 90 | }); 91 | 92 | // Responds with the deleted resources 93 | function sendResponse (err, resources) { 94 | // Extract the "data" of each Resource 95 | resources = _.map(resources, "data"); 96 | 97 | // Use the current date/time as the "last-modified" header 98 | res.swagger.lastModified = new Date(); 99 | 100 | // Set the response body (unless it's already been set by other middleware) 101 | if (err || res.body) { 102 | next(err); 103 | } 104 | else if (!res.swagger.isCollection) { 105 | // Response body is a single value, so only return the first item in the collection 106 | res.body = _.first(resources); 107 | next(); 108 | } 109 | else { 110 | // Response body is the resource that was created/update/deleted 111 | res.body = resources; 112 | next(); 113 | } 114 | } 115 | } 116 | 117 | /** 118 | * Filters the given {@link Resource} array, using the "query" params defined in the Swagger API. 119 | * 120 | * @param {Resource[]} resources 121 | * @param {Request} req 122 | * @returns {Resource[]} 123 | */ 124 | function filter (resources, req) { 125 | util.debug("There are %d resources in %s", resources.length, req.path); 126 | 127 | if (resources.length > 0) { 128 | // If there are query params, then filter the collection by them 129 | let queryParams = _.filter(req.swagger.params, { in: "query" }); 130 | if (queryParams.length > 0) { 131 | // Build the filter object 132 | let filterCriteria = { data: {}}; 133 | queryParams.forEach((param) => { 134 | if (req.query[param.name] !== undefined) { 135 | setDeepProperty(filterCriteria.data, param.name, req.query[param.name]); 136 | } 137 | }); 138 | 139 | if (!_.isEmpty(filterCriteria.data)) { 140 | // Filter the collection 141 | util.debug("Filtering resources by %j", filterCriteria.data); 142 | resources = _.filter(resources, filterCriteria); 143 | util.debug("%d resources matched the filter criteria", resources.length); 144 | } 145 | } 146 | } 147 | 148 | return resources; 149 | } 150 | 151 | /** 152 | * Sets a deep property of the given object. 153 | * 154 | * @param {object} obj - The object whose property is to be set. 155 | * @param {string} propName - The deep property name (e.g. "Vet.Address.State") 156 | * @param {*} propValue - The value to set 157 | */ 158 | function setDeepProperty (obj, propName, propValue) { 159 | propName = propName.split("."); 160 | for (let i = 0; i < propName.length - 1; i++) { 161 | obj = obj[propName[i]] = obj[propName[i]] || {}; 162 | } 163 | obj[propName[propName.length - 1]] = propValue; 164 | } 165 | -------------------------------------------------------------------------------- /test/specs/json-schema/parse/parse-object.spec.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const swagger = require("../../../../"); 4 | const expect = require("chai").expect; 5 | const _ = require("lodash"); 6 | const specs = require("../../../fixtures/specs"); 7 | const helper = require("./helper"); 8 | 9 | let api, petParam; 10 | 11 | describe("JSON Schema - parse object params", () => { 12 | 13 | beforeEach(() => { 14 | api = _.cloneDeep(specs.swagger2.samples.petStore); 15 | petParam = api.paths["/pets/{PetName}"].patch.parameters[0]; 16 | }); 17 | 18 | it("should parse a valid object param", (done) => { 19 | swagger(api, (err, middleware) => { 20 | let express = helper.express(middleware.metadata(), middleware.parseRequest()); 21 | 22 | helper.supertest(express) 23 | .patch("/api/pets/fido") 24 | .send({ Name: "Fido", Type: "dog" }) 25 | .end(helper.checkSpyResults(done)); 26 | 27 | express.patch("/api/pets/fido", helper.spy((req, res, next) => { 28 | expect(req.body).to.deep.equal({ 29 | Name: "Fido", 30 | Type: "dog" 31 | }); 32 | })); 33 | }); 34 | }); 35 | 36 | it("should parse an optional, unspecified object param", (done) => { 37 | petParam.required = false; 38 | 39 | swagger(api, (err, middleware) => { 40 | let express = helper.express(middleware.metadata(), middleware.parseRequest()); 41 | 42 | helper.supertest(express) 43 | .patch("/api/pets/fido") 44 | .end(helper.checkSpyResults(done)); 45 | 46 | express.patch("/api/pets/fido", helper.spy((req, res, next) => { 47 | expect(req.body || "").to.have.lengthOf(0); 48 | })); 49 | }); 50 | }); 51 | 52 | it("should parse the default Object value if no value is specified", (done) => { 53 | petParam.required = false; 54 | petParam.schema.default = { Name: "Fido", Type: "dog" }; 55 | 56 | swagger(api, (err, middleware) => { 57 | let express = helper.express(middleware.metadata(), middleware.parseRequest()); 58 | 59 | helper.supertest(express) 60 | .patch("/api/pets/fido") 61 | .end(helper.checkSpyResults(done)); 62 | 63 | express.patch("/api/pets/fido", helper.spy((req, res, next) => { 64 | expect(req.body).to.deep.equal({ 65 | Name: "Fido", 66 | Type: "dog" 67 | }); 68 | })); 69 | }); 70 | }); 71 | 72 | it("should parse the default String value if no value is specified", (done) => { 73 | petParam.required = false; 74 | petParam.schema.default = '{"Name": "Fido", "Type": "dog"}'; 75 | 76 | swagger(api, (err, middleware) => { 77 | let express = helper.express(middleware.metadata(), middleware.parseRequest()); 78 | 79 | helper.supertest(express) 80 | .patch("/api/pets/fido") 81 | .end(helper.checkSpyResults(done)); 82 | 83 | express.patch("/api/pets/fido", helper.spy((req, res, next) => { 84 | expect(req.body).to.deep.equal({ 85 | Name: "Fido", 86 | Type: "dog" 87 | }); 88 | })); 89 | }); 90 | }); 91 | 92 | it("should parse the default value if the specified value is blank", (done) => { 93 | petParam.required = false; 94 | petParam.schema.default = '{"Name": "Fido", "Type": "dog"}'; 95 | 96 | swagger(api, (err, middleware) => { 97 | let express = helper.express(middleware.metadata(), middleware.parseRequest()); 98 | 99 | helper.supertest(express) 100 | .patch("/api/pets/fido") 101 | .set("content-type", "text/plain") 102 | .send("") 103 | .end(helper.checkSpyResults(done)); 104 | 105 | express.patch("/api/pets/fido", helper.spy((req, res, next) => { 106 | expect(req.body).to.deep.equal({ 107 | Name: "Fido", 108 | Type: "dog" 109 | }); 110 | })); 111 | }); 112 | }); 113 | 114 | it("should throw an error if the value is blank", (done) => { 115 | swagger(api, (err, middleware) => { 116 | let express = helper.express(middleware.metadata(), middleware.parseRequest()); 117 | 118 | helper.supertest(express) 119 | .patch("/api/pets/fido") 120 | .set("content-type", "text/plain") 121 | .send("") 122 | .end(helper.checkSpyResults(done)); 123 | 124 | express.use("/api/pets/fido", helper.spy((err, req, res, next) => { 125 | expect(err).to.be.an.instanceOf(Error); 126 | expect(err.status).to.equal(400); 127 | expect(err.message).to.contain('Missing required body parameter "PetData"'); 128 | })); 129 | }); 130 | }); 131 | 132 | it("should throw an error if schema validation fails", (done) => { 133 | swagger(api, (err, middleware) => { 134 | let express = helper.express(middleware.metadata(), middleware.parseRequest()); 135 | 136 | helper.supertest(express) 137 | .patch("/api/pets/fido") 138 | .send({ Name: "Fido", Type: "kitty kat" }) 139 | .end(helper.checkSpyResults(done)); 140 | 141 | express.use("/api/pets/fido", helper.spy((err, req, res, next) => { 142 | expect(err).to.be.an.instanceOf(Error); 143 | expect(err.status).to.equal(400); 144 | expect(err.message).to.contain('No enum match for: "kitty kat"'); 145 | })); 146 | }); 147 | }); 148 | 149 | it("should throw an error if required and not specified", (done) => { 150 | swagger(api, (err, middleware) => { 151 | let express = helper.express(middleware.metadata(), middleware.parseRequest()); 152 | 153 | helper.supertest(express) 154 | .patch("/api/pets/fido") 155 | .end(helper.checkSpyResults(done)); 156 | 157 | express.use("/api/pets/fido", helper.spy((err, req, res, next) => { 158 | expect(err).to.be.an.instanceOf(Error); 159 | expect(err.status).to.equal(400); 160 | expect(err.message).to.contain('Missing required body parameter "PetData"'); 161 | })); 162 | }); 163 | }); 164 | }); 165 | -------------------------------------------------------------------------------- /docs/exports/Resource.md: -------------------------------------------------------------------------------- 1 | The `Resource` class 2 | ============================ 3 | The `Resource` class represents a single [REST resource](http://restful-api-design.readthedocs.org/en/latest/resources.html) in your API. If you are unfamiliar with RESTful API design, [here is a good article](http://www.thoughtworks.com/insights/blog/rest-api-design-resource-modeling) on the topic. 4 | 5 | 6 | Every `Resource` object corresponds to a single URL. That URL consists of two parts: 7 | 8 | * __Collection__
9 | This is what groups multiple resources of the same type together. For example, the "_/users_" collection holds user resources, and the "_/store/products_" collection holds product resources. 10 | 11 | * __Name__
12 | This is what uniquely identifies a given resource within its collection. For example, "_/users/jdoe_" is a resource named "_/jdoe_" in the "_/users_" collection, and "_/store/products/widget.html_" is a resource named "_/widget.html_" in the "_/store/products_" collection. 13 | 14 | Here are some examples of URLs and their corresponding collections and names: 15 | 16 | | URL | Collection Path | Resource Name | 17 | |:-------------------------------------------|:--------------------------------|:------------------| 18 | | /static/pages/index.html | /static/pages | /index.html | 19 | | /restaurants/washington/seattle/ | /restaurants/washington | /seattle/ | 20 | | /restaurants/washington/seattle/joes-diner | /restaurants/washington/seattle | /joes-diner | 21 | | / _(the root of your API)_ | _(empty string)_ | / | 22 | 23 | 24 | > **TIP:** Swagger Express Middleware honors your [Express App's settings](http://expressjs.com/4x/api.html#app.set), such as case-sensitivity and strict-routing. By default, the URLs "/users/jdoe", "/users/JDoe", and "/users/jdoe/" all map to the same `Resource` object. But if you enable strict routing and case-sensitive routing in your app, then those URLs will be treated as three different resources. 25 | 26 | 27 | Constructors 28 | ----------------------- 29 | ### `Resource(path, data)` 30 | 31 | * __path__ (_required_) - `string`
32 | The full resource path (such as `"/pets/Fido"`). The constructor will automatically split the path and set the `collection` and `name` properties accordingly. 33 | 34 | * __data__ (_optional_) - `any`
35 | The resource's data. This can be any value that is serializable as JSON, such as a string, a number, an object, an array, etc. 36 | 37 | 38 | ### `Resource(collection, name, data)` 39 | 40 | * __collection__ (_required_) - `string`
41 | The resource's collection path (such as `"/pets"`). 42 | 43 | * __name__ (_required_) - `string`
44 | The resource's unique name within its collection (such as `"/Fido"`). 45 | 46 | * __data__ (_required_) - `any`
47 | The resource's data. This can be any value that is serializable as JSON, such as a string, a number, an object, an array, etc. 48 | 49 | 50 | > **TIP:** Remember, JSON does not support literal values for some JavaScript types. `Date` objects are serialized as strings (in [ISO 8601 format](http://www.w3.org/TR/NOTE-datetime)), `undefined` is sometimes serialized as `null` (such as when in an array), and `RegExp` objects are serialized as empty objects. So you might need to sanitize your data prior to passing it to the `Resource` constructor. 51 | 52 | 53 | Properties 54 | ----------------------- 55 | 56 | | Property Name | Data Type | Description 57 | |:--------------------|:------------------------|:------------- 58 | | `collection` | string | The resource's collection path. This property can be an empty string, if the resource is at the root of your API.

The collection path should always begin with a forward slash and should _not_ end with one. The `Resource` constructor automatically handles this normalization. For example, "pets/" becomes "/pets". 59 | | `name` | string | The resource's unique name within its collection. This property _cannot_ be an empty string. It will always contain at least a single forward slash.

Resource names should always begin with a forward slash and _may_ also end with one. The `Resource` constructor automatically handles this normalization. For example, "Fido" becomes "/Fido". 60 | | `data` | any | The resource's data. This can be any value that is serializable as JSON. 61 | | `createdOn` | Date object | The date/time that the resource was first saved to a `DataStore`. This property is automatically set by the [DataStore](DataStore.md) class. 62 | | `modifiedOn` | Date object | The date/time that the resource was last saved (or updated) in a `DataStore`. This property is automatically set by the [DataStore](DataStore.md) class. 63 | 64 | 65 | Instance Methods 66 | ----------------------- 67 | ### `toString()` 68 | The `Resource` class overrides the default `Object.toString()` method to return the full resource path (`collection` + `name`). 69 | 70 | 71 | ### `valueOf()` 72 | The `Resource` class overrides the default `Object.valueOf()` method to return the full resource path (`collection` + `name`). It also supports extra parameters to control case-sensitivity and optionally return only the collection name, but these parameters should be considered a __private API__ and should not be used. 73 | 74 | 75 | ### `merge(other)` 76 | Merges the specified data with this resource's data. If the data cannot be merged, then this resource's data is overwritten with the new data. 77 | 78 | * __other__ (_required_) - `any` or `Resource`
79 | The data to be merged. It can be any value that is serializable as JSON, such as a string, a number, an object, an array, etc. If it is another `Resource` object, then that resource's data will be merged. 80 | 81 | 82 | Static Methods 83 | ----------------------- 84 | ### `parse(json)` 85 | Parses JSON data into `Resource` objects. 86 | 87 | * __json__ (_required_) - `string`
88 | The JSON data to be parsed. This JSON data __must__ be one or more `Resource` objects that were serialized using `JSON.stringify()`. If the JSON is invalid, then an error will be thrown. If the JSON is a single `object`, then a single `Resource` will be returned; otherwise, an array of `Resource` objects will be returned. 89 | -------------------------------------------------------------------------------- /test/specs/json-schema/parse/parse-number.spec.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const expect = require("chai").expect; 4 | const helper = require("./helper"); 5 | 6 | describe("JSON Schema - parse number params", () => { 7 | 8 | it("should parse a valid number param", (done) => { 9 | let schema = { 10 | type: "number", 11 | multipleOf: 5.5, 12 | minimum: 11, 13 | exclusiveMinimum: true, 14 | maximum: 16.5, 15 | exclusiveMaximum: false 16 | }; 17 | 18 | let express = helper.parse(schema, 16.5, done); 19 | 20 | express.post("/api/test", helper.spy((req) => { 21 | expect(req.header("Test")).to.equal(16.5); 22 | })); 23 | }); 24 | 25 | it("should parse an optional, unspecified number param", (done) => { 26 | let schema = { 27 | type: "number" 28 | }; 29 | 30 | let express = helper.parse(schema, undefined, done); 31 | 32 | express.post("/api/test", helper.spy((req) => { 33 | expect(req.header("Test")).to.equal(undefined); 34 | })); 35 | }); 36 | 37 | it("should parse the default value if no value is specified", (done) => { 38 | let schema = { 39 | type: "number", 40 | default: 3.402823e38 41 | }; 42 | 43 | let express = helper.parse(schema, undefined, done); 44 | 45 | express.post("/api/test", helper.spy((req) => { 46 | expect(req.header("Test")).to.equal(3.402823e38); 47 | })); 48 | }); 49 | 50 | it("should parse the default value if the specified value is blank", (done) => { 51 | let schema = { 52 | type: "number", 53 | default: -3.402823e38 54 | }; 55 | 56 | let express = helper.parse(schema, "", done); 57 | 58 | express.post("/api/test", helper.spy((req) => { 59 | expect(req.header("Test")).to.equal(-3.402823e38); 60 | })); 61 | }); 62 | 63 | it("should throw an error if the value is blank", (done) => { 64 | let schema = { 65 | type: "number" 66 | }; 67 | 68 | let express = helper.parse(schema, "", done); 69 | 70 | express.use("/api/test", helper.spy((err, req, res, next) => { 71 | expect(err).to.be.an.instanceOf(Error); 72 | expect(err.status).to.equal(400); 73 | expect(err.message).to.contain('"" is not a valid numeric value'); 74 | })); 75 | }); 76 | 77 | it("should throw an error if the value is not a valid number", (done) => { 78 | let schema = { 79 | type: "number" 80 | }; 81 | 82 | let express = helper.parse(schema, "hello world", done); 83 | 84 | express.use("/api/test", helper.spy((err, req, res, next) => { 85 | expect(err).to.be.an.instanceOf(Error); 86 | expect(err.status).to.equal(400); 87 | expect(err.message).to.contain('"hello world" is not a valid numeric value'); 88 | })); 89 | }); 90 | 91 | it("should throw an error if the value fails schema validation", (done) => { 92 | let schema = { 93 | type: "number", 94 | multipleOf: 87.29 95 | }; 96 | 97 | let express = helper.parse(schema, "94.8", done); 98 | 99 | express.use("/api/test", helper.spy((err, req, res, next) => { 100 | expect(err).to.be.an.instanceOf(Error); 101 | expect(err.status).to.equal(400); 102 | expect(err.message).to.contain("Value 94.8 is not a multiple of 87.29"); 103 | })); 104 | }); 105 | 106 | it("should throw an error if the value is above the float maximum", (done) => { 107 | let schema = { 108 | type: "number", 109 | format: "float" 110 | }; 111 | 112 | let express = helper.parse(schema, "3.402824e+38", done); 113 | 114 | express.use("/api/test", helper.spy((err, req, res, next) => { 115 | expect(err).to.be.an.instanceOf(Error); 116 | expect(err.status).to.equal(400); 117 | expect(err.message).to.contain('"3.402824e+38" is not a valid float. Must be between'); 118 | })); 119 | }); 120 | 121 | it("should throw an error if the value is below the float minimum", (done) => { 122 | let schema = { 123 | type: "number", 124 | format: "float" 125 | }; 126 | 127 | let express = helper.parse(schema, "-3.402824e+38", done); 128 | 129 | express.use("/api/test", helper.spy((err, req, res, next) => { 130 | expect(err).to.be.an.instanceOf(Error); 131 | expect(err.status).to.equal(400); 132 | expect(err.message).to.contain('"-3.402824e+38" is not a valid float. Must be between'); 133 | })); 134 | }); 135 | 136 | it("should throw an error if the value is above the double maximum", (done) => { 137 | let schema = { 138 | type: "number", 139 | format: "double" 140 | }; 141 | 142 | let express = helper.parse(schema, "1.7976931348629999E+308", done); 143 | 144 | express.use("/api/test", helper.spy((err, req, res, next) => { 145 | expect(err).to.be.an.instanceOf(Error); 146 | expect(err.status).to.equal(400); 147 | expect(err.message).to.contain('"1.7976931348629999E+308" is not a valid numeric value'); 148 | })); 149 | }); 150 | 151 | it("should throw an error if the value is below the double minimum", (done) => { 152 | let schema = { 153 | type: "number", 154 | format: "double" 155 | }; 156 | 157 | let express = helper.parse(schema, "-1.7976931348629999E+308", done); 158 | 159 | express.use("/api/test", helper.spy((err, req, res, next) => { 160 | expect(err).to.be.an.instanceOf(Error); 161 | expect(err.status).to.equal(400); 162 | expect(err.message).to.contain('"-1.7976931348629999E+308" is not a valid numeric value'); 163 | })); 164 | }); 165 | 166 | it("should throw an error if required and not specified", (done) => { 167 | let schema = { 168 | type: "number", 169 | required: true 170 | }; 171 | 172 | let express = helper.parse(schema, undefined, done); 173 | 174 | express.use("/api/test", helper.spy((err, req, res, next) => { 175 | expect(err).to.be.an.instanceOf(Error); 176 | expect(err.status).to.equal(400); 177 | expect(err.message).to.contain('Missing required header parameter "Test"'); 178 | })); 179 | }); 180 | 181 | it("should be more strict than parseFloat()", (done) => { 182 | let schema = { 183 | type: "number", 184 | required: true 185 | }; 186 | 187 | let express = helper.parse(schema, "1z", done); 188 | 189 | express.use("/api/test", helper.spy((err, req, res, next) => { 190 | expect(err).to.be.an.instanceOf(Error); 191 | expect(err.status).to.equal(400); 192 | expect(err.message).to.contain('"1z" is not a valid numeric value'); 193 | })); 194 | }); 195 | }); 196 | -------------------------------------------------------------------------------- /test/specs/json-schema/parse/parse-integer.spec.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const expect = require("chai").expect; 4 | const helper = require("./helper"); 5 | 6 | describe("JSON Schema - parse integer params", () => { 7 | 8 | it("should parse a valid integer param", (done) => { 9 | let schema = { 10 | type: "integer", 11 | multipleOf: 5, 12 | minimum: 40, 13 | exclusiveMinimum: true, 14 | maximum: 45, 15 | exclusiveMaximum: false 16 | }; 17 | 18 | let express = helper.parse(schema, 45, done); 19 | 20 | express.post("/api/test", helper.spy((req) => { 21 | expect(req.header("Test")).to.equal(45); 22 | })); 23 | }); 24 | 25 | it("should parse an optional, unspecified integer param", (done) => { 26 | let schema = { 27 | type: "integer" 28 | }; 29 | 30 | let express = helper.parse(schema, undefined, done); 31 | 32 | express.post("/api/test", helper.spy((req) => { 33 | expect(req.header("Test")).to.equal(undefined); 34 | })); 35 | }); 36 | 37 | it("should parse the default value if no value is specified", (done) => { 38 | let schema = { 39 | type: "integer", 40 | default: 9223372036854775807 41 | }; 42 | 43 | let express = helper.parse(schema, undefined, done); 44 | 45 | express.post("/api/test", helper.spy((req) => { 46 | expect(req.header("Test")).to.equal(9223372036854775807); 47 | })); 48 | }); 49 | 50 | it("should parse the default value if the specified value is blank", (done) => { 51 | let schema = { 52 | type: "integer", 53 | default: 1 54 | }; 55 | 56 | let express = helper.parse(schema, "", done); 57 | 58 | express.post("/api/test", helper.spy((req) => { 59 | expect(req.header("Test")).to.equal(1); 60 | })); 61 | }); 62 | 63 | it("should throw an error if the value is blank", (done) => { 64 | let schema = { 65 | type: "integer" 66 | }; 67 | 68 | let express = helper.parse(schema, "", done); 69 | 70 | express.use("/api/test", helper.spy((err, req, res, next) => { 71 | expect(err).to.be.an.instanceOf(Error); 72 | expect(err.status).to.equal(400); 73 | expect(err.message).to.contain('"" is not a properly-formatted whole number'); 74 | })); 75 | }); 76 | 77 | it("should throw an error if the value is not a valid integer", (done) => { 78 | let schema = { 79 | type: "integer" 80 | }; 81 | 82 | let express = helper.parse(schema, "hello world", done); 83 | 84 | express.use("/api/test", helper.spy((err, req, res, next) => { 85 | expect(err).to.be.an.instanceOf(Error); 86 | expect(err.status).to.equal(400); 87 | expect(err.message).to.contain('"hello world" is not a properly-formatted whole number'); 88 | })); 89 | }); 90 | 91 | it("should throw an error if the value is a float", (done) => { 92 | let schema = { 93 | type: "integer" 94 | }; 95 | 96 | let express = helper.parse(schema, "3.5", done); 97 | 98 | express.use("/api/test", helper.spy((err, req, res, next) => { 99 | expect(err).to.be.an.instanceOf(Error); 100 | expect(err.status).to.equal(400); 101 | expect(err.message).to.contain('"3.5" is not a properly-formatted whole number'); 102 | })); 103 | }); 104 | 105 | it("should throw an error if the value fails schema validation", (done) => { 106 | let schema = { 107 | type: "integer", 108 | multipleOf: 3 109 | }; 110 | 111 | let express = helper.parse(schema, "14", done); 112 | 113 | express.use("/api/test", helper.spy((err, req, res, next) => { 114 | expect(err).to.be.an.instanceOf(Error); 115 | expect(err.status).to.equal(400); 116 | expect(err.message).to.contain("Value 14 is not a multiple of 3"); 117 | })); 118 | }); 119 | 120 | it("should throw an error if the value is above the int32 maximum", (done) => { 121 | let schema = { 122 | type: "integer", 123 | format: "int32" 124 | }; 125 | 126 | let express = helper.parse(schema, "2147483648", done); 127 | 128 | express.use("/api/test", helper.spy((err, req, res, next) => { 129 | expect(err).to.be.an.instanceOf(Error); 130 | expect(err.status).to.equal(400); 131 | expect(err.message).to.contain('"2147483648" is not a valid int32. Must be between -2147483648 and 2147483647'); 132 | })); 133 | }); 134 | 135 | it("should throw an error if the value is below the int32 minimum", (done) => { 136 | let schema = { 137 | type: "integer", 138 | format: "int32" 139 | }; 140 | 141 | let express = helper.parse(schema, "-2147483649", done); 142 | 143 | express.use("/api/test", helper.spy((err, req, res, next) => { 144 | expect(err).to.be.an.instanceOf(Error); 145 | expect(err.status).to.equal(400); 146 | expect(err.message).to.contain('"-2147483649" is not a valid int32. Must be between -2147483648 and 2147483647'); 147 | })); 148 | }); 149 | 150 | it("should throw an error if the value is above the int64 maximum", (done) => { 151 | let schema = { 152 | type: "integer", 153 | format: "int64" 154 | }; 155 | 156 | let express = helper.parse(schema, "9223372036854779999", done); 157 | 158 | express.use("/api/test", helper.spy((err, req, res, next) => { 159 | expect(err).to.be.an.instanceOf(Error); 160 | expect(err.status).to.equal(400); 161 | expect(err.message).to.contain('"9223372036854780000" is not a valid int64. Must be between'); 162 | })); 163 | }); 164 | 165 | it("should throw an error if the value is below the int64 minimum", (done) => { 166 | let schema = { 167 | type: "integer", 168 | format: "int64" 169 | }; 170 | 171 | let express = helper.parse(schema, "-9223372036854779999", done); 172 | 173 | express.use("/api/test", helper.spy((err, req, res, next) => { 174 | expect(err).to.be.an.instanceOf(Error); 175 | expect(err.status).to.equal(400); 176 | expect(err.message).to.contain('"-9223372036854780000" is not a valid int64. Must be between'); 177 | })); 178 | }); 179 | 180 | it("should throw an error if required and not specified", (done) => { 181 | let schema = { 182 | type: "integer", 183 | required: true 184 | }; 185 | 186 | let express = helper.parse(schema, undefined, done); 187 | 188 | express.use("/api/test", helper.spy((err, req, res, next) => { 189 | expect(err).to.be.an.instanceOf(Error); 190 | expect(err.status).to.equal(400); 191 | expect(err.message).to.contain('Missing required header parameter "Test"'); 192 | })); 193 | }); 194 | }); 195 | -------------------------------------------------------------------------------- /lib/mock/semantic-response.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | module.exports = SemanticResponse; 4 | 5 | const _ = require("lodash"); 6 | const util = require("../helpers/util"); 7 | 8 | /** 9 | * Describes the semantics of a Swagger response. 10 | * 11 | * @param {object} response - The Response object, from the Swagger API 12 | * @param {object} path - The Path object that contains the response. Used for semantic analysis. 13 | * @constructor 14 | */ 15 | function SemanticResponse (response, path) { 16 | /** 17 | * The JSON schema of the response 18 | * @type {object|null} 19 | */ 20 | this.schema = response.schema || null; 21 | 22 | /** 23 | * The response headers, from the Swagger API 24 | * @type {object|null} 25 | */ 26 | this.headers = response.headers || null; 27 | 28 | /** 29 | * If true, then an empty response should be sent. 30 | * @type {boolean} 31 | */ 32 | this.isEmpty = !response.schema; 33 | 34 | /** 35 | * Indicates whether the response should be a single resource, or a collection. 36 | * @type {boolean} 37 | */ 38 | this.isCollection = false; 39 | 40 | /** 41 | * Indicates whether the response schema is a wrapper around the actual resource data. 42 | * It's common for RESTful APIs to include a response wrapper with additional metadata, 43 | * and one of the properties of the wrapper is the actual resource data. 44 | * @type {boolean} 45 | */ 46 | this.isWrapped = false; 47 | 48 | /** 49 | * If the response is wrapped, then this is the name of the wrapper property that 50 | * contains the actual resource data. 51 | * @type {string} 52 | */ 53 | this.wrapperProperty = ""; 54 | 55 | /** 56 | * The date/time that the response data was last modified. 57 | * This is used to set the Last-Modified HTTP header (if defined in the Swagger API) 58 | * 59 | * Each mock implementation sets this to the appropriate value. 60 | * 61 | * @type {Date} 62 | */ 63 | this.lastModified = null; 64 | 65 | /** 66 | * The location (URL) of the REST resource. 67 | * This is used to set the Location HTTP header (if defined in the Swagger API) 68 | * 69 | * Some mocks implementations set this value. If left blank, then the Location header 70 | * will be set to the current path. 71 | * 72 | * @type {string} 73 | */ 74 | this.location = ""; 75 | 76 | if (!this.isEmpty) { 77 | this.setWrapperInfo(response, path); 78 | } 79 | } 80 | 81 | /** 82 | * Wraps the given data in the appropriate wrapper object, if applicable. 83 | * 84 | * @param {*} data - The data to (possibly) be wrapped 85 | * @returns {*} - The (possibly) wrapped data 86 | */ 87 | SemanticResponse.prototype.wrap = function (data) { 88 | if (this.isWrapped) { 89 | let wrapper = {}; 90 | wrapper[this.wrapperProperty] = data; 91 | return wrapper; 92 | } 93 | else { 94 | return data; 95 | } 96 | }; 97 | 98 | /** 99 | * Determines whether the response schema is a wrapper object, and sets the corresponding properties. 100 | * 101 | * @param {object} response - The Response object, from the Swagger API 102 | * @param {object} path - The Path object that contains the response. Used for semantic analysis. 103 | */ 104 | SemanticResponse.prototype.setWrapperInfo = function (response, path) { 105 | let self = this; 106 | 107 | if (response.schema.type === "array") { 108 | // Assume that it's a collection. It's also NOT wrapped 109 | self.isCollection = true; 110 | } 111 | else if (response.schema.type === "object" || response.schema.type === undefined) { 112 | let resourceSchemas = getResourceSchemas(path); 113 | 114 | // If the response schema matches one of the resource schemas, then it's NOT wrapped 115 | if (!schemasMatch(resourceSchemas, response.schema)) { 116 | // The response schema doesn't match any of the resource schemas, 117 | // so check each of its properties to see if any of them match 118 | _.some(response.schema.properties, (propSchema, propName) => { 119 | let isArray = false; 120 | if (propSchema.type === "array") { 121 | isArray = true; 122 | propSchema = propSchema.items; 123 | } 124 | 125 | if (propSchema.type === "object" || propSchema.type === undefined) { 126 | if (schemasMatch(resourceSchemas, propSchema)) { 127 | // The response schema is a wrapper object, 128 | // and this property contains the actual resource data 129 | self.isWrapped = true; 130 | self.wrapperProperty = propName; 131 | self.isCollection = isArray; 132 | return true; 133 | } 134 | } 135 | }); 136 | } 137 | } 138 | }; 139 | 140 | /** 141 | * Returns the JSON schemas for the given path's PUT, POST, and PATCH operations. 142 | * Usually these operations are not wrapped, so we can assume that they are the actual resource schema. 143 | * 144 | * @param {object} path - A Path object, from the Swagger API. 145 | * @returns {object[]} - An array of JSON schema objects 146 | */ 147 | function getResourceSchemas (path) { 148 | let schemas = []; 149 | 150 | ["post", "put", "patch"].forEach((operation) => { 151 | if (path[operation]) { 152 | schemas.push(util.getRequestSchema(path, path[operation])); 153 | } 154 | }); 155 | 156 | return schemas; 157 | } 158 | 159 | /** 160 | * Determines whether the given JSON schema matches any of the given JSON schemas. 161 | * 162 | * @param {object[]} schemasToMatch - An array of JSON schema objects 163 | * @param {object} schemaToTest - The JSON schema object to test against the other schemas 164 | * @returns {boolean} - Returns true if the schema matches any of the other schemas 165 | */ 166 | function schemasMatch (schemasToMatch, schemaToTest) { 167 | let propertiesToTest = 0; 168 | if (schemaToTest.properties) { 169 | propertiesToTest = Object.keys(schemaToTest.properties).length; 170 | } 171 | 172 | return schemasToMatch.some((schemaToMatch) => { 173 | let propertiesToMatch = 0; 174 | if (schemaToMatch.properties) { 175 | propertiesToMatch = Object.keys(schemaToMatch.properties).length; 176 | } 177 | 178 | // Make sure both schemas are the same type and have the same number of properties 179 | if (schemaToTest.type === schemaToMatch.type && propertiesToTest === propertiesToMatch) { 180 | // Compare each property in both schemas 181 | return _.every(schemaToMatch.properties, (propertyToMatch, propName) => { 182 | let propertyToTest = schemaToTest.properties[propName]; 183 | return propertyToTest && propertyToMatch.type === propertyToTest.type; 184 | }); 185 | } 186 | }); 187 | } 188 | -------------------------------------------------------------------------------- /test/specs/data-store/file-data-store.spec.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const swagger = require("../../../"); 4 | const expect = require("chai").expect; 5 | const sinon = require("sinon"); 6 | const path = require("path"); 7 | const fs = require("fs"); 8 | const files = require("../../fixtures/files"); 9 | const Resource = swagger.Resource; 10 | const FileDataStore = swagger.FileDataStore; 11 | 12 | let tempDir; 13 | 14 | describe("FileDataStore", () => { 15 | beforeEach((done) => { 16 | files.createTempDir((temp) => { 17 | tempDir = temp; 18 | done(); 19 | }); 20 | }); 21 | 22 | it("can be passed a base directory", (done) => { 23 | let dir = path.join(tempDir, "foo", "bar", "baz"); 24 | let dataStore = new FileDataStore(dir); 25 | let resource = new Resource("/users", "/JDoe", { name: "John Doe" }); 26 | let file = path.join(dir, "users.json"); 27 | 28 | dataStore.save(resource, (err, retrieved) => { 29 | if (err) { 30 | return done(err); 31 | } 32 | expect(fs.existsSync(file)).to.equal(true); 33 | done(); 34 | }); 35 | }); 36 | 37 | it("creates a nameless file for the root collection", (done) => { 38 | let dataStore = new FileDataStore(tempDir); 39 | let resource = new Resource("/", "/JDoe", { name: "John Doe" }); 40 | let file = path.join(tempDir, ".json"); 41 | 42 | dataStore.save(resource, (err, retrieved) => { 43 | if (err) { 44 | return done(err); 45 | } 46 | expect(fs.existsSync(file)).to.equal(true); 47 | done(); 48 | }); 49 | }); 50 | 51 | it("does not create any subdirectories if the collection is one level deep", (done) => { 52 | let dataStore = new FileDataStore(tempDir); 53 | let resource = new Resource("/users", "/JDoe", { name: "John Doe" }); 54 | let file = path.join(tempDir, "users.json"); 55 | 56 | dataStore.save(resource, (err, retrieved) => { 57 | if (err) { 58 | return done(err); 59 | } 60 | expect(fs.existsSync(file)).to.equal(true); 61 | done(); 62 | }); 63 | }); 64 | 65 | it("creates a single subdirectory if the collection is two levels deep", (done) => { 66 | let dataStore = new FileDataStore(tempDir); 67 | let resource = new Resource("/users/JDoe/", "orders", [{ orderId: 12345 }, { orderId: 45678 }]); 68 | let file = path.join(tempDir, "users", "jdoe.json"); 69 | 70 | dataStore.save(resource, (err, retrieved) => { 71 | if (err) { 72 | return done(err); 73 | } 74 | expect(fs.existsSync(file)).to.equal(true); 75 | done(); 76 | }); 77 | }); 78 | 79 | it("creates a multiple subdirectories for deeper collection paths", (done) => { 80 | let dataStore = new FileDataStore(tempDir); 81 | let resource = new Resource("/users/JDoe/orders/1234/products", "4567", { productId: 4567 }); 82 | let file = path.join(tempDir, "users", "jdoe", "orders", "1234", "products.json"); 83 | 84 | dataStore.save(resource, (err, retrieved) => { 85 | if (err) { 86 | return done(err); 87 | } 88 | expect(fs.existsSync(file)).to.equal(true); 89 | done(); 90 | }); 91 | }); 92 | 93 | it("returns an error if the file cannot be opened", (done) => { 94 | let dataStore = new FileDataStore(tempDir); 95 | let resource = new Resource("/users", "JDoe", { name: "John Doe" }); 96 | 97 | let stub = sinon.stub(fs, "readFile").callsFake((path, opts, callback) => { 98 | setImmediate(callback, new Error("Test Error")); 99 | }); 100 | 101 | function assert (err, data) { 102 | expect(err).to.be.an.instanceOf(Error); 103 | expect(data).to.equal(undefined); 104 | } 105 | 106 | dataStore.get(resource, (err, data) => { 107 | assert(err, data); 108 | 109 | dataStore.save(resource, (err, data) => { 110 | assert(err, data); 111 | 112 | dataStore.delete(resource, (err, data) => { 113 | assert(err, data); 114 | 115 | dataStore.getCollection("users", (err, data) => { 116 | assert(err, data); 117 | 118 | dataStore.deleteCollection("users", (err, data) => { 119 | assert(err, data); 120 | stub.restore(); 121 | done(); 122 | }); 123 | }); 124 | }); 125 | }); 126 | }); 127 | }); 128 | 129 | it("returns an error if the file cannot be saved", (done) => { 130 | let dataStore = new FileDataStore(tempDir); 131 | let resource = new Resource("/users", "JDoe", { name: "John Doe" }); 132 | 133 | // Save the resource successfully first, so we can accurately test `delete` and `deleteCollection` 134 | dataStore.save(resource, (err, data) => { 135 | if (err) { 136 | return done(err); 137 | } 138 | 139 | let stub = sinon.stub(fs, "writeFile").callsFake((path, data, callback) => { 140 | setImmediate(callback, new Error("Test Error")); 141 | }); 142 | 143 | function assert (err, data) { 144 | expect(err).to.be.an.instanceOf(Error); 145 | expect(data).to.equal(undefined); 146 | } 147 | 148 | dataStore.save(resource, (err, data) => { 149 | assert(err, data); 150 | 151 | dataStore.delete(resource, (err, data) => { 152 | assert(err, data); 153 | 154 | dataStore.deleteCollection("users", (err, data) => { 155 | assert(err, data); 156 | stub.restore(); 157 | done(); 158 | }); 159 | }); 160 | }); 161 | }); 162 | }); 163 | 164 | it("returns an error if the directory cannot be created", (done) => { 165 | let dataStore = new FileDataStore(tempDir); 166 | let resource = new Resource("/users/JDoe/orders", "12345", { orderId: 12345 }); 167 | 168 | // Save the resource successfully first, so we can accurately test `delete` and `deleteCollection` 169 | dataStore.save(resource, (err, data) => { 170 | if (err) { 171 | return done(err); 172 | } 173 | 174 | let mkdirStub = sinon.stub(fs, "mkdir").callsFake((path, data, callback) => { 175 | setImmediate(callback, new Error("Test Error")); 176 | }); 177 | 178 | let statStub = sinon.stub(fs, "stat").callsFake((path, callback) => { 179 | setImmediate(callback, new Error("Test Error")); 180 | }); 181 | 182 | function assert (err, data) { 183 | expect(err).to.be.an.instanceOf(Error); 184 | expect(data).to.equal(undefined); 185 | } 186 | 187 | dataStore.save(resource, (err, data) => { 188 | assert(err, data); 189 | 190 | dataStore.delete(resource, (err, data) => { 191 | assert(err, data); 192 | 193 | dataStore.deleteCollection("users/JDoe/orders", (err, data) => { 194 | assert(err, data); 195 | mkdirStub.restore(); 196 | statStub.restore(); 197 | done(); 198 | }); 199 | }); 200 | }); 201 | }); 202 | }); 203 | }); 204 | -------------------------------------------------------------------------------- /lib/middleware.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | module.exports = Middleware; 4 | 5 | const _ = require("lodash"); 6 | const SwaggerParser = require("@apidevtools/swagger-parser"); 7 | const util = require("./helpers/util"); 8 | const MiddlewareContext = require("./context"); 9 | const DataStore = require("./data-store"); 10 | const requestMetadata = require("./request-metadata"); 11 | const fileServer = require("./file-server"); 12 | const CORS = require("./cors"); 13 | const requestParser = require("./request-parser"); 14 | const paramParser = require("./param-parser"); 15 | const pathParser = require("./path-parser"); 16 | const requestValidator = require("./request-validator"); 17 | const mock = require("./mock"); 18 | 19 | /** 20 | * Express middleware for the given Swagger API. 21 | * 22 | * @param {express#Router} [sharedRouter] 23 | * - An Express Application or Router. If provided, this will be used to determine routing settings 24 | * (case sensitivity, strictness), and to register path-param middleware via {@link Router#param} 25 | * (see http://expressjs.com/4x/api.html#router.param). 26 | * 27 | * @constructor 28 | */ 29 | function Middleware (sharedRouter) { 30 | sharedRouter = util.isExpressRouter(sharedRouter) ? sharedRouter : undefined; 31 | 32 | let self = this; 33 | let context = new MiddlewareContext(sharedRouter); 34 | 35 | /** 36 | * Initializes the middleware with the given Swagger API. 37 | * This method can be called again to re-initialize with a new or modified API. 38 | * 39 | * @param {string|object} [swagger] 40 | * - The file path or URL of a Swagger 2.0 API spec, in YAML or JSON format. 41 | * Or a valid Swagger API object (see https://github.com/OAI/OpenAPI-Specification/blob/master/versions/2.0.md#swagger-object). 42 | * 43 | * @param {function} [callback] 44 | * - It will be called when the API has been parsed, validated, and dereferenced, or when an error occurs. 45 | */ 46 | this.init = function (swagger, callback) { 47 | // the swagger variable should only ever be a string or a populated object. 48 | let invalidSwagger = _.isFunction(swagger) || _.isDate(swagger) || _.isEmpty(swagger); 49 | 50 | if (invalidSwagger) { 51 | throw new Error("Expected a Swagger file or object"); 52 | } 53 | 54 | // Need to retrieve the Swagger API and metadata from the Swagger .yaml or .json file. 55 | let parser = new SwaggerParser(); 56 | parser.dereference(swagger, (err, api) => { 57 | if (err) { 58 | util.warn(err); 59 | } 60 | 61 | context.error = err; 62 | context.api = api; 63 | context.pathRegexCache = {}; 64 | context.parser = parser; 65 | context.emit("change"); 66 | 67 | if (_.isFunction(callback)) { 68 | callback(err, self, context.api, context.parser); 69 | } 70 | }); 71 | }; 72 | 73 | /** 74 | * Serves the Swagger API file(s) in JSON and YAML formats, 75 | * so they can be used with third-party front-end tools like Swagger UI and Swagger Editor. 76 | * 77 | * @param {express#Router} [router] 78 | * - Express routing options (e.g. `caseSensitive`, `strict`). 79 | * If an Express Application or Router is passed, then its routing settings will be used. 80 | * 81 | * @param {fileServer.defaultOptions} [options] 82 | * - Options for how the files are served (see {@link fileServer.defaultOptions}) 83 | * 84 | * @returns {function[]} 85 | */ 86 | this.files = function (router, options) { 87 | if (arguments.length === 1 && !util.isExpressRouter(router) && !util.isExpressRoutingOptions(router)) { 88 | // Shift arguments 89 | options = router; 90 | router = sharedRouter; 91 | } 92 | 93 | return fileServer(context, router, options); 94 | }; 95 | 96 | /** 97 | * Annotates the HTTP request (the `req` object) with Swagger metadata. 98 | * This middleware populates {@link Request#swagger}. 99 | * 100 | * @param {express#Router} [router] 101 | * - Express routing options (e.g. `caseSensitive`, `strict`). 102 | * If an Express Application or Router is passed, then its routing settings will be used. 103 | * 104 | * @returns {function[]} 105 | */ 106 | this.metadata = function (router) { 107 | return requestMetadata(context, router); 108 | }; 109 | 110 | /** 111 | * Handles CORS preflight requests and sets CORS headers for all requests 112 | * according the Swagger API definition. 113 | * 114 | * @returns {function[]} 115 | */ 116 | this.CORS = function () { 117 | return CORS(); 118 | }; 119 | 120 | /** 121 | * Parses the HTTP request into typed values. 122 | * This middleware populates {@link Request#params}, {@link Request#headers}, {@link Request#cookies}, 123 | * {@link Request#signedCookies}, {@link Request#query}, {@link Request#body}, and {@link Request#files}. 124 | * 125 | * @param {express#Router} [router] 126 | * - An Express Application or Router. If provided, this will be used to register path-param middleware 127 | * via {@link Router#param} (see http://expressjs.com/4x/api.html#router.param). 128 | * If not provided, then path parameters will always be parsed as strings. 129 | * 130 | * @param {requestParser.defaultOptions} [options] 131 | * - Options for each of the request-parsing middleware (see {@link requestParser.defaultOptions}) 132 | * 133 | * @returns {function[]} 134 | */ 135 | this.parseRequest = function (router, options) { 136 | if (arguments.length === 1 && !util.isExpressRouter(router) && !util.isExpressRoutingOptions(router)) { 137 | // Shift arguments 138 | options = router; 139 | router = sharedRouter; 140 | } 141 | 142 | return requestParser(options) 143 | .concat(paramParser()) 144 | .concat(pathParser(context, router)); 145 | }; 146 | 147 | /** 148 | * Validates the HTTP request against the Swagger API. 149 | * An error is sent downstream if the request is invalid for any reason. 150 | * 151 | * @returns {function[]} 152 | */ 153 | this.validateRequest = function () { 154 | return requestValidator(context); 155 | }; 156 | 157 | /** 158 | * Implements mock behavior for HTTP requests, based on the Swagger API. 159 | * 160 | * @param {express#Router} [router] 161 | * - Express routing options (e.g. `caseSensitive`, `strict`). 162 | * If an Express Application or Router is passed, then its routing settings will be used. 163 | * 164 | * @param {DataStore} [dataStore] 165 | * - The data store that will be used to persist REST resources. 166 | * If `router` is an Express Application, then you can set/get the data store 167 | * using `router.get("mock data store")`. 168 | * 169 | * @returns {function[]} 170 | */ 171 | this.mock = function (router, dataStore) { 172 | if (arguments.length === 1 && 173 | router instanceof DataStore) { 174 | // Shift arguments 175 | dataStore = router; 176 | router = undefined; 177 | } 178 | 179 | return mock(context, router, dataStore); 180 | }; 181 | } 182 | -------------------------------------------------------------------------------- /test/specs/mock/response.spec.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const _ = require("lodash"); 4 | const specs = require("../../fixtures/specs"); 5 | const helper = require("./helper"); 6 | 7 | for (let spec of specs) { 8 | describe(`Mock Response (${spec.name})`, () => { 9 | 10 | let api; 11 | beforeEach(() => { 12 | api = _.cloneDeep(spec.samples.petStore); 13 | }); 14 | 15 | it("should use the 200 response, if it exists", (done) => { 16 | api.paths["/pets"].get.responses = { 17 | 100: { description: "" }, 18 | 204: { description: "" }, 19 | default: { description: "" }, 20 | 300: { description: "" }, 21 | 200: { description: "" }, 22 | 400: { description: "" } 23 | }; 24 | 25 | helper.initTest(api, (supertest) => { 26 | supertest 27 | .get("/api/pets") 28 | .expect(200) 29 | .end(helper.checkResults(done)); 30 | }); 31 | }); 32 | 33 | it("should use the lowest 2XX response that exists", (done) => { 34 | api.paths["/pets"].get.responses = { 35 | 100: { description: "" }, 36 | 204: { description: "" }, 37 | default: { description: "" }, 38 | 203: { description: "" }, 39 | 201: { description: "" }, 40 | 404: { description: "" } 41 | }; 42 | 43 | helper.initTest(api, (supertest) => { 44 | supertest 45 | .get("/api/pets") 46 | .expect(201) 47 | .end(helper.checkResults(done)); 48 | }); 49 | }); 50 | 51 | it("should use the lowest 3XX response that exists", (done) => { 52 | api.paths["/pets"].get.responses = { 53 | 100: { description: "" }, 54 | 304: { description: "" }, 55 | default: { description: "" }, 56 | 302: { description: "" }, 57 | 303: { description: "" }, 58 | 400: { description: "" } 59 | }; 60 | 61 | helper.initTest(api, (supertest) => { 62 | supertest 63 | .get("/api/pets") 64 | .expect(302) 65 | .end(helper.checkResults(done)); 66 | }); 67 | }); 68 | 69 | it("should use the lowest 2XX response that exists", (done) => { 70 | api.paths["/pets"].get.responses = { 71 | 102: { description: "" }, 72 | 404: { description: "" }, 73 | 500: { description: "" }, 74 | 201: { description: "" }, 75 | 400: { description: "" }, 76 | 504: { description: "" } 77 | }; 78 | 79 | helper.initTest(api, (supertest) => { 80 | supertest 81 | .get("/api/pets") 82 | .expect(201) 83 | .end(helper.checkResults(done)); 84 | }); 85 | }); 86 | 87 | it('should use a 200 response if "default" exists', (done) => { 88 | api.paths["/pets"].get.responses = { 89 | 100: { description: "" }, 90 | 400: { description: "" }, 91 | default: { description: "" }, 92 | 402: { description: "" }, 93 | 500: { description: "" } 94 | }; 95 | 96 | helper.initTest(api, (supertest) => { 97 | supertest 98 | .get("/api/pets") 99 | .expect(200) 100 | .end(helper.checkResults(done)); 101 | }); 102 | }); 103 | 104 | it('should use a 201 response for POST operations if "default" exists', (done) => { 105 | api.paths["/pets"].post.responses = { 106 | 100: { description: "" }, 107 | 400: { description: "" }, 108 | default: { description: "" }, 109 | 402: { description: "" }, 110 | 500: { description: "" }, 111 | 201: { description: "" } 112 | }; 113 | 114 | helper.initTest(api, (supertest) => { 115 | supertest 116 | .post("/api/pets") 117 | .send({ Name: "Fido", Type: "dog" }) 118 | .expect(201) 119 | .end(helper.checkResults(done)); 120 | }); 121 | }); 122 | 123 | it('should not use a 201 response for POST operations if "default" does not exist', (done) => { 124 | api.paths["/pets"].post.responses = { 125 | 400: { description: "" }, 126 | 402: { description: "" }, 127 | 500: { description: "" }, 128 | 201: { description: "" } 129 | }; 130 | 131 | helper.initTest(api, (supertest) => { 132 | supertest 133 | .post("/api/pets") 134 | .send({ Name: "Fido", Type: "dog" }) 135 | .expect(201) 136 | .end(helper.checkResults(done)); 137 | }); 138 | }); 139 | 140 | it('should use a 201 response for PUT operations if "default" exists', (done) => { 141 | api.paths["/pets"].put = api.paths["/pets"].post; 142 | api.paths["/pets"].put.responses = { 143 | 100: { description: "" }, 144 | 400: { description: "" }, 145 | default: { description: "" }, 146 | 402: { description: "" }, 147 | 500: { description: "" }, 148 | 201: { description: "" } 149 | }; 150 | 151 | helper.initTest(api, (supertest) => { 152 | supertest 153 | .put("/api/pets") 154 | .send({ Name: "Fido", Type: "dog" }) 155 | .expect(201) 156 | .end(helper.checkResults(done)); 157 | }); 158 | }); 159 | 160 | it('should not use a 201 response for PUT operations if "default" does not exist', (done) => { 161 | api.paths["/pets"].put = api.paths["/pets"].post; 162 | api.paths["/pets"].put.responses = { 163 | 101: { description: "" }, 164 | 400: { description: "" }, 165 | 402: { description: "" }, 166 | 500: { description: "" }, 167 | 201: { description: "" } 168 | }; 169 | 170 | helper.initTest(api, (supertest) => { 171 | supertest 172 | .put("/api/pets") 173 | .send({ Name: "Fido", Type: "dog" }) 174 | .expect(201) 175 | .end(helper.checkResults(done)); 176 | }); 177 | }); 178 | 179 | it('should use a 204 response for DELETE operations if "default" exists', (done) => { 180 | api.paths["/pets/{PetName}"].delete.responses = { 181 | 100: { description: "" }, 182 | 400: { description: "" }, 183 | default: { description: "" }, 184 | 402: { description: "" }, 185 | 500: { description: "" } 186 | }; 187 | 188 | helper.initTest(api, (supertest) => { 189 | supertest 190 | .delete("/api/pets/Fido") 191 | .expect(204) 192 | .end(helper.checkResults(done)); 193 | }); 194 | }); 195 | 196 | it('should not use a 204 response for DELETE operations if "default" does not exist', (done) => { 197 | api.paths["/pets/{PetName}"].delete.responses = { 198 | 101: { description: "" }, 199 | 400: { description: "" }, 200 | 402: { description: "" }, 201 | 500: { description: "" }, 202 | 204: { description: "" } 203 | }; 204 | 205 | helper.initTest(api, (supertest) => { 206 | supertest 207 | .delete("/api/pets/Fido") 208 | .expect(204) 209 | .end(helper.checkResults(done)); 210 | }); 211 | }); 212 | }); 213 | } 214 | -------------------------------------------------------------------------------- /lib/request-metadata.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | module.exports = requestMetadata; 4 | 5 | const util = require("./helpers/util"); 6 | const _ = require("lodash"); 7 | 8 | /** 9 | * Adds a {@link Request#swagger} property with Swagger metadata for each HTTP request. 10 | * 11 | * @param {MiddlewareContext} context 12 | * @param {express#Router} router 13 | * @returns {function[]} 14 | */ 15 | function requestMetadata (context, router) { 16 | router = router || context.router; 17 | 18 | return [ 19 | swaggerMetadata, 20 | swaggerApiMetadata, 21 | swaggerPathMetadata, 22 | swaggerOperationMetadata, 23 | swaggerParamsMetadata, 24 | swaggerSecurityMetadata 25 | ]; 26 | 27 | /** 28 | * Sets `req.swagger.api` 29 | */ 30 | function swaggerApiMetadata (req, res, next) { 31 | // Only set req.swagger.api if the request is under the API's basePath 32 | if (context.api) { 33 | let basePath = util.normalizePath(context.api.basePath, router); 34 | let reqPath = util.normalizePath(req.path, router); 35 | if (_.startsWith(reqPath, basePath)) { 36 | req.swagger.api = context.api; 37 | } 38 | } 39 | 40 | next(); 41 | } 42 | 43 | /** 44 | * Sets `req.swagger.path` 45 | */ 46 | function swaggerPathMetadata (req, res, next) { 47 | if (req.swagger.api) { 48 | let relPath = getRelativePath(req); 49 | let relPathNormalized = util.normalizePath(relPath, router); 50 | 51 | // Search for a matching path 52 | Object.keys(req.swagger.api.paths).some((swaggerPath) => { 53 | let swaggerPathNormalized = util.normalizePath(swaggerPath, router); 54 | 55 | if (swaggerPathNormalized === relPathNormalized) { 56 | // We found an exact match (i.e. a path with no parameters) 57 | req.swagger.path = req.swagger.api.paths[swaggerPath]; 58 | req.swagger.pathName = swaggerPath; 59 | return true; 60 | } 61 | else if (req.swagger.path === null && pathMatches(relPathNormalized, swaggerPathNormalized, context)) { 62 | // We found a possible match, but keep searching in case we find an exact match 63 | req.swagger.path = req.swagger.api.paths[swaggerPath]; 64 | req.swagger.pathName = swaggerPath; 65 | } 66 | }); 67 | 68 | if (req.swagger.path) { 69 | util.debug("%s %s matches Swagger path %s", req.method, req.path, req.swagger.pathName); 70 | } 71 | else { 72 | util.warn('WARNING! Unable to find a Swagger path that matches "%s"', req.path); 73 | } 74 | } 75 | 76 | next(); 77 | } 78 | } 79 | 80 | /** 81 | * Creates the `req.swagger` object. 82 | */ 83 | function swaggerMetadata (req, res, next) { 84 | /** 85 | * The Swagger Metadata that is added to each HTTP request. 86 | * This object is exposed as `req.swagger`. 87 | * 88 | * @name Request#swagger 89 | */ 90 | req.swagger = { 91 | /** 92 | * The complete Swagger API object. 93 | * (see https://github.com/OAI/OpenAPI-Specification/blob/master/versions/2.0.md#swagger-object) 94 | * @type {SwaggerObject|null} 95 | */ 96 | api: null, 97 | 98 | /** 99 | * The Swagger path name, as it appears in the Swagger API. 100 | * (e.g. "/users/{username}/orders/{orderId}") 101 | * @type {string} 102 | */ 103 | pathName: "", 104 | 105 | /** 106 | * The Path object from the Swagger API. 107 | * (see https://github.com/OAI/OpenAPI-Specification/blob/master/versions/2.0.md#pathItemObject) 108 | * @type {object|null} 109 | */ 110 | path: null, 111 | 112 | /** 113 | * The Operation object from the Swagger API. 114 | * (see https://github.com/OAI/OpenAPI-Specification/blob/master/versions/2.0.md#operationObject) 115 | * @type {object|null} 116 | */ 117 | operation: null, 118 | 119 | /** 120 | * The Parameter objects that apply to this request. 121 | * (see https://github.com/OAI/OpenAPI-Specification/blob/master/versions/2.0.md#parameter-object-) 122 | * @type {object[]} 123 | */ 124 | params: [], 125 | 126 | /** 127 | * The Security Requirement objects that apply to this request. 128 | * (see https://github.com/OAI/OpenAPI-Specification/blob/master/versions/2.0.md#securityRequirementObject) 129 | * @type {object[]} 130 | */ 131 | security: [] 132 | }; 133 | 134 | next(); 135 | } 136 | 137 | /** 138 | * Sets `req.swagger.operation` 139 | */ 140 | function swaggerOperationMetadata (req, res, next) { 141 | if (req.swagger.path) { 142 | let method = req.method.toLowerCase(); 143 | 144 | if (method in req.swagger.path) { 145 | req.swagger.operation = req.swagger.path[method]; 146 | } 147 | else { 148 | util.warn("WARNING! Unable to find a Swagger operation that matches %s %s", req.method.toUpperCase(), req.path); 149 | } 150 | } 151 | 152 | next(); 153 | } 154 | 155 | /** 156 | * Sets `req.swagger.params` 157 | */ 158 | function swaggerParamsMetadata (req, res, next) { 159 | req.swagger.params = util.getParameters(req.swagger.path, req.swagger.operation); 160 | next(); 161 | } 162 | 163 | /** 164 | * Sets `req.swagger.security` 165 | */ 166 | function swaggerSecurityMetadata (req, res, next) { 167 | if (req.swagger.operation) { 168 | // Get the security requirements for this operation (or the global API security) 169 | req.swagger.security = req.swagger.operation.security || req.swagger.api.security || []; 170 | } 171 | else if (req.swagger.api) { 172 | // Get the global security requirements for the API 173 | req.swagger.security = req.swagger.api.security || []; 174 | } 175 | 176 | next(); 177 | } 178 | 179 | /** 180 | * Returns the HTTP request path, relative to the Swagger API's basePath. 181 | * 182 | * @param {Request} req 183 | * @returns {string} 184 | */ 185 | function getRelativePath (req) { 186 | if (!req.swagger.api.basePath) { 187 | return req.path; 188 | } 189 | else { 190 | return req.path.substr(req.swagger.api.basePath.length); 191 | } 192 | } 193 | 194 | /** 195 | * Determines whether the given HTTP request path matches the given Swagger path. 196 | * 197 | * @param {string} path - The request path (e.g. "/users/jdoe/orders/1234") 198 | * @param {string} swaggerPath - The Swagger path (e.g. "/users/{username}/orders/{orderId}") 199 | * @param {object} context - The Middleware context 200 | * @returns {boolean} 201 | */ 202 | function pathMatches (path, swaggerPath, context) { 203 | if (context.pathRegexCache[swaggerPath]) { 204 | return context.pathRegexCache[swaggerPath].test(path); 205 | } 206 | 207 | // Convert the Swagger path to a RegExp 208 | let pathPattern = swaggerPath.replace(util.swaggerParamRegExp, (match, paramName) => { 209 | return "([^/]+)"; 210 | }); 211 | 212 | // NOTE: This checks for an EXACT, case-sensitive match 213 | let pathRegExp = new RegExp("^" + pathPattern + "$"); 214 | 215 | // Cache swagger path regex for performance 216 | context.pathRegexCache[swaggerPath] = pathRegExp; 217 | 218 | return pathRegExp.test(path); 219 | } 220 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Swagger Express Middleware 2 | ============================ 3 | ### Swagger 2.0 middleware and mocks for Express.js 4 | 5 | > :warning: **No longer maintained** 6 | > This package has been abandoned for some time, and there are plenty of modern alternatives around which support OpenAPI v3.x listed on [OpenAPI.Tools](https://openapi.tools/) which you can use instead. 7 | 8 | [![npm](https://img.shields.io/npm/v/@apidevtools/swagger-express-middleware.svg)](https://www.npmjs.com/package/@apidevtools/swagger-express-middleware) 9 | [![License](https://img.shields.io/npm/l/@apidevtools/swagger-express-middleware.svg)](LICENSE) 10 | [![Buy us a tree](https://img.shields.io/badge/Treeware-%F0%9F%8C%B3-lightgreen)](https://plant.treeware.earth/APIDevTools/swagger-express-middleware) 11 | 12 | 13 | 14 | Features 15 | -------------------------- 16 | - **Supports Swagger 2.0 specs in JSON or YAML**
17 | Swagger Express Middleware uses [Swagger-Parser](https://github.com/APIDevTools/swagger-parser) to parse, validate, and dereference Swagger files. You can even split your spec into multiple different files using `$ref` pointers. 18 | 19 | - **Thoroughly tested**
20 | Over 1,000 unit tests and integration tests with 100% code coverage. Tested on [**over 1,500 real-world APIs**](https://apis.guru/browse-apis/) from Google, Microsoft, Facebook, Spotify, etc. All tests are run on Mac, Linux, and Windows using all LTS versions of Node. 21 | 22 | - [**Mock middleware**](https://apitools.dev/swagger-express-middleware/docs/middleware/mock.html)
23 | **Fully-functional mock** implementations for every operation in your API, including data persistence, all with **zero code!** This is a great way to test-drive your API as you write it, or for quick demos and POCs. You can even extend the mock middleware with your own logic and data to fill in any gaps. 24 | 25 | - [**Metadata middleware**](https://apitools.dev/swagger-express-middleware/docs/middleware/metadata.html)
26 | Annotates each request with all the relevant information from the Swagger definition. The path, the operation, the parameters, the security requirements - they're all easily accessible at `req.swagger`. 27 | 28 | - [**Parse Request middleware**](https://apitools.dev/swagger-express-middleware/docs/middleware/parseRequest.html)
29 | Parses incoming requests and converts everything into the correct data types, according to your Swagger API definition. 30 | 31 | - [**Validate Request middleware**](https://apitools.dev/swagger-express-middleware/docs/middleware/validateRequest.html)
32 | Ensures that every request complies with your Swagger API definition, or returns the appropriate HTTP error codes if needed. Of course, you can catch any validation errors and handle them however you want. 33 | 34 | - [**CORS middleware**](https://apitools.dev/swagger-express-middleware/docs/middleware/CORS.html)
35 | Adds the appropriate CORS headers to each request and automatically responds to CORS preflight requests, all in compliance with your Swagger API definition. 36 | 37 | - [**Files middleware**](https://apitools.dev/swagger-express-middleware/docs/middleware/files.html)
38 | Serves the Swagger API file(s) in JSON or YAML format so they can be used with front-end tools like [Swagger UI](http://www.swagger.io), [Swagger Editor](http://editor.swagger.io), and [Postman](http://getpostman.com). 39 | 40 | 41 | 42 | Installation and Use 43 | -------------------------- 44 | Install using [npm](https://docs.npmjs.com/about-npm/). 45 | 46 | ```bash 47 | npm install @apidevtools/swagger-express-middleware 48 | ``` 49 | Then use it in your [Node.js](http://nodejs.org/) script like this: 50 | 51 | ```javascript 52 | const express = require('express'); 53 | const createMiddleware = require('@apidevtools/swagger-express-middleware'); 54 | 55 | let app = express(); 56 | 57 | createMiddleware('PetStore.yaml', app, function(err, middleware) { 58 | // Add all the Swagger Express Middleware, or just the ones you need. 59 | // NOTE: Some of these accept optional options (omitted here for brevity) 60 | app.use( 61 | middleware.metadata(), 62 | middleware.CORS(), 63 | middleware.files(), 64 | middleware.parseRequest(), 65 | middleware.validateRequest(), 66 | middleware.mock() 67 | ); 68 | 69 | app.listen(8000, function() { 70 | console.log('The PetStore sample is now running at http://localhost:8000'); 71 | }); 72 | }); 73 | ``` 74 | 75 | 76 | 77 | Samples & Walkthroughs 78 | -------------------------- 79 | Swagger Express Middleware comes two samples that use the [Swagger Pet Store API](https://github.com/APIDevTools/swagger-express-middleware/blob/master/samples/PetStore.yaml). 80 | 81 | #### Sample 1 82 | This sample demonstrates the most simplistic usage of Swagger Express Middleware. It simply creates a new Express Application and adds all of the Swagger middleware without changing any options, and without adding any custom middleware. 83 | 84 | * [Source Code](https://github.com/APIDevTools/swagger-express-middleware/blob/master/samples/sample1.js) 85 | * [Walkthrough](https://apitools.dev/swagger-express-middleware/docs/walkthroughs/running.html) 86 | 87 | 88 | #### Sample 2 89 | This sample demonstrates a few more advanced features of Swagger Express Middleware, such as setting a few options, initializing the mock data store, and adding custom middleware logic. 90 | 91 | * [Source Code](https://github.com/APIDevTools/swagger-express-middleware/blob/master/samples/sample2.js) 92 | * [Walkthrough](https://apitools.dev/swagger-express-middleware/docs/walkthroughs/walkthrough2.html) 93 | 94 | 95 | 96 | Contributing 97 | -------------------------- 98 | I welcome any contributions, enhancements, and bug-fixes. [Open an issue](https://github.com/APIDevTools/swagger-express-middleware/issues) on GitHub and [submit a pull request](https://github.com/APIDevTools/swagger-express-middleware/pulls). 99 | 100 | #### Building/Testing 101 | To build/test the project locally on your computer: 102 | 103 | 1. **Clone this repo**
104 | `git clone https://github.com/APIDevTools/swagger-express-middleware.git` 105 | 106 | 2. **Install dependencies**
107 | `npm install` 108 | 109 | 3. **Run the tests**
110 | `npm test` 111 | 112 | 4. **Run the sample app**
113 | `npm start` 114 | 115 | 116 | 117 | License 118 | -------------------------- 119 | Swagger Express Middleware is 100% free and open-source, under the [MIT license](LICENSE). Use it however you want. 120 | 121 | This package is [Treeware](http://treeware.earth). If you use it in production, then we ask that you [**buy the world a tree**](https://plant.treeware.earth/APIDevTools/swagger-express-middleware) to thank us for our work. By contributing to the Treeware forest you’ll be creating employment for local families and restoring wildlife habitats. 122 | 123 | 124 | 125 | Big Thanks To 126 | -------------------------- 127 | Thanks to these awesome companies for their support of Open Source developers ❤ 128 | 129 | [![Travis CI](https://jstools.dev/img/badges/travis-ci.svg)](https://travis-ci.com) 130 | [![SauceLabs](https://jstools.dev/img/badges/sauce-labs.svg)](https://saucelabs.com) 131 | [![Coveralls](https://jstools.dev/img/badges/coveralls.svg)](https://coveralls.io) 132 | -------------------------------------------------------------------------------- /docs/middleware/validateRequest.md: -------------------------------------------------------------------------------- 1 | Validate Request middleware 2 | ============================ 3 | Ensures that every request complies with your Swagger API definition, or returns the appropriate HTTP error codes if needed. Of course, you can catch any validation errors and handle them however you want. 4 | 5 | 6 | Example 7 | -------------------------- 8 | This example uses the [PetStore.yaml](https://github.com/APIDevTools/swagger-express-middleware/blob/master/samples/PetStore.yaml) sample Swagger API. If you aren't familiar with using middleware in Express.js, then [read this first](http://expressjs.com/guide/using-middleware.html). 9 | 10 | ```javascript 11 | const express = require('express'); 12 | const createMiddleware = require('@apidevtools/swagger-express-middleware'); 13 | 14 | let app = express(); 15 | 16 | createMiddleware('PetStore.yaml', app, function(err, middleware) { 17 | app.use(middleware.metadata()); 18 | app.use(middleware.parseRequest()); 19 | app.use(middleware.validateRequest()); 20 | 21 | // An HTML page to help you produce a validation error 22 | app.use(function(req, res, next) { 23 | res.send( 24 | 'Click this button to see a validation error:' + 25 | '
' + 26 | '' + 27 | '
' 28 | ); 29 | }); 30 | 31 | // Error handler to display the validation error as HTML 32 | app.use(function(err, req, res, next) { 33 | res.status(err.status); 34 | res.send( 35 | '

' + err.status + ' Error

' + 36 | '
' + err.message + '
' 37 | ); 38 | }); 39 | 40 | app.listen(8000, function() { 41 | console.log('Go to http://localhost:8000'); 42 | }); 43 | }); 44 | ``` 45 | 46 | Run the above example and then browse to [http://localhost:8000](http://localhost:8000). When you click the button, it will send a `POST` request to the `/pets/{petName}` path in the [Swagger PetStore API](https://github.com/APIDevTools/swagger-express-middleware/blob/master/samples/PetStore.yaml). However, that path does not allow `POST` requests, so the Validate Request middleware will throw an [HTTP 405 (Method Not Allowed)](http://httpstatusdogs.com/405-method-not-allowed) error. 47 | 48 | 49 | Options 50 | -------------------------- 51 | ### `middleware.validateRequest(router)` 52 | This is the function you call to create the Validate Request middleware. 53 | 54 | * __router__ (_optional_) - `express.App` or `express.Router`
55 | An [Express Application](http://expressjs.com/4x/api.html#application) or [Router](http://expressjs.com/4x/api.html#router) that will be used to determine settings (such as case-sensitivity and strict routing). 56 |

57 | All Swagger Express Middleware modules accept this optional first parameter. Rather than passing it to each middleware, you can just pass it to the [createMiddleware function](../exports/createMiddleware.md) (as shown in the example above) and all middleware will use it. 58 | 59 | 60 | Dependencies 61 | -------------------------- 62 | The Validate Request middleware requires the following middleware to come before it in the middleware pipeline (as shown in the example above): 63 | 64 | * [Metadata middleware](metadata.md) 65 | * [Parse Request middleware](parseRequest.md) 66 | 67 | 68 | Behavior 69 | -------------------------- 70 | The Validate Request middleware checks each HTTP request for several different things and throws the appropriate HTTP error if validation fails. You can then handle the error by adding your own own [error-handling middleware](http://expressjs.com/guide/error-handling.html). You might choose to respond with a friendly error message, or you may choose to ignore the error and allow the request to continue being processed as normal. Be careful if you decide to continue processing - depending on how invalid the HTTP request is, it may cause other errors in other middleware. 71 | 72 | 73 | ### HTTP 401 (Unauthorized) 74 | If your Swagger API has [security requirements](https://github.com/OAI/OpenAPI-Specification/blob/master/versions/2.0.md#securityRequirementObject) defined, then the Validate Request middleware will check to make sure each request contains the necessary security info. For example, if you're using `basic` security, then it will verify that the `Authorization` HTTP header is present. If you're using `apiKey` security, then it will verify that the corresponding HTTP header or query parameter exists. 75 | 76 | If the request doesn't contain the necessary security information, then it will throw an [HTTP 401 (Unauthorized)](http://httpstatusdogs.com/401-unauthorized) error. For `basic` security, it will also set the `WWW-Authenticate` response header. 77 | 78 | > **NOTE:** The Validate Request middleware does not perform any authentication or authorization. It simply verifies that authentication info is present. 79 | 80 | 81 | ### HTTP 404 (Not Found) 82 | The Validate Request middleware will throw an [HTTP 404 (Not Found)](http://httpstatusdogs.com/404-not-found) error for any request that doesn't match one of the paths in your Swagger API. If your API has a `basePath` specified, then the Validate Request middleware will _only_ validate requests that are within the base path. So it will _not_ throw a 404 for requests that are outside of the base path. 83 | 84 | 85 | ### HTTP 405 (Method Not Allowed) 86 | If the HTTP request method does not match one of the methods allowed by your Swagger API, then the Validate Request middleware will throw an [HTTP 405 (Method Not Allowed)](http://httpstatusdogs.com/405-method-not-allowed) error. For example, if your Swagger API has a `/pets/{petName}` path with `GET`, `POST`, and `DELETE` operations, and somebody sends a `PATCH /pets/Fido` request, then a 405 error will be thrown. 87 | 88 | In addition, the `Allow` response header will be set to the methods that _are_ allowed by your Swagger API. 89 | 90 | 91 | ### HTTP 406 (Not Acceptable) 92 | If your Swagger API includes a `produces` list of [MIME types](https://github.com/OAI/OpenAPI-Specification/blob/master/versions/2.0.md#mimeTypes) that your API can produce, then the Validate Request middleware will check the `Accept` header of incoming requests to make sure the client accepts at least one of your MIME types. If none of your MIME types are accepted, then an [HTTP 406 (Not Acceptable)](http://httpstatusdogs.com/406-not-acceptable) error is thrown. 93 | 94 | 95 | ### HTTP 413 (Request Entity Too Large) 96 | If the request includes a payload (an HTTP body or form-data), and your Swagger operation does not have any `body` or `formData` parameters defined, then an [HTTP 413 (Request Entity Too Large)](http://httpstatusdogs.com/413-request-entity-too-large) error is thrown. 97 | 98 | 99 | ### HTTP 415 (Unsupported Media Type) 100 | If your Swagger API includes a `consumes` list of [MIME types](https://github.com/OAI/OpenAPI-Specification/blob/master/versions/2.0.md#mimeTypes) that your API can consume, then the Validate Request middleware will check the `Content-Type` header of incoming requests to make sure it matches one of your MIME types. If the content does not match any of your MIME types, then an [HTTP 415 (Unsupported Media Type)](https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#415) is thrown. 101 | 102 | 103 | ### HTTP 500 (Internal Server Error) 104 | If there's an error in the Swagger API itself — for example, the file couldn't be found, couldn't be parsed, or is invalid — then the Validate Request middleware will throw an [HTTP 500 (Internal Server Error)](http://httpstatusdogs.com/500-internal-server-error) error. 105 | --------------------------------------------------------------------------------