Коммит
4abe7299a9
|
@ -21,6 +21,7 @@ bin/*
|
|||
/.vscode/*
|
||||
ValidationTool.njsproj
|
||||
ValidationTool.sln
|
||||
.vscode/launch.json
|
||||
|
||||
#### win gitignore
|
||||
|
||||
|
|
|
@ -12,9 +12,7 @@
|
|||
"cwd": "${workspaceRoot}",
|
||||
"args": [
|
||||
"validate-example",
|
||||
"..\\upstream\\azure-rest-api-specs\\arm-customer-insights\\2016-01-01\\swagger\\customer-insights.json",
|
||||
"-o",
|
||||
"Interactions_CreateOrUpdate",
|
||||
"https://github.com/Azure/azure-rest-api-specs/blob/master/arm-redis/2016-04-01/swagger/redis.json",
|
||||
"-j"
|
||||
],
|
||||
"env": {
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
|
||||
'use strict';
|
||||
var util = require('util'),
|
||||
ExecutionEngine = require('../executionEngine'),
|
||||
log = require('../util/logging'),
|
||||
validate = require('../../validate');
|
||||
|
||||
|
@ -27,7 +28,7 @@ exports.handler = function (argv) {
|
|||
if (specPath.match(/.*composite.*/ig) !== null) {
|
||||
return validate.validateExamplesInCompositeSpec(specPath);
|
||||
} else {
|
||||
return validate.liveTest(specPath, operationIds);
|
||||
return new ExecutionEngine().liveTest(specPath, operationIds);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -3,7 +3,125 @@
|
|||
|
||||
'use strict';
|
||||
|
||||
var util = require('util'),
|
||||
msRest = require('ms-rest'),
|
||||
msrestazure = require('ms-rest-azure'),
|
||||
ResourceManagementClient = require('azure-arm-resource').ResourceManagementClient,
|
||||
HttpRequest = msRest.WebResource,
|
||||
ResponseWrapper = require('./responseWrapper'),
|
||||
SpecValidator = require('./specValidator'),
|
||||
utils = require('./util/utils'),
|
||||
log = require('./util/logging'),
|
||||
Constants = require('./util/constants'),
|
||||
EnvironmentVariables = Constants.EnvironmentVariables;
|
||||
|
||||
class ExecutionEngine {
|
||||
constructor() {
|
||||
}
|
||||
|
||||
sanitizeParameters(exampleParameters) {
|
||||
let self = this;
|
||||
if (exampleParameters) {
|
||||
if (exampleParameters.subscriptionId) {
|
||||
exampleParameters.subscriptionId = process.env['AZURE_SUBSCRIPTION_ID'];
|
||||
}
|
||||
if (exampleParameters.resourceGroupName) {
|
||||
exampleParameters.resourceGroupName = process.env['AZURE_RESOURCE_GROUP'];
|
||||
}
|
||||
}
|
||||
return exampleParameters;
|
||||
};
|
||||
|
||||
liveTest(specPath, operationId) {
|
||||
let self = this;
|
||||
self.validateEnvironmentVariables();
|
||||
log.transports.console.level = 'info';
|
||||
let clientId = process.env[EnvironmentVariables.ClientId];
|
||||
let domain = process.env[EnvironmentVariables.Domain];
|
||||
let secret = process.env[EnvironmentVariables.ApplicationSecret];
|
||||
let subscriptionId = process.env[EnvironmentVariables.AzureSubscriptionId];
|
||||
let location = process.env[EnvironmentVariables.AzureLocation] || 'westus';
|
||||
let resourceGroupName = process.env[EnvironmentVariables.AzureResourcegroup] || utils.generateRandomId('testrg');
|
||||
let validator = new SpecValidator(specPath);
|
||||
let operation, xmsExamples;
|
||||
validator.initialize().then(function () {
|
||||
log.info(`Running live test using x-ms-examples in ${operationId} in ${specPath}:\n`);
|
||||
operation = validator.getOperationById(operationId);
|
||||
xmsExamples = operation[Constants.xmsExamples];
|
||||
if (xmsExamples) {
|
||||
for (let scenario in xmsExamples) {
|
||||
let xmsExample = xmsExamples[scenario];
|
||||
let parameters = self.sanitizeParameters(xmsExample.parameters);
|
||||
let result = validator.validateRequest(operation, xmsExample.parameters);
|
||||
if (result.validationResult && result.validationResult.errors && result.validationResult.errors.length) {
|
||||
let msg = `Cannot proceed ahead with the live test for operation: ${operationId}. Found validation errors in the request.\n` +
|
||||
`${util.inspect(result.validationResult.errors, { depth: null })}`;
|
||||
throw new Error(msg);
|
||||
}
|
||||
let req = result.request;
|
||||
if (!req) {
|
||||
throw new Error(`Cannot proceed ahead with the live test for operation: ${operationId}. The request object is undefined.`);
|
||||
}
|
||||
if (req.body !== null && req.body !== undefined) {
|
||||
req.body = JSON.stringify(req.body);
|
||||
}
|
||||
msrestazure.loginWithServicePrincipalSecret(clientId, secret, domain, function (err, creds, subscriptions) {
|
||||
if (err) {
|
||||
throw err;
|
||||
}
|
||||
let resourceClient = new ResourceManagementClient(creds, subscriptionId);
|
||||
let client = new msrestazure.AzureServiceClient(creds);
|
||||
self.createResourceGroup(resourceClient, location, resourceGroupName, function (err, resourceGroup) {
|
||||
if (err) {
|
||||
throw err;
|
||||
}
|
||||
client.sendRequest(req, function (err, result, request, response) {
|
||||
log.info(request);
|
||||
log.info(response);
|
||||
if (err) {
|
||||
throw err;
|
||||
}
|
||||
log.info(result);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
}).catch(function (err) {
|
||||
log.error(err);
|
||||
});
|
||||
}
|
||||
|
||||
createResourceGroup(resourceClient, location, resourceGroupName, callback) {
|
||||
let self = this;
|
||||
resourceClient.resourceGroups.get(resourceGroupName, function (err, result, request, response) {
|
||||
if (err && err.statusCode === 404) {
|
||||
log.info(`Creating resource group: \'${resourceGroupName}\', if not present.`);
|
||||
let groupParameters = { location: location, tags: { 'live-test': 'live-test' } };
|
||||
return resourceClient.resourceGroups.createOrUpdate(resourceGroupName, groupParameters, callback);
|
||||
} else {
|
||||
return callback(null, result);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
getResourceGroup(resourceClient, resourceGroupName, callback) {
|
||||
let self = this;
|
||||
log.info(`Searching ResourceGroup: ${resourceGroupName}.`)
|
||||
resourceClient.resourceGroups.get(resourceGroupName, callback);
|
||||
}
|
||||
|
||||
validateEnvironmentVariables() {
|
||||
let self = this;
|
||||
var envs = [];
|
||||
if (!process.env[EnvironmentVariables.ClientId]) envs.push(EnvironmentVariables.ClientId);
|
||||
if (!process.env[EnvironmentVariables.Domain]) envs.push(EnvironmentVariables.Domain);
|
||||
if (!process.env[EnvironmentVariables.ApplicationSecret]) envs.push(EnvironmentVariables.ApplicationSecret);
|
||||
if (!process.env[EnvironmentVariables.AzureSubscriptionId]) envs.push(EnvironmentVariables.AzureSubscriptionId);
|
||||
if (envs.length > 0) {
|
||||
throw new Error(util.format('please set/export the following environment variables: %s', envs.toString()));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,233 @@
|
|||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
|
||||
'use strict';
|
||||
|
||||
var util = require('util'),
|
||||
path = require('path'),
|
||||
JsonRefs = require('json-refs'),
|
||||
utils = require('./util/utils'),
|
||||
Constants = require('./util/constants'),
|
||||
log = require('./util/logging'),
|
||||
ErrorCodes = Constants.ErrorCodes;
|
||||
|
||||
class SpecResolver {
|
||||
|
||||
constructor(specPath, specInJson) {
|
||||
if (specPath === null || specPath === undefined || typeof specPath.valueOf() !== 'string' || !specPath.trim().length) {
|
||||
throw new Error('specPath is a required property of type string and it cannot be an empty string.')
|
||||
}
|
||||
|
||||
if (specInJson === null || specInJson === undefined || typeof specInJson !== 'object') {
|
||||
throw new Error('specInJson is a required property of type object')
|
||||
}
|
||||
this.specInJson = specInJson;
|
||||
this.specPath = specPath;
|
||||
this.specDir = path.dirname(this.specPath);
|
||||
this.visitedEntities = {};
|
||||
this.resolvedAllOfModels = {};
|
||||
}
|
||||
|
||||
unifyXmsPaths() {
|
||||
let self = this;
|
||||
//unify x-ms-paths into paths
|
||||
let xmsPaths = self.specInJson['x-ms-paths'];
|
||||
let paths = self.specInJson.paths;
|
||||
if (xmsPaths && xmsPaths instanceof Object && Object.keys(xmsPaths).length > 0) {
|
||||
for (let property in xmsPaths) {
|
||||
paths[property] = xmsPaths[property];
|
||||
}
|
||||
self.specInJson.paths = utils.mergeObjects(xmsPaths, paths);
|
||||
}
|
||||
return Promise.resolve(self);
|
||||
}
|
||||
|
||||
resolve() {
|
||||
let self = this;
|
||||
return self.unifyXmsPaths()
|
||||
.then(function() { return self.resolveRelativePaths(); })
|
||||
.then(function() { self.resolveAllOfInDefinitions(); })
|
||||
.then(function() { self.deleteReferencesToAllOf(); })
|
||||
.then(function() { self.setAdditionalPropertiesFalse(); });
|
||||
}
|
||||
|
||||
resolveRelativePaths(doc, docPath, filterType) {
|
||||
let self = this;
|
||||
let docDir;
|
||||
let options = {
|
||||
relativeBase: docDir,
|
||||
filter: ['relative', 'remote']
|
||||
};
|
||||
|
||||
if (!doc) {
|
||||
doc = self.specInJson;
|
||||
}
|
||||
if (!docPath) {
|
||||
docPath = self.specPath;
|
||||
docDir = self.specDir;
|
||||
}
|
||||
if (!docDir) {
|
||||
docDir = path.dirname(docPath);
|
||||
}
|
||||
if (filterType === 'all') {
|
||||
delete options.filter;
|
||||
}
|
||||
|
||||
let allRefsRemoteRelative = JsonRefs.findRefs(doc, options);
|
||||
let promiseFactories = Object.keys(allRefsRemoteRelative).map(function (refName) {
|
||||
let refDetails = allRefsRemoteRelative[refName];
|
||||
return function () { return self.resolveRelativeReference(refName, refDetails, docPath); };
|
||||
});
|
||||
if (promiseFactories.length) {
|
||||
return utils.executePromisesSequentially(promiseFactories);
|
||||
} else {
|
||||
return Promise.resolve();
|
||||
}
|
||||
}
|
||||
|
||||
resolveRelativeReference(refName, refDetails, docPath) {
|
||||
let self = this;
|
||||
if (!docPath) {
|
||||
docPath = self.specPath;
|
||||
}
|
||||
let node = refDetails.def;
|
||||
let slicedRefName = refName.slice(1);
|
||||
let reference = node['$ref'];
|
||||
let parsedReference = utils.parseReferenceInSwagger(reference);
|
||||
|
||||
if (parsedReference.filePath) {
|
||||
//assuming that everything in the spec is relative to it, let us join the spec directory and the file path in reference.
|
||||
docPath = utils.joinPath(self.specDir, parsedReference.filePath);
|
||||
}
|
||||
return utils.parseJson(docPath).then(function (result) {
|
||||
if (!parsedReference.localReference) {
|
||||
//Since there is no local reference we will replace the key in the object with the parsed json (relative) file it is refering to.
|
||||
return Promise.resolve(utils.setObject(self.specInJson, slicedRefName, result));
|
||||
} else {
|
||||
//resolve the local reference.
|
||||
//make the reference local to the doc being processed
|
||||
node['$ref'] = parsedReference.localReference.value;
|
||||
utils.setObject(self.specInJson, slicedRefName, node);
|
||||
let slicedLocalReferenceValue = parsedReference.localReference.value.slice(1);
|
||||
let referencedObj = self.visitedEntities[slicedLocalReferenceValue];
|
||||
if (!referencedObj) {
|
||||
//We get the definition/parameter from the relative file and then add it (make it local) to the doc being processed.
|
||||
referencedObj = utils.getObject(result, slicedLocalReferenceValue);
|
||||
utils.setObject(self.specInJson, slicedLocalReferenceValue, referencedObj);
|
||||
self.visitedEntities[slicedLocalReferenceValue] = referencedObj;
|
||||
return Promise.resolve(self.resolveRelativePaths(referencedObj, docPath, 'all'));
|
||||
} else {
|
||||
return Promise.resolve();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
resolveAllOfInDefinitions() {
|
||||
let self = this;
|
||||
let spec = self.specInJson;
|
||||
let definitions = spec.definitions;
|
||||
let modelNames = Object.keys(self.specInJson.definitions);
|
||||
modelNames.map(function (modelName) {
|
||||
let model = definitions[modelName];
|
||||
let modelRef = '/definitions/' + modelName;
|
||||
return self.resolveAllOfInModel(model, modelRef);
|
||||
});
|
||||
return Promise.resolve(self);
|
||||
}
|
||||
|
||||
resolveAllOfInModel(model, modelRef) {
|
||||
let self = this;
|
||||
let spec = self.specInJson;
|
||||
if (!model || (model && typeof model !== 'object')) {
|
||||
throw new Error(`model cannot be null or undefined and must of type "object".`);
|
||||
}
|
||||
|
||||
if (!modelRef || (modelRef && typeof modelRef.valueOf() !== 'string')) {
|
||||
throw new Error(`model cannot be null or undefined and must of type "string".`);
|
||||
}
|
||||
|
||||
if (modelRef.startsWith('#')) modelRef = modelRef.slice(1);
|
||||
|
||||
if (!self.resolvedAllOfModels[modelRef]) {
|
||||
if (model && model.allOf) {
|
||||
model.allOf.map(function (item) {
|
||||
let referencedModel = item;
|
||||
let ref = item['$ref'];
|
||||
let slicedRef = ref ? ref.slice(1) : undefined;
|
||||
if (ref) {
|
||||
referencedModel = utils.getObject(spec, slicedRef);
|
||||
}
|
||||
if (referencedModel.allOf) {
|
||||
self.resolveAllOfInModel(referencedModel, slicedRef);
|
||||
}
|
||||
model = self.mergeParentAllOfInChild(referencedModel, model);
|
||||
self.resolvedAllOfModels[slicedRef] = referencedModel;
|
||||
return model;
|
||||
});
|
||||
} else {
|
||||
self.resolvedAllOfModels[modelRef] = model;
|
||||
return model;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
mergeParentAllOfInChild(source, target) {
|
||||
let self = this;
|
||||
if (!source || (source && typeof source !== 'object')) {
|
||||
throw new Error(`source must be of type "object".`);
|
||||
}
|
||||
if (!target || (target && typeof target !== 'object')) {
|
||||
throw new Error(`target must be of type "object".`);
|
||||
}
|
||||
//merge the source (Resource) model's properties into the properties
|
||||
//of the target (StorageAccount) model.
|
||||
target.properties = utils.mergeObjects(source.properties, target.properties);
|
||||
//merge the array of required properties
|
||||
if (source.required) {
|
||||
if (!target.required) {
|
||||
target.required = [];
|
||||
}
|
||||
target.required = [...new Set([...source.required, ...target.required])];
|
||||
}
|
||||
//merge x-ms-azure-resource
|
||||
if (source['x-ms-azure-resource']) {
|
||||
target['x-ms-azure-resource'] = source['x-ms-azure-resource'];
|
||||
}
|
||||
return target;
|
||||
}
|
||||
|
||||
deleteReferencesToAllOf() {
|
||||
let self = this;
|
||||
let spec = self.specInJson;
|
||||
let definitions = spec.definitions;
|
||||
let modelNames = Object.keys(definitions);
|
||||
modelNames.map(function (modelName) {
|
||||
if (definitions[modelName].allOf) {
|
||||
delete definitions[modelName].allOf;
|
||||
}
|
||||
});
|
||||
return Promise.resolve(self);
|
||||
}
|
||||
|
||||
setAdditionalPropertiesFalse(modelNames, force) {
|
||||
let self = this;
|
||||
let spec = self.specInJson;
|
||||
let definitions = spec.definitions;
|
||||
|
||||
if (!modelNames) {
|
||||
modelNames = Object.keys(definitions);
|
||||
}
|
||||
modelNames.forEach(function iterator(modelName) {
|
||||
let model = definitions[modelName];
|
||||
if (model) {
|
||||
if (!model.additionalProperties || force) {
|
||||
model.additionalProperties = false;
|
||||
}
|
||||
}
|
||||
});
|
||||
return Promise.resolve(self);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = SpecResolver;
|
|
@ -5,11 +5,10 @@
|
|||
|
||||
var util = require('util'),
|
||||
path = require('path'),
|
||||
fs = require('fs'),
|
||||
Sway = require('sway'),
|
||||
msRest = require('ms-rest'),
|
||||
JsonRefs = require('json-refs'),
|
||||
HttpRequest = msRest.WebResource,
|
||||
SpecResolver = require('./specResolver'),
|
||||
utils = require('./util/utils'),
|
||||
Constants = require('./util/constants'),
|
||||
log = require('./util/logging'),
|
||||
|
@ -29,146 +28,36 @@ class SpecValidator {
|
|||
this.specPath = specPath;
|
||||
this.specDir = path.dirname(this.specPath);
|
||||
this.specInJson = specInJson;
|
||||
this.specResolver = null;
|
||||
this.specValidationResult = { validityStatus: true, operations: { } };
|
||||
this.swaggerApi = null;
|
||||
}
|
||||
|
||||
unifyXmsPaths() {
|
||||
//unify x-ms-paths into paths
|
||||
if (this.specInJson['x-ms-paths'] && this.specInJson['x-ms-paths'] instanceof Object &&
|
||||
Object.keys(this.specInJson['x-ms-paths']).length > 0) {
|
||||
let paths = this.specInJson.paths;
|
||||
for (let property in this.specInJson['x-ms-paths']) {
|
||||
paths[property] = this.specInJson['x-ms-paths'][property];
|
||||
}
|
||||
this.specInJson.paths = paths;
|
||||
}
|
||||
}
|
||||
|
||||
findObject(what, where, actualReference, docPath) {
|
||||
initialize() {
|
||||
let self = this;
|
||||
let result = eval(`where.${what}`);
|
||||
if (!result) {
|
||||
let msg = `Object '${what}' from the given reference ` +
|
||||
`'${actualReference}' is not found in the swagger spec '${docPath}'.`;
|
||||
let e = self.constructErrorObject('OBJECT_NOT_FOUND', msg);
|
||||
log.error(e);
|
||||
throw e;
|
||||
} else {
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
getDefinitionFromReference(reference) {
|
||||
let self = this;
|
||||
if (!reference || (reference && reference.trim().length === 0)) {
|
||||
throw new Error('reference cannot be null or undefined and it must be a non-empty string.');
|
||||
}
|
||||
let refObj = utils.parseReferenceInSwagger(reference);
|
||||
if (refObj.filePath) {
|
||||
let docPath = utils.joinPath(self.specDir, refObj.filePath);
|
||||
return utils.parseJson(docPath).then(function(result) {
|
||||
return self.findObject(refObj.localReference.accessorProperty, result, refObj.localReference.value, docPath);
|
||||
});
|
||||
} else {
|
||||
return self.findObject(refObj.localReference.accessorProperty, self.specInJson, refObj.localReference.value, self.specPath);
|
||||
}
|
||||
}
|
||||
|
||||
mergeResolvedAllOfObjects(source, target) {
|
||||
let self = this;
|
||||
if (!source || (source && typeof source !== 'object')) {
|
||||
return Promise.reject(new Error(`source must be of type "object".`));
|
||||
}
|
||||
if (!target || (target && typeof target !== 'object')) {
|
||||
return Promise.reject(new Error(`target must be of type "object".`));
|
||||
}
|
||||
//merge the target model's properties
|
||||
source.properties = utils.merge(source.properties, target.properties);
|
||||
//merge the array of required properties
|
||||
if (target.required) {
|
||||
if (!source.required) {
|
||||
source.required = [];
|
||||
}
|
||||
source.required = [...new Set([...source.required, ...target.required])];
|
||||
}
|
||||
//merge x-ms-azure-resource
|
||||
if (target['x-ms-azure-resource']) {
|
||||
source['x-ms-azure-resource'] = target['x-ms-azure-resource'];
|
||||
}
|
||||
return Promise.resolve(source);
|
||||
}
|
||||
|
||||
getRefDefinitionInAllOf(item) {
|
||||
let self = this;
|
||||
if (!item || (item && typeof item !== 'object')) {
|
||||
return Promise.reject(new Error(`item must be of type "object".`));
|
||||
}
|
||||
if (item['$ref']) {
|
||||
return Promise.resolve(self.getDefinitionFromReference(item['$ref']));
|
||||
} else {
|
||||
return Promise.resolve(item);
|
||||
}
|
||||
}
|
||||
|
||||
composeAllOf(model, item) {
|
||||
let self = this;
|
||||
return self.getRefDefinitionInAllOf(item).then(function(result) {
|
||||
if (result && result.allOf) {
|
||||
return self.resolveAllOf(result).then(function(res) {
|
||||
return Promise.resolve(self.mergeResolvedAllOfObjects(model, result));
|
||||
}).catch(function (err) {
|
||||
log.error(err);
|
||||
return Promise.reject(err);
|
||||
});
|
||||
} else {
|
||||
return Promise.resolve(self.mergeResolvedAllOfObjects(model, result));
|
||||
}
|
||||
return utils.parseJson(self.specPath).then(function (result) {
|
||||
self.specInJson = result;
|
||||
self.specResolver = new SpecResolver(self.specPath, self.specInJson);
|
||||
return self.specResolver.resolve();
|
||||
}).then(function() {
|
||||
let options = {};
|
||||
options.definition = self.specInJson;
|
||||
options.jsonRefs = {};
|
||||
options.jsonRefs.relativeBase = self.specDir;
|
||||
return Sway.create(options);
|
||||
}).then(function (api) {
|
||||
self.swaggerApi = api;
|
||||
return Promise.resolve(api);
|
||||
}).catch(function (err) {
|
||||
console.dir(err);
|
||||
let e = self.constructErrorObject(ErrorCodes.ResolveSpecError, err.message, [err]);
|
||||
self.specValidationResult.resolveSpec = e;
|
||||
log.error(`${ErrorCodes.ResolveSpecError}: ${err.message}.`);
|
||||
log.silly(err.stack);
|
||||
return Promise.reject(e);
|
||||
});
|
||||
}
|
||||
|
||||
resolveAllOf(model) {
|
||||
let self = this;
|
||||
if (!model || (model && typeof model !== 'object')) {
|
||||
return Promise.reject(new Error(`model cannot be null or undefined and must of type object.`));
|
||||
}
|
||||
if (model.allOf) {
|
||||
let allOfItemPromiseFactories = model.allOf.map(function(item) {
|
||||
return Promise.resolve(self.composeAllOf(model, item));
|
||||
});
|
||||
return utils.executePromisesSequentially(allOfItemPromiseFactories);
|
||||
}
|
||||
return Promise.resolve(model);
|
||||
}
|
||||
|
||||
makeModelsStricter() {
|
||||
let self = this;
|
||||
let spec = self.specInJson;
|
||||
let definitions = spec.definitions;
|
||||
let modelNames = Object.keys(self.specInJson.definitions);
|
||||
let modelPromiseFactories = modelNames.map(function (modelName) {
|
||||
let model = definitions[modelName];
|
||||
if (model && !model.additionalProperties) {
|
||||
model.additionalProperties = false;
|
||||
}
|
||||
return self.resolveAllOf(model);
|
||||
});
|
||||
return utils.executePromisesSequentially(modelPromiseFactories);
|
||||
}
|
||||
|
||||
// resolveRelativePaths() {
|
||||
// let self = this;
|
||||
// let spec = self.specInJson;
|
||||
// let options = {
|
||||
// relativeBase: self.specDir,
|
||||
// filter: ['relative', 'remote']
|
||||
// };
|
||||
// let allRefsRemoteRelative = JsonRefs.findRefs(spec, options);
|
||||
// Object.keys(allRefsRemoteRelative).map(function(refName) {
|
||||
// let refDetails = allRefsRemoteRelative[refName];
|
||||
// })
|
||||
|
||||
// }
|
||||
|
||||
updateValidityStatus(value) {
|
||||
if (!Boolean(value)) {
|
||||
this.specValidationResult.validityStatus = false;
|
||||
|
@ -192,40 +81,6 @@ class SpecValidator {
|
|||
return err;
|
||||
}
|
||||
|
||||
initialize() {
|
||||
let self = this;
|
||||
return utils.parseJson(self.specPath).then(function (result) {
|
||||
self.specInJson = result;
|
||||
self.unifyXmsPaths();
|
||||
return self;
|
||||
}).then(function () {
|
||||
return self.makeModelsStricter();
|
||||
}).then(function() {
|
||||
return Object.keys(self.specInJson.definitions).map(function(definitionName){
|
||||
if (self.specInJson.definitions[definitionName].allOf) {
|
||||
delete self.specInJson.definitions[definitionName].allOf;
|
||||
}
|
||||
});
|
||||
}).then(function() {
|
||||
let options = {};
|
||||
options.definition = self.specInJson;
|
||||
options.jsonRefs = {};
|
||||
options.jsonRefs.includeInvalid = true;
|
||||
options.jsonRefs.relativeBase = self.specDir;
|
||||
return Sway.create(options);
|
||||
}).then(function (api) {
|
||||
self.swaggerApi = api;
|
||||
return Promise.resolve(api);
|
||||
}).catch(function (err) {
|
||||
console.dir(err);
|
||||
let e = self.constructErrorObject(ErrorCodes.ResolveSpecError, err.message, [err]);
|
||||
self.specValidationResult.resolveSpec = e;
|
||||
log.error(`${ErrorCodes.ResolveSpecError}: ${err.message}.`);
|
||||
log.silly(err.stack);
|
||||
return Promise.reject(e);
|
||||
});
|
||||
}
|
||||
|
||||
validateSpec() {
|
||||
let self = this;
|
||||
self.specValidationResult.validateSpec = {};
|
||||
|
|
|
@ -0,0 +1,634 @@
|
|||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
|
||||
'use strict';
|
||||
|
||||
var fs = require('fs'),
|
||||
path = require('path'),
|
||||
util = require('util'),
|
||||
swt = require('swagger-tools').specs.v2,
|
||||
RefParser = require('json-schema-ref-parser'),
|
||||
utils = require('./util/utils'),
|
||||
cwd = process.cwd();
|
||||
|
||||
const constraints = ['minLength', 'maxLength', 'minimum', 'maximum', 'enum',
|
||||
'maxItems', 'minItems', 'uniqueItems', 'multipleOf', 'pattern'];
|
||||
const xmsExamples = 'x-ms-examples', exampleInSpec = 'example-in-spec', BodyParameterValid = 'BODY_PARAMAETER_VALID',
|
||||
InternalError = 'INTERNAL_ERROR', RefNotFoundError = 'REF_NOTFOUND_ERROR',
|
||||
JsonParsingError = 'JSON_PARSING_ERROR', XmsExampleNotFoundError = 'X-MS-EXAMPLE_NOTFOUND_ERROR',
|
||||
ResponseBodyValidationError = 'RESPONSE_BODY_VALIDATION_ERROR',
|
||||
RequiredParameterNotInExampleError = 'REQUIRED_PARAMETER_NOT_IN_EXAMPLE_ERROR',
|
||||
BodyParameterValidationError = 'BODY_PARAMETER_VALIDATION_ERROR', TypeValidationError = 'TYPE_VALIDATION_ERROR',
|
||||
ConstraintValidationError = 'CONSTRAINT_VALIDATION_ERROR', StatuscodeNotInExampleError = 'STATUS_CODE_NOT_IN_EXAMPLE_ERROR';
|
||||
|
||||
class SpecValidator2 {
|
||||
|
||||
constructor(filePath) {
|
||||
this.filePath = filePath;
|
||||
this.specInJson = null;
|
||||
this.refSpecInJson = null;
|
||||
this.specValidationResult = {};
|
||||
}
|
||||
|
||||
unifyXmsPaths() {
|
||||
//unify x-ms-paths into paths
|
||||
if (this.specInJson['x-ms-paths'] && this.specInJson['x-ms-paths'] instanceof Object &&
|
||||
Object.keys(this.specInJson['x-ms-paths']).length > 0) {
|
||||
let paths = this.specInJson.paths;
|
||||
for (let property in this.specInJson['x-ms-paths']) {
|
||||
paths[property] = this.specInJson['x-ms-paths'][property];
|
||||
}
|
||||
this.specInJson.paths = paths;
|
||||
}
|
||||
}
|
||||
|
||||
resolveFileReferencesInXmsExamples() {
|
||||
//Resolve all the file references for scenarios in x-ms-examples. This needs to be done before the spec is provided as an input to
|
||||
//swagger-tools' validateModel().
|
||||
this.specValidationResult.operations = {};
|
||||
for (let apiPath in this.specInJson.paths) {
|
||||
for (let verb in this.specInJson.paths[apiPath]) {
|
||||
let operationId = this.specInJson.paths[apiPath][verb]['operationId'];
|
||||
this.specValidationResult.operations[operationId] = {};
|
||||
this.specValidationResult.operations[operationId][xmsExamples] = {};
|
||||
if (this.specInJson.paths[apiPath][verb][xmsExamples]) {
|
||||
//populate all the scenarios for the examples inline.
|
||||
this.specValidationResult.operations[operationId][xmsExamples]['scenarios'] = {};
|
||||
for (let scenarioName in this.specInJson.paths[apiPath][verb][xmsExamples]) {
|
||||
this.specValidationResult.operations[operationId][xmsExamples]['scenarios'][scenarioName] = {};
|
||||
if (!this.specInJson.paths[apiPath][verb][xmsExamples][scenarioName]["$ref"]) {
|
||||
let msg = `$ref not found in ${this.specInJson.paths[apiPath][verb][xmsExamples][scenarioName]}`;
|
||||
let e = utils.constructErrorObject(RefNotFoundError, msg);
|
||||
this.specValidationResult.operations[operationId][xmsExamples]['scenarios'][scenarioName]['populateScenariosInline'] = {};
|
||||
this.specValidationResult.operations[operationId][xmsExamples]['scenarios'][scenarioName]['populateScenariosInline'].error = e;
|
||||
console.log(`${e.code} - ${e.message}`);
|
||||
continue;
|
||||
}
|
||||
//derefence the scenarioName
|
||||
let scenarioPath = path.resolve(path.dirname(this.filePath), this.specInJson.paths[apiPath][verb][xmsExamples][scenarioName]["$ref"]);
|
||||
let scenarioData;
|
||||
try {
|
||||
scenarioData = utils.parseJSONSync(scenarioPath);
|
||||
} catch (parsingError) {
|
||||
let msg = `Error occured while parsing example data from "${scenarioPath}".`;
|
||||
let e = utils.constructErrorObject(JsonParsingError, msg, [parsingError]);
|
||||
this.specValidationResult.operations[operationId][xmsExamples]['scenarios'][scenarioName]['populateScenariosInline'] = {};
|
||||
this.specValidationResult.operations[operationId][xmsExamples]['scenarios'][scenarioName]['populateScenariosInline'].error = e;
|
||||
console.log(`${e.code} - ${e.message}`);
|
||||
continue;
|
||||
}
|
||||
//assign the parsed example data to the scenarioName in the spec, so everything is inline.
|
||||
this.specInJson.paths[apiPath][verb][xmsExamples][scenarioName] = scenarioData;
|
||||
}
|
||||
} else {
|
||||
let msg = `Operation - "${this.specInJson.paths[apiPath][verb].operationId}", does not contain the extension: "x-ms-examples".`;
|
||||
let e = utils.constructErrorObject(XmsExampleNotFoundError, msg);
|
||||
this.specValidationResult.operations[operationId][xmsExamples].error = e;
|
||||
console.log(`\t> ${e.code} - ${e.message}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
initialize(options, callback) {
|
||||
let self = this;
|
||||
if (!callback && typeof options === 'function') {
|
||||
callback = options;
|
||||
options = {skipInitialization: false};
|
||||
}
|
||||
if (options) {
|
||||
if (!options.skipInitialization) {
|
||||
try {
|
||||
self.specInJson = utils.parseJSONSync(self.filePath);
|
||||
} catch (err) {
|
||||
let msg = `Error occured while parsing the spec ${self.filePath}. \t${err.message}.`;
|
||||
err.message = msg;
|
||||
err.code = JsonParsingError;
|
||||
self.specValidationResult.parseJsonSpec = {};
|
||||
self.specValidationResult.parseJsonSpec.error = err;
|
||||
console.log(`${err.code} - ${err.message}`);
|
||||
return callback(err);
|
||||
}
|
||||
self.unifyXmsPaths();
|
||||
self.resolveFileReferencesInXmsExamples();
|
||||
process.chdir(path.dirname(self.filePath));
|
||||
RefParser.resolve(self.specInJson, function (err, result) {
|
||||
process.chdir(cwd);
|
||||
if (err) {
|
||||
let msg = `Error occurred in resolving the spec "${selg.filePath}". \t${err.message}.`;
|
||||
err.code = 'RESOLVE_SPEC_ERROR';
|
||||
err.message = msg;
|
||||
self.specValidationResult.resolveSpec = {};
|
||||
self.specValidationResult.resolveSpec.error = err;
|
||||
console.log(`${err.code} - ${err.message}`);
|
||||
return callback(err);
|
||||
}
|
||||
self.refSpecInJson = result;
|
||||
return callback(null);
|
||||
});
|
||||
} else {
|
||||
return callback(null);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//wrapper to validateModel of swagger-tools
|
||||
validateModel(modelReference, data) {
|
||||
let self = this;
|
||||
if (!modelReference) {
|
||||
throw new Error('modelReference cannot be null or undefined. It must be of a string. Example: "#/definitions/foo".');
|
||||
}
|
||||
if (!data) {
|
||||
throw new Error('data cannot be null or undefined. It must be of a JSON object or a JSON array.');
|
||||
}
|
||||
|
||||
return function (callback) {
|
||||
swt.validateModel(self.specInJson, modelReference, data, callback);
|
||||
}
|
||||
}
|
||||
|
||||
validateDateTypes(schema, value) {
|
||||
if (value !== null && value !== undefined) {
|
||||
if (schema.format.match(/^date$/ig) !== null) {
|
||||
if (!(value instanceof Date ||
|
||||
(typeof value.valueOf() === 'string' && !isNaN(Date.parse(value))))) {
|
||||
throw new Error(`${schema.name} must be an instanceof Date or a string in ISO8601 format.`);
|
||||
}
|
||||
} else if (schema.format.match(/^date-time$/ig) !== null) {
|
||||
if (!(value instanceof Date ||
|
||||
(typeof value.valueOf() === 'string' && !isNaN(Date.parse(value))))) {
|
||||
throw new Error(`${schema.name} must be an instanceof Date or a string in ISO8601 format.`);
|
||||
}
|
||||
} else if (schema.format.match(/^date-time-rfc-1123$/ig) !== null) {
|
||||
if (!(value instanceof Date ||
|
||||
(typeof value.valueOf() === 'string' && !isNaN(Date.parse(value))))) {
|
||||
throw new Error(`${schema.name} must be an instanceof Date or a string in RFC-1123 format.`);
|
||||
}
|
||||
} else if (schema.format.match(/^unixtime$/ig) !== null) {
|
||||
if (!(value instanceof Date ||
|
||||
(typeof value.valueOf() === 'string' && !isNaN(Date.parse(value))))) {
|
||||
throw new Error(`${schema.name} must be an instanceof Date or a string in RFC-1123/ISO8601 format ` +
|
||||
`for it to be serialized in UnixTime/Epoch format.`);
|
||||
}
|
||||
} else if (schema.format.match(/^(duration|timespan)$/ig) !== null) {
|
||||
if (!moment.isDuration(value)) {
|
||||
throw new Error(`${schema.name} must be a TimeSpan/Duration.`);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
validateBufferType(schema, value) {
|
||||
if (value !== null && value !== undefined) {
|
||||
if (!Buffer.isBuffer(value)) {
|
||||
throw new Error(`${schema.name} must be of type Buffer.`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
isValidUuid(uuid) {
|
||||
let validUuidRegex = new RegExp('^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$', 'ig');
|
||||
return validUuidRegex.test(uuid);
|
||||
};
|
||||
|
||||
validateBasicTypes(schema, value) {
|
||||
if (value !== null && value !== undefined) {
|
||||
if (schema.type.match(/^(number|integer)$/ig) !== null) {
|
||||
if (typeof value !== 'number') {
|
||||
throw new Error(`${schema.name} with value ${value} must be of type number.`);
|
||||
}
|
||||
} else if (schema.type.match(/^string$/ig) !== null) {
|
||||
if (typeof value.valueOf() !== 'string') {
|
||||
throw new Error(`${schema.name} with value '${value}' must be of type string.`);
|
||||
}
|
||||
if (schema.format && schema.format.match(/^uuid$/ig) !== null) {
|
||||
if (!(typeof value.valueOf() === 'string' && this.isValidUuid(value))) {
|
||||
throw new Error(`${schema.name} with value '${value}' must be of type string and a valid uuid.`);
|
||||
}
|
||||
}
|
||||
} else if (schema.type.match(/^boolean$/ig) !== null) {
|
||||
if (typeof value !== 'boolean') {
|
||||
throw new Error(`${schema.name} with value ${value} must be of type boolean.`);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//entry point for type validations
|
||||
validateType(schema, data) {
|
||||
if (schema.type.match(/^(number|string|boolean|integer)$/ig) !== null) {
|
||||
if (schema.format) {
|
||||
if (schema.format.match(/^(date|date-time|timespan|duration|date-time-rfc1123|unixtime)$/ig) !== null) {
|
||||
this.validateDateTypes(schema, data);
|
||||
} else if (schema.format.match(/^b(yte|ase64url)$/ig) !== null) {
|
||||
this.validateBufferType(schema, data);
|
||||
}
|
||||
} else {
|
||||
this.validateBasicTypes(schema, data);
|
||||
}
|
||||
} else {
|
||||
throw new Error(`Unknown type: "${schema.type}" provided for parameter: "${schema.name}".`);
|
||||
}
|
||||
};
|
||||
|
||||
//validates constraints
|
||||
validateConstraints(schema, value, objectName) {
|
||||
let constraintErrors = [];
|
||||
constraints.forEach(function (constraintType) {
|
||||
if (schema[constraintType] !== null && schema[constraintType] !== undefined) {
|
||||
if (constraintType.match(/^maximum$/ig) !== null) {
|
||||
if (schema['exclusiveMaximum']) {
|
||||
if (value >= schema[constraintType]) {
|
||||
let msg = `'${objectName}' with value '${value}' should satify the constraint ` +
|
||||
`'exclusiveMaximum': true and 'maximum': ${schema[constraintType]}.`;
|
||||
let e = utils.constructErrorObject('EXCLUSIVE_MAXIMUM_FAILURE', msg);
|
||||
constraintErrors.push(e);
|
||||
}
|
||||
} else {
|
||||
if (value > schema[constraintType]) {
|
||||
let msg = `'${objectName}' with value '${value}' should satify the constraint 'maximum': ${schema[constraintType]}.`;
|
||||
let e = utils.constructErrorObject('MAXIMUM_FAILURE', msg);
|
||||
constraintErrors.push(e);
|
||||
}
|
||||
}
|
||||
} else if (constraintType.match(/^minimum$/ig) !== null) {
|
||||
if (schema['exclusiveMinimum']) {
|
||||
if (value <= schema[constraintType]) {
|
||||
let msg = `'${objectName}' with value '${value}' should satify the constraint ` +
|
||||
`'exclusiveMinimum': true and 'minimum': ${schema[constraintType]}.`;
|
||||
e.code = 'MINIMUM_FAILURE';
|
||||
let e = utils.constructErrorObject('MINIMUM_FAILURE', msg);
|
||||
constraintErrors.push(e);
|
||||
}
|
||||
} else {
|
||||
if (value < schema[constraintType]) {
|
||||
let msg = `'${objectName}' with value '${value}' should satify the constraint 'minimum': ${schema[constraintType]}.`;
|
||||
e.code = 'MINIMUM_FAILURE';
|
||||
let e = utils.constructErrorObject('MINIMUM_FAILURE', msg);
|
||||
constraintErrors.push(e);
|
||||
}
|
||||
}
|
||||
} else if (constraintType.match(/^maxItems$/ig) !== null) {
|
||||
if (value.length > schema[constraintType]) {
|
||||
let msg = `'${objectName}' with value '${value}' should satify the constraint 'maxItems': ${schema[constraintType]}.`;
|
||||
e.code = 'MAXITEMS_FAILURE';
|
||||
let e = utils.constructErrorObject('MAXITEMS_FAILURE', msg);
|
||||
constraintErrors.push(e);
|
||||
}
|
||||
} else if (constraintType.match(/^maxLength$/ig) !== null) {
|
||||
if (value.length > schema[constraintType]) {
|
||||
let msg = `'${objectName}' with value '${value}' should satify the constraint 'maxLength': ${schema[constraintType]}.`;
|
||||
e.code = 'MAXLENGTH_FAILURE';
|
||||
let e = utils.constructErrorObject('MAXLENGTH_FAILURE', msg);
|
||||
constraintErrors.push(e);
|
||||
}
|
||||
} else if (constraintType.match(/^minItems$/ig) !== null) {
|
||||
if (value.length < schema[constraintType]) {
|
||||
let msg = `'${objectName}' with value '${value}' should satify the constraint 'minItems': ${schema[constraintType]}.`;
|
||||
e.code = 'MINITEMS_FAILURE';
|
||||
let e = utils.constructErrorObject('MINITEMS_FAILURE', msg);
|
||||
constraintErrors.push(e);
|
||||
}
|
||||
} else if (constraintType.match(/^minLength$/ig) !== null) {
|
||||
if (value.length < schema[constraintType]) {
|
||||
throw `'${objectName}' with value '${value}' should satify the constraint 'minLength': ${schema[constraintType]}.`;
|
||||
e.code = 'MINLENGTH_FAILURE';
|
||||
let e = utils.constructErrorObject('MINLENGTH_FAILURE', msg);
|
||||
constraintErrors.push(e);
|
||||
}
|
||||
} else if (constraintType.match(/^multipleOf$/ig) !== null) {
|
||||
if (value.length % schema[constraintType] !== 0) {
|
||||
let msg = `'${objectName}' with value '${value}' should satify the constraint 'multipleOf': ${schema[constraintType]}.`;
|
||||
let e = utils.constructErrorObject('MULTIPLEOF_FAILURE', msg);
|
||||
constraintErrors.push(e);
|
||||
}
|
||||
} else if (constraintType.match(/^pattern$/ig) !== null) {
|
||||
if (value.match(schema[constraintType].split('/').join('\/')) === null) {
|
||||
let msg = `'${objectName}' with value '${value}' should satify the constraint 'pattern': ${schema[constraintType]}.`;
|
||||
let e = utils.constructErrorObject('REGEX_PATTERN_FAILURE', msg);
|
||||
constraintErrors.push(e);
|
||||
}
|
||||
} else if (constraintType.match(/^uniqueItems/ig) !== null) {
|
||||
if (schema[constraintType]) {
|
||||
if (value.length !== value.filter(function (item, i, ar) { { return ar.indexOf(item) === i; } }).length) {
|
||||
let msg = `'${objectName}' with value '${value}' should satify the constraint 'uniqueItems': ${schema[constraintType]}.`;
|
||||
let e = utils.constructErrorObject('UNIQUEITEMS_FAILURE', msg);
|
||||
constraintErrors.push(e);
|
||||
}
|
||||
}
|
||||
} else if (constraintType.match(/^enum/ig) !== null) {
|
||||
let isPresent = schema[constraintType].some(function (item) {
|
||||
return item === value;
|
||||
});
|
||||
if (!isPresent && schema['x-ms-enum'] && !Boolean(schema['x-ms-enum']['modelAsString'])) {
|
||||
let msg = `${value} is not a valid value for ${objectName}. The valid values are: ${JSON.stringify(schema[constraintType])}.`;
|
||||
let e = utils.constructErrorObject('ENUM_FAILURE', msg);
|
||||
constraintErrors.push(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
if (constraintErrors.length > 0) {
|
||||
return constraintErrors;
|
||||
}
|
||||
}
|
||||
|
||||
validateNonBodyParameter(schema, data, operationId) {
|
||||
let result = {
|
||||
validationErrors: null,
|
||||
typeError: null
|
||||
};
|
||||
//constraint validation
|
||||
result.validationErrors = this.validateConstraints(schema, data, schema.name);
|
||||
//type validation
|
||||
try {
|
||||
this.validateType(schema, data);
|
||||
} catch (typeError) {
|
||||
result.typeError = `${typeError.message} in operation: "${operationId}".`;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
validateDataModels(callback) {
|
||||
let self = this;
|
||||
let options = { skipInitialization: false };
|
||||
if (self.specInJson && Object.keys(self.specInJson).length > 0) {
|
||||
options.skipInitialization = true;
|
||||
}
|
||||
self.initialize(options, function(initializationErr, result) {
|
||||
if (initializationErr) {
|
||||
return callback(initializationErr);
|
||||
}
|
||||
utils.run(function* () {
|
||||
let apiPaths = Object.keys(self.specInJson.paths);
|
||||
for (let i = 0; i < apiPaths.length; i++) {
|
||||
let verbs = Object.keys(self.specInJson.paths[apiPaths[i]]);
|
||||
for (let j = 0; j < verbs.length; j++) {
|
||||
let apiPath = apiPaths[i], verb = verbs[j];
|
||||
let operation = self.specInJson.paths[apiPath][verb];
|
||||
let operationId = operation.operationId;
|
||||
console.log(`\t> Operation: ${operationId}`);
|
||||
if (operation[xmsExamples]) {
|
||||
let scenarios = Object.keys(operation[xmsExamples]);
|
||||
for (let k = 0; k < scenarios.length; k++) {
|
||||
let scenarioName = scenarios[k], scenarioData = operation[xmsExamples][scenarioName];
|
||||
//validate parameters
|
||||
console.log(`\t\t> ${xmsExamples}`);
|
||||
console.log(`\t\t\t> Scenario: ${scenarioName}`);
|
||||
self.specValidationResult.operations[operationId][xmsExamples]['scenarios'][scenarioName].parameters = {};
|
||||
for (let l = 0; l < operation.parameters.length; l++) {
|
||||
let parameter = operation.parameters[l];
|
||||
let dereferencedParameter = parameter;
|
||||
if (parameter['$ref']) {
|
||||
dereferencedParameter = self.refSpecInJson.get(parameter['$ref']);
|
||||
}
|
||||
let parameterName = dereferencedParameter.name;
|
||||
self.specValidationResult.operations[operationId][xmsExamples]['scenarios'][scenarioName].parameters[parameterName] = {};
|
||||
self.specValidationResult.operations[operationId][xmsExamples]['scenarios'][scenarioName].parameters[parameterName]['isValid'] = true;
|
||||
//check for requiredness
|
||||
if (dereferencedParameter.required && !scenarioData.parameters[parameterName]) {
|
||||
let msg = `Swagger spec has a parameter named "${dereferencedParameter.name}" as required for operation "${operationId}", ` +
|
||||
`however this parameter is not defined in the example for scenario "${scenarioName}".`;
|
||||
self.specValidationResult.operations[operationId][xmsExamples]['scenarios'][scenarioName].parameters[parameterName]['isValid'] = false;
|
||||
let reqError = utils.constructErrorObject(RequiredParameterNotInExampleError, msg);
|
||||
self.specValidationResult.operations[operationId][xmsExamples]['scenarios'][scenarioName].parameters[parameterName].error = reqError;
|
||||
console.error(`\t\t\t\t> ${reqError.code}: ${reqError.message}`);
|
||||
continue;
|
||||
}
|
||||
if (dereferencedParameter.in === 'body') {
|
||||
//TODO: Handle inline model definition
|
||||
let modelReference = dereferencedParameter.schema['$ref'];
|
||||
try {
|
||||
let result = yield self.validateModel(modelReference, scenarioData.parameters[parameterName]);
|
||||
if (result) {
|
||||
self.specValidationResult.operations[operationId][xmsExamples]['scenarios'][scenarioName].parameters[parameterName]['isValid'] = false;
|
||||
let msg = `Found errors in validating the body parameter for example "${scenarioName}" in operation "${operationId}".`;
|
||||
let bodyValidationError = utils.constructErrorObject(BodyParameterValidationError, msg, result.errors);
|
||||
self.specValidationResult.operations[operationId][xmsExamples]['scenarios'][scenarioName].parameters[parameterName].error = bodyValidationError;
|
||||
console.error(`\t\t\t\t> ${bodyValidationError.code}: ${bodyValidationError.message}`);
|
||||
} else {
|
||||
let msg = `The body parameter for example "${scenarioName}" in operation "${operationId}" is valid.`;
|
||||
self.specValidationResult.operations[operationId][xmsExamples]['scenarios'][scenarioName].parameters[parameterName]['result'] = msg;
|
||||
console.log(`\t\t\t\t> ${BodyParameterValid}: ${msg}`);
|
||||
}
|
||||
} catch(err) {
|
||||
self.specValidationResult.operations[operationId][xmsExamples]['scenarios'][scenarioName].parameters[parameterName]['isValid'] = false;
|
||||
let msg = `An internal error occured while validating the body parameter for example "${scenarioName}" in operation "${operationId}".`;
|
||||
let e = utils.constructErrorObject(InternalError, msg, [err]);
|
||||
self.specValidationResult.operations[operationId][xmsExamples]['scenarios'][scenarioName].parameters[parameterName].error = e;
|
||||
console.error(`\t\t\t\t> ${e.code}: ${e.message}`);
|
||||
}
|
||||
} else {
|
||||
let errors = self.validateNonBodyParameter(dereferencedParameter, scenarioData.parameters[parameterName], operationId);
|
||||
let nonBodyError;
|
||||
if (errors.validationErrors) {
|
||||
self.specValidationResult.operations[operationId][xmsExamples]['scenarios'][scenarioName].parameters[parameterName]['isValid'] = false;
|
||||
let msg = `${parameterName} in operation ${operationId} failed validation constraints.`;
|
||||
nonBodyError = utils.constructErrorObject(ConstraintValidationError, msg, errors.validationErrors);
|
||||
self.specValidationResult.operations[operationId][xmsExamples]['scenarios'][scenarioName].parameters[parameterName].error = nonBodyError;
|
||||
console.log(`\t\t\t\t> ${nonBodyError.code}: ${nonBodyError.message}`);
|
||||
}
|
||||
if (errors.typeError) {
|
||||
self.specValidationResult.operations[operationId][xmsExamples]['scenarios'][scenarioName].parameters[parameterName]['isValid'] = false;
|
||||
errors.typeError.code = TypeValidationError;
|
||||
if (errors.validationErrors) {
|
||||
nonBodyError.code += ' + ' + errors.typeError.code
|
||||
nonBodyError.innerErrors.push(errors.typeError);
|
||||
self.specValidationResult.operations[operationId][xmsExamples]['scenarios'][scenarioName].parameters[parameterName].error = nonBodyError;
|
||||
console.log(`\t\t\t\t> ${nonBodyError.code}: ${nonBodyError.message}`);
|
||||
} else {
|
||||
self.specValidationResult.operations[operationId][xmsExamples]['scenarios'][scenarioName].parameters[parameterName].error = errors.typeError;
|
||||
console.log(`\t\t\t\t> ${errors.typeError.code}: ${errors.typeError.message}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//validate responses
|
||||
self.specValidationResult.operations[operationId][xmsExamples]['scenarios'][scenarioName].responses = {};
|
||||
let res = Object.keys(operation.responses);
|
||||
for (let m = 0; m < res.length; m++) {
|
||||
let statusCode = res[m];
|
||||
self.specValidationResult.operations[operationId][xmsExamples]['scenarios'][scenarioName].responses[statusCode] = {};
|
||||
self.specValidationResult.operations[operationId][xmsExamples]['scenarios'][scenarioName].responses[statusCode]['isValid'] = true;
|
||||
if (!scenarioData.responses[statusCode]) {
|
||||
let msg = `Response with statusCode "${statusCode}" for operation "${operationId}" is present in the spec, but not in the example "${scenarioName}".`;
|
||||
let e = utils.constructErrorObject(StatuscodeNotInExampleError, msg);
|
||||
self.specValidationResult.operations[operationId][xmsExamples]['scenarios'][scenarioName].responses[statusCode]['isValid'] = false;
|
||||
self.specValidationResult.operations[operationId][xmsExamples]['scenarios'][scenarioName].responses[statusCode].error = e;
|
||||
continue;
|
||||
}
|
||||
//TODO: Currently only validating $ref in responses. What if it is an inline schema? Will have to add the logic to
|
||||
//add the schema in the definitions section and replace the inline schema with a ref corresponding to that definition.
|
||||
if (operation.responses[statusCode]['schema'] && operation.responses[statusCode]['schema']['$ref']) {
|
||||
let modelReference = operation.responses[statusCode]['schema']['$ref'];
|
||||
try {
|
||||
let result = yield self.validateModel(modelReference, scenarioData.responses[statusCode].body);
|
||||
if (result) {
|
||||
let msg = `Found errors in validating the response with statusCode "${statusCode}" for ` +
|
||||
`example "${scenarioName}" in operation "${operationId}".`;
|
||||
let responseBodyValidationError = utils.constructErrorObject(ResponseBodyValidationError, msg, result.errors);
|
||||
self.specValidationResult.operations[operationId][xmsExamples]['scenarios'][scenarioName].responses[statusCode]['isValid'] = false;
|
||||
self.specValidationResult.operations[operationId][xmsExamples]['scenarios'][scenarioName].responses[statusCode].error = responseBodyValidationError;
|
||||
console.error(`\t\t\t\t> ${responseBodyValidationError.code}: ${responseBodyValidationError.message}`);
|
||||
} else {
|
||||
let msg = `Response with statusCode "${statusCode}" for example "${scenarioName}" in operation "${operationId}" is valid.`
|
||||
self.specValidationResult.operations[operationId][xmsExamples]['scenarios'][scenarioName].responses[statusCode]['result'] = msg;
|
||||
//console.log(`\t\t\t\t> RESPONSE_${statusCode}_VALID: ${msg}`);
|
||||
}
|
||||
} catch(err) {
|
||||
self.specValidationResult.operations[operationId][xmsExamples]['scenarios'][scenarioName].responses[statusCode]['isValid'] = false;
|
||||
err.code = InternalError + (err.code ? ' ' + err.code : '');
|
||||
let msg = `An internal error occured while validating the response with statusCode "${statusCode}" for ` +
|
||||
`example "${scenarioName}" in operation "${operationId}".`;
|
||||
let e = utils.constructErrorObject(InternalError, msg, [err]);
|
||||
self.specValidationResult.operations[operationId][xmsExamples]['scenarios'][scenarioName].responses[statusCode].error = e;
|
||||
console.log(`\t\t\t\t> ${e.code}: ${e.message}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//validate examples present in the spec if present
|
||||
//let's validate the example for body parameter if it is present
|
||||
let parameters = operation.parameters;
|
||||
let exampleDisplay = false;
|
||||
if (parameters) {
|
||||
for (let p = 0; p < parameters.length; p++) {
|
||||
let param = parameters[p];
|
||||
if (param && param.in && param.in === 'body') {
|
||||
//TODO: Need to handle inline parameters
|
||||
if (param.schema && param.schema['$ref']) {
|
||||
let paramRef = param.schema['$ref'];
|
||||
let dereferencedParam = self.refSpecInJson.get(paramRef);
|
||||
if (dereferencedParam.example) {
|
||||
console.log(`\t\t> ${exampleInSpec}`);
|
||||
exampleDisplay = true;
|
||||
self.specValidationResult.operations[operationId][exampleInSpec] = {};
|
||||
self.specValidationResult.operations[operationId][exampleInSpec]['parameters'] = {};
|
||||
self.specValidationResult.operations[operationId][exampleInSpec]['parameters'][param.name] = {};
|
||||
self.specValidationResult.operations[operationId][exampleInSpec]['parameters'][param.name]['isValid'] = true;
|
||||
try {
|
||||
let bodyParamExampleResult = yield self.validateModel(paramRef, dereferencedParam.example);
|
||||
if (bodyParamExampleResult) {
|
||||
self.specValidationResult.operations[operationId][exampleInSpec]['parameters'][param.name]['isValid'] = false;
|
||||
let msg = `Found errors in validating the example provided for body parameter ` +
|
||||
`${param.name} with reference ${paramRef} in operation "${operationId}".`;
|
||||
let bodyValidationError = utils.constructErrorObject(BodyParameterValidationError, msg, bodyParamExampleResult.errors);
|
||||
self.specValidationResult.operations[operationId][exampleInSpec]['parameters'][param.name]['error'] = bodyValidationError;
|
||||
console.error(`\t\t\t> ${bodyValidationError.code}: ${bodyValidationError.message}`);
|
||||
} else {
|
||||
let msg = `The example for body parameter ${param.name} with reference ${paramRef} in operation "${operationId}" is valid.`;
|
||||
self.specValidationResult.operations[operationId][exampleInSpec]['parameters'][param.name]['result'] = msg;
|
||||
console.log(`\t\t\t> ${BodyParameterValid}: ${msg}`);
|
||||
}
|
||||
} catch(err) {
|
||||
self.specValidationResult.operations[operationId][exampleInSpec]['parameters'][param.name]['isValid'] = false;
|
||||
err.code = InternalError + (err.code ? ' ' + err.code : '');
|
||||
let msg = `An internal error occured while validating the example provided for body parameter ` +
|
||||
`${param.name} with reference ${paramRef} in operation "${operationId}".`;
|
||||
let e = utils.constructErrorObject(InternalError, msg, [err]);
|
||||
self.specValidationResult.operations[operationId][exampleInSpec]['parameters'][param.name].error = e;
|
||||
console.error(`\t\t\t> ${e.code}: ${e.message}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
//let's validate the examples for response status codes if present
|
||||
let responses = operation.responses;
|
||||
let statusCodes = Object.keys(responses);
|
||||
if (statusCodes) {
|
||||
for(let q = 0; q < statusCodes.length; q++) {
|
||||
if (responses[statusCodes[q]].examples) {
|
||||
if (!exampleDisplay) {
|
||||
console.log(`\t\t> ${exampleInSpec}`);
|
||||
if (!self.specValidationResult.operations) {
|
||||
self.specValidationResult.operations = {};
|
||||
self.specValidationResult.operations[operationId] = {};
|
||||
}
|
||||
self.specValidationResult.operations[operationId][exampleInSpec] = {};
|
||||
}
|
||||
let responseExamples = responses[statusCodes[q]].examples;
|
||||
//TODO: Handle the case for inline schema definitions.
|
||||
let responseRef;
|
||||
if (responses[statusCodes[q]].schema && responses[statusCodes[q]].schema['$ref']) {
|
||||
responseRef = responses[statusCodes[q]].schema['$ref'];
|
||||
}
|
||||
let mimeTypes = Object.keys(responseExamples);
|
||||
for (let r = 0; r < mimeTypes.length; r++) {
|
||||
let responseData = responseExamples[mimeTypes[r]];
|
||||
self.specValidationResult.operations[operationId][exampleInSpec]['responses'] = {};
|
||||
self.specValidationResult.operations[operationId][exampleInSpec]['responses'][statusCodes[q]] = {};
|
||||
self.specValidationResult.operations[operationId][exampleInSpec]['responses'][statusCodes[q]]['isValid'] = true;
|
||||
try {
|
||||
let responseResult = yield self.validateModel(responseRef, responseData);
|
||||
if (responseResult) {
|
||||
let msg = `Found errors in validating the example for response with statusCode "${statusCodes[q]}" in operation "${operationId}".`;
|
||||
let responseBodyValidationError = utils.constructErrorObject(ResponseBodyValidationError, msg, responseResult.errors);
|
||||
self.specValidationResult.operations[operationId][exampleInSpec]['responses'][statusCodes[q]]['isValid'] = false;
|
||||
self.specValidationResult.operations[operationId][exampleInSpec]['responses'][statusCodes[q]].error = responseBodyValidationError;
|
||||
console.log(`\t\t\t> ${responseBodyValidationError.code}: ${responseBodyValidationError.message}`);
|
||||
} else {
|
||||
let msg = `The example for response with statusCode "${statusCodes[q]}" in operation "${operationId}" is valid.`
|
||||
self.specValidationResult.operations[operationId][exampleInSpec]['responses'][statusCodes[q]]['result'] = msg;
|
||||
console.log(`\t\t\t> RESPONSE_${statusCodes[q]}_VALID: ${msg}`);
|
||||
}
|
||||
} catch(err) {
|
||||
self.specValidationResult.operations[operationId][exampleInSpec]['responses'][statusCodes[q]]['isValid'] = false;
|
||||
let msg = `An internal error occured while validating the example for response with statusCode "${statusCodes[q]}" in operation "${operationId}".`;
|
||||
let e = utils.constructErrorObject(InternalError, msg, [err]);
|
||||
self.specValidationResult.operations[operationId][exampleInSpec]['responses'][statusCodes[q]].error = e;
|
||||
console.log(`\t\t\t> ${e.code}: ${e.message}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return callback(null);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
validateSpec(callback) {
|
||||
let self = this;
|
||||
let options = { skipInitialization: false };
|
||||
if (self.specInJson && Object.keys(self.specInJson).length > 0) {
|
||||
options.skipInitialization = true;
|
||||
}
|
||||
self.initialize(options, function(initializationError, initializationResult) {
|
||||
if (initializationError) {
|
||||
return callback(initializationError);
|
||||
}
|
||||
swt.validate(self.specInJson, function (err, result) {
|
||||
if (err) {
|
||||
return callback(err);
|
||||
}
|
||||
if (result && result.errors.length > 0) {
|
||||
console.log('');
|
||||
console.log('Errors');
|
||||
console.log('------');
|
||||
|
||||
result.errors.forEach(function (err) {
|
||||
if (err.path[0] === 'paths') {
|
||||
err.path.shift();
|
||||
console.log(`${err.code} : ${err.path.join('/')} : ${err.message}`);
|
||||
} else {
|
||||
console.log(`${err.code} : #/${err.path.join('/')} : ${err.message}`);
|
||||
}
|
||||
});
|
||||
return callback(util.inspect(result.errors, {depth: null}));
|
||||
} else {
|
||||
return callback(null);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = SpecValidator;
|
|
@ -0,0 +1,586 @@
|
|||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
|
||||
'use strict';
|
||||
|
||||
var fs = require('fs'),
|
||||
path = require('path'),
|
||||
util = require('util'),
|
||||
deasync = require('deasync'),
|
||||
swt = require('swagger-tools').specs.v2,
|
||||
RefParser = require('json-schema-ref-parser'),
|
||||
utils = require('./util/utils'),
|
||||
constants = require('./util/constants'),
|
||||
cwd = process.cwd();
|
||||
|
||||
var validateModel = deasync(swt.validateModel);
|
||||
|
||||
class SpecValidator {
|
||||
|
||||
constructor(specPath) {
|
||||
if (!specPath && typeof specPath.valueOf() !== 'string') {
|
||||
throw new Error('specPath (A file or a rawgithub url) cannot be null or undefined and must of type string');
|
||||
}
|
||||
this.specPath = specPath;
|
||||
this.specInJson = null;
|
||||
this.refSpecInJson = null;
|
||||
this.specValidationResult = {};
|
||||
this.initialize();
|
||||
}
|
||||
|
||||
//wrapper to validateModel of swagger-tools
|
||||
bundleSpec() {
|
||||
let self = this;
|
||||
return function (callback) {
|
||||
RefParser.bundle(self.specPath, callback);
|
||||
}
|
||||
}
|
||||
|
||||
//wrapper to validateModel of swagger-tools
|
||||
resolveSpec(specPath, data) {
|
||||
let self = this;
|
||||
return function (callback) {
|
||||
RefParser.validateModel(self.specPath, callback);
|
||||
}
|
||||
}
|
||||
|
||||
initialize() {
|
||||
let self = this;
|
||||
utils.run(function* () {
|
||||
try {
|
||||
self.specInJson = yield self.bundleSpec();
|
||||
self.refSpecInJson = yield self.resolveSpec();
|
||||
} catch (err) {
|
||||
throw err;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
initialize1() {
|
||||
let self = this;
|
||||
try {
|
||||
let resolveDone = false;
|
||||
let bundleDone = false;
|
||||
RefParser.bundle(self.specPath, function (err, result) {
|
||||
if (err) throw err;
|
||||
self.specInJson = result;
|
||||
bundleDone = true;
|
||||
});
|
||||
deasync.loopWhile(function () { return !bundleDone; });
|
||||
RefParser.resolve(self.specPath, function (err, result) {
|
||||
if (err) throw err;
|
||||
self.refSpecInJson = result;
|
||||
resolveDone = true;
|
||||
});
|
||||
deasync.loopWhile(function () { return !resolveDone; });
|
||||
} catch (err) {
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
unifyXmsPaths() {
|
||||
//unify x-ms-paths into paths
|
||||
if (this.specInJson['x-ms-paths'] && this.specInJson['x-ms-paths'] instanceof Object &&
|
||||
Object.keys(this.specInJson['x-ms-paths']).length > 0) {
|
||||
let paths = this.specInJson.paths;
|
||||
for (let property in this.specInJson['x-ms-paths']) {
|
||||
paths[property] = this.specInJson['x-ms-paths'][property];
|
||||
}
|
||||
this.specInJson.paths = paths;
|
||||
}
|
||||
}
|
||||
|
||||
simpleValidateModel(modelReference, model) {
|
||||
let self = this;
|
||||
console.log(modelReference);
|
||||
let result = validateModel(self.specInJson, modelReference, model);
|
||||
console.log(result);
|
||||
return;
|
||||
}
|
||||
|
||||
//wrapper to validateModel of swagger-tools
|
||||
validateModelsss(modelReference, data) {
|
||||
let self = this;
|
||||
if (!modelReference) {
|
||||
throw new Error('modelReference cannot be null or undefined. It must be of a string. Example: "#/definitions/foo".');
|
||||
}
|
||||
if (!data) {
|
||||
throw new Error('data cannot be null or undefined. It must be of a JSON object or a JSON array.');
|
||||
}
|
||||
|
||||
return function (callback) {
|
||||
swt.validateModel(self.specInJson, modelReference, data, callback);
|
||||
}
|
||||
}
|
||||
|
||||
validateDateTypes(schema, value) {
|
||||
if (value !== null && value !== undefined) {
|
||||
if (schema.format.match(/^date$/ig) !== null) {
|
||||
if (!(value instanceof Date ||
|
||||
(typeof value.valueOf() === 'string' && !isNaN(Date.parse(value))))) {
|
||||
throw new Error(`${schema.name} must be an instanceof Date or a string in ISO8601 format.`);
|
||||
}
|
||||
} else if (schema.format.match(/^date-time$/ig) !== null) {
|
||||
if (!(value instanceof Date ||
|
||||
(typeof value.valueOf() === 'string' && !isNaN(Date.parse(value))))) {
|
||||
throw new Error(`${schema.name} must be an instanceof Date or a string in ISO8601 format.`);
|
||||
}
|
||||
} else if (schema.format.match(/^date-time-rfc-1123$/ig) !== null) {
|
||||
if (!(value instanceof Date ||
|
||||
(typeof value.valueOf() === 'string' && !isNaN(Date.parse(value))))) {
|
||||
throw new Error(`${schema.name} must be an instanceof Date or a string in RFC-1123 format.`);
|
||||
}
|
||||
} else if (schema.format.match(/^unixtime$/ig) !== null) {
|
||||
if (!(value instanceof Date ||
|
||||
(typeof value.valueOf() === 'string' && !isNaN(Date.parse(value))))) {
|
||||
throw new Error(`${schema.name} must be an instanceof Date or a string in RFC-1123/ISO8601 format ` +
|
||||
`for it to be serialized in UnixTime/Epoch format.`);
|
||||
}
|
||||
} else if (schema.format.match(/^(duration|timespan)$/ig) !== null) {
|
||||
if (!moment.isDuration(value)) {
|
||||
throw new Error(`${schema.name} must be a TimeSpan/Duration.`);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
validateBufferType(schema, value) {
|
||||
if (value !== null && value !== undefined) {
|
||||
if (!Buffer.isBuffer(value)) {
|
||||
throw new Error(`${schema.name} must be of type Buffer.`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
isValidUuid(uuid) {
|
||||
let validUuidRegex = new RegExp('^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$', 'ig');
|
||||
return validUuidRegex.test(uuid);
|
||||
};
|
||||
|
||||
validateBasicTypes(schema, value) {
|
||||
if (value !== null && value !== undefined) {
|
||||
if (schema.type.match(/^(number|integer)$/ig) !== null) {
|
||||
if (typeof value !== 'number') {
|
||||
throw new Error(`${schema.name} with value ${value} must be of type number.`);
|
||||
}
|
||||
} else if (schema.type.match(/^string$/ig) !== null) {
|
||||
if (typeof value.valueOf() !== 'string') {
|
||||
throw new Error(`${schema.name} with value '${value}' must be of type string.`);
|
||||
}
|
||||
if (schema.format && schema.format.match(/^uuid$/ig) !== null) {
|
||||
if (!(typeof value.valueOf() === 'string' && this.isValidUuid(value))) {
|
||||
throw new Error(`${schema.name} with value '${value}' must be of type string and a valid uuid.`);
|
||||
}
|
||||
}
|
||||
} else if (schema.type.match(/^boolean$/ig) !== null) {
|
||||
if (typeof value !== 'boolean') {
|
||||
throw new Error(`${schema.name} with value ${value} must be of type boolean.`);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//entry point for type validations
|
||||
validateType(schema, data) {
|
||||
if (schema.type.match(/^(number|string|boolean|integer)$/ig) !== null) {
|
||||
if (schema.format) {
|
||||
if (schema.format.match(/^(date|date-time|timespan|duration|date-time-rfc1123|unixtime)$/ig) !== null) {
|
||||
this.validateDateTypes(schema, data);
|
||||
} else if (schema.format.match(/^b(yte|ase64url)$/ig) !== null) {
|
||||
this.validateBufferType(schema, data);
|
||||
}
|
||||
} else {
|
||||
this.validateBasicTypes(schema, data);
|
||||
}
|
||||
} else {
|
||||
throw new Error(`Unknown type: "${schema.type}" provided for parameter: "${schema.name}".`);
|
||||
}
|
||||
};
|
||||
|
||||
//validates constraints
|
||||
validateConstraints(schema, value, objectName) {
|
||||
let constraintErrors = [];
|
||||
constraints.forEach(function (constraintType) {
|
||||
if (schema[constraintType] !== null && schema[constraintType] !== undefined) {
|
||||
if (constraintType.match(/^maximum$/ig) !== null) {
|
||||
if (schema['exclusiveMaximum']) {
|
||||
if (value >= schema[constraintType]) {
|
||||
let msg = `'${objectName}' with value '${value}' should satify the constraint ` +
|
||||
`'exclusiveMaximum': true and 'maximum': ${schema[constraintType]}.`;
|
||||
let e = utils.constructErrorObject('EXCLUSIVE_MAXIMUM_FAILURE', msg);
|
||||
constraintErrors.push(e);
|
||||
}
|
||||
} else {
|
||||
if (value > schema[constraintType]) {
|
||||
let msg = `'${objectName}' with value '${value}' should satify the constraint 'maximum': ${schema[constraintType]}.`;
|
||||
let e = utils.constructErrorObject('MAXIMUM_FAILURE', msg);
|
||||
constraintErrors.push(e);
|
||||
}
|
||||
}
|
||||
} else if (constraintType.match(/^minimum$/ig) !== null) {
|
||||
if (schema['exclusiveMinimum']) {
|
||||
if (value <= schema[constraintType]) {
|
||||
let msg = `'${objectName}' with value '${value}' should satify the constraint ` +
|
||||
`'exclusiveMinimum': true and 'minimum': ${schema[constraintType]}.`;
|
||||
e.code = 'MINIMUM_FAILURE';
|
||||
let e = utils.constructErrorObject('MINIMUM_FAILURE', msg);
|
||||
constraintErrors.push(e);
|
||||
}
|
||||
} else {
|
||||
if (value < schema[constraintType]) {
|
||||
let msg = `'${objectName}' with value '${value}' should satify the constraint 'minimum': ${schema[constraintType]}.`;
|
||||
e.code = 'MINIMUM_FAILURE';
|
||||
let e = utils.constructErrorObject('MINIMUM_FAILURE', msg);
|
||||
constraintErrors.push(e);
|
||||
}
|
||||
}
|
||||
} else if (constraintType.match(/^maxItems$/ig) !== null) {
|
||||
if (value.length > schema[constraintType]) {
|
||||
let msg = `'${objectName}' with value '${value}' should satify the constraint 'maxItems': ${schema[constraintType]}.`;
|
||||
e.code = 'MAXITEMS_FAILURE';
|
||||
let e = utils.constructErrorObject('MAXITEMS_FAILURE', msg);
|
||||
constraintErrors.push(e);
|
||||
}
|
||||
} else if (constraintType.match(/^maxLength$/ig) !== null) {
|
||||
if (value.length > schema[constraintType]) {
|
||||
let msg = `'${objectName}' with value '${value}' should satify the constraint 'maxLength': ${schema[constraintType]}.`;
|
||||
e.code = 'MAXLENGTH_FAILURE';
|
||||
let e = utils.constructErrorObject('MAXLENGTH_FAILURE', msg);
|
||||
constraintErrors.push(e);
|
||||
}
|
||||
} else if (constraintType.match(/^minItems$/ig) !== null) {
|
||||
if (value.length < schema[constraintType]) {
|
||||
let msg = `'${objectName}' with value '${value}' should satify the constraint 'minItems': ${schema[constraintType]}.`;
|
||||
e.code = 'MINITEMS_FAILURE';
|
||||
let e = utils.constructErrorObject('MINITEMS_FAILURE', msg);
|
||||
constraintErrors.push(e);
|
||||
}
|
||||
} else if (constraintType.match(/^minLength$/ig) !== null) {
|
||||
if (value.length < schema[constraintType]) {
|
||||
throw `'${objectName}' with value '${value}' should satify the constraint 'minLength': ${schema[constraintType]}.`;
|
||||
e.code = 'MINLENGTH_FAILURE';
|
||||
let e = utils.constructErrorObject('MINLENGTH_FAILURE', msg);
|
||||
constraintErrors.push(e);
|
||||
}
|
||||
} else if (constraintType.match(/^multipleOf$/ig) !== null) {
|
||||
if (value.length % schema[constraintType] !== 0) {
|
||||
let msg = `'${objectName}' with value '${value}' should satify the constraint 'multipleOf': ${schema[constraintType]}.`;
|
||||
let e = utils.constructErrorObject('MULTIPLEOF_FAILURE', msg);
|
||||
constraintErrors.push(e);
|
||||
}
|
||||
} else if (constraintType.match(/^pattern$/ig) !== null) {
|
||||
if (value.match(schema[constraintType].split('/').join('\/')) === null) {
|
||||
let msg = `'${objectName}' with value '${value}' should satify the constraint 'pattern': ${schema[constraintType]}.`;
|
||||
let e = utils.constructErrorObject('REGEX_PATTERN_FAILURE', msg);
|
||||
constraintErrors.push(e);
|
||||
}
|
||||
} else if (constraintType.match(/^uniqueItems/ig) !== null) {
|
||||
if (schema[constraintType]) {
|
||||
if (value.length !== value.filter(function (item, i, ar) { { return ar.indexOf(item) === i; } }).length) {
|
||||
let msg = `'${objectName}' with value '${value}' should satify the constraint 'uniqueItems': ${schema[constraintType]}.`;
|
||||
let e = utils.constructErrorObject('UNIQUEITEMS_FAILURE', msg);
|
||||
constraintErrors.push(e);
|
||||
}
|
||||
}
|
||||
} else if (constraintType.match(/^enum/ig) !== null) {
|
||||
let isPresent = schema[constraintType].some(function (item) {
|
||||
return item === value;
|
||||
});
|
||||
if (!isPresent && schema['x-ms-enum'] && !Boolean(schema['x-ms-enum']['modelAsString'])) {
|
||||
let msg = `${value} is not a valid value for ${objectName}. The valid values are: ${JSON.stringify(schema[constraintType])}.`;
|
||||
let e = utils.constructErrorObject('ENUM_FAILURE', msg);
|
||||
constraintErrors.push(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
if (constraintErrors.length > 0) {
|
||||
return constraintErrors;
|
||||
}
|
||||
}
|
||||
|
||||
validateNonBodyParameter(schema, data, operationId) {
|
||||
let result = {
|
||||
validationErrors: null,
|
||||
typeError: null
|
||||
};
|
||||
//constraint validation
|
||||
result.validationErrors = this.validateConstraints(schema, data, schema.name);
|
||||
//type validation
|
||||
try {
|
||||
this.validateType(schema, data);
|
||||
} catch (typeError) {
|
||||
result.typeError = `${typeError.message} in operation: "${operationId}".`;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
validateScenario(apiPath, verb, scenarioName) {
|
||||
let self = this;
|
||||
let operation = specInJson.paths[apiPath][verb];
|
||||
let operationId = operation.operationId;
|
||||
let scenarioData = operation[xmsExamples][scenarioName];
|
||||
self.specValidationResult.operations[operationId][xmsExamples]['scenarios'][scenarioName].parameters = {};
|
||||
for (let l = 0; l < operation.parameters.length; l++) {
|
||||
let parameter = operation.parameters[l];
|
||||
let dereferencedParameter = parameter;
|
||||
if (parameter['$ref']) {
|
||||
dereferencedParameter = self.refSpecInJson.get(parameter['$ref']);
|
||||
}
|
||||
let parameterName = dereferencedParameter.name;
|
||||
self.specValidationResult.operations[operationId][xmsExamples]['scenarios'][scenarioName].parameters[parameterName] = {};
|
||||
self.specValidationResult.operations[operationId][xmsExamples]['scenarios'][scenarioName].parameters[parameterName]['isValid'] = true;
|
||||
//check for requiredness
|
||||
if (dereferencedParameter.required && !scenarioData.parameters[parameterName]) {
|
||||
let msg = `Swagger spec has a parameter named "${dereferencedParameter.name}" as required for operation "${operationId}", ` +
|
||||
`however this parameter is not defined in the example for scenario "${scenarioName}".`;
|
||||
self.specValidationResult.operations[operationId][xmsExamples]['scenarios'][scenarioName].parameters[parameterName]['isValid'] = false;
|
||||
let reqError = utils.constructErrorObject(RequiredParameterNotInExampleError, msg);
|
||||
self.specValidationResult.operations[operationId][xmsExamples]['scenarios'][scenarioName].parameters[parameterName].error = reqError;
|
||||
console.error(`\t\t\t\t> ${reqError.code}: ${reqError.message}`);
|
||||
continue;
|
||||
}
|
||||
if (dereferencedParameter.in === 'body') {
|
||||
//TODO: Handle inline model definition
|
||||
let modelReference = dereferencedParameter.schema['$ref'];
|
||||
try {
|
||||
let bodyValidationResult = null;
|
||||
let bodyValidationDone = false
|
||||
swt.validateModel(self.specInJson, modelReference, scenarioData.parameters[parameterName], function (err, result) {
|
||||
bodyValidationResult
|
||||
});
|
||||
if (result) {
|
||||
self.specValidationResult.operations[operationId][xmsExamples]['scenarios'][scenarioName].parameters[parameterName]['isValid'] = false;
|
||||
let msg = `Found errors in validating the body parameter for example "${scenarioName}" in operation "${operationId}".`;
|
||||
let bodyValidationError = utils.constructErrorObject(BodyParameterValidationError, msg, result.errors);
|
||||
self.specValidationResult.operations[operationId][xmsExamples]['scenarios'][scenarioName].parameters[parameterName].error = bodyValidationError;
|
||||
console.error(`\t\t\t\t> ${bodyValidationError.code}: ${bodyValidationError.message}`);
|
||||
} else {
|
||||
let msg = `The body parameter for example "${scenarioName}" in operation "${operationId}" is valid.`;
|
||||
self.specValidationResult.operations[operationId][xmsExamples]['scenarios'][scenarioName].parameters[parameterName]['result'] = msg;
|
||||
console.log(`\t\t\t\t> ${BodyParameterValid}: ${msg}`);
|
||||
}
|
||||
} catch(err) {
|
||||
self.specValidationResult.operations[operationId][xmsExamples]['scenarios'][scenarioName].parameters[parameterName]['isValid'] = false;
|
||||
let msg = `An internal error occured while validating the body parameter for example "${scenarioName}" in operation "${operationId}".`;
|
||||
let e = utils.constructErrorObject(InternalError, msg, [err]);
|
||||
self.specValidationResult.operations[operationId][xmsExamples]['scenarios'][scenarioName].parameters[parameterName].error = e;
|
||||
console.error(`\t\t\t\t> ${e.code}: ${e.message}`);
|
||||
}
|
||||
} else {
|
||||
let errors = self.validateNonBodyParameter(dereferencedParameter, scenarioData.parameters[parameterName], operationId);
|
||||
let nonBodyError;
|
||||
if (errors.validationErrors) {
|
||||
self.specValidationResult.operations[operationId][xmsExamples]['scenarios'][scenarioName].parameters[parameterName]['isValid'] = false;
|
||||
let msg = `${parameterName} in operation ${operationId} failed validation constraints.`;
|
||||
nonBodyError = utils.constructErrorObject(ConstraintValidationError, msg, errors.validationErrors);
|
||||
self.specValidationResult.operations[operationId][xmsExamples]['scenarios'][scenarioName].parameters[parameterName].error = nonBodyError;
|
||||
console.log(`\t\t\t\t> ${nonBodyError.code}: ${nonBodyError.message}`);
|
||||
}
|
||||
if (errors.typeError) {
|
||||
self.specValidationResult.operations[operationId][xmsExamples]['scenarios'][scenarioName].parameters[parameterName]['isValid'] = false;
|
||||
errors.typeError.code = TypeValidationError;
|
||||
if (errors.validationErrors) {
|
||||
nonBodyError.code += ' + ' + errors.typeError.code
|
||||
nonBodyError.innerErrors.push(errors.typeError);
|
||||
self.specValidationResult.operations[operationId][xmsExamples]['scenarios'][scenarioName].parameters[parameterName].error = nonBodyError;
|
||||
console.log(`\t\t\t\t> ${nonBodyError.code}: ${nonBodyError.message}`);
|
||||
} else {
|
||||
self.specValidationResult.operations[operationId][xmsExamples]['scenarios'][scenarioName].parameters[parameterName].error = errors.typeError;
|
||||
console.log(`\t\t\t\t> ${errors.typeError.code}: ${errors.typeError.message}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//validate responses
|
||||
self.specValidationResult.operations[operationId][xmsExamples]['scenarios'][scenarioName].responses = {};
|
||||
let res = Object.keys(operation.responses);
|
||||
for (let m = 0; m < res.length; m++) {
|
||||
let statusCode = res[m];
|
||||
self.specValidationResult.operations[operationId][xmsExamples]['scenarios'][scenarioName].responses[statusCode] = {};
|
||||
self.specValidationResult.operations[operationId][xmsExamples]['scenarios'][scenarioName].responses[statusCode]['isValid'] = true;
|
||||
if (!scenarioData.responses[statusCode]) {
|
||||
let msg = `Response with statusCode "${statusCode}" for operation "${operationId}" is present in the spec, but not in the example "${scenarioName}".`;
|
||||
let e = utils.constructErrorObject(StatuscodeNotInExampleError, msg);
|
||||
self.specValidationResult.operations[operationId][xmsExamples]['scenarios'][scenarioName].responses[statusCode]['isValid'] = false;
|
||||
self.specValidationResult.operations[operationId][xmsExamples]['scenarios'][scenarioName].responses[statusCode].error = e;
|
||||
continue;
|
||||
}
|
||||
//TODO: Currently only validating $ref in responses. What if it is an inline schema? Will have to add the logic to
|
||||
//add the schema in the definitions section and replace the inline schema with a ref corresponding to that definition.
|
||||
if (operation.responses[statusCode]['schema'] && operation.responses[statusCode]['schema']['$ref']) {
|
||||
let modelReference = operation.responses[statusCode]['schema']['$ref'];
|
||||
try {
|
||||
let result = self.validateModel(modelReference, scenarioData.responses[statusCode].body);
|
||||
if (result) {
|
||||
let msg = `Found errors in validating the response with statusCode "${statusCode}" for ` +
|
||||
`example "${scenarioName}" in operation "${operationId}".`;
|
||||
let responseBodyValidationError = utils.constructErrorObject(ResponseBodyValidationError, msg, result.errors);
|
||||
self.specValidationResult.operations[operationId][xmsExamples]['scenarios'][scenarioName].responses[statusCode]['isValid'] = false;
|
||||
self.specValidationResult.operations[operationId][xmsExamples]['scenarios'][scenarioName].responses[statusCode].error = responseBodyValidationError;
|
||||
console.error(`\t\t\t\t> ${responseBodyValidationError.code}: ${responseBodyValidationError.message}`);
|
||||
} else {
|
||||
let msg = `Response with statusCode "${statusCode}" for example "${scenarioName}" in operation "${operationId}" is valid.`
|
||||
self.specValidationResult.operations[operationId][xmsExamples]['scenarios'][scenarioName].responses[statusCode]['result'] = msg;
|
||||
console.log(`\t\t\t\t> RESPONSE_${statusCode}_VALID: ${msg}`);
|
||||
}
|
||||
} catch(err) {
|
||||
self.specValidationResult.operations[operationId][xmsExamples]['scenarios'][scenarioName].responses[statusCode]['isValid'] = false;
|
||||
err.code = InternalError + (err.code ? ' ' + err.code : '');
|
||||
let msg = `An internal error occured while validating the response with statusCode "${statusCode}" for ` +
|
||||
`example "${scenarioName}" in operation "${operationId}".`;
|
||||
let e = utils.constructErrorObject(InternalError, msg, [err]);
|
||||
self.specValidationResult.operations[operationId][xmsExamples]['scenarios'][scenarioName].responses[statusCode].error = e;
|
||||
console.log(`\t\t\t\t> ${e.code}: ${e.message}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
validateDataModels() {
|
||||
let self = this;
|
||||
utils.run(function* () {
|
||||
let apiPaths = Object.keys(self.specInJson.paths);
|
||||
for (let i = 0; i < apiPaths.length; i++) {
|
||||
let verbs = Object.keys(self.specInJson.paths[apiPaths[i]]);
|
||||
for (let j = 0; j < verbs.length; j++) {
|
||||
let apiPath = apiPaths[i], verb = verbs[j];
|
||||
let operation = self.specInJson.paths[apiPath][verb];
|
||||
let operationId = operation.operationId;
|
||||
console.log(`\t> Operation: ${operationId}`);
|
||||
if (operation[xmsExamples]) {
|
||||
let scenarios = Object.keys(operation[xmsExamples]);
|
||||
for (let k = 0; k < scenarios.length; k++) {
|
||||
let scenarioName = scenarios[k], scenarioData = operation[xmsExamples][scenarioName];
|
||||
//validate parameters
|
||||
console.log(`\t\t> ${xmsExamples}`);
|
||||
console.log(`\t\t\t> Scenario: ${scenarioName}`);
|
||||
self.specValidationResult.operations[operationId][xmsExamples]['scenarios'][scenarioName].parameters = {};
|
||||
self.validateScenario(apiPath, verb, scenarioName)
|
||||
}
|
||||
|
||||
//validate examples present in the spec if present
|
||||
//let's validate the example for body parameter if it is present
|
||||
let parameters = operation.parameters;
|
||||
let exampleDisplay = false;
|
||||
if (parameters) {
|
||||
for (let p = 0; p < parameters.length; p++) {
|
||||
let param = parameters[p];
|
||||
if (param && param.in && param.in === 'body') {
|
||||
//TODO: Need to handle inline parameters
|
||||
if (param.schema && param.schema['$ref']) {
|
||||
let paramRef = param.schema['$ref'];
|
||||
let dereferencedParam = self.refSpecInJson.get(paramRef);
|
||||
if (dereferencedParam.example) {
|
||||
console.log(`\t\t> ${exampleInSpec}`);
|
||||
exampleDisplay = true;
|
||||
self.specValidationResult.operations[operationId][exampleInSpec] = {};
|
||||
self.specValidationResult.operations[operationId][exampleInSpec]['parameters'] = {};
|
||||
self.specValidationResult.operations[operationId][exampleInSpec]['parameters'][param.name] = {};
|
||||
self.specValidationResult.operations[operationId][exampleInSpec]['parameters'][param.name]['isValid'] = true;
|
||||
try {
|
||||
let bodyParamExampleResult = yield self.validateModel(paramRef, dereferencedParam.example);
|
||||
if (bodyParamExampleResult) {
|
||||
self.specValidationResult.operations[operationId][exampleInSpec]['parameters'][param.name]['isValid'] = false;
|
||||
let msg = `Found errors in validating the example provided for body parameter ` +
|
||||
`${param.name} with reference ${paramRef} in operation "${operationId}".`;
|
||||
let bodyValidationError = utils.constructErrorObject(BodyParameterValidationError, msg, bodyParamExampleResult.errors);
|
||||
self.specValidationResult.operations[operationId][exampleInSpec]['parameters'][param.name]['error'] = bodyValidationError;
|
||||
console.error(`\t\t\t> ${bodyValidationError.code}: ${bodyValidationError.message}`);
|
||||
} else {
|
||||
let msg = `The example for body parameter ${param.name} with reference ${paramRef} in operation "${operationId}" is valid.`;
|
||||
self.specValidationResult.operations[operationId][exampleInSpec]['parameters'][param.name]['result'] = msg;
|
||||
console.log(`\t\t\t> ${BodyParameterValid}: ${msg}`);
|
||||
}
|
||||
} catch(err) {
|
||||
self.specValidationResult.operations[operationId][exampleInSpec]['parameters'][param.name]['isValid'] = false;
|
||||
err.code = InternalError + (err.code ? ' ' + err.code : '');
|
||||
let msg = `An internal error occured while validating the example provided for body parameter ` +
|
||||
`${param.name} with reference ${paramRef} in operation "${operationId}".`;
|
||||
let e = utils.constructErrorObject(InternalError, msg, [err]);
|
||||
self.specValidationResult.operations[operationId][exampleInSpec]['parameters'][param.name].error = e;
|
||||
console.error(`\t\t\t> ${e.code}: ${e.message}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
//let's validate the examples for response status codes if present
|
||||
let responses = operation.responses;
|
||||
let statusCodes = Object.keys(responses);
|
||||
if (statusCodes) {
|
||||
for(let q = 0; q < statusCodes.length; q++) {
|
||||
if (responses[statusCodes[q]].examples) {
|
||||
if (!exampleDisplay) {
|
||||
console.log(`\t\t> ${exampleInSpec}`);
|
||||
if (!self.specValidationResult.operations) {
|
||||
self.specValidationResult.operations = {};
|
||||
self.specValidationResult.operations[operationId] = {};
|
||||
}
|
||||
self.specValidationResult.operations[operationId][exampleInSpec] = {};
|
||||
}
|
||||
let responseExamples = responses[statusCodes[q]].examples;
|
||||
//TODO: Handle the case for inline schema definitions.
|
||||
let responseRef;
|
||||
if (responses[statusCodes[q]].schema && responses[statusCodes[q]].schema['$ref']) {
|
||||
responseRef = responses[statusCodes[q]].schema['$ref'];
|
||||
}
|
||||
let mimeTypes = Object.keys(responseExamples);
|
||||
for (let r = 0; r < mimeTypes.length; r++) {
|
||||
let responseData = responseExamples[mimeTypes[r]];
|
||||
self.specValidationResult.operations[operationId][exampleInSpec]['responses'] = {};
|
||||
self.specValidationResult.operations[operationId][exampleInSpec]['responses'][statusCodes[q]] = {};
|
||||
self.specValidationResult.operations[operationId][exampleInSpec]['responses'][statusCodes[q]]['isValid'] = true;
|
||||
try {
|
||||
let responseResult = yield self.validateModel(responseRef, responseData);
|
||||
if (responseResult) {
|
||||
let msg = `Found errors in validating the example for response with statusCode "${statusCodes[q]}" in operation "${operationId}".`;
|
||||
let responseBodyValidationError = utils.constructErrorObject(ResponseBodyValidationError, msg, responseResult.errors);
|
||||
self.specValidationResult.operations[operationId][exampleInSpec]['responses'][statusCodes[q]]['isValid'] = false;
|
||||
self.specValidationResult.operations[operationId][exampleInSpec]['responses'][statusCodes[q]].error = responseBodyValidationError;
|
||||
console.log(`\t\t\t> ${responseBodyValidationError.code}: ${responseBodyValidationError.message}`);
|
||||
} else {
|
||||
let msg = `The example for response with statusCode "${statusCodes[q]}" in operation "${operationId}" is valid.`
|
||||
self.specValidationResult.operations[operationId][exampleInSpec]['responses'][statusCodes[q]]['result'] = msg;
|
||||
console.log(`\t\t\t> RESPONSE_${statusCodes[q]}_VALID: ${msg}`);
|
||||
}
|
||||
} catch(err) {
|
||||
self.specValidationResult.operations[operationId][exampleInSpec]['responses'][statusCodes[q]]['isValid'] = false;
|
||||
let msg = `An internal error occured while validating the example for response with statusCode "${statusCodes[q]}" in operation "${operationId}".`;
|
||||
let e = utils.constructErrorObject(InternalError, msg, [err]);
|
||||
self.specValidationResult.operations[operationId][exampleInSpec]['responses'][statusCodes[q]].error = e;
|
||||
console.log(`\t\t\t> ${e.code}: ${e.message}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return callback(null);
|
||||
});
|
||||
}
|
||||
|
||||
validateSpec() {
|
||||
let self = this;
|
||||
let validationResult;
|
||||
try {
|
||||
let done = false;
|
||||
swt.validate(self.specInJson, function (err, result) {
|
||||
if (err) { throw err; }
|
||||
validationResult = result;
|
||||
done = true;
|
||||
});
|
||||
deasync.loopWhile(function () { return !done; });
|
||||
} catch (err) {
|
||||
throw err;
|
||||
}
|
||||
console.dir(validationResult, { depth: null, colors: true });
|
||||
return validationResult;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = SpecValidator;
|
|
@ -31,6 +31,14 @@ var Constants = {
|
|||
ConstraintValidationError: 'CONSTRAINT_VALIDATION_ERROR',
|
||||
StatuscodeNotInExampleError: 'STATUS_CODE_NOT_IN_EXAMPLE_ERROR',
|
||||
SemanticValidationError: 'SEMANTIC_VALIDATION_ERROR'
|
||||
},
|
||||
EnvironmentVariables: {
|
||||
ClientId: 'CLIENT_ID',
|
||||
Domain: 'DOMAIN',
|
||||
ApplicationSecret: 'APPLICATION_SECRET',
|
||||
AzureSubscriptionId: 'AZURE_SUBSCRIPTION_ID',
|
||||
AzureLocation: 'AZURE_LOCATION',
|
||||
AzureResourcegroup: 'AZURE_RESOURCE_GROUP'
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
var fs = require('fs'),
|
||||
util = require('util'),
|
||||
path = require('path'),
|
||||
jsonPointer = require('json-pointer'),
|
||||
log = require('./logging'),
|
||||
request = require('request');
|
||||
|
||||
|
@ -37,7 +38,7 @@ exports.stripBOM = function stripBOM(content) {
|
|||
* @param {string} specPath - A local file path or a (github) url to the swagger spec.
|
||||
* The method will auto convert a github url to rawgithub url.
|
||||
*
|
||||
* @returns {object} Swagger - Parsed Swagger document in JSON format.
|
||||
* @returns {object} jsonDoc - Parsed document in JSON format.
|
||||
*/
|
||||
exports.parseJson = function parseJson(specPath) {
|
||||
let result = null;
|
||||
|
@ -238,20 +239,70 @@ exports.parseReferenceInSwagger = function parseReferenceInSwagger(reference) {
|
|||
* @return {string} resolved path
|
||||
*/
|
||||
exports.joinPath = function joinPath() {
|
||||
let finalPath = '';
|
||||
for (let arg in arguments) {
|
||||
finalPath = path.join(finalPath, arguments[arg]);
|
||||
}
|
||||
let finalPath = path.join.apply(path, arguments);
|
||||
finalPath = finalPath.replace(/\\/gi, '/');
|
||||
finalPath = finalPath.replace(/^(http|https):\/(.*)/gi, '$1://$2');
|
||||
log.silly(`The final path is: ${finalPath}.`);
|
||||
return finalPath;
|
||||
};
|
||||
|
||||
/*
|
||||
* Provides a parsed JSON from the given file path or a url. Same as exports.parseJson(). However,
|
||||
* this method accepts variable number of path segments as strings and joins them together.
|
||||
* After joining the path, it internally calls exports.parseJson().
|
||||
*
|
||||
* @param variable number of arguments and all the arguments must be of type string.
|
||||
*
|
||||
* @returns {object} jsonDoc - Parsed document in JSON format.
|
||||
*/
|
||||
exports.parseJsonWithPathFragments = function parseJsonWithPathFragments() {
|
||||
let specPath = exports.joinPath.apply(this, arguments);
|
||||
return exports.parseJson(specPath);
|
||||
};
|
||||
|
||||
exports.merge = function merge(obj, src) {
|
||||
Object.keys(src).forEach(function(key) { obj[key] = src[key]; });
|
||||
return obj;
|
||||
/*
|
||||
* Merges source object into the target object
|
||||
* @param {object} source The object that needs to be merged
|
||||
*
|
||||
* @param {object} target The object to be merged into
|
||||
*
|
||||
* @returns {object} target - Returns the merged target object.
|
||||
*/
|
||||
exports.mergeObjects = function mergeObjects(source, target) {
|
||||
Object.keys(source).forEach(function(key) {
|
||||
target[key] = source[key];
|
||||
});
|
||||
return target;
|
||||
}
|
||||
|
||||
exports.getObject = function getObject(doc, ptr) {
|
||||
let result;
|
||||
try {
|
||||
result = jsonPointer.get(doc, ptr);
|
||||
} catch (err) {
|
||||
console.log(err);
|
||||
}
|
||||
return result;
|
||||
};
|
||||
|
||||
exports.setObject = function setObject(doc, ptr, value) {
|
||||
let result;
|
||||
try {
|
||||
result = jsonPointer.set(doc, ptr, value);
|
||||
} catch (err) {
|
||||
console.log(err);
|
||||
}
|
||||
return result;
|
||||
};
|
||||
|
||||
exports.removeObject = function removeObject(doc, ptr) {
|
||||
let result;
|
||||
try {
|
||||
result = jsonPointer.remove(doc, ptr);
|
||||
} catch (err) {
|
||||
console.log(err);
|
||||
}
|
||||
return result;
|
||||
};
|
||||
|
||||
exports = module.exports;
|
|
@ -11,6 +11,7 @@
|
|||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"azure-arm-resource": "^1.6.1-preview",
|
||||
"json-pointer": "^0.6.0",
|
||||
"moment": "^2.14.1",
|
||||
"ms-rest": "^1.15.2",
|
||||
"ms-rest-azure": "^1.15.2",
|
||||
|
|
100
validate.js
100
validate.js
|
@ -122,104 +122,4 @@ exports.logDetailedInfo = function logDetailedInfo(validator, json) {
|
|||
}
|
||||
};
|
||||
|
||||
exports.sanitizeParameters = function sanitizeParameters(exampleParameters) {
|
||||
if (exampleParameters) {
|
||||
if (exampleParameters.subscriptionId) {
|
||||
exampleParameters.subscriptionId = process.env['AZURE_SUBSCRIPTION_ID'];
|
||||
}
|
||||
if (exampleParameters.resourceGroupName) {
|
||||
exampleParameters.resourceGroupName = process.env['AZURE_RESOURCE_GROUP'];
|
||||
}
|
||||
}
|
||||
return exampleParameters;
|
||||
};
|
||||
|
||||
exports.liveTest = function liveTest(specPath, operationId) {
|
||||
exports.validateEnvironmentVariables();
|
||||
log.transports.console.level = 'info';
|
||||
let clientId = process.env['CLIENT_ID'];
|
||||
let domain = process.env['DOMAIN'];
|
||||
let secret = process.env['APPLICATION_SECRET'];
|
||||
let subscriptionId = process.env['AZURE_SUBSCRIPTION_ID'];
|
||||
let location = process.env['AZURE_LOCATION'] || 'westus';
|
||||
let resourceGroupName = process.env['AZURE_RESOURCE_GROUP'] || utils.generateRandomId('testrg');
|
||||
let validator = new SpecValidator(specPath);
|
||||
let operation, xmsExamples;
|
||||
validator.initialize().then(function() {
|
||||
log.info(`Running live test using x-ms-examples in ${operationId} in ${specPath}:\n`);
|
||||
operation = validator.getOperationById(operationId);
|
||||
xmsExamples = operation[Constants.xmsExamples];
|
||||
if (xmsExamples) {
|
||||
for (let scenario in xmsExamples) {
|
||||
let xmsExample = xmsExamples[scenario];
|
||||
let parameters = exports.sanitizeParameters(xmsExample.parameters);
|
||||
let result = validator.validateRequest(operation, xmsExample.parameters);
|
||||
if (result.validationResult && result.validationResult.errors && result.validationResult.errors.length) {
|
||||
let msg = `Cannot proceed ahead with the live test for operation: ${operationId}. Found validation errors in the request.\n` +
|
||||
`${util.inspect(result.validationResult.errors, {depth: null})}`;
|
||||
throw new Error(msg);
|
||||
}
|
||||
let req = result.request;
|
||||
if (!req) {
|
||||
throw new Error(`Cannot proceed ahead with the live test for operation: ${operationId}. The request object is undefined.`);
|
||||
}
|
||||
if (req.body !== null && req.body !== undefined) {
|
||||
req.body = JSON.stringify(req.body);
|
||||
}
|
||||
msrestazure.loginWithServicePrincipalSecret(clientId, secret, domain, function(err, creds, subscriptions) {
|
||||
if (err) {
|
||||
throw err;
|
||||
}
|
||||
let resourceClient = new ResourceManagementClient(creds, subscriptionId);
|
||||
let client = new msrestazure.AzureServiceClient(creds);
|
||||
exports.createResourceGroup(resourceClient, location, resourceGroupName, function(err, resourceGroup) {
|
||||
if (err) {
|
||||
throw err;
|
||||
}
|
||||
client.sendRequest(req, function(err, result, request, response) {
|
||||
log.info(request);
|
||||
log.info(response);
|
||||
if (err) {
|
||||
throw err;
|
||||
}
|
||||
log.info(result);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
}).catch(function (err) {
|
||||
log.error(err);
|
||||
});
|
||||
};
|
||||
|
||||
exports.createResourceGroup = function createResourceGroup(resourceClient, location, resourceGroupName, callback) {
|
||||
resourceClient.resourceGroups.get(resourceGroupName, function (err, result, request, response) {
|
||||
if (err && err.statusCode === 404) {
|
||||
log.info(`Creating resource group: \'${resourceGroupName}\', if not present.`);
|
||||
let groupParameters = { location: location, tags: { 'live-test': 'live-test'} };
|
||||
return resourceClient.resourceGroups.createOrUpdate(resourceGroupName, groupParameters, callback);
|
||||
} else {
|
||||
return callback(null, result);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
exports.getResourceGroup = function getResourceGroup(resourceClient, resourceGroupName, callback) {
|
||||
log.info(`Searching ResourceGroup: ${resourceGroupName}.`)
|
||||
resourceClient.resourceGroups.get(resourceGroupName, callback);
|
||||
}
|
||||
|
||||
exports.validateEnvironmentVariables = function validateEnvironmentVariables() {
|
||||
var envs = [];
|
||||
if (!process.env['CLIENT_ID']) envs.push('CLIENT_ID');
|
||||
if (!process.env['DOMAIN']) envs.push('DOMAIN');
|
||||
if (!process.env['APPLICATION_SECRET']) envs.push('APPLICATION_SECRET');
|
||||
if (!process.env['AZURE_SUBSCRIPTION_ID']) envs.push('AZURE_SUBSCRIPTION_ID');
|
||||
if (envs.length > 0) {
|
||||
throw new Error(util.format('please set/export the following environment variables: %s', envs.toString()));
|
||||
}
|
||||
};
|
||||
|
||||
exports = module.exports;
|
Загрузка…
Ссылка в новой задаче