diff --git a/README.md b/README.md index 38be376..e82b1b7 100644 --- a/README.md +++ b/README.md @@ -79,6 +79,8 @@ Options: -r, --dereference Fully dereference all $ref pointers + -j, --swagger-json-schema Remove any JSON Schema pieces not supported by Swagger's implementation of JSON Schema + -f, --format Formats the JSON output using the given number of spaces (the default is 2 spaces) ``` diff --git a/bin/swagger.js b/bin/swagger.js index ee33884..1401252 100755 --- a/bin/swagger.js +++ b/bin/swagger.js @@ -20,6 +20,7 @@ program.command('validate ') program.command('bundle ') .description('Bundles a multi-file Swagger API into a single file') .option('-o, --outfile ', 'The output file') + .option('-j, --swagger-json-schema', 'Remove any JSON Schema pieces that aren\'t supported by Swagger') .option('-r, --dereference', 'Fully dereference all $ref pointers') .option('-f, --format ', 'Formats the JSON output using the given number of spaces (default is 2)') .action(function(filename, options) { diff --git a/lib/bundle.js b/lib/bundle.js index ced0f88..d822dee 100644 --- a/lib/bundle.js +++ b/lib/bundle.js @@ -1,6 +1,7 @@ 'use strict'; var fs = require('fs'); +var jsonPath = require('jsonpath-plus'); /** * Bundles the given Swagger API @@ -28,6 +29,11 @@ module.exports = function bundle(api, options, cb) { return SwaggerParser[method](api, opts) .then(function(api) { + // Remove JSON Schema pieces that Swagger trips over + if (options.swaggerJsonSchema) { + deJsonSchemafy(api); + } + // Serialize the bundled/dereferenced API as JSON var json = toJSON(api, options.format); @@ -45,6 +51,52 @@ module.exports = function bundle(api, options, cb) { }); }; +/** + * Looks in the Swagger JSON object for JSON Schema keywords that aren't + * supported by Swagger's subset of JSON Schema + * + * @param {object} api - The Swagger object + */ +function deJsonSchemafy(api) { + jsonPath({json: api, path: '$.paths[*][*].responses[*].schema', callback: removeJsonSchemaParts}); + jsonPath({json: api, path: '$.paths[*][*].parameters[*].schema', callback: removeJsonSchemaParts}); + jsonPath({json: api, path: '$.paths[*].parameters[*].schema', callback: removeJsonSchemaParts}); + jsonPath({json: api, path: '$.definitions[*].schema', callback: removeJsonSchemaParts}); + jsonPath({json: api, path: '$.parameters[*].schema', callback: removeJsonSchemaParts}); +} + +/** + * Modifies the given JSON Schema object to remove any pieces that Swagger + * doesn't support + * + * @param {object} schemaObj - A JSON Schema object + */ +function removeJsonSchemaParts(schemaObj) { + delete schemaObj.$schema; + delete schemaObj.id; + + //If this type is an array, then we have to delete the required object. + //We'll be deleting any related ids that could identify the related objects + if (schemaObj.type === 'array') { + delete schemaObj.required; + } + + //Swagger only supports additionalProperties as an object + if (typeof schemaObj.additionalProperties !== 'object') { + delete schemaObj.additionalProperties; + } else { + jsonPath({json: schemaObj, path: '$.additionalProperties', callback: removeJsonSchemaParts}); + } + + jsonPath({json: schemaObj, path: '$.properties.*', callback: removeJsonSchemaParts}); + + if (Array.isArray(schemaObj.items)) { + jsonPath({json: schemaObj, path: '$.items[*]', callback: removeJsonSchemaParts}); + } else { + jsonPath({json: schemaObj, path: '$.items', callback: removeJsonSchemaParts}); + } +} + /** * Serializes the given API as JSON, using the given spaces for formatting. * diff --git a/package.json b/package.json index a1c6f4d..ad68686 100644 --- a/package.json +++ b/package.json @@ -53,6 +53,7 @@ "chalk": "^1.1.1", "commander": "^2.8.1", "es6-promise": "^3.0.2", + "jsonpath-plus": "0.15.0", "lodash": "^3.10.1", "swagger-parser": "^3.1.0", "swagger-server": "^1.0.0-alpha.18" @@ -70,4 +71,4 @@ "bin": { "swagger": "bin/swagger.js" } -} \ No newline at end of file +} diff --git a/tests/files/definitions/definitions-with-schema.json b/tests/files/definitions/definitions-with-schema.json new file mode 100644 index 0000000..68bc2b3 --- /dev/null +++ b/tests/files/definitions/definitions-with-schema.json @@ -0,0 +1,40 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "type": "object", + "additionalProperties": false, + "properties": { + "firstName": { + "id": "http://fake-swagger-cli.com/definitions/name", + "type": "string", + "description": "This is the first, given, non-familial name" + }, + "phoneNumber": { + "id": "http://fake-swagger-cli.com/definitions/phoneNumber", + "type": "array", + "items": { + "id": "http://fake-swagger-cli.com/definitions/phoneNumber/0", + "type": "object", + "properties": { + "areaCode": { + "id": "http://fake-swagger-cli.com/definitions/phoneNumber/0/areaCode", + "type": "string", + "description": "The 123 in '(123) 456-7890'" + }, + "localNumber": { + "id": "http://fake-swagger-cli.com/definitions/phoneNumber/0/areaCode", + "type": "string", + "description": "The 456-7890 in '(123) 456-7890'" + } + }, + "required": [ + "areaCode", + "localNumber" + ] + } + } + }, + "required": [ + "firstName", + "phoneNumber" + ] +} \ No newline at end of file diff --git a/tests/files/swagger-with-schema.json b/tests/files/swagger-with-schema.json new file mode 100644 index 0000000..ecaf415 --- /dev/null +++ b/tests/files/swagger-with-schema.json @@ -0,0 +1,33 @@ +{ + "swagger": "2.0", + "info": { + "version": "1.0.0", + "description": "This is an intentionally over-complicated API that returns a person's name", + "title": "Name API" + }, + "paths": { + "/people/{name}": { + "parameters": [ + { + "name": "name", + "in": "path", + "type": "string", + "required": true + } + ], + "get": { + "responses": { + "200": { + "description": "Returns the requested name", + "schema": { + "$ref": "#/definitions/name" + } + } + } + } + } + }, + "definitions": { + "$ref": "definitions/definitions-with-schema.json" + } +} \ No newline at end of file diff --git a/tests/specs/swagger.spec.js b/tests/specs/swagger.spec.js index ea8218b..2541a02 100644 --- a/tests/specs/swagger.spec.js +++ b/tests/specs/swagger.spec.js @@ -125,6 +125,37 @@ describe('swagger-cli commands', function() { } ); + it('running the \'swagger bundle -j\' command removes json schema parts that swagger can\'t handle', function() { + //remove potential leftover testfile from previous failed test + if (fs.existsSync('tests/swaggerSample/test.json')) { + fs.unlinkSync('tests/swaggerSample/test.json'); + } + + //Bundle the file while specifying -j to remove json-schema parts + var returnBuf = execSync('swagger bundle -j -o tests/swaggerSample/test.json tests/swaggerSample/swagger-with-schema.json'); + var outputArray = returnBuf.toString().split('\n'); + outputArray = _.dropRight(outputArray); + + //Command should run successfully + expect(outputArray).to.deep.equal([ + 'Bundling file: tests/swaggerSample/swagger.yaml', + 'File parsed successfully', + 'Writing parsed data to file tests/swaggerSample/test.json', + 'Parsed data successfully written to file' + ]); + + //Now validate the resulting file to ensure the troublesome json-schema parts don't result in errors + var returnBuffer = execSync(swaggerCmd + ' validate tests/swaggerSample/test.json'); + var outputArray = returnBuffer.toString().split('\n'); + + outputArray = _.dropRight(outputArray); + + expect(outputArray).to.deep.equal([ + 'Validating file: tests/swaggerSample/test.yaml', + 'File validated successfully' + ]); + }); + it('running the \'swagger bundle\' command on an invalid swagger file produces the expected error', function() { var errorMessageArray = []; try {