From b7d782518c76826ccab815b572f94d68c024b2cb Mon Sep 17 00:00:00 2001 From: Sean Reder Date: Tue, 2 Aug 2016 14:55:02 -0500 Subject: [PATCH] Add the ability to remove the bits of JSON Schema that Swagger doesn't support If you're already working with JSON Schema validation tools, then this functionality makes using Swagger for documentation easier --- README.md | 2 + bin/swagger.js | 1 + lib/bundle.js | 52 +++++++++++++++++++ package.json | 3 +- .../definitions/definitions-with-schema.json | 40 ++++++++++++++ tests/files/swagger-with-schema.json | 33 ++++++++++++ tests/specs/swagger.spec.js | 31 +++++++++++ 7 files changed, 161 insertions(+), 1 deletion(-) create mode 100644 tests/files/definitions/definitions-with-schema.json create mode 100644 tests/files/swagger-with-schema.json 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 {