From 79304cd4982165bba7f325ba637d63ff40e82acb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Markacz?= Date: Tue, 12 Apr 2016 11:20:09 +0200 Subject: [PATCH 1/8] compatibility with protagonist@1.0.0 and newer --- package.json | 2 +- src/validator.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 9c6a13b..07c131d 100644 --- a/package.json +++ b/package.json @@ -14,7 +14,7 @@ "node": ">=0.11.x" }, "dependencies": { - "protagonist": ">=0.19.3", + "protagonist": ">=1.0.0", "yargs": "1.2.1", "jsonlint": "1.6.0" }, diff --git a/src/validator.js b/src/validator.js index 4021b68..2a78939 100644 --- a/src/validator.js +++ b/src/validator.js @@ -64,7 +64,7 @@ module.exports = function (fileName, validateRequests, validateResponses) { return; } - protagonist.parse(data, function (error, result) { + protagonist.parse(data, {type: 'ast'}, function (error, result) { if (error) { var lineNumber = lineNumberFromCharacterIndex(data, error.location[0].index); console.error('Error: ' + error.message + ' on line ' + lineNumber); From a7222c13dbc7515ee37067fab11b4df6276b5cf9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Markacz?= Date: Tue, 12 Apr 2016 11:54:16 +0200 Subject: [PATCH 2/8] add cli option fail-on-warnings --- bin/api-blueprint-validator | 7 ++++--- src/validator.js | 5 ++++- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/bin/api-blueprint-validator b/bin/api-blueprint-validator index d798021..ffd4f0f 100755 --- a/bin/api-blueprint-validator +++ b/bin/api-blueprint-validator @@ -1,9 +1,10 @@ #!/usr/bin/env node var yargs = require('yargs') .usage('Usage: $0 apiary.apib ') - .boolean(['validate-requests', 'validate-responses']) + .boolean(['validate-requests', 'validate-responses', 'fail-on-warnings']) .default('validate-requests', true) - .default('validate-responses', true), + .default('validate-responses', true) + .default('fail-on-warnings', false), argv = yargs.argv; if (argv.help) { @@ -18,4 +19,4 @@ if (argv._[0] === undefined) { } var validator = require('../src/validator'); -validator(argv._[0], argv.validateRequests, argv.validateResponses); \ No newline at end of file +validator(argv._[0], argv.validateRequests, argv.validateResponses, argv.failOnWarnings); diff --git a/src/validator.js b/src/validator.js index 2a78939..e2158ee 100644 --- a/src/validator.js +++ b/src/validator.js @@ -57,7 +57,7 @@ function errorPosition(example, action, resource, resourceGroup) { return 'in ' + output.join(', '); } -module.exports = function (fileName, validateRequests, validateResponses) { +module.exports = function (fileName, validateRequests, validateResponses, failOnWarnings) { fs.readFile(fileName, 'utf8', function (error, data) { if (error) { console.error('Could not open ' + fileName); @@ -104,6 +104,9 @@ module.exports = function (fileName, validateRequests, validateResponses) { if (errors.length > 0) { console.error(errors.join('\n\n')); + } + + if (errors.length > 0 || failOnWarnings && result.warnings.length > 0) { process.exit(1); } }); From 5d925b6ac24e6c91a49845b4879e644600faf9ca Mon Sep 17 00:00:00 2001 From: Oleg Artemiev Date: Tue, 6 Dec 2016 16:14:36 +0300 Subject: [PATCH 3/8] no fail if no warnings (apply https://github.com/dougbeasley/api-blueprint-validator/commit/a3dfc668b94fd8c0e81e905899ba66f72d6294cb), no fail on undefined result.ast (apply https://github.com/dougbeasley/api-blueprint-validator/commit/f7fcb8a54892c882bbe48d555c861fd482059179) --- src/validator.js | 56 ++++++++++++++++++++++++++---------------------- 1 file changed, 30 insertions(+), 26 deletions(-) diff --git a/src/validator.js b/src/validator.js index e2158ee..1233bd0 100644 --- a/src/validator.js +++ b/src/validator.js @@ -71,36 +71,40 @@ module.exports = function (fileName, validateRequests, validateResponses, failOn process.exit(1); } - result.warnings.forEach(function (warning) { - var lineNumber = lineNumberFromCharacterIndex(data, warning.location[0].index); - console.error('Warning: ' + warning.message + ' on line ' + lineNumber); - }); + if(result.wanrings) { + result.warnings.forEach(function (warning) { + var lineNumber = lineNumberFromCharacterIndex(data, warning.location[0].index); + console.error('Warning: ' + warning.message + ' on line ' + lineNumber); + }); + } var errors = []; - examples(result.ast, function (example, action, resource, resourceGroup) { - if (validateRequests) { - example.requests.forEach(function (request) { - var valid = isValidRequestOrResponse(request); - if (valid !== true) { - var message = ' ' + valid.message.replace(/\n/g, '\n '); - var position = errorPosition(example, action, resource, resourceGroup); - errors.push('Error in JSON request ' + position + '\n' + message); - } - }); - } + if(result.ast) { + examples(result.ast, function (example, action, resource, resourceGroup) { + if (validateRequests) { + example.requests.forEach(function (request) { + var valid = isValidRequestOrResponse(request); + if (valid !== true) { + var message = ' ' + valid.message.replace(/\n/g, '\n '); + var position = errorPosition(example, action, resource, resourceGroup); + errors.push('Error in JSON request ' + position + '\n' + message); + } + }); + } - if (validateResponses) { - example.responses.forEach(function (response) { - var valid = isValidRequestOrResponse(response); - if (valid !== true) { - var message = ' ' + valid.message.replace(/\n/g, '\n '); - var position = errorPosition(example, action, resource, resourceGroup); - errors.push('Error in JSON response ' + position + '\n' + message); - } - }); - } - }); + if (validateResponses) { + example.responses.forEach(function (response) { + var valid = isValidRequestOrResponse(response); + if (valid !== true) { + var message = ' ' + valid.message.replace(/\n/g, '\n '); + var position = errorPosition(example, action, resource, resourceGroup); + errors.push('Error in JSON response ' + position + '\n' + message); + } + }); + } + }); + } if (errors.length > 0) { console.error(errors.join('\n\n')); From 7d1ea307b96a5bde78c6dfdaf48ccba90556d422 Mon Sep 17 00:00:00 2001 From: Oleg Artemiev Date: Tue, 6 Dec 2016 21:31:22 +0300 Subject: [PATCH 4/8] apply of changes similar to https://github.com/JakubOnderka/api-blueprint-validator/pull/12/files (use requireBlueprintName arg option) --- bin/api-blueprint-validator | 7 +- package.json | 6 +- src/validator.js | 177 ++++++++++++++++++++++++------------ 3 files changed, 129 insertions(+), 61 deletions(-) diff --git a/bin/api-blueprint-validator b/bin/api-blueprint-validator index ffd4f0f..2be7a43 100755 --- a/bin/api-blueprint-validator +++ b/bin/api-blueprint-validator @@ -1,10 +1,11 @@ #!/usr/bin/env node var yargs = require('yargs') .usage('Usage: $0 apiary.apib ') - .boolean(['validate-requests', 'validate-responses', 'fail-on-warnings']) + .boolean(['validate-requests', 'validate-responses', 'fail-on-warnings', 'require-name']) .default('validate-requests', true) .default('validate-responses', true) - .default('fail-on-warnings', false), + .default('fail-on-warnings', false) + .default('require-name', false), argv = yargs.argv; if (argv.help) { @@ -19,4 +20,4 @@ if (argv._[0] === undefined) { } var validator = require('../src/validator'); -validator(argv._[0], argv.validateRequests, argv.validateResponses, argv.failOnWarnings); +validator(argv._[0], argv.validateRequests, argv.validateResponses, argv.failOnWarnings, argv.requireName); diff --git a/package.json b/package.json index 07c131d..b26a711 100644 --- a/package.json +++ b/package.json @@ -14,9 +14,11 @@ "node": ">=0.11.x" }, "dependencies": { - "protagonist": ">=1.0.0", + "glob-fs": "^0.1.6", + "is-glob": "^2.0.1", + "jsonlint": "1.6.0", "yargs": "1.2.1", - "jsonlint": "1.6.0" + "protagonist": ">=1.0.0" }, "license": "MIT" } diff --git a/src/validator.js b/src/validator.js index 1233bd0..f732551 100644 --- a/src/validator.js +++ b/src/validator.js @@ -1,4 +1,6 @@ var fs = require('fs'), + glob = require('glob-fs')({ gitignore: true }), + isGlob = require('is-glob'), protagonist = require('protagonist'), jsonParser = require('jsonlint').parser; @@ -6,6 +8,109 @@ function lineNumberFromCharacterIndex(string, index) { return string.substring(0, index).split("\n").length; } +function hasImportantWarning(result,parserOptions) { + has_important_warnings = False; + result.warnings.forEach(function(warning) { + if ((warning.indexOf('expected API name') !== -1) && ! parserOptions.requireBlueprintName) return; + has_important_warnings = True; + }); + return has_important_warnings; +} + +function lint(file, data, options) { + + function shouldSkip(event) { + return (!options.requireBlueprintName && event.message.indexOf('expected API name') !== -1 ); + } + + var parserOptions = { + requireBlueprintName: options.requireBlueprintName, + type: 'ast' + }; + + protagonist.parse(data, parserOptions, function (error, result) { + + if (error) { + var lineNumber = lineNumberFromCharacterIndex(data, error.location[0].index); + console.error('(' + file + ')' + ' ' + 'Error: ' + error.message + ' on line ' + lineNumber); + process.exit(1); + } + + if(result.wanrings) { + result.warnings.forEach(function (warning) { + if (!shouldSkip(warning)) { + var lineNumber = lineNumberFromCharacterIndex(data, warning.location[0].index); + console.error('(' + file + ')' + ' ' + 'Warning: ' + warning.message + ' on line ' + lineNumber); + } + }); + } + + var errors = []; + + if(result.ast) { + examples(result.ast, function (example, action, resource, resourceGroup) { + if (options.validateRequests) { + example.requests.forEach(function (request) { + var valid = isValidRequestOrResponse(request); + if (valid !== true) { + var message = ' ' + valid.message.replace(/\n/g, '\n '); + var position = errorPosition(example, action, resource, resourceGroup); + errors.push('(' + file + ')' + ' ' + 'Error in JSON request ' + position + '\n' + message); + } + }); + } + + if (options.validateResponses) { + example.responses.forEach(function (response) { + var valid = isValidRequestOrResponse(response); + if (valid !== true) { + var message = ' ' + valid.message.replace(/\n/g, '\n '); + var position = errorPosition(example, action, resource, resourceGroup); + errors.push('(' + file + ')' + ' ' + 'Error in JSON response ' + position + '\n' + message); + } + }); + } + }); + } + + if (errors.length > 0) { + console.error(errors.join('\n\n')); + } + + if (errors.length > 0 || + ( options.failOnWarnings && + ( result.warnings.length > 0 && hasImportantWarning(result.warnings,parserOptions) ) + ) + ) { + process.exit(1); + } + }); +} + +function processFile(path, options) { + fs.readFile(path, 'utf8', function (error, data) { + if (error) { + console.error('Could not open ' + path); + process.exit(1); + } + else { + lint(path, data, options); + } + }); +} + +function processGlob(path, options) { + glob.readdir(path, function (error, files) { + if (error) { + console.error('Unable to read files ' + path); + process.exit(1); + } + files.forEach(function (path) { + processFile(path, options); + }); + }); +} + function examples(ast, callback) { ast.resourceGroups.forEach(function (resourceGroup) { resourceGroup.resources.forEach(function (resource) { @@ -57,62 +162,22 @@ function errorPosition(example, action, resource, resourceGroup) { return 'in ' + output.join(', '); } -module.exports = function (fileName, validateRequests, validateResponses, failOnWarnings) { - fs.readFile(fileName, 'utf8', function (error, data) { - if (error) { - console.error('Could not open ' + fileName); - return; - } +module.exports = function (fileName, validateRequests, validateResponses, failOnWarnings, requireBlueprintName ) { + + var options = { + validateRequests: validateRequests, + validateResponses: validateResponses, + failOnWarnings: failOnWarnings, + requireBlueprintName: requireBlueprintName + }; - protagonist.parse(data, {type: 'ast'}, function (error, result) { - if (error) { - var lineNumber = lineNumberFromCharacterIndex(data, error.location[0].index); - console.error('Error: ' + error.message + ' on line ' + lineNumber); - process.exit(1); - } - - if(result.wanrings) { - result.warnings.forEach(function (warning) { - var lineNumber = lineNumberFromCharacterIndex(data, warning.location[0].index); - console.error('Warning: ' + warning.message + ' on line ' + lineNumber); - }); - } - - var errors = []; - - if(result.ast) { - examples(result.ast, function (example, action, resource, resourceGroup) { - if (validateRequests) { - example.requests.forEach(function (request) { - var valid = isValidRequestOrResponse(request); - if (valid !== true) { - var message = ' ' + valid.message.replace(/\n/g, '\n '); - var position = errorPosition(example, action, resource, resourceGroup); - errors.push('Error in JSON request ' + position + '\n' + message); - } - }); - } - - if (validateResponses) { - example.responses.forEach(function (response) { - var valid = isValidRequestOrResponse(response); - if (valid !== true) { - var message = ' ' + valid.message.replace(/\n/g, '\n '); - var position = errorPosition(example, action, resource, resourceGroup); - errors.push('Error in JSON response ' + position + '\n' + message); - } - }); - } - }); - } - - if (errors.length > 0) { - console.error(errors.join('\n\n')); - } + if (isGlob(fileName)) { + // never require the blueprint name for multiple files + options.requireBlueprintName = false; + processGlob(fileName, options); + } + else { + processFile(fileName, options); + } - if (errors.length > 0 || failOnWarnings && result.warnings.length > 0) { - process.exit(1); - } - }); - }); }; From bcdc92a9d8380b4678bae5f0a1a79a875b6a77f6 Mon Sep 17 00:00:00 2001 From: Oleg Artemiev Date: Fri, 9 Dec 2016 22:06:27 +0300 Subject: [PATCH 5/8] isJsonContentType() - determine json by regexp: Content-type may contain also encoding. --- src/validator.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/validator.js b/src/validator.js index f732551..445d5f9 100644 --- a/src/validator.js +++ b/src/validator.js @@ -125,8 +125,8 @@ function examples(ast, callback) { function isJsonContentType(headers) { return headers.some(function (header) { - return header.name === 'Content-Type' && header.value === 'application/json'; - }); + return header.name === 'Content-Type' && /application\/json/.test(header.value); // header may also contain other info, i.e. encoding: + }); //"application/json; charset=utf-8", so use regexp here. } function isValidRequestOrResponse(requestOrResponse) { From f9e70caedd29447510c7e12fa15a47149873cc9a Mon Sep 17 00:00:00 2001 From: Oleg Artemiev Date: Fri, 9 Dec 2016 22:08:46 +0300 Subject: [PATCH 6/8] + json checks for non-empty schemas, + check schema is correctly defined - validate via ajv module. --- package.json | 1 + src/validator.js | 34 ++++++++++++++++++++++++++++++++++ 2 files changed, 35 insertions(+) diff --git a/package.json b/package.json index b26a711..ff47eb0 100644 --- a/package.json +++ b/package.json @@ -18,6 +18,7 @@ "is-glob": "^2.0.1", "jsonlint": "1.6.0", "yargs": "1.2.1", + "ajv":"^4.9.0", "protagonist": ">=1.0.0" }, "license": "MIT" diff --git a/src/validator.js b/src/validator.js index 445d5f9..26b7ed0 100644 --- a/src/validator.js +++ b/src/validator.js @@ -3,6 +3,7 @@ var fs = require('fs'), isGlob = require('is-glob'), protagonist = require('protagonist'), jsonParser = require('jsonlint').parser; + ajv = require('ajv'); function lineNumberFromCharacterIndex(string, index) { return string.substring(0, index).split("\n").length; @@ -72,6 +73,7 @@ function lint(file, data, options) { } }); } + else console.log("Warning: No result.ast."); if (errors.length > 0) { console.error(errors.join('\n\n')); @@ -131,12 +133,44 @@ function isJsonContentType(headers) { function isValidRequestOrResponse(requestOrResponse) { if (isJsonContentType(requestOrResponse.headers)) { + // check body try { var body = requestOrResponse.body; jsonParser.parse(body); } catch (e) { return e; } + // schema, if exists, should be a valid json + try { + var schema = requestOrResponse.schema; + if ( schema.length > 0 ) { + jsonParser.parse(schema); + } + } catch (e) { + return e; + } + // also check schema definitions with ajv + try { + if ( schema.length > 0 ) { + schema = JSON.parse(requestOrResponse.schema); + if ( Object.keys(schema).length > 0 ) { + var jsconSchemaParser = ajv({verbose:true, allErrors:true, format:'full',v5:true,unicode:true}); + jsconSchemaParser.validateSchema(schema); + if (jsconSchemaParser.errors !== null ) { + exceptionMsg = "Error validating schema:\n"; + for (var key in jsconSchemaParser.errors) { + exceptionMsg = exceptionMsg + '\tValue rasing validation error:\t\t\t"' + jsconSchemaParser.errors[key].data + '".\n'; + exceptionMsg = exceptionMsg + '\tPath within schema:\t\t\t\t"' + jsconSchemaParser.errors[key].dataPath + '".\n'; + exceptionMsg = exceptionMsg + "\tProbably " + jsconSchemaParser.errors[key].message + ': "' + + jsconSchemaParser.errors[key].schema + '"' + '.\n\n'; + } + throw new Error(exceptionMsg); + } + } else console.log("schema.length < 0"); + } + } catch (e) { + return e; + } } return true; From 946fff74457fd606d453a0537b2bc84118448a5b Mon Sep 17 00:00:00 2001 From: root Date: Tue, 24 Jan 2017 18:50:18 +0300 Subject: [PATCH 7/8] mistype fix for logical values. --- src/validator.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/validator.js b/src/validator.js index 26b7ed0..5eedc62 100644 --- a/src/validator.js +++ b/src/validator.js @@ -10,10 +10,10 @@ function lineNumberFromCharacterIndex(string, index) { } function hasImportantWarning(result,parserOptions) { - has_important_warnings = False; + has_important_warnings = false; result.warnings.forEach(function(warning) { if ((warning.indexOf('expected API name') !== -1) && ! parserOptions.requireBlueprintName) return; - has_important_warnings = True; + has_important_warnings = true; }); return has_important_warnings; } From 9ce5197970ef934aa57953945ea0a5d487731412 Mon Sep 17 00:00:00 2001 From: root Date: Tue, 24 Jan 2017 19:15:40 +0300 Subject: [PATCH 8/8] +1 place to check for undefined value --- src/validator.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/validator.js b/src/validator.js index 5eedc62..69c78c8 100644 --- a/src/validator.js +++ b/src/validator.js @@ -11,10 +11,12 @@ function lineNumberFromCharacterIndex(string, index) { function hasImportantWarning(result,parserOptions) { has_important_warnings = false; + if(result.wanrings) { result.warnings.forEach(function(warning) { if ((warning.indexOf('expected API name') !== -1) && ! parserOptions.requireBlueprintName) return; has_important_warnings = true; }); + } return has_important_warnings; }