add multiple versions support

add multiple versions support
This commit is contained in:
Luis Tejeda
2022-04-19 16:41:21 -05:00
committed by Erik Mendoza
parent 36e83721dd
commit 30cc74b720
7 changed files with 342 additions and 28 deletions

View File

@@ -32,9 +32,67 @@ function getFileByContent(data) {
return element.content.match(version2RegExp) || element.content.match(version3RegExp); return element.content.match(version2RegExp) || element.content.match(version3RegExp);
}); });
if (!file) { if (!file) {
return { hasDefinedVersion: false }; throw new Error('Not files with version');
} }
return { hasDefinedVersion: true, content: file.content }; return file.content;
}
/**
* compares a version with an input
* @param {string} input The input to compare
* @param {string} version The version that will be used
* @returns {boolean} wheter the input corresponds to the version
*/
function compareVersion(input, version) {
let numberInput,
numberVersion;
numberInput = parseFloat(input);
numberVersion = parseFloat(version);
if (!isNaN(numberInput) && !isNaN(numberVersion)) {
return numberInput === numberVersion;
}
return false;
}
/**
* Determins the version regex according to the specificationVersion
* @param {string} specificationVersion the string of the desired version
* @returns {object} the resultant regular expresion using the provided data
*/
function getVersionRegexBySpecificationVersion(specificationVersion) {
let versionRegExp;
if (compareVersion(specificationVersion, '2.0')) {
versionRegExp = getVersionRegexp(VERSION_20);
}
else if (compareVersion(specificationVersion, '3.1')) {
versionRegExp = getVersionRegexp(VERSION_31);
}
else {
versionRegExp = getVersionRegexp(VERSION_30);
}
return versionRegExp;
}
/**
* When the array of files is provided as a list of parsed objects
* it returns the content from file that contains the version data
* specified in the specificationVersion parameter
* @param {array} data An array of the provided file's content parsed
* @param {string} specificationVersion the string of the desired version
* @returns {object} object with hasDefinedVersion property and
* The content of the file that contains the version data
*/
function getFileByContentSpecificationVersion(data, specificationVersion) {
let versionRegExp,
file;
versionRegExp = getVersionRegexBySpecificationVersion(specificationVersion);
file = data.find((element) => {
return element.content.match(versionRegExp);
});
if (!file) {
throw new Error('Not files with version');
}
return file.content;
} }
/** /**
@@ -57,14 +115,20 @@ function getFileByFileName(data) {
/** When the user provides a folder, this function returns the file /** When the user provides a folder, this function returns the file
* that contains the version data * that contains the version data
* @param {array} data An array of file's paths * @param {array} data An array of file's paths
* @param {string} specificationVersion the string of the desired version
* @returns {object} object with hasDefinedVersion property and * @returns {object} object with hasDefinedVersion property and
* The content of the file that contains the version data * The content of the file that contains the version data
*/ */
function getFileWithVersion(data) { function getFileWithVersion(data, specificationVersion) {
let file; let file;
if (data[0].hasOwnProperty('content')) { if (data[0].hasOwnProperty('content')) {
file = getFileByContent(data); if (specificationVersion) {
file = getFileByContentSpecificationVersion(data, specificationVersion);
}
else {
file = getFileByContent(data, specificationVersion);
}
} }
else if (data[0].hasOwnProperty('fileName')) { else if (data[0].hasOwnProperty('fileName')) {
file = getFileByFileName(data); file = getFileByFileName(data);
@@ -77,13 +141,13 @@ function getFileWithVersion(data) {
* @param {string} spec Data from input file * @param {string} spec Data from input file
* @returns {string} version of specification * @returns {string} version of specification
*/ */
function getSpecVersion({ type, data }) { function getSpecVersion({ type, data, specificationVersion }) {
if (!data) { if (!data) {
return DEFAULT_SPEC_VERSION; return DEFAULT_SPEC_VERSION;
} }
if (['folder'].includes(type)) { if (['folder'].includes(type)) {
data = getFileWithVersion(data); data = getFileWithVersion(data, specificationVersion);
} }
else if (['file'].includes(type)) { else if (['file'].includes(type)) {
try { try {
@@ -93,12 +157,6 @@ function getSpecVersion({ type, data }) {
return DEFAULT_SPEC_VERSION; // If path is invalid it will follow the OAS 3.0 way return DEFAULT_SPEC_VERSION; // If path is invalid it will follow the OAS 3.0 way
} }
} }
if (!data.hasDefinedVersion) {
return;
}
else {
data = data.content;
}
if (type === 'json') { if (type === 'json') {
data = JSON.stringify(data); data = JSON.stringify(data);
@@ -128,8 +186,8 @@ function getSpecVersion({ type, data }) {
* @param {string} specVersion - the OAS specification version * @param {string} specVersion - the OAS specification version
* @returns {NodeRequire} the schema utils according to version * @returns {NodeRequire} the schema utils according to version
*/ */
function getConcreteSchemaUtils({ type, data }) { function getConcreteSchemaUtils({ type, data, specificationVersion }) {
const specVersion = getSpecVersion({ type, data }); const specVersion = getSpecVersion({ type, data, specificationVersion });
if (!specVersion) { if (!specVersion) {
return; return;
} }
@@ -176,5 +234,7 @@ module.exports = {
getSpecVersion, getSpecVersion,
getConcreteSchemaUtils, getConcreteSchemaUtils,
filterOptionsByVersion, filterOptionsByVersion,
isSwagger isSwagger,
compareVersion,
getVersionRegexBySpecificationVersion
}; };

View File

@@ -5,7 +5,8 @@ var yaml = require('js-yaml'),
path = require('path'), path = require('path'),
pathBrowserify = require('path-browserify'), pathBrowserify = require('path-browserify'),
resolver = require('oas-resolver-browser'), resolver = require('oas-resolver-browser'),
yamlParse = require('yaml'); yamlParse = require('yaml'),
{ compareVersion } = require('./common/versionUtils.js');
const BROWSER = 'browser'; const BROWSER = 'browser';
module.exports = { module.exports = {
@@ -79,9 +80,10 @@ module.exports = {
* @param {Object} inputValidation Validator according to version * @param {Object} inputValidation Validator according to version
* @param {Object} options computed process options * @param {Object} options computed process options
* @param {Object} files Files map * @param {Object} files Files map
* @param {string} specificationVersion the string of the desired version
* @return {String} rootFile * @return {String} rootFile
*/ */
getRootFiles: function (input, inputValidation, options, files = {}) { getRootFiles: function (input, inputValidation, options, files = {}, specificationVersion) {
let rootFilesArray = [], let rootFilesArray = [],
filesPathArray = input.data, filesPathArray = input.data,
origin = input.origin || ''; origin = input.origin || '';
@@ -110,7 +112,14 @@ module.exports = {
throw new Error(obj.reason); throw new Error(obj.reason);
} }
if (inputValidation.validateSpec(oasObject, options).result) { if (inputValidation.validateSpec(oasObject, options).result) {
rootFilesArray.push(filePath.fileName); if (specificationVersion) {
if (compareVersion(specificationVersion, oasObject.openapi)) {
rootFilesArray.push(filePath.fileName);
}
}
else {
rootFilesArray.push(filePath.fileName);
}
} }
} }
catch (e) { catch (e) {

View File

@@ -4788,6 +4788,11 @@ module.exports = {
}, },
inputValidation, inputValidation,
/**
* Maps the input from detect root files to get root files
* @param {object} input - input schema
* @returns {Array} - Array of all MISSING_ENDPOINT objects
*/
mapDetectRootFilesInputToGetRootFilesInput(input) { mapDetectRootFilesInputToGetRootFilesInput(input) {
let adaptedData = input.data.map((file) => { let adaptedData = input.data.map((file) => {
return { fileName: file.path }; return { fileName: file.path };
@@ -4795,6 +4800,12 @@ module.exports = {
return { data: adaptedData }; return { data: adaptedData };
}, },
/**
* Maps the output from get root files to detect root files
* @param {object} output - output schema
* @param {string} version - specified version of the process
* @returns {object} - Detect root files result object
*/
mapGetRootFilesOutputToDetectRootFilesOutput(output, version) { mapGetRootFilesOutputToDetectRootFilesOutput(output, version) {
let adaptedData = output.map((file) => { let adaptedData = output.map((file) => {
return { path: file }; return { path: file };

View File

@@ -52,9 +52,13 @@ class SchemaPack {
this.computedOptions.schemaFaker = true; this.computedOptions.schemaFaker = true;
let indentCharacter = this.computedOptions.indentCharacter; let indentCharacter = this.computedOptions.indentCharacter;
this.computedOptions.indentCharacter = indentCharacter === 'tab' ? '\t' : ' '; this.computedOptions.indentCharacter = indentCharacter === 'tab' ? '\t' : ' ';
concreteUtils = getConcreteSchemaUtils(input); try {
this.hasDefinedVersion = concreteUtils !== undefined; this.hasDefinedVersion = true;
concreteUtils = getConcreteSchemaUtils(input);
}
catch (error) {
this.hasDefinedVersion = false;
}
this.validate(); this.validate();
} }
@@ -631,7 +635,8 @@ class SchemaPack {
}); });
} }
adaptedInput = schemaUtils.mapDetectRootFilesInputToGetRootFilesInput(input); adaptedInput = schemaUtils.mapDetectRootFilesInputToGetRootFilesInput(input);
rootFiles = parse.getRootFiles(adaptedInput, concreteUtils.inputValidation, this.computedOptions, files); rootFiles = parse.getRootFiles(adaptedInput, concreteUtils.inputValidation, this.computedOptions, files,
input.specificationVersion);
res = schemaUtils.mapGetRootFilesOutputToDetectRootFilesOutput(rootFiles, input.specificationVersion); res = schemaUtils.mapGetRootFilesOutputToDetectRootFilesOutput(rootFiles, input.specificationVersion);
return res; return res;
} }

View File

@@ -3,11 +3,37 @@ var expect = require('chai').expect,
fs = require('fs'), fs = require('fs'),
path = require('path'), path = require('path'),
VALID_OPENAPI_PATH = '../data/valid_openapi', VALID_OPENAPI_PATH = '../data/valid_openapi',
VALID_OPENAPI_31_PATH = '../data/valid_openapi31X',
PET_STORE_SEPARATED = '../data/petstore separate yaml/spec',
PET_STORE_SEPARATED_JSON = '../data/petstore-separate/spec',
validPetstore = path.join(__dirname, VALID_OPENAPI_PATH + '/petstore.yaml'), validPetstore = path.join(__dirname, VALID_OPENAPI_PATH + '/petstore.yaml'),
noauth = path.join(__dirname, VALID_OPENAPI_PATH + '/noauth.yaml'); noauth = path.join(__dirname, VALID_OPENAPI_PATH + '/noauth.yaml'),
petstoreSeparated = path.join(__dirname, PET_STORE_SEPARATED + '/swagger.yaml'),
petstoreSeparatedPet = path.join(__dirname, PET_STORE_SEPARATED + '/pet.yaml'),
petstoreSeparatedJson = path.join(__dirname, PET_STORE_SEPARATED_JSON + '/swagger.json'),
petstoreSeparatedPetJson = path.join(__dirname, PET_STORE_SEPARATED_JSON + '/Pet.json'),
validHopService31x = path.join(__dirname, VALID_OPENAPI_31_PATH + '/yaml/hopService.yaml');
describe('requestNameSource option', function() { describe('detectRoot method', function() {
it('should return one root 3.0 correctly no specific version', async function() {
let contentFile = fs.readFileSync(validPetstore, 'utf8'),
input = {
type: 'folder',
data: [
{
path: '/petstore.yaml',
content: contentFile
}
]
};
const res = await Converter.detectRootFiles(input);
expect(res).to.not.be.empty;
expect(res.result).to.be.true;
expect(res.output.data[0].path).to.equal('/petstore.yaml');
});
it('should return one root 3.0 correctly', async function() { it('should return one root 3.0 correctly', async function() {
let contentFile = fs.readFileSync(validPetstore, 'utf8'), let contentFile = fs.readFileSync(validPetstore, 'utf8'),
input = { input = {
@@ -26,10 +52,118 @@ describe('requestNameSource option', function() {
expect(res.output.data[0].path).to.equal('/petstore.yaml'); expect(res.output.data[0].path).to.equal('/petstore.yaml');
}); });
it('should return no root when specific version is not present', async function() {
let contentFile = fs.readFileSync(validPetstore, 'utf8'),
input = {
type: 'folder',
specificationVersion: '3.1',
data: [
{
path: '/petstore.yaml',
content: contentFile
}
]
};
const res = await Converter.detectRootFiles(input);
expect(res).to.not.be.empty;
expect(res.result).to.be.true;
expect(res.output.data.length).to.equal(0);
});
it('should return one root 3.0 correctly with other files in folder', async function() {
let petRoot = fs.readFileSync(petstoreSeparated, 'utf8'),
petSchema = fs.readFileSync(petstoreSeparatedPet, 'utf8'),
input = {
type: 'folder',
specificationVersion: '3.0',
data: [
{
path: '/swagger.yaml',
content: petRoot
},
{
path: '/Pet.yaml',
content: petSchema
}
]
};
const res = await Converter.detectRootFiles(input);
expect(res).to.not.be.empty;
expect(res.result).to.be.true;
expect(res.output.data.length).to.equal(1);
expect(res.output.data[0].path).to.equal('/swagger.yaml');
});
it('should return one root 3.1 correctly', async function() {
let contentFile = fs.readFileSync(validHopService31x, 'utf8'),
input = {
type: 'folder',
specificationVersion: '3.1',
data: [
{
path: '/hopService.yaml',
content: contentFile
}
]
};
const res = await Converter.detectRootFiles(input);
expect(res).to.not.be.empty;
expect(res.result).to.be.true;
expect(res.output.data[0].path).to.equal('/hopService.yaml');
});
it('should return one root when multiple versions are present correctly', async function() {
let petstoreContent = fs.readFileSync(validPetstore, 'utf8'),
hopService31x = fs.readFileSync(validHopService31x, 'utf8'),
input = {
type: 'folder',
specificationVersion: '3.0.0',
data: [
{
path: '/petstore.yaml',
content: petstoreContent
},
{
path: '/hopService.yaml',
content: hopService31x
}
]
};
const res = await Converter.detectRootFiles(input);
expect(res).to.not.be.empty;
expect(res.result).to.be.true;
expect(res.output.data.length).to.equal(1);
expect(res.output.data[0].path).to.equal('/petstore.yaml');
});
it('should return one root when multiple versions are present correctly 3.1', async function() {
let petstoreContent = fs.readFileSync(validPetstore, 'utf8'),
hopService31x = fs.readFileSync(validHopService31x, 'utf8'),
input = {
type: 'folder',
specificationVersion: '3.1.0',
data: [
{
path: '/petstore.yaml',
content: petstoreContent
},
{
path: '/hopService.yaml',
content: hopService31x
}
]
};
const res = await Converter.detectRootFiles(input);
expect(res).to.not.be.empty;
expect(res.result).to.be.true;
expect(res.output.data.length).to.equal(1);
expect(res.output.data[0].path).to.equal('/hopService.yaml');
});
it('should return no root file when there is not a root file present', async function() { it('should return no root file when there is not a root file present', async function() {
let input = { let input = {
type: 'folder', type: 'folder',
specificationVersion: '3.0', specificationVersion: '3.0.0',
data: [ data: [
{ {
path: '/petstore.yaml', path: '/petstore.yaml',
@@ -48,7 +182,7 @@ describe('requestNameSource option', function() {
noAuthContent = fs.readFileSync(noauth, 'utf8'), noAuthContent = fs.readFileSync(noauth, 'utf8'),
input = { input = {
type: 'folder', type: 'folder',
specificationVersion: '3.0', specificationVersion: '3.0.0',
data: [ data: [
{ {
path: '/petstore.yaml', path: '/petstore.yaml',
@@ -63,6 +197,7 @@ describe('requestNameSource option', function() {
const res = await Converter.detectRootFiles(input); const res = await Converter.detectRootFiles(input);
expect(res).to.not.be.empty; expect(res).to.not.be.empty;
expect(res.result).to.be.true; expect(res.result).to.be.true;
expect(res.output.data.length).to.equal(2);
expect(res.output.data[0].path).to.equal('/petstore.yaml'); expect(res.output.data[0].path).to.equal('/petstore.yaml');
expect(res.output.data[1].path).to.equal('/noauth.yaml'); expect(res.output.data[1].path).to.equal('/noauth.yaml');
}); });
@@ -70,7 +205,7 @@ describe('requestNameSource option', function() {
it('should propagate one error correctly', async function () { it('should propagate one error correctly', async function () {
let input = { let input = {
type: 'folder', type: 'folder',
specificationVersion: '3.0', specificationVersion: '3.0.0',
data: [ data: [
{ {
path: '', path: '',
@@ -86,4 +221,28 @@ describe('requestNameSource option', function() {
} }
}); });
it('should return one root 3.0 correctly with other files in folder json', async function() {
let petRoot = fs.readFileSync(petstoreSeparatedJson, 'utf8'),
petSchema = fs.readFileSync(petstoreSeparatedPetJson, 'utf8'),
input = {
type: 'folder',
specificationVersion: '3.0',
data: [
{
path: '/swagger.json',
content: petRoot
},
{
path: '/Pet.json',
content: petSchema
}
]
};
const res = await Converter.detectRootFiles(input);
expect(res).to.not.be.empty;
expect(res.result).to.be.true;
expect(res.output.data.length).to.equal(1);
expect(res.output.data[0].path).to.equal('/swagger.json');
});
}); });

View File

@@ -21,6 +21,22 @@ describe('PARSE FUNCTION TESTS', function() {
expect(result[0]).to.equal(folderPath + '/index.yaml'); expect(result[0]).to.equal(folderPath + '/index.yaml');
}); });
it('getRootFiles should exclude files when the version is not the one in files', function() {
let folderPath = path.join(__dirname, '../data/multiFile_with_one_root'),
array = [
{ fileName: folderPath + '/index.yaml' },
{ fileName: folderPath + '/definitions/index.yaml' },
{ fileName: folderPath + '/definitions/User.yaml' },
{ fileName: folderPath + '/info/index.yaml' },
{ fileName: folderPath + '/paths/index.yaml' },
{ fileName: folderPath + '/paths/foo.yaml' },
{ fileName: folderPath + '/paths/bar.yaml' }
],
result = parse.getRootFiles({ data: array, type: 'folder' }, inputValidation, {}, {},
'2.0');
expect(result.length).to.equal(0);
});
it('getOasObject function should return a valid oas object from a yaml file', function() { it('getOasObject function should return a valid oas object from a yaml file', function() {
let filePath = path.join(__dirname, '../data/multiFile_with_one_root/index.yaml'), let filePath = path.join(__dirname, '../data/multiFile_with_one_root/index.yaml'),
file = fs.readFileSync(filePath, 'utf8'), file = fs.readFileSync(filePath, 'utf8'),

View File

@@ -1,4 +1,7 @@
const { getSpecVersion, filterOptionsByVersion } = require('../../lib/common/versionUtils'), const { getSpecVersion,
filterOptionsByVersion,
compareVersion,
getVersionRegexBySpecificationVersion } = require('../../lib/common/versionUtils'),
expect = require('chai').expect; expect = require('chai').expect;
describe('getSpecVersion', function() { describe('getSpecVersion', function() {
@@ -274,3 +277,54 @@ describe('filterOptionsByVersion method', function() {
})).to.include.members(['optionC', 'optionA']); })).to.include.members(['optionC', 'optionA']);
}); });
}); });
describe('compareVersion method', function () {
it('should return true when input and version are equal', function () {
const result = compareVersion('3.0.0', '3.0.0');
expect(result).to.be.true;
});
it('should return false when input and version are different', function () {
const result = compareVersion('3.1.0', '3.0.0');
expect(result).to.be.false;
});
it('should return true when input and version are semantically equal', function () {
const result = compareVersion('3.0', '3.0.0');
expect(result).to.be.true;
});
it('should return false when input is not a valid version string', function () {
const result = compareVersion('invalid', '3.0.0');
expect(result).to.be.false;
});
it('should return false when version is not a valid version string', function () {
const result = compareVersion('3.0.0', 'invalid');
expect(result).to.be.false;
});
it('should return false when version and input are not valid', function () {
const result = compareVersion('invalid', 'invalid');
expect(result).to.be.false;
});
});
describe('getVersionRegexBySpecificationVersion method', function () {
it('should return regex for 3.0', function () {
const result = getVersionRegexBySpecificationVersion('3.0');
expect(result.toString()).to.equal('/openapi[\'|\"]?:\\s?[\\]?[\'|\"]?3.0/');
});
it('should return regex for 3.0.0', function () {
const result = getVersionRegexBySpecificationVersion('3.0.0');
expect(result.toString()).to.equal('/openapi[\'|\"]?:\\s?[\\]?[\'|\"]?3.0/');
});
it('should return regex for 3.1', function () {
const result = getVersionRegexBySpecificationVersion('3.1');
expect(result.toString()).to.equal('/openapi[\'|\"]?:\\s?[\\]?[\'|\"]?3.1/');
});
it('should return regex for 2.0', function () {
const result = getVersionRegexBySpecificationVersion('2.0');
expect(result.toString()).to.equal('/swagger[\'|\"]?:\\s?[\\]?[\'|\"]?2.0/');
});
it('should return regex for 3.0 as default', function () {
const result = getVersionRegexBySpecificationVersion('invalid');
expect(result.toString()).to.equal('/openapi[\'|\"]?:\\s?[\\]?[\'|\"]?3.0/');
});
});