From 2940a1cbc6ef193d8e82372fdcb6e8792136fa0f Mon Sep 17 00:00:00 2001 From: Amar Zavery Date: Mon, 6 Feb 2017 12:37:59 -0800 Subject: [PATCH] Adding semantic and model validator to CI (#910) * added model validation and more refactoring * Dependency on master of openapi-validation-tools --- .travis.yml | 14 +++- LICENSE | 2 +- package.json | 11 ++- test/linter.js | 55 ++----------- test/model.js | 19 +++++ test/semantic.js | 19 +++++ test/syntax.js | 142 ++++++++++++++++++++++++++++++++ test/test.js | 200 --------------------------------------------- test/util/utils.js | 169 ++++++++++++++++++++++++++++++++++++++ 9 files changed, 374 insertions(+), 257 deletions(-) create mode 100644 test/model.js create mode 100644 test/semantic.js create mode 100644 test/syntax.js delete mode 100644 test/test.js create mode 100644 test/util/utils.js diff --git a/.travis.yml b/.travis.yml index 1290280a2818..e7ae966cbeec 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,6 +1,6 @@ language: node_js node_js: - - "4" + - "6" services: - docker env: @@ -11,6 +11,10 @@ env: - MODE=ruby - MODE=linter PR_ONLY=false - MODE=linter PR_ONLY=true + - MODE=semantic PR_ONLY=false + - MODE=semantic PR_ONLY=true + - MODE=model PR_ONLY=false + - MODE=model PR_ONLY=true global: secure: IwPVExGJQfL6QpTd6Utv3R/qBz7yApgMspzMLSkrq9cMP6t+NHk1vYg0n2evcWaq2rqNZE9xBMfRZi7Yy/RCUT++qALE89xlNvZ/A94PaDUiK3tYYNj1XxsgRauCHshXz6smmLwQMpTKsEl6lyjXeXpwQMDNRehhTu0Hg2tE7ZikfQVb+jjRNcP41RfzzGQyxEhd3fxF2G1G21WXH0cXtZssOd1MYcXhmXk8dR3DyEEMbPzDV1Bk3auXOvbqS79bnZ/Pi4p/7P/aTOm8O2ACKM0XAvww95vAQ0LcPzKnNe3sNjFVAssBiZbIk0Zs30wULBphlTxVoufHKSZuTf+QEBTKpH99v/+SBDDPu9+0q2esq7TKgf8bvzkeXjh54fECdJUqEo9E2gW08+RQxWCqryMJouOPcY2OMs4lZwSMOXQY68a/CYVRWFaFg5s6jntC8sLHtDxV0qem3xyjc+852v8rUfkyvMhOBoZJjWmnqYVqdamHOOfrzjc7AzPUEzSeiN6OPPVli+SzwLHdip0GxdK46pAISCOcbdyYn11VvTIn1QosE66eWhF6SViVH6lNWgSfVTpcQ2zq/qSKh0/zpwn82Ys+wKDOf3EwQAanndgk26npi7Ik4nIuexZ/TE56rQ7qjZqmHoxFz7QMeTZDiVmLFxtR19cTT3GLxDz8nBQ= matrix: @@ -18,6 +22,10 @@ matrix: allow_failures: - env: MODE=linter PR_ONLY=false - env: MODE=linter PR_ONLY=true + - env: MODE=semantic PR_ONLY=false + - env: MODE=semantic PR_ONLY=true + - env: MODE=model PR_ONLY=false + - env: MODE=model PR_ONLY=true before_install: - docker pull lmazuel/swagger-to-sdk - python -c "import os; print('\n'.join(v for v in os.environ.keys() if v.startswith('TRAVIS')))" > /tmp/env_file @@ -38,5 +46,7 @@ script: - if [[ $MODE == 'python' ]]; then $DOCKER_CMD AutorestCI/azure-sdk-for-python --pr-repo-id Azure/azure-sdk-for-python -o master -v; fi - if [[ $MODE == 'node' ]]; then $DOCKER_CMD AutorestCI/azure-sdk-for-node --pr-repo-id Azure/azure-sdk-for-node -o master -v; fi - if [[ $MODE == 'ruby' ]]; then $DOCKER_CMD AutorestCI/azure-sdk-for-ruby --pr-repo-id Azure/azure-sdk-for-ruby -o master -v; fi - - if [[ $MODE == 'syntax' ]]; then npm test -- test/test.js; fi + - if [[ $MODE == 'syntax' ]]; then npm test -- test/syntax.js; fi - if [[ $MODE == 'linter' ]]; then npm test -- test/linter.js; fi + - if [[ $MODE == 'semantic' ]]; then npm test -- test/semantic.js; fi + - if [[ $MODE == 'model' ]]; then npm test -- test/model.js; fi diff --git a/LICENSE b/LICENSE index dc1cf39d1359..6acfdd593962 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ The MIT License (MIT) -Copyright (c) 2016 Microsoft +Copyright (c) 2017 Microsoft Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/package.json b/package.json index ec948442bb11..1d18d9d500b2 100644 --- a/package.json +++ b/package.json @@ -9,16 +9,15 @@ "version": "0.1.0", "description": "Tests for Azure REST API Specifications", "license": "MIT", - "dependencies": { + "devDependencies": { + "mocha": "*", "async": "^2.1.4", "glob": "^5.0.14", "json-schema-ref-parser": "^3.1.2", "lodash": "^3.10.1", "request": "^2.61.0", - "z-schema": "^3.16.1" - }, - "devDependencies": { - "mocha": "*" + "z-schema": "^3.16.1", + "openapi-validation-tools": "azure/openapi-validation-tools#master" }, "homepage": "https://github.com/azure/azure-rest-api-specs", "repository": { @@ -31,4 +30,4 @@ "scripts": { "test": "mocha -t 500000" } -} +} \ No newline at end of file diff --git a/test/linter.js b/test/linter.js index 731f78cc6f45..6f7eb70e9216 100644 --- a/test/linter.js +++ b/test/linter.js @@ -2,58 +2,17 @@ // Licensed under the MIT License. See License in the project root for license information. 'use strict'; -var glob = require('glob'), - path = require('path'), - _ = require('lodash'); - -var globPath, swaggers; -var execSync = require('child_process').execSync; -var isWindows = (process.platform.lastIndexOf('win') === 0); -var prOnly = undefined !== process.env['PR_ONLY'] ? process.env['PR_ONLY'] : 'false'; -globPath = path.join(__dirname, '../', '/**/swagger/*.json'); -swaggers = _(glob.sync(globPath)); - -/** - * Converts command to OS specific command by prepending `mono` for non-windows prOnlySwaggers - * @returns {string} clr command - */ -function clrCmd(cmd) { - return isWindows ? cmd : ('mono ' + cmd); -}; - -/** - * Retrieves list of swagger files to be processed for linting - * @returns {Array} list of files to be processed for linting - */ -function getFilesForLinter() { - if (prOnly === 'true') { - // TODO: Currently works for PR into master branch only - var cmd = 'git diff --name-only HEAD $(git merge-base HEAD master)'; - let result; - try { - result = execSync(cmd, { encoding: 'utf8' }); - console.log(result); - var swaggerFileInPR = result.split('\n').filter(function (item) { - return (item.match(/.*\/swagger\/*/ig) !== null); - }); - console.log(`>>>> Number of swaggers found in this PR: ${swaggerFileInPR.length}`); - return swaggerFileInPR; - } catch (err) { - throw err; - } - } else { - // Return all the swagger files for linter processing - return swaggers; - } -} +var _ = require('lodash'), + execSync = require('child_process').execSync, + utils = require('./util/utils'); describe('AutoRest Linter validation:', function () { var autoRestLocation = './AutoRest.*/tools/AutoRest.exe'; - let swaggersToProcess = getFilesForLinter(); + let swaggersToProcess = utils.getFilesChangedInPR(); _(swaggersToProcess).each(function (swagger) { - it(swagger + ' should follow linter rules.', function (done) { - var cmd = clrCmd(autoRestLocation + ' -CodeGenerator None -I ' + swagger); - console.log(cmd); + it(swagger + ' should honor linter validation rules.', function (done) { + var cmd = utils.clrCmd(autoRestLocation + ' -CodeGenerator None -I ' + swagger); + console.log(`Executing: ${cmd}`); let result; try { result = execSync(cmd, { encoding: 'utf8' }); diff --git a/test/model.js b/test/model.js new file mode 100644 index 000000000000..eeb474232dd0 --- /dev/null +++ b/test/model.js @@ -0,0 +1,19 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License in the project root for license information. + +'use strict'; +var _ = require('lodash'), + utils = require('./util/utils'), + oav = require('openapi-validation-tools'); + +describe('Azure swagger model validation using x-ms-examples and examples in spec', function () { + let swaggersToProcess = utils.getFilesChangedInPR(); + _(swaggersToProcess).each(function (swagger) { + it(swagger + ' should have valid examples.', function (done) { + oav.validateExamples(swagger, null, false, 'error').catch(function (err) { + console.log(err); + }); + done(); + }); + }).value(); +}); \ No newline at end of file diff --git a/test/semantic.js b/test/semantic.js new file mode 100644 index 000000000000..c6e6a7591f6b --- /dev/null +++ b/test/semantic.js @@ -0,0 +1,19 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License in the project root for license information. + +'use strict'; +var _ = require('lodash'), + utils = require('./util/utils'), + oav = require('openapi-validation-tools'); + +describe('Azure swagger semantic validation:', function () { + let swaggersToProcess = utils.getFilesChangedInPR(); + _(swaggersToProcess).each(function (swagger) { + it(swagger + ' should be semantically valid.', function (done) { + oav.validateSpec(swagger, false, 'error').catch(function (err) { + console.log(err); + }); + done(); + }); + }).value(); +}); \ No newline at end of file diff --git a/test/syntax.js b/test/syntax.js new file mode 100644 index 000000000000..519b597426c0 --- /dev/null +++ b/test/syntax.js @@ -0,0 +1,142 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License in the project root for license information. + +'use strict'; +var assert = require("assert"), + fs = require('fs'), + path = require('path'), + _ = require('lodash'), + async = require('async'), + RefParser = require('json-schema-ref-parser'), + util = require('util'), + utils = require('./util/utils'); + +var context; + + +// Useful when debugging a test for a particular swagger. +// Just update the regex. That will return an array of filtered items. +// swaggers = swaggers.filter(function(item) { +// return (item.match(/.*arm-redis.*/ig) !== null); +// }); + + +describe('Azure swagger schema validation:', function () { + before(function (done) { + utils.initializeValidator(function (err, result) { + if (err) { + done(err); + } + context = result; + done(); + }); + }); + + _(utils.swaggers).each(function (swagger) { + it(swagger + ' should be a valid Swagger document.', function (done) { + utils.parseJsonFromFile(swagger, function (err, parsedData) { + if (err) { done(err); } + if (parsedData.documents && util.isArray(parsedData.documents)) { + console.log(util.format('Skipping the test for \'%s\' document as it seems to be a composite swagger doc.', swagger)); + done(); + } + var valid = context.validator.validate(parsedData, context.extensionSwaggerSchema); + if (!valid) { + var error = context.validator.getLastErrors(); + throw new Error("Schema validation failed: " + util.inspect(error, { depth: null })); + } + assert(valid === true); + done(); + }); + }); + }).value(); + + describe('Azure composite swagger schema validation:', function () { + _(utils.compositeSwaggers).each(function (compositeSwagger) { + it('composite: ' + compositeSwagger + ' should be a valid Composite Swagger document.', function (done) { + utils.parseJsonFromFile(compositeSwagger, function (err, parsedData) { + if (err) { done(err); } + var valid = context.validator.validate(parsedData, context.compositeSchema); + if (!valid) { + var error = context.validator.getLastErrors(); + throw new Error("Schema validation for Composite Swagger failed: " + util.inspect(error, { depth: null })); + } + assert(valid === true); + var compositeSwaggerDir = path.dirname(compositeSwagger); + var messages = []; + if (parsedData.documents && parsedData.documents.length > 0) { + async.eachSeries(parsedData.documents, function (docUrl, loopCallback) { + //construct the absolue path if the item in the documents array is a relative path + if (!path.isAbsolute(docUrl) && !docUrl.startsWith('http')) { + docUrl = path.join(compositeSwaggerDir, docUrl); + } + //make a request if it is a url + if (docUrl.startsWith('http')) { + request({ url: docUrl, json: true }, function (error, response, responseBody) { + if (error) { + messages.push('An error occurred while accessing the swagger doc ' + + docUrl + ' from the documents list. The error is ' + util.inspect(error, { depth: null })); + } + if (response.statusCode !== 200) { + messages.push('\'' + response.statusCode + '\': \'File Not Found\'- error occurred while accessing the swagger doc ' + + docUrl + ' from the documents list.'); + } + loopCallback(); + }); + } else { + //check whether the file exists + if (!fs.existsSync(docUrl)) { + messages.push('\'File Not Found\': error occurred while accessing the swagger doc ' + + docUrl + ' from the documents list on the host filesystem.'); + } + loopCallback(); + } + }, function (err) { + if (err) { + throw err; + } + if (messages.length > 0) { + throw new Error(JSON.stringify(messages)); + } + done(); + }); + } else { + done(); + } + }); + }); + }).value(); + }); + + describe('Azure x-ms-example schema validation:', function () { + _(utils.examples).each(function (example) { + it('x-ms-examples: ' + example + ' should be a valid x-ms-example.', function (done) { + utils.parseJsonFromFile(example, function (err, parsedData) { + if (err) { done(err); } + var valid = context.validator.validate(parsedData, context.exampleSchema); + if (!valid) { + var error = context.validator.getLastErrors(); + throw new Error("Schema validation failed: " + util.inspect(error, { depth: null })); + } + assert(valid === true); + done(); + }); + }); + }).value(); + }); +}); + +describe('External file or url references ("$ref") in a swagger spec:', function () { + _(utils.swaggers).each(function (swagger) { + it(swagger + ' should be completely resolvable.', function (done) { + RefParser.bundle(swagger, function (bundleErr, bundleResult) { + if (bundleErr) { + var msg = swagger + ' has references that cannot be resolved. They are as follows: \n' + util.inspect(bundleErr.message, { depth: null }); + console.log(msg); + throw new Error(msg); + } + done(); + }); + }); + }).value(); +}); \ No newline at end of file diff --git a/test/test.js b/test/test.js deleted file mode 100644 index 5e15beb29813..000000000000 --- a/test/test.js +++ /dev/null @@ -1,200 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. See License in the project root for license information. - -'use strict'; -var assert = require("assert"), - fs = require('fs'), - glob = require('glob'), - path = require('path'), - _ = require('lodash'), - z = require('z-schema'), - request = require('request'), - async = require('async'), - RefParser = require('json-schema-ref-parser'), - util = require('util'); - -var extensionSwaggerSchemaUrl = "https://raw.githubusercontent.com/Azure/autorest/master/schema/swagger-extensions.json"; -var swaggerSchemaUrl = "http://json.schemastore.org/swagger-2.0"; -var swaggerSchemaAltUrl = "http://swagger.io/v2/schema.json"; -var schemaUrl = "http://json-schema.org/draft-04/schema"; -var exampleSchemaUrl = "https://raw.githubusercontent.com/Azure/autorest/master/schema/example-schema.json"; -var compositeSchemaUrl = "https://raw.githubusercontent.com/Azure/autorest/master/schema/composite-swagger.json"; - -var swaggerSchema, extensionSwaggerSchema, schema4, exampleSchema, compositeSchema, - globPath, compositeGlobPath, exampleGlobPath, swaggers, compositeSwaggers, examples, validator; - -globPath = path.join(__dirname, '../', '/**/swagger/*.json'); -swaggers = _(glob.sync(globPath)); - - -// Useful when debugging a test for a particular swagger. -// Just update the regex. That will return an array of filtered items. -// swaggers = swaggers.filter(function(item) { -// return (item.match(/.*arm-redis.*/ig) !== null); -// }) - -compositeGlobPath = path.join(__dirname, '../', '/**/composite*.json'); -compositeSwaggers = _(glob.sync(compositeGlobPath)); - -exampleGlobPath = path.join(__dirname, '../', '/**/examples/*.json'); -examples = _(glob.sync(exampleGlobPath)); - -// Remove byte order marker. This catches EF BB BF (the UTF-8 BOM) -// because the buffer-to-string conversion in `fs.readFile()` -// translates it to FEFF, the UTF-16 BOM. -function stripBOM(content) { - if (Buffer.isBuffer(content)) { - content = content.toString(); - } - if (content.charCodeAt(0) === 0xFEFF || content.charCodeAt(0) === 0xFFFE) { - content = content.slice(1); - } - return content; -} - -describe('Azure Swagger Schema Validation', function () { - before(function (done) { - request({ url: extensionSwaggerSchemaUrl, json: true }, function (error, response, extensionSwaggerSchemaBody) { - request({ url: swaggerSchemaAltUrl, json: true }, function (error, response, swaggerSchemaBody) { - request({ url: exampleSchemaUrl, json: true }, function (error, response, exampleSchemaBody) { - request({ url: compositeSchemaUrl, json: true }, function (error, response, compositeSchemaBody) { - extensionSwaggerSchema = extensionSwaggerSchemaBody; - swaggerSchema = swaggerSchemaBody; - exampleSchema = exampleSchemaBody; - compositeSchema = compositeSchemaBody; - validator = new z({ breakOnFirstError: false }); - validator.setRemoteReference(swaggerSchemaUrl, swaggerSchema); - validator.setRemoteReference(exampleSchemaUrl, exampleSchema); - validator.setRemoteReference(compositeSchemaUrl, compositeSchema); - done(); - }); - }); - }); - }); - }); - - _(swaggers).each(function (swagger) { - it(swagger + ' should be a valid Swagger document.', function (done) { - fs.readFile(swagger, 'utf8', function (err, data) { - if (err) { done(err); } - var parsedData; - try { - parsedData = JSON.parse(stripBOM(data)); - } catch (err) { - throw new Error("swagger " + swagger + " is an invalid JSON. " + util.inspect(err, { depth: null })); - } - - if (parsedData.documents && util.isArray(parsedData.documents)) { - console.log(util.format('Skipping the test for \'%s\' document as it seems to be a composite swagger doc.', swagger)); - done(); - } - var valid = validator.validate(parsedData, extensionSwaggerSchema); - if (!valid) { - var error = validator.getLastErrors(); - throw new Error("Schema validation failed: " + util.inspect(error, { depth: null })); - } - assert(valid === true); - done(); - }); - }); - }).value(); - - _(compositeSwaggers).each(function (compositeSwagger) { - it('composite: ' + compositeSwagger + ' should be a valid Composite Swagger document.', function (done) { - fs.readFile(compositeSwagger, 'utf8', function (err, data) { - if (err) { done(err); } - var parsedData; - try { - parsedData = JSON.parse(stripBOM(data)); - } catch (err) { - throw new Error("compositeSwagger " + compositeSwagger + " is an invalid JSON. " + util.inspect(err, { depth: null })); - } - var valid = validator.validate(parsedData, compositeSchema); - if (!valid) { - var error = validator.getLastErrors(); - throw new Error("Schema validation for Composite Swagger failed: " + util.inspect(error, { depth: null })); - } - assert(valid === true); - var compositeSwaggerDir = path.dirname(compositeSwagger); - var messages = []; - if (parsedData.documents && parsedData.documents.length > 0) { - async.eachSeries(parsedData.documents, function (docUrl, loopCallback) { - //construct the absolue path if the item in the documents array is a relative path - if (!path.isAbsolute(docUrl) && !docUrl.startsWith('http')) { - docUrl = path.join(compositeSwaggerDir, docUrl); - } - //make a request if it is a url - if (docUrl.startsWith('http')) { - request({ url: docUrl, json: true }, function (error, response, responseBody) { - if (error) { - messages.push('An error occurred while accessing the swagger doc ' + - docUrl + ' from the documents list. The error is ' + util.inspect(error, { depth: null })); - } - if (response.statusCode !== 200) { - messages.push('\'' + response.statusCode + '\': \'File Not Found\'- error occurred while accessing the swagger doc ' + - docUrl + ' from the documents list.'); - } - loopCallback(); - }); - } else { - //check whether the file exists - if (!fs.existsSync(docUrl)) { - messages.push('\'File Not Found\': error occurred while accessing the swagger doc ' + - docUrl + ' from the documents list on the host filesystem.'); - } - loopCallback(); - } - }, function (err) { - if (err) { - throw err; - } - if (messages.length > 0) { - throw new Error(JSON.stringify(messages)); - } - done(); - }); - } else { - done(); - } - }); - }); - }).value(); - - _(examples).each(function (example) { - it('x-ms-examples: ' + example + ' should be a valid x-ms-example.', function (done) { - fs.readFile(example, 'utf8', function (err, data) { - if (err) { done(err); } - var parsedData; - try { - parsedData = JSON.parse(stripBOM(data)); - } catch (err) { - throw new Error("example file " + example + " is an invalid JSON. " + util.inspect(err, { depth: null })); - } - - var valid = validator.validate(parsedData, exampleSchema); - if (!valid) { - var error = validator.getLastErrors(); - throw new Error("Schema validation failed: " + util.inspect(error, { depth: null })); - } - assert(valid === true); - done(); - }); - }); - }).value(); - -}); - -describe('External file or url references ("$ref") in a swagger spec', function () { - _(swaggers).each(function(swagger) { - it(swagger + ' should be completely resolvable.', function(done) { - RefParser.bundle(swagger, function(bundleErr, bundleResult) { - if (bundleErr) { - var msg = swagger + ' has references that cannot be resolved. They are as follows: \n' + util.inspect(bundleErr.message, {depth : null}); - console.log(msg); - throw new Error (msg); - } - done(); - }); - }); - }).value(); -}); \ No newline at end of file diff --git a/test/util/utils.js b/test/util/utils.js new file mode 100644 index 000000000000..c1c9f8846560 --- /dev/null +++ b/test/util/utils.js @@ -0,0 +1,169 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License in the project root for license information. + +'use strict'; +var assert = require("assert"), + fs = require('fs'), + glob = require('glob'), + path = require('path'), + _ = require('lodash'), + z = require('z-schema'), + request = require('request'), + util = require('util'), + execSync = require('child_process').execSync; + +exports = module.exports; + +exports.extensionSwaggerSchemaUrl = "https://raw.githubusercontent.com/Azure/autorest/master/schema/swagger-extensions.json"; +exports.swaggerSchemaUrl = "http://json.schemastore.org/swagger-2.0"; +exports.swaggerSchemaAltUrl = "http://swagger.io/v2/schema.json"; +exports.schemaUrl = "http://json-schema.org/draft-04/schema"; +exports.exampleSchemaUrl = "https://raw.githubusercontent.com/Azure/autorest/master/schema/example-schema.json"; +exports.compositeSchemaUrl = "https://raw.githubusercontent.com/Azure/autorest/master/schema/composite-swagger.json"; + +exports.isWindows = (process.platform.lastIndexOf('win') === 0); +exports.prOnly = undefined !== process.env['PR_ONLY'] ? process.env['PR_ONLY'] : 'false'; + +exports.globPath = path.join(__dirname, '../', '../', '/**/swagger/*.json'); +exports.swaggers = _(glob.sync(exports.globPath)); +exports.compositeGlobPath = path.join(__dirname, '../', '../', '/**/composite*.json'); +exports.compositeSwaggers = _(glob.sync(exports.compositeGlobPath)); +exports.exampleGlobPath = path.join(__dirname, '../', '../', '/**/examples/*.json'); +exports.examples = _(glob.sync(exports.exampleGlobPath)); + +// Remove byte order marker. This catches EF BB BF (the UTF-8 BOM) +// because the buffer-to-string conversion in `fs.readFile()` +// translates it to FEFF, the UTF-16 BOM. +exports.stripBOM = function stripBOM(content) { + if (Buffer.isBuffer(content)) { + content = content.toString(); + } + if (content.charCodeAt(0) === 0xFEFF || content.charCodeAt(0) === 0xFFFE) { + content = content.slice(1); + } + return content; +}; + +/** + * Parses the json from the given filepath + * @returns {string} clr command + */ +exports.parseJsonFromFile = function parseJsonFromFile(filepath, callback) { + fs.readFile(filepath, 'utf8', function (err, data) { + if (err) return callback(err); + try { + return callback(null, JSON.parse(exports.stripBOM(data))); + } catch (error) { + let e = new Error(`swagger "${filepath}" is an invalid JSON.\n${util.inspect(err, { depth: null })}`); + return callback(e); + } + }); +}; + +/** + * Converts command to OS specific command by prepending `mono` for non-windows prOnlySwaggers + * @returns {string} clr command + */ +exports.clrCmd = function clrCmd(cmd) { + return exports.isWindows ? cmd : ('mono ' + cmd); +}; + +/** + * Gets the name of the target branch to which the PR is sent. We are using the environment + * variable provided by travis-ci. It is called TRAVIS_BRANCH. More info can be found here: + * https://docs.travis-ci.com/user/environment-variables/#Default-Environment-Variables + * If the environment variable is undefined then the method returns 'master' as the default value. + * @returns {string} branchName The target branch name. + */ +exports.getTargetBranch = function getTargetBranch() { + console.log(`@@@@@ process.env['TRAVIS_BRANCH'] - ${process.env['TRAVIS_BRANCH']}`); + let result = process.env['TRAVIS_BRANCH'] || 'master'; + result = result.trim(); + console.log(`>>>>> The target branch is: "${result}".`); + return result; +}; + +/** + * Gets the name of the source branch from which the PR is sent. + * @returns {string} branchName The source branch name. + */ +exports.getSourceBranch = function getSourceBranch() { + let cmd = 'git rev-parse --abbrev-ref HEAD'; + let result = process.env['TRAVIS_PULL_REQUEST_BRANCH']; + console.log(`@@@@@ process.env['TRAVIS_PULL_REQUEST_BRANCH'] - ${process.env['TRAVIS_PULL_REQUEST_BRANCH']}`); + if (!result) { + try { + result = execSync(cmd, { encoding: 'utf8' }); + } catch (err) { + console.log(`An error occurred while getting the current branch ${util.inspect(err, { depth: null })}.`); + } + } + result = result.trim(); + console.log(`>>>>> The source branch is: "${result}".`); + return result; +}; + +/** + * Retrieves list of swagger files to be processed for linting + * @returns {Array} list of files to be processed for linting + */ +exports.getFilesChangedInPR = function getFilesChangedInPR() { + let result = exports.swaggers; + if (exports.prOnly === 'true') { + let targetBranch, cmd, filesChanged, swaggerFilesInPR; + try { + targetBranch = exports.getTargetBranch(); + cmd = `git diff --name-only HEAD $(git merge-base HEAD ${targetBranch})`; + filesChanged = execSync(cmd, { encoding: 'utf8' }); + console.log('>>>>> Files changed in this PR are as follows:') + console.log(filesChanged); + swaggerFilesInPR = filesChanged.split('\n').filter(function (item) { + return (item.match(/.*\/swagger\/*/ig) !== null); + }); + console.log(`>>>> Number of swaggers found in this PR: ${swaggerFilesInPR.length}`); + result = swaggerFilesInPR; + } catch (err) { + throw err; + } + } + return result; +}; + +/** + * Downloads the remote schemas and initializes the validator with remote references. + * @returns {Object} context Provides the schemas in json format and the validator. + */ +exports.initializeValidator = function initializeValidator(callback) { + request({ url: exports.extensionSwaggerSchemaUrl, json: true }, function (error, response, extensionSwaggerSchemaBody) { + if (error) { + return callback(error); + } + request({ url: exports.swaggerSchemaAltUrl, json: true }, function (error, response, swaggerSchemaBody) { + if (error) { + return callback(error); + } + request({ url: exports.exampleSchemaUrl, json: true }, function (error, response, exampleSchemaBody) { + if (error) { + return callback(error); + } + request({ url: exports.compositeSchemaUrl, json: true }, function (error, response, compositeSchemaBody) { + if (error) { + return callback(error); + } + let context = { + extensionSwaggerSchema: extensionSwaggerSchemaBody, + swaggerSchema: swaggerSchemaBody, + exampleSchema: exampleSchemaBody, + compositeSchema: compositeSchemaBody + }; + let validator = new z({ breakOnFirstError: false }); + validator.setRemoteReference(exports.swaggerSchemaUrl, context.swaggerSchema); + validator.setRemoteReference(exports.exampleSchemaUrl, context.exampleSchema); + validator.setRemoteReference(exports.compositeSchemaUrl, context.compositeSchema); + context.validator = validator; + return callback(null, context); + }); + }); + }); + }); +}; \ No newline at end of file