├── index.js ├── .travis.yml ├── test ├── index.js ├── swagger3.json ├── swagger1.json ├── swagger2.json └── testSpec.coffee ├── .gitignore ├── gulpfile.js ├── README.md ├── package.json └── src └── index.js /index.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | var merger = require('./src') 3 | module.exports = merger; 4 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - 6 4 | - 5 5 | - 4 6 | after_success: 7 | - npm run coveralls 8 | -------------------------------------------------------------------------------- /test/index.js: -------------------------------------------------------------------------------- 1 | var swaggermerge = require('../index') 2 | 3 | var swaggerOne = require('./swagger1.json') 4 | var swaggerTwo = require('./swagger2.json') 5 | 6 | var info = { 7 | version: "0.0.1", 8 | title: "merged swaggers", 9 | description: "all mighty services merged together\n" 10 | } 11 | var schemes = ['http'] 12 | 13 | swaggerOne.paths = JSON.parse(JSON.stringify(swaggerTwo.paths)) 14 | swaggerOne.basePath = swaggerTwo.basePath 15 | 16 | swaggermerge.on('warn', function (msg) { 17 | console.warn(msg) 18 | }) 19 | 20 | merged = swaggermerge.merge([swaggerOne, swaggerTwo], info, '/api', 'test.com', schemes) 21 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | 5 | # Runtime data 6 | pids 7 | *.pid 8 | *.seed 9 | 10 | *.suo 11 | *.ntvs_analysis.dat 12 | 13 | # Directory for instrumented libs generated by jscoverage/JSCover 14 | lib-cov 15 | 16 | # Coverage directory used by tools like istanbul 17 | coverage 18 | 19 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 20 | .grunt 21 | 22 | # node-waf configuration 23 | .lock-wscript 24 | 25 | # Compiled binary addons (http://nodejs.org/api/addons.html) 26 | build/Release 27 | 28 | obj 29 | 30 | # Dependency directory 31 | # https://docs.npmjs.com/misc/faq#should-i-check-my-node-modules-folder-into-git 32 | node_modules 33 | 34 | *.DS_Store 35 | -------------------------------------------------------------------------------- /gulpfile.js: -------------------------------------------------------------------------------- 1 | /* jshint node: true */ 2 | 'use strict'; 3 | 4 | require('coffee-script/register'); 5 | const istanbul = require('gulp-istanbul') 6 | const eslint = require('gulp-eslint') 7 | const mocha = require('gulp-mocha') 8 | const gulp = require('gulp') 9 | const jasmine = require('gulp-jasmine') 10 | const reporters = require('jasmine-reporters') 11 | 12 | gulp.task('pre-test', function() { 13 | return gulp.src(['src/*.js']) 14 | .pipe(istanbul()) 15 | .pipe(istanbul.hookRequire()) 16 | }) 17 | 18 | gulp.task('cover', ['pre-test'], function() { 19 | return gulp.src('test/**/*Spec.{js,coffee}') 20 | .pipe(jasmine()) 21 | .pipe(istanbul.writeReports()) 22 | }) 23 | 24 | gulp.task('test', function(){ 25 | return gulp.src('test/**/*Spec.{js,coffee}') 26 | .pipe(jasmine({ 27 | reporter: new reporters.TerminalReporter() 28 | })); 29 | }); 30 | 31 | gulp.task('watch', function(){ 32 | gulp.watch([ 33 | 'test/**/*Spec.{js,coffee}', 34 | 'src/**/*.{js,coffee}' 35 | ], ['test']); 36 | }); 37 | 38 | gulp.task('default', ['test', 'watch']); 39 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | # swagger-merge 4 | 5 | [![Build Status](https://travis-ci.org/HoS0/SwaggerMerge.svg?branch=master)](https://travis-ci.org/HoS0/SwaggerMerge) [![Coverage Status](https://coveralls.io/repos/github/HoS0/SwaggerMerge/badge.svg?branch=master)](https://coveralls.io/github/HoS0/SwaggerMerge?branch=master) 6 | 7 | merging multiple swagger 2.0 JSONs into one JSON 8 | 9 | `npm install swagger-merge` 10 | 11 | ## Example 12 | 13 | ``` javascript 14 | var swaggermerge = require('swagger-merge') 15 | var swaggerOne = require('./swagger1.json') 16 | var swaggerTwo = require('./swagger2.json') 17 | var info = { 18 | version: "0.0.1", 19 | title: "merged swaggers", 20 | description: "all mighty services merged together\n" 21 | } 22 | var schemes = ['http'] 23 | 24 | swaggermerge.on('warn', function (msg) { 25 | console.log(msg) 26 | }) 27 | 28 | merged = swaggermerge.merge([swaggerOne, swaggerTwo], info, '/api', 'test.com', schemes) 29 | ``` 30 | 31 | ## Running Tests 32 | 33 | Run test by: 34 | 35 | `gulp test` 36 | 37 | This software is licensed under the MIT License. 38 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "swagger-merge", 3 | "version": "0.3.3", 4 | "description": "merging multiple swagger 2.0 JSONs into one JSON.", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "gulp test", 8 | "coveralls": "gulp cover && cat ./coverage/lcov.info | ./node_modules/coveralls/bin/coveralls.js && rm -rf ./coverage" 9 | }, 10 | "repository": { 11 | "type": "git", 12 | "url": "git+https://github.com/HoS0/SwaggerMerge.git" 13 | }, 14 | "keywords": [ 15 | "HoS", 16 | "swagger", 17 | "merge", 18 | "JSON" 19 | ], 20 | "author": "Ali Khoramshahi (al-kh.me)", 21 | "license": "MIT", 22 | "bugs": { 23 | "url": "https://github.com/HoS0/SwaggerMerge" 24 | }, 25 | "homepage": "https://github.com/HoS0/SwaggerMerge#readme", 26 | "devDependencies": { 27 | "coffee-script": "^1.10.0", 28 | "coveralls": "^2.11.15", 29 | "gulp": "^3.9.1", 30 | "gulp-coffee": "^2.3.1", 31 | "gulp-eslint": "^3.0.1", 32 | "gulp-istanbul": "^1.1.1", 33 | "gulp-jasmine": "^2.3.0", 34 | "gulp-mocha": "^3.0.1", 35 | "jasmine-reporters": "^2.1.1", 36 | "mocha": "^3.2.0", 37 | "swagger2-utils": "^2.0.10" 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /test/swagger3.json: -------------------------------------------------------------------------------- 1 | { 2 | "swagger": "2.0", 3 | "info": { 4 | "version": "0.1.0", 5 | "title": "Zettabox HoS Admin", 6 | "description": "administration API for zettabox service\n" 7 | }, 8 | "x-extension-3": "correct-3", 9 | "x-extension-1b": "wrong-should-not-be-here-1b", 10 | "host": "test.com", 11 | "basePath": "/zbadmin", 12 | "schemes": [ 13 | "http", 14 | "https" 15 | ], 16 | "consumes": [ 17 | "application/json" 18 | ], 19 | "produces": [ 20 | "application/json" 21 | ], 22 | "tags": [ 23 | { 24 | "name": "Security", 25 | "description": "Security goes here" 26 | } 27 | ], 28 | "securityDefinitions": { 29 | "securitylogin": { 30 | "type": "basic", 31 | "description": "username:password" 32 | }, 33 | "securitytoken": { 34 | "type": "basic", 35 | "description": "registeredUserId:token" 36 | } 37 | }, 38 | "paths": { 39 | "/login": { 40 | "post": { 41 | "security": [ 42 | { 43 | "securitylogin": [] 44 | } 45 | ], 46 | "responses": { 47 | "200": { 48 | "description": "token and registration id info", 49 | "schema": { 50 | "$ref": "#/definitions/Token" 51 | } 52 | }, 53 | "403": { 54 | "description": "You do not have necessary permissions for the resource" 55 | } 56 | } 57 | }, 58 | "get": { 59 | "security": [ 60 | { 61 | "securitytoken": [] 62 | } 63 | ], 64 | "responses": { 65 | "200": { 66 | "description": "valid token and registered id" 67 | }, 68 | "403": { 69 | "description": "You do not have necessary permissions for the resource" 70 | } 71 | } 72 | } 73 | }, 74 | "/users": { 75 | "get": { 76 | "security": [ 77 | { 78 | "securitytoken": [] 79 | } 80 | ], 81 | "parameters": [ 82 | { 83 | "name": "username", 84 | "in": "query", 85 | "description": "email string, have regex", 86 | "default": "%@mail.com", 87 | "type": "string", 88 | "required": false 89 | } 90 | ], 91 | "responses": { 92 | "200": { 93 | "description": "List all users", 94 | "schema": { 95 | "$ref": "#/definitions/Token" 96 | } 97 | } 98 | } 99 | } 100 | } 101 | }, 102 | "definitions": { 103 | "Token": { 104 | "type": "object", 105 | "properties": { 106 | "token": { 107 | "type": "string" 108 | }, 109 | "registeredUserId": { 110 | "type": "string" 111 | }, 112 | "expration": { 113 | "type": "string" 114 | } 115 | } 116 | } 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /test/swagger1.json: -------------------------------------------------------------------------------- 1 | { 2 | "swagger": "2.0", 3 | "info": { 4 | "version": "0.1.1", 5 | "title": "User service", 6 | "description": "User Management and Authentication service" 7 | }, 8 | "x-extension-1": "correct-1", 9 | "x-extension-1b": "correct-1b", 10 | "host": "test.com", 11 | "basePath": "/user", 12 | "schemes": [ 13 | "http" 14 | ], 15 | "consumes": [ 16 | "application/json" 17 | ], 18 | "produces": [ 19 | "application/json" 20 | ], 21 | "tags": [ 22 | { 23 | "name": "Users", 24 | "description": "Users go here" 25 | } 26 | ], 27 | "paths": { 28 | "/users": { 29 | "get": { 30 | "parameters": [{ 31 | "name": "limit", 32 | "in": "query", 33 | "description": "number of users to return", 34 | "type": "integer", 35 | "default": 1000, 36 | "minimum": 100, 37 | "maximum": 10000 38 | }], 39 | "responses": { 40 | "200": { 41 | "description": "List all users", 42 | "schema": { 43 | "title": "Users", 44 | "type": "array", 45 | "items": { 46 | "$ref": "#/definitions/User" 47 | } 48 | } 49 | } 50 | } 51 | }, 52 | "post": { 53 | "parameters": [{ 54 | "name": "user", 55 | "in": "body", 56 | "description": "The user JSON you want to post", 57 | "schema": { 58 | "$ref": "#/definitions/NewUser" 59 | }, 60 | "required": true 61 | }], 62 | "responses": { 63 | "200": { 64 | "description": "Make a new User" 65 | } 66 | } 67 | } 68 | }, 69 | "/user/{userId}": { 70 | "get": { 71 | "parameters": [{ 72 | "$ref": "#/parameters/userId" 73 | }], 74 | "responses": { 75 | "200": { 76 | "description": "Sends the user with userId", 77 | "schema": { 78 | "$ref": "#/definitions/User" 79 | } 80 | }, 81 | "404": { 82 | "$ref": "#/responses/UserNotFoundError" 83 | } 84 | } 85 | }, 86 | "put": { 87 | "parameters": [{ 88 | "$ref": "#/parameters/userId" 89 | }], 90 | "responses": { 91 | "200": { 92 | "description": "Updates the user", 93 | "schema": { 94 | "$ref": "#/definitions/User" 95 | } 96 | }, 97 | "404": { 98 | "$ref": "#/responses/UserNotFoundError" 99 | } 100 | } 101 | }, 102 | "delete": { 103 | "parameters": [{ 104 | "$ref": "#/parameters/userId" 105 | }], 106 | "responses": { 107 | "200": { 108 | "description": "Deletes the user", 109 | "schema": { 110 | "$ref": "#/definitions/User" 111 | } 112 | }, 113 | "404": { 114 | "$ref": "#/responses/UserNotFoundError" 115 | } 116 | } 117 | } 118 | } 119 | }, 120 | "definitions": { 121 | "User": { 122 | "type": "object", 123 | "properties": { 124 | "firstname": { 125 | "type": "string" 126 | }, 127 | "lastname": { 128 | "type": "string" 129 | }, 130 | "email": { 131 | "type": "string" 132 | }, 133 | "Id": { 134 | "type": "string" 135 | } 136 | } 137 | }, 138 | "NewUser": { 139 | "type": "object", 140 | "properties": { 141 | "firstname": { 142 | "type": "string" 143 | }, 144 | "lastname": { 145 | "type": "string" 146 | }, 147 | "email": { 148 | "type": "string" 149 | } 150 | } 151 | } 152 | }, 153 | "parameters": { 154 | "userId": { 155 | "name": "userId", 156 | "in": "path", 157 | "type": "string", 158 | "description": "ID of the user", 159 | "required": true 160 | } 161 | }, 162 | "responses": { 163 | "UserNotFoundError": { 164 | "description": "User Not Found Error", 165 | "schema": { 166 | "type": "object", 167 | "properties": { 168 | "code": { 169 | "type": "string" 170 | }, 171 | "message": { 172 | "type": "string" 173 | } 174 | } 175 | } 176 | } 177 | } 178 | } 179 | -------------------------------------------------------------------------------- /test/swagger2.json: -------------------------------------------------------------------------------- 1 | { 2 | "swagger": "2.0", 3 | "info": { 4 | "version": "1.0.0", 5 | "title": "Swagger Petstore", 6 | "description": "A sample API that uses a petstore as an example to demonstrate features in the swagger-2.0 specification", 7 | "termsOfService": "http://swagger.io/terms/", 8 | "contact": { 9 | "name": "Swagger API Team" 10 | }, 11 | "license": { 12 | "name": "MIT" 13 | } 14 | }, 15 | "x-extension-2": "correct-2", 16 | "x-extension-1b": "wrong-should-not-be-here-1b", 17 | "host": "test.com", 18 | "basePath": "/petstore", 19 | "schemes": [ 20 | "http" 21 | ], 22 | "consumes": [ 23 | "application/json" 24 | ], 25 | "produces": [ 26 | "application/json" 27 | ], 28 | "tags": [ 29 | { 30 | "name": "Pets", 31 | "description": "Pets go here" 32 | } 33 | ], 34 | "paths": { 35 | "/pets": { 36 | "get": { 37 | "description": "Returns all pets from the system that the user has access to", 38 | "operationId": "findPets", 39 | "produces": [ 40 | "application/json", 41 | "application/xml", 42 | "text/xml", 43 | "text/html" 44 | ], 45 | "parameters": [ 46 | { 47 | "name": "tags", 48 | "in": "query", 49 | "description": "tags to filter by", 50 | "required": false, 51 | "type": "array", 52 | "items": { 53 | "type": "string" 54 | }, 55 | "collectionFormat": "csv" 56 | }, 57 | { 58 | "name": "limit", 59 | "in": "query", 60 | "description": "maximum number of results to return", 61 | "required": false, 62 | "type": "integer", 63 | "format": "int32" 64 | } 65 | ], 66 | "responses": { 67 | "200": { 68 | "description": "pet response", 69 | "schema": { 70 | "type": "array", 71 | "items": { 72 | "$ref": "#/definitions/Pet" 73 | } 74 | } 75 | }, 76 | "default": { 77 | "description": "unexpected error", 78 | "schema": { 79 | "$ref": "#/definitions/ErrorModel" 80 | } 81 | } 82 | } 83 | }, 84 | "post": { 85 | "description": "Creates a new pet in the store. Duplicates are allowed", 86 | "operationId": "addPet", 87 | "produces": [ 88 | "application/json" 89 | ], 90 | "parameters": [ 91 | { 92 | "name": "pet", 93 | "in": "body", 94 | "description": "Pet to add to the store", 95 | "required": true, 96 | "schema": { 97 | "$ref": "#/definitions/NewPet" 98 | } 99 | } 100 | ], 101 | "responses": { 102 | "200": { 103 | "description": "pet response", 104 | "schema": { 105 | "$ref": "#/definitions/Pet" 106 | } 107 | }, 108 | "default": { 109 | "description": "unexpected error", 110 | "schema": { 111 | "$ref": "#/definitions/ErrorModel" 112 | } 113 | } 114 | } 115 | } 116 | }, 117 | "/pets/{id}": { 118 | "get": { 119 | "description": "Returns a user based on a single ID, if the user does not have access to the pet", 120 | "operationId": "findPetById", 121 | "produces": [ 122 | "application/json", 123 | "application/xml", 124 | "text/xml", 125 | "text/html" 126 | ], 127 | "parameters": [ 128 | { 129 | "$ref": "#/parameters/petId" 130 | } 131 | ], 132 | "responses": { 133 | "200": { 134 | "description": "pet response", 135 | "schema": { 136 | "$ref": "#/definitions/Pet" 137 | } 138 | }, 139 | "404": { 140 | "$ref": "#/responses/PetNotFoundError" 141 | }, 142 | "default": { 143 | "description": "unexpected error", 144 | "schema": { 145 | "$ref": "#/definitions/ErrorModel" 146 | } 147 | } 148 | } 149 | }, 150 | "delete": { 151 | "description": "deletes a single pet based on the ID supplied", 152 | "operationId": "deletePet", 153 | "parameters": [ 154 | { 155 | "$ref": "#/parameters/petId" 156 | } 157 | ], 158 | "responses": { 159 | "204": { 160 | "description": "pet deleted" 161 | }, 162 | "404": { 163 | "$ref": "#/responses/PetNotFoundError" 164 | }, 165 | "default": { 166 | "description": "unexpected error", 167 | "schema": { 168 | "$ref": "#/definitions/ErrorModel" 169 | } 170 | } 171 | } 172 | } 173 | } 174 | }, 175 | "definitions": { 176 | "Pet": { 177 | "type": "object", 178 | "allOf": [ 179 | { 180 | "$ref": "#/definitions/NewPet" 181 | }, 182 | { 183 | "required": [ 184 | "id" 185 | ], 186 | "properties": { 187 | "id": { 188 | "type": "integer", 189 | "format": "int64" 190 | } 191 | } 192 | } 193 | ] 194 | }, 195 | "NewPet": { 196 | "type": "object", 197 | "required": [ 198 | "name" 199 | ], 200 | "properties": { 201 | "name": { 202 | "type": "string" 203 | }, 204 | "tag": { 205 | "type": "string" 206 | } 207 | } 208 | }, 209 | "ErrorModel": { 210 | "type": "object", 211 | "required": [ 212 | "code", 213 | "message" 214 | ], 215 | "properties": { 216 | "code": { 217 | "type": "integer", 218 | "format": "int32" 219 | }, 220 | "message": { 221 | "type": "string" 222 | } 223 | } 224 | } 225 | }, 226 | "parameters": { 227 | "petId": { 228 | "name": "id", 229 | "in": "path", 230 | "description": "ID of pet to fetch", 231 | "required": true, 232 | "type": "integer", 233 | "format": "int64" 234 | } 235 | }, 236 | "responses": { 237 | "PetNotFoundError": { 238 | "description": "PetNotFoundError", 239 | "schema": { 240 | "type": "object", 241 | "properties": { 242 | "code": { 243 | "type": "string" 244 | }, 245 | "message": { 246 | "type": "string" 247 | } 248 | } 249 | } 250 | } 251 | } 252 | } 253 | -------------------------------------------------------------------------------- /test/testSpec.coffee: -------------------------------------------------------------------------------- 1 | swagger = require('swagger2-utils'); 2 | 3 | describe "Run swagger merge", ()-> 4 | swaggerOne = {} 5 | swaggerTwo = {} 6 | swaggerThree = {} 7 | swaggermerge = {} 8 | 9 | beforeEach (done)-> 10 | swaggerOne = JSON.parse(JSON.stringify(require('./swagger1'))) 11 | swaggerTwo = JSON.parse(JSON.stringify(require('./swagger2'))) 12 | swaggerThree = JSON.parse(JSON.stringify(require('./swagger3'))) 13 | 14 | swaggermerge = require('../index') 15 | done() 16 | 17 | it "and load any verify two swagger json", (done)-> 18 | expect(swagger.validate(swaggerOne)).toBe(true) 19 | expect(swagger.validate(swaggerTwo)).toBe(true) 20 | expect(swagger.validate(swaggerThree)).toBe(true) 21 | done() 22 | 23 | it "and merge two simple swaggers", (done)-> 24 | info = 25 | version: "0.0.1", 26 | title: "merged swaggers", 27 | description: "all mighty services merged together\n" 28 | 29 | merged = swaggermerge.merge([swaggerOne, swaggerTwo, swaggerThree], info, '/api', 'test.com') 30 | expect(swagger.validate(merged)).toBe(true) 31 | done() 32 | 33 | it "and merge two simple swaggers and rewrite schemes", (done)-> 34 | info = 35 | version: "0.0.1", 36 | title: "merged swaggers", 37 | description: "all mighty services merged together\n" 38 | 39 | merged = swaggermerge.merge([swaggerOne, swaggerTwo, swaggerThree], info, '/api', 'test.com', ['http']) 40 | expect(swagger.validate(merged)).toBe(true) 41 | expect(merged.schemes.length).toBe(1) 42 | expect(merged.schemes[0]).toBe('http') 43 | done() 44 | 45 | it "merge swagger with no basePath", (done)-> 46 | info = 47 | version: "0.0.1", 48 | title: "merged swaggers", 49 | description: "all mighty services merged together\n" 50 | 51 | delete swaggerOne.basePath 52 | delete swaggerTwo.basePath 53 | delete swaggerThree.basePath 54 | 55 | merged = swaggermerge.merge([swaggerOne, swaggerTwo, swaggerThree], info, '/api', 'test.com') 56 | expect(swagger.validate(merged)).toBe(true) 57 | expect(Object.keys(merged.paths)[0].startsWith('undefined')).toBe(false) 58 | done() 59 | 60 | it "merge swagger with no basePath", (done)-> 61 | info = 62 | version: "0.0.1", 63 | title: "merged swaggers", 64 | description: "all mighty services merged together\n" 65 | 66 | delete swaggerOne.securityDefinitions 67 | delete swaggerTwo.securityDefinitions 68 | delete swaggerThree.securityDefinitions 69 | 70 | merged = swaggermerge.merge([swaggerOne, swaggerTwo, swaggerThree], info, '/api', 'test.com') 71 | expect(swagger.validate(merged)).toBe(true) 72 | done() 73 | 74 | it "merge swagger with collision paths", (done)-> 75 | info = 76 | version: "0.0.1", 77 | title: "merged swaggers", 78 | description: "all mighty services merged together\n" 79 | 80 | swaggerOne.paths = JSON.parse JSON.stringify swaggerTwo.paths 81 | swaggerOne.basePath = swaggerTwo.basePath 82 | 83 | swaggermerge.on 'warn', (msg)=> 84 | done() 85 | 86 | merged = swaggermerge.merge([swaggerOne, swaggerTwo, swaggerThree], info, '/api', 'test.com') 87 | expect(swagger.validate(merged)).toBe(true) 88 | 89 | it "merge swagger with collision definitions", (done)-> 90 | info = 91 | version: "0.0.1", 92 | title: "merged swaggers", 93 | description: "all mighty services merged together\n" 94 | 95 | swaggerOne.definitions = JSON.parse JSON.stringify swaggerTwo.definitions 96 | 97 | swaggermerge.on 'warn', (msg)=> 98 | done() 99 | 100 | merged = swaggermerge.merge([swaggerOne, swaggerTwo, swaggerThree], info, '/api', 'test.com') 101 | expect(swagger.validate(merged)).toBe(true) 102 | 103 | it "merge swagger with collision security definitions", (done)-> 104 | info = 105 | version: "0.0.1", 106 | title: "merged swaggers", 107 | description: "all mighty services merged together\n" 108 | 109 | swaggerOne.securityDefinitions = JSON.parse JSON.stringify swaggerThree.securityDefinitions 110 | 111 | swaggermerge.on 'warn', (msg)=> 112 | done() 113 | 114 | merged = swaggermerge.merge([swaggerOne, swaggerTwo, swaggerThree], info, '/api', 'test.com') 115 | expect(swagger.validate(merged)).toBe(true) 116 | 117 | it "merge swagger with collision parameters", (done)-> 118 | info = 119 | version: "0.0.1", 120 | title: "merged swaggers", 121 | description: "all mighty services merged together\n" 122 | 123 | swaggerOne.parameters = JSON.parse JSON.stringify swaggerTwo.parameters 124 | 125 | swaggermerge.on 'warn', (msg)=> 126 | done() 127 | 128 | merged = swaggermerge.merge([swaggerOne, swaggerTwo, swaggerThree], info, '/api', 'test.com') 129 | expect(swagger.validate(merged)).toBe(true) 130 | 131 | it "merge swagger with collision responses", (done)-> 132 | info = 133 | version: "0.0.1", 134 | title: "merged swaggers", 135 | description: "all mighty services merged together\n" 136 | 137 | swaggerOne.responses = JSON.parse JSON.stringify swaggerTwo.responses 138 | 139 | swaggermerge.on 'warn', (msg)=> 140 | done() 141 | 142 | merged = swaggermerge.merge([swaggerOne, swaggerTwo, swaggerThree], info, '/api', 'test.com') 143 | expect(swagger.validate(merged)).toBe(true) 144 | it "merge swagger with tags", (done)-> 145 | info = 146 | version: "0.0.1", 147 | title: "merged swaggers", 148 | description: "all mighty services merged together\n" 149 | swaggerOne.responses = JSON.parse JSON.stringify swaggerTwo.responses 150 | 151 | swaggermerge.on 'warn', (msg)=> 152 | done() 153 | 154 | merged = swaggermerge.merge([swaggerOne, swaggerTwo, swaggerThree], info, '/api', 'test.com') 155 | expect(swagger.validate(merged)).toBe(true) 156 | expect(merged.tags).not.toBeNull() 157 | 158 | it "merge swagger with extenstions", (done)-> 159 | info = 160 | version: "0.0.1", 161 | title: "merged swaggers", 162 | description: "all mighty services merged together\n" 163 | swaggerOne.responses = JSON.parse JSON.stringify swaggerTwo.responses 164 | 165 | swaggermerge.on 'warn', (msg)=> 166 | done() 167 | 168 | merged = swaggermerge.merge([swaggerOne, swaggerTwo, swaggerThree], info, '/api', 'test.com') 169 | expect(swagger.validate(merged)).toBe(true) 170 | expect(merged['x-extension-1']).toBe('correct-1') 171 | expect(merged['x-extension-1b']).toBe('correct-1b') 172 | expect(merged['x-extension-2']).toBe('correct-2') 173 | expect(merged['x-extension-3']).toBe('correct-3') 174 | 175 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | const EventEmitter = require('events') 3 | 4 | class Merger extends EventEmitter { 5 | _mergeSecurityDefinitions(swaggers) { 6 | let ret = null; 7 | for (let i = 0; i < swaggers.length; i++) { 8 | let swagger = swaggers[i]; 9 | if (swagger.securityDefinitions) { 10 | let ref = Object.keys(swagger.securityDefinitions); 11 | for (let j = 0; j < ref.length; j++) { 12 | let key = ref[j]; 13 | if (ret == null) { 14 | ret = {}; 15 | } 16 | if (!ret[key]) { 17 | ret[key] = swagger.securityDefinitions[key]; 18 | } else { 19 | this.emit("warn", `multiple security definitions with the same name has define in swagger collection: ${key}`); 20 | } 21 | } 22 | } 23 | } 24 | return ret; 25 | } 26 | _mergedTags(swaggers) { 27 | let ret = []; 28 | for (let i = 0; i < swaggers.length; i++) { 29 | let swagger = swaggers[i]; 30 | if (swagger.tags) { 31 | let ref = swagger.tags; 32 | for (let j = 0; j < ref.length; j++) { 33 | let tag = ref[j]; 34 | if (tag && ret.indexOf(tag) < 0) { 35 | ret.push(tag); 36 | } 37 | } 38 | } 39 | } 40 | return ret; 41 | } 42 | _mergedSchemes(swaggers) { 43 | let ret = []; 44 | for (let i = 0; i < swaggers.length; i++) { 45 | let swagger = swaggers[i]; 46 | if (swagger.schemes) { 47 | let ref = swagger.schemes; 48 | for (let j = 0; j < ref.length; j++) { 49 | let scheme = ref[j]; 50 | if (scheme && ret.indexOf(scheme) < 0) { 51 | ret.push(scheme); 52 | } 53 | } 54 | } 55 | } 56 | return ret; 57 | } 58 | _mergedConsume(swaggers) { 59 | let ret = []; 60 | for (let i = 0; i < swaggers.length; i++) { 61 | let swagger = swaggers[i]; 62 | if (swagger.consumes) { 63 | let ref = swagger.consumes; 64 | for (let j = 0; j < ref.length; j++) { 65 | let consume = ref[j]; 66 | if (consume && ret.indexOf(consume) < 0) { 67 | ret.push(consume); 68 | } 69 | } 70 | } 71 | } 72 | return ret; 73 | }; 74 | _mergedProduces(swaggers) { 75 | let ret = []; 76 | for (let i = 0; i < swaggers.length; i++) { 77 | let swagger = swaggers[i]; 78 | if (swagger.produces) { 79 | let ref = swagger.produces; 80 | for (let j = 0; j < ref.length; j++) { 81 | let produce = ref[j]; 82 | if (produce && ret.indexOf(produce) < 0) { 83 | ret.push(produce); 84 | } 85 | } 86 | } 87 | } 88 | return ret; 89 | } 90 | _mergedPaths(swaggers) { 91 | let ret = null; 92 | for (let i = 0; i < swaggers.length; i++) { 93 | let swagger = swaggers[i]; 94 | if (swagger.paths) { 95 | let pathPrefix = ''; 96 | if (!!swagger.basePath && swagger.basePath !== '/') { 97 | pathPrefix = swagger.basePath; 98 | } 99 | let ref = Object.keys(swagger.paths); 100 | for (let j = 0; j < ref.length; j++) { 101 | let key = ref[j]; 102 | if (ret == null) { 103 | ret = {}; 104 | } 105 | if (!ret[pathPrefix + key]) { 106 | ret[pathPrefix + key] = swagger.paths[key]; 107 | } else { 108 | this.emit("warn", "multiple routes with the same name and base path has define in swagger collection: " + (swagger.basePath + key)); 109 | } 110 | } 111 | } 112 | } 113 | return ret; 114 | } 115 | _mergedDefinitions(swaggers) { 116 | let ret = null; 117 | for (let i = 0; i < swaggers.length; i++) { 118 | let swagger = swaggers[i]; 119 | if (swagger.definitions) { 120 | let ref = Object.keys(swagger.definitions); 121 | for (let j = 0; j < ref.length; j++) { 122 | let key = ref[j]; 123 | if (ret == null) { 124 | ret = {}; 125 | } 126 | if (!ret[key]) { 127 | ret[key] = swagger.definitions[key]; 128 | } else { 129 | this.emit("warn", "multiple definitions with the same name has define in swagger collection: " + key); 130 | } 131 | } 132 | } 133 | } 134 | return ret; 135 | } 136 | _mergedExtensions(swaggers) { 137 | let ret = {}; 138 | for (let i = 0; i < swaggers.length; i++) { 139 | let swagger = swaggers[i]; 140 | let extensionNames = Object.keys(swagger).filter(key => key.startsWith('x-')) 141 | for (let j = 0; j < extensionNames.length; j++) { 142 | let extensionName = extensionNames[j]; 143 | let extension = swagger[extensionName]; 144 | if (extension && !ret[extensionName]) { 145 | ret[extensionName] = extension; 146 | } 147 | } 148 | } 149 | return ret; 150 | } 151 | _mergedParameters(swaggers) { 152 | return this._mergedFieldObject(swaggers, 'parameters'); 153 | } 154 | _mergedResponses(swaggers) { 155 | return this._mergedFieldObject(swaggers, 'responses'); 156 | } 157 | _mergedFieldObject(swaggers, field) { 158 | let ret = null; 159 | for (let i = 0; i < swaggers.length; i++) { 160 | let swagger = swaggers[i]; 161 | if (swagger[field]) { 162 | let ref = Object.keys(swagger[field]); 163 | for (let j = 0; j < ref.length; j++) { 164 | let key = ref[j]; 165 | if (ret == null) { 166 | ret = {}; 167 | } 168 | if (!ret[key]) { 169 | ret[key] = swagger[field][key]; 170 | } else { 171 | this.emit("warn", "multiple " + field + " with the same name has define in swagger collection: " + key); 172 | } 173 | } 174 | } 175 | } 176 | return ret; 177 | } 178 | merge(swaggers, info, basePath, host, schemes) { 179 | let definitions, parameters, paths, responses, ret, securityDefinitions, extensions; 180 | if (typeof info !== 'object') { 181 | throw 'no info object as input or different format'; 182 | } 183 | if (typeof basePath !== 'string') { 184 | throw 'no basePath string as input or different format'; 185 | } 186 | if (typeof host !== 'string') { 187 | throw 'no host string as input or different format'; 188 | } 189 | ret = { 190 | swagger: "2.0", 191 | info: info, 192 | host: host, 193 | basePath: basePath, 194 | schemes: schemes || this._mergedSchemes(swaggers), 195 | consumes: this._mergedConsume(swaggers), 196 | produces: this._mergedProduces(swaggers), 197 | tags: this._mergedTags(swaggers) 198 | }; 199 | securityDefinitions = this._mergeSecurityDefinitions(swaggers); 200 | if (securityDefinitions) { 201 | ret.securityDefinitions = securityDefinitions; 202 | } 203 | definitions = this._mergedDefinitions(swaggers); 204 | if (definitions) { 205 | ret.definitions = definitions; 206 | } 207 | paths = this._mergedPaths(swaggers); 208 | if (paths) { 209 | ret.paths = paths; 210 | } 211 | parameters = this._mergedParameters(swaggers); 212 | if (parameters) { 213 | ret.parameters = parameters; 214 | } 215 | responses = this._mergedResponses(swaggers); 216 | if (responses) { 217 | ret.responses = responses; 218 | } 219 | extensions = this._mergedExtensions(swaggers); 220 | if (extensions) { 221 | Object.keys(extensions).forEach(key => { 222 | ret[key] = extensions[key]; 223 | }) 224 | } 225 | return ret; 226 | } 227 | } 228 | 229 | const merger = new Merger() 230 | module.exports = merger 231 | --------------------------------------------------------------------------------