Tool available for dogfooding
This commit is contained in:
Родитель
3fd2355d7b
Коммит
401c66b7a2
18
README.md
18
README.md
|
@ -6,11 +6,25 @@ Tools for validating OpenAPI (Swagger) files.
|
|||
|
||||
|
||||
### How to run the tool
|
||||
- After cloning the repo execute following steps from your terminal/cmd prompt
|
||||
|
||||
Clone the repo and then execute following steps from your terminal/cmd prompt
|
||||
```
|
||||
npm install
|
||||
node index.js
|
||||
node validate.js
|
||||
```
|
||||
|
||||
Command usage:
|
||||
```
|
||||
D:\sdk\openapi-validation-tools>node validate.js
|
||||
|
||||
Usage: node validate.js <command> <spec-path> [--json]
|
||||
|
||||
|
||||
Commands:
|
||||
|
||||
- spec <raw-github-url OR local file-path to the swagger spec> [--json] | Description: Performs semantic validation of the spec.
|
||||
|
||||
- example <raw-github-url OR local file-path to the swagger spec> [--json] | Description: Performs validation of x-ms-examples and examples present in the spec.
|
||||
```
|
||||
---
|
||||
This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/). For more information see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments.
|
||||
|
|
|
@ -6,18 +6,25 @@
|
|||
<ProjectGuid>{4b66fcde-3ae4-41ab-9455-1e995c19c78a}</ProjectGuid>
|
||||
<ProjectHome />
|
||||
<ProjectView>ShowAllFiles</ProjectView>
|
||||
<StartupFile>index.js</StartupFile>
|
||||
<StartupFile>validate.js</StartupFile>
|
||||
<WorkingDirectory>.</WorkingDirectory>
|
||||
<OutputPath>.</OutputPath>
|
||||
<ProjectTypeGuids>{3AF33F2E-1136-4D97-BBB7-1795711AC8B8};{349c5851-65df-11da-9384-00065b846f21};{9092AA53-FB77-4645-B42D-1CCCA6BD08BD}</ProjectTypeGuids>
|
||||
<VisualStudioVersion Condition="'$(VisualStudioVersion)' == ''">14.0</VisualStudioVersion>
|
||||
<VSToolsPath Condition="'$(VSToolsPath)' == ''">$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)</VSToolsPath>
|
||||
<ScriptArguments>example .\search\2015-02-28\swagger\searchindex.json --details</ScriptArguments>
|
||||
<StartWebBrowser>True</StartWebBrowser>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)' == 'Debug'" />
|
||||
<PropertyGroup Condition="'$(Configuration)' == 'Release'" />
|
||||
<ItemGroup>
|
||||
<Compile Include="index.js" />
|
||||
<Compile Include="lib\utils.js" />
|
||||
<Compile Include="validate.js" />
|
||||
<Compile Include="lib\specValidatorTemp.js" />
|
||||
<Compile Include="lib\specValidator1 - Copy.js" />
|
||||
<Compile Include="lib\util\constants.js">
|
||||
<SubType>Code</SubType>
|
||||
</Compile>
|
||||
<Compile Include="lib\util\utils.js" />
|
||||
<Content Include="LICENSE.txt" />
|
||||
<Content Include="package.json" />
|
||||
<Content Include="README.md" />
|
||||
|
@ -58,6 +65,7 @@
|
|||
<Folder Include="arm-storage\2016-01-01\examples" />
|
||||
<Folder Include="arm-storage\2016-01-01\swagger" />
|
||||
<Folder Include="lib" />
|
||||
<Folder Include="lib\util\" />
|
||||
<Folder Include="search" />
|
||||
<Folder Include="search\2015-02-28" />
|
||||
<Folder Include="search\2015-02-28\swagger" />
|
||||
|
|
80
index.js
80
index.js
|
@ -1,80 +0,0 @@
|
|||
// 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 glob = require('glob'),
|
||||
path = require('path'),
|
||||
async = require('async'),
|
||||
globPath = path.join(__dirname, '/**/swagger/*.json'),
|
||||
SpecValidator = require('./lib/specValidator'),
|
||||
RefParser = require('json-schema-ref-parser'),
|
||||
swt = require('swagger-tools').specs.v2,
|
||||
finalValidationResult = {};
|
||||
|
||||
var swaggers = glob.sync(globPath).filter(function (entry) {
|
||||
if (entry.match(/.*arm-storage\/2016-01-01\/swagger.*/ig) !== null ||
|
||||
entry.match(/.*arm-search\/2015-08-19\/swagger.*/ig) !== null ||
|
||||
entry.match(/.*arm-mediaservices\/2015-10-01\/swagger.*/ig) !== null) {
|
||||
return entry;
|
||||
}
|
||||
});
|
||||
|
||||
//async.eachSeries(swaggers, function (spec, callback) {
|
||||
// let specValidator = new SpecValidator(spec);
|
||||
// finalValidationResult[spec] = specValidator.specValidationResult;
|
||||
// console.log(`\n> Validating DataModels in ${spec}:\n`);
|
||||
// specValidator.validateDataModels(function (err, result) {
|
||||
// return callback(null);
|
||||
// });
|
||||
//}, function (err) {
|
||||
// if (err) {
|
||||
// console.log(err);
|
||||
// }
|
||||
// console.log('\n> Final Validation Result object:\n')
|
||||
// console.dir(finalValidationResult, { depth: null, colors: true });
|
||||
// console.log('\n> Validated DataModels in all the requested specs.');
|
||||
//});
|
||||
function specValidator(parsedSpecInJson, callback) {
|
||||
swt.validate(parsedSpecInJson, function (err, result) {
|
||||
if (err) {
|
||||
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);
|
||||
}
|
||||
});
|
||||
callback(new Error(util.inspect(result.errors, { depth: null })));
|
||||
} else {
|
||||
callback();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
let mySchema = 'https://raw.githubusercontent.com/Azure/azure-rest-api-specs/master/arm-network/2016-09-01/swagger/network.json';
|
||||
RefParser.bundle(mySchema, function (err, api) {
|
||||
if (err) {
|
||||
console.log(err);
|
||||
}
|
||||
else {
|
||||
//console.log(api);
|
||||
specValidator(api, function (err, result) {
|
||||
if (err) {
|
||||
console.log(err);
|
||||
} else {
|
||||
console.log(result);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
exports = module.exports;
|
|
@ -8,7 +8,7 @@ path = require('path'),
|
|||
util = require('util'),
|
||||
swt = require('swagger-tools').specs.v2,
|
||||
RefParser = require('json-schema-ref-parser'),
|
||||
utils = require('./utils'),
|
||||
utils = require('./util/utils'),
|
||||
cwd = process.cwd();
|
||||
|
||||
const constraints = ['minLength', 'maxLength', 'minimum', 'maximum', 'enum',
|
||||
|
@ -23,11 +23,11 @@ ConstraintValidationError = 'CONSTRAINT_VALIDATION_ERROR', StatuscodeNotInExampl
|
|||
|
||||
class SpecValidator {
|
||||
|
||||
constructor(filePath) {
|
||||
this.filePath = filePath;
|
||||
constructor(specPath) {
|
||||
this.specPath = specPath;
|
||||
this.specInJson = null;
|
||||
this.refSpecInJson = null;
|
||||
this.specValidationResult = {};
|
||||
this.specValidationResult = { operations: {} };
|
||||
}
|
||||
|
||||
unifyXmsPaths() {
|
||||
|
@ -42,80 +42,31 @@ class SpecValidator {
|
|||
}
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
if (options && !options.skipInitialization) {
|
||||
//process.chdir(path.dirname(self.specPath));
|
||||
RefParser.bundle(self.specPath, function(bundleErr, bundleResult){
|
||||
if (bundleErr) {
|
||||
let msg = `Error occurred in parsing the spec "${self.specPath}". \t${bundleErr.message}.`;
|
||||
bundleErr.code = 'PARSE_SPEC_ERROR';
|
||||
bundleErr.message = msg;
|
||||
self.specValidationResult.resolveSpec = {};
|
||||
self.specValidationResult.resolveSpec.error = bundleErr;
|
||||
console.log(`${bundleErr.code} - ${bundleErr.message}`);
|
||||
return callback(bundleErr);
|
||||
}
|
||||
self.specInJson = bundleResult;
|
||||
self.unifyXmsPaths();
|
||||
self.resolveFileReferencesInXmsExamples();
|
||||
process.chdir(path.dirname(self.filePath));
|
||||
RefParser.resolve(self.specInJson, function (err, result) {
|
||||
process.chdir(cwd);
|
||||
//process.chdir(cwd);
|
||||
if (err) {
|
||||
let msg = `Error occurred in resolving the spec "${selg.filePath}". \t${err.message}.`;
|
||||
let msg = `Error occurred in resolving the spec "${self.specPath}". \t${err.message}.`;
|
||||
err.code = 'RESOLVE_SPEC_ERROR';
|
||||
err.message = msg;
|
||||
self.specValidationResult.resolveSpec = {};
|
||||
|
@ -126,10 +77,10 @@ class SpecValidator {
|
|||
self.refSpecInJson = result;
|
||||
return callback(null);
|
||||
});
|
||||
} else {
|
||||
return callback(null);
|
||||
}
|
||||
}
|
||||
});
|
||||
} else {
|
||||
return callback(null);
|
||||
}
|
||||
}
|
||||
|
||||
//wrapper to validateModel of swagger-tools
|
||||
|
@ -348,6 +299,24 @@ class SpecValidator {
|
|||
}
|
||||
return result;
|
||||
}
|
||||
printInnerErrors(tabs, prefix, innerErrors) {
|
||||
console.log(`${tabs}> ${prefix}:`);
|
||||
let t = tabs + ' ';
|
||||
if (innerErrors) {
|
||||
for(let i = 0; i < innerErrors.length; i++) {
|
||||
let err = innerErrors[i];
|
||||
let keys = Object.keys(err);
|
||||
for (let j = 0; j < keys.length; j++) {
|
||||
if (j === 0) {
|
||||
console.log(`${t}${i+1}. ${keys[j]}: ${err[keys[j]]}`);
|
||||
} else {
|
||||
console.log(`${t} ${keys[j]}: ${err[keys[j]]}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
validateDataModels(callback) {
|
||||
let self = this;
|
||||
|
@ -367,14 +336,18 @@ class SpecValidator {
|
|||
let apiPath = apiPaths[i], verb = verbs[j];
|
||||
let operation = self.specInJson.paths[apiPath][verb];
|
||||
let operationId = operation.operationId;
|
||||
console.log(`\t> Operation: ${operationId}`);
|
||||
let operationDisplayed = false;
|
||||
self.specValidationResult.operations[operationId] = {};
|
||||
if (operation[xmsExamples]) {
|
||||
self.specValidationResult.operations[operationId][xmsExamples] = {};
|
||||
self.specValidationResult.operations[operationId][xmsExamples]['scenarios'] = {};
|
||||
let xmsExamplesDisplayed = false;
|
||||
let scenarios = Object.keys(operation[xmsExamples]);
|
||||
for (let k = 0; k < scenarios.length; k++) {
|
||||
let scenarioName = scenarios[k], scenarioData = operation[xmsExamples][scenarioName];
|
||||
let scenarioNameDisplayed = false;
|
||||
//validate parameters
|
||||
console.log(`\t\t> ${xmsExamples}`);
|
||||
console.log(`\t\t\t> Scenario: ${scenarioName}`);
|
||||
self.specValidationResult.operations[operationId][xmsExamples]['scenarios'][scenarioName] = {};
|
||||
self.specValidationResult.operations[operationId][xmsExamples]['scenarios'][scenarioName].parameters = {};
|
||||
for (let l = 0; l < operation.parameters.length; l++) {
|
||||
let parameter = operation.parameters[l];
|
||||
|
@ -392,6 +365,9 @@ class SpecValidator {
|
|||
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;
|
||||
if (!operationDisplayed) { console.log(`\n\t> Operation: ${operationId}`); operationDisplayed = true; }
|
||||
if (!xmsExamplesDisplayed) { console.log(`\n\t\t> ${xmsExamples}`); xmsExamplesDisplayed = true; }
|
||||
if (!scenarioNameDisplayed) { console.log(`\n\t\t\t> Scenario: ${scenarioName}`); scenarioNameDisplayed = true; }
|
||||
console.error(`\t\t\t\t> ${reqError.code}: ${reqError.message}`);
|
||||
continue;
|
||||
}
|
||||
|
@ -405,41 +381,52 @@ class SpecValidator {
|
|||
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;
|
||||
if (!operationDisplayed) { console.log(`\n\t> Operation: ${operationId}`); operationDisplayed = true; }
|
||||
if (!xmsExamplesDisplayed) { console.log(`\n\t\t> ${xmsExamples}`); xmsExamplesDisplayed = true; }
|
||||
if (!scenarioNameDisplayed) { console.log(`\n\t\t\t> Scenario: ${scenarioName}`); scenarioNameDisplayed = true; }
|
||||
console.error(`\t\t\t\t> ${bodyValidationError.code}: ${bodyValidationError.message}`);
|
||||
self.printInnerErrors('\t\t\t\t', 'InnerErrors', bodyValidationError.innerErrors);
|
||||
} 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;
|
||||
if (!operationDisplayed) { console.log(`\n\t> Operation: ${operationId}`); operationDisplayed = true; }
|
||||
if (!xmsExamplesDisplayed) { console.log(`\n\t\t> ${xmsExamples}`); xmsExamplesDisplayed = true; }
|
||||
if (!scenarioNameDisplayed) { console.log(`\n\t\t\t> Scenario: ${scenarioName}`); scenarioNameDisplayed = true; }
|
||||
console.error(`\t\t\t\t> ${e.code}: ${e.message}`);
|
||||
console.log(`\t\t\t\t> ${util.inspect(e, {depth: null})}`);
|
||||
}
|
||||
} 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 || errors.typeError) {
|
||||
let nonBodyError = {code: '', message: ''};
|
||||
if (errors.validationErrors) {
|
||||
nonBodyError.code += ' + ' + errors.typeError.code
|
||||
nonBodyError.innerErrors.push(errors.typeError);
|
||||
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}`);
|
||||
} 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}`);
|
||||
}
|
||||
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;
|
||||
} else {
|
||||
self.specValidationResult.operations[operationId][xmsExamples]['scenarios'][scenarioName].parameters[parameterName].error = errors.typeError;
|
||||
}
|
||||
}
|
||||
if (!operationDisplayed) { console.log(`\n\t> Operation: ${operationId}`); operationDisplayed = true; }
|
||||
if (!xmsExamplesDisplayed) { console.log(`\n\t\t> ${xmsExamples}`); xmsExamplesDisplayed = true; }
|
||||
if (!scenarioNameDisplayed) { console.log(`\n\t\t\t> Scenario: ${scenarioName}`); scenarioNameDisplayed = true; }
|
||||
console.error(`\t\t\t\t> ${nonBodyError.code}: ${nonBodyError.message}`);
|
||||
console.log(`\t\t\t\t\t> ${util.inspect(errors, {depth: null})}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -470,11 +457,14 @@ class SpecValidator {
|
|||
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;
|
||||
if (!operationDisplayed) { console.log(`\n\t> Operation: ${operationId}`); operationDisplayed = true; }
|
||||
if (!xmsExamplesDisplayed) { console.log(`\n\t\t> ${xmsExamples}`); xmsExamplesDisplayed = true; }
|
||||
if (!scenarioNameDisplayed) { console.log(`\n\t\t\t> Scenario: ${scenarioName}`); scenarioNameDisplayed = true; }
|
||||
console.error(`\t\t\t\t> ${responseBodyValidationError.code}: ${responseBodyValidationError.message}`);
|
||||
self.printInnerErrors('\t\t\t\t', 'InnerErrors', responseBodyValidationError.innerErrors);
|
||||
} 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;
|
||||
|
@ -483,7 +473,11 @@ class SpecValidator {
|
|||
`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}`);
|
||||
if (!operationDisplayed) { console.log(`\n\t> Operation: ${operationId}`); operationDisplayed = true; }
|
||||
if (!xmsExamplesDisplayed) { console.log(`\n\t\t> ${xmsExamples}`); xmsExamplesDisplayed = true; }
|
||||
if (!scenarioNameDisplayed) { console.log(`\n\t\t\t> Scenario: ${scenarioName}`); scenarioNameDisplayed = true; }
|
||||
console.error(`\t\t\t\t> ${e.code}: ${e.message}`);
|
||||
console.log(`\t\t\t\t> ${util.inspect(e, {depth: null})}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -493,7 +487,7 @@ class SpecValidator {
|
|||
//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;
|
||||
let exampleInSpecDisplayed = false;
|
||||
if (parameters) {
|
||||
for (let p = 0; p < parameters.length; p++) {
|
||||
let param = parameters[p];
|
||||
|
@ -503,8 +497,9 @@ class SpecValidator {
|
|||
let paramRef = param.schema['$ref'];
|
||||
let dereferencedParam = self.refSpecInJson.get(paramRef);
|
||||
if (dereferencedParam.example) {
|
||||
console.log(`\t\t> ${exampleInSpec}`);
|
||||
exampleDisplay = true;
|
||||
if (!self.specValidationResult.operations[operationId]) {
|
||||
self.specValidationResult.operations[operationId] = {};
|
||||
}
|
||||
self.specValidationResult.operations[operationId][exampleInSpec] = {};
|
||||
self.specValidationResult.operations[operationId][exampleInSpec]['parameters'] = {};
|
||||
self.specValidationResult.operations[operationId][exampleInSpec]['parameters'][param.name] = {};
|
||||
|
@ -517,11 +512,13 @@ class SpecValidator {
|
|||
`${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;
|
||||
if (!operationDisplayed) { console.log(`\n\t> Operation: ${operationId}`); operationDisplayed = true; }
|
||||
if (!exampleInSpecDisplayed) { console.log(`\n\t\t> ${exampleInSpec}`); exampleInSpecDisplayed = true; }
|
||||
console.error(`\t\t\t> ${bodyValidationError.code}: ${bodyValidationError.message}`);
|
||||
self.printInnerErrors('\t\t\t\t', 'InnerErrors', bodyValidationError.innerErrors);
|
||||
} 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;
|
||||
|
@ -530,7 +527,10 @@ class SpecValidator {
|
|||
`${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;
|
||||
if (!operationDisplayed) { console.log(`\n\t> Operation: ${operationId}`); operationDisplayed = true; }
|
||||
if (!exampleInSpecDisplayed) { console.log(`\n\t\t> ${exampleInSpec}`); exampleInSpecDisplayed = true; }
|
||||
console.error(`\t\t\t> ${e.code}: ${e.message}`);
|
||||
console.log(`\t\t\t> ${util.inspect(e, {depth: null})}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -543,12 +543,10 @@ class SpecValidator {
|
|||
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] = {};
|
||||
}
|
||||
if (!self.specValidationResult.operations[operationId]) {
|
||||
self.specValidationResult.operations[operationId] = {};
|
||||
}
|
||||
if (!self.specValidationResult.operations[operationId][exampleInSpec]) {
|
||||
self.specValidationResult.operations[operationId][exampleInSpec] = {};
|
||||
}
|
||||
let responseExamples = responses[statusCodes[q]].examples;
|
||||
|
@ -570,18 +568,23 @@ class SpecValidator {
|
|||
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;
|
||||
if (!operationDisplayed) { console.log(`\n\t> Operation: ${operationId}`); operationDisplayed = true; }
|
||||
if (!exampleInSpecDisplayed) { console.log(`\n\t\t> ${exampleInSpec}`); exampleInSpecDisplayed = true; }
|
||||
console.log(`\t\t\t> ${responseBodyValidationError.code}: ${responseBodyValidationError.message}`);
|
||||
self.printInnerErrors('\t\t\t\t', 'InnerErrors', responseBodyValidationError.innerErrors);
|
||||
} 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;
|
||||
if (!operationDisplayed) { console.log(`\n\t> Operation: ${operationId}`); operationDisplayed = true; }
|
||||
if (!exampleInSpecDisplayed) { console.log(`\n\t\t> ${exampleInSpec}`); exampleInSpecDisplayed = true; }
|
||||
console.log(`\t\t\t> ${e.code}: ${e.message}`);
|
||||
console.log(`\t\t\t> ${util.inspect(e, {depth: null} )}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -606,29 +609,33 @@ class SpecValidator {
|
|||
}
|
||||
swt.validate(self.specInJson, function (err, result) {
|
||||
if (err) {
|
||||
let msg = `An Internal Error occurred in validating the spec "${self.specPath}". \t${err.message}.`;
|
||||
err.code = InternalError;
|
||||
err.message = msg;
|
||||
self.specValidationResult.validateSpec = {};
|
||||
self.specValidationResult.validateSpec.error = err;
|
||||
console.log(`${err}`, {depth: null, colors:true });
|
||||
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);
|
||||
if (result) {
|
||||
if (result.errors && result.errors.length > 0) {
|
||||
console.log('');
|
||||
console.log('Errors');
|
||||
console.log('------');
|
||||
console.dir(result.errors, { depth: null, colors: true });
|
||||
}
|
||||
if (result.warnings && result.warnings.length > 0) {
|
||||
console.log('');
|
||||
console.log('Warnings');
|
||||
console.log('--------');
|
||||
console.dir(result.warnings, { depth: null, colors: true });
|
||||
}
|
||||
}
|
||||
return callback(null, result);
|
||||
});
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
module.exports = SpecValidator;
|
|
@ -0,0 +1,25 @@
|
|||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
|
||||
'use strict';
|
||||
|
||||
exports.Constants = {
|
||||
constraints: ['minLength', 'maxLength', 'minimum', 'maximum', 'enum', 'maxItems', 'minItems', 'uniqueItems', 'multipleOf', 'pattern'],
|
||||
xmsExamples: 'x-ms-examples',
|
||||
exampleInSpec: 'example-in-spec',
|
||||
BodyParameterValid: 'BODY_PARAMAETER_VALID',
|
||||
ErrorCodes: {
|
||||
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'
|
||||
}
|
||||
};
|
||||
|
||||
exports : module.exports;
|
11
package.json
11
package.json
|
@ -1,28 +1,25 @@
|
|||
{
|
||||
"name": "openapi-validator-tools",
|
||||
"private": true,
|
||||
"version": "0.1.0",
|
||||
"author": {
|
||||
"name": "Microsoft Corporation",
|
||||
"email": "azsdkteam@microsoft.com",
|
||||
"url": "https://github.com/azure/openapi-validator-tools"
|
||||
},
|
||||
"version": "0.1.0",
|
||||
"description": "Validate Azure REST API Specifications",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"async": "^2.1.2",
|
||||
"glob": "^5.0.14",
|
||||
"json-schema-ref-parser": "^3.1.2",
|
||||
"lodash": "^3.10.1",
|
||||
"request": "^2.61.0",
|
||||
"swagger-tools": "^0.10.1",
|
||||
"z-schema": "^3.16.1",
|
||||
"moment": "^2.14.1"
|
||||
"moment": "^2.14.1",
|
||||
"swagger-tools": "^0.10.1"
|
||||
},
|
||||
"homepage": "https://github.com/azure/openapi-validator-tools",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git@github.com:azure/openapi-validator-tools.git"
|
||||
"url": "https://github.com/azure/openapi-validator-tools.git"
|
||||
},
|
||||
"bugs": {
|
||||
"url": "http://github.com/azure/openapi-validator-tools/issues"
|
||||
|
|
|
@ -1,289 +0,0 @@
|
|||
{
|
||||
"swagger": "2.0",
|
||||
"info": {
|
||||
"title": "SearchIndexClient",
|
||||
"description": "Client that can be used to query an Azure Search index and upload, merge, or delete documents.",
|
||||
"version": "2015-02-28-Preview",
|
||||
"x-ms-code-generation-settings": {
|
||||
"useDateTimeOffset": true,
|
||||
"syncMethods": "None"
|
||||
}
|
||||
},
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"paths": {
|
||||
"/docs/$count": {
|
||||
"get": {
|
||||
"tags": [
|
||||
"DocumentsProxy"
|
||||
],
|
||||
"operationId": "DocumentsProxy_Count",
|
||||
"description": "Queries the number of documents in the Azure Search index.",
|
||||
"externalDocs": {
|
||||
"url": "https://msdn.microsoft.com/library/azure/dn798924.aspx"
|
||||
},
|
||||
"parameters": [
|
||||
{
|
||||
"name": "client-request-id",
|
||||
"in": "header",
|
||||
"required": false,
|
||||
"type": "string",
|
||||
"format": "uuid",
|
||||
"description": "The tracking ID sent with the request to help with debugging.",
|
||||
"x-ms-client-request-id": true,
|
||||
"x-ms-parameter-grouping": { "name": "search-request-options" }
|
||||
},
|
||||
{
|
||||
"$ref": "#/parameters/ApiVersionParameter"
|
||||
}
|
||||
],
|
||||
"x-ms-request-id": "request-id",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "",
|
||||
"schema": {
|
||||
"type": "integer",
|
||||
"format": "int64"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"definitions": {
|
||||
"IndexingResult": {
|
||||
"properties": {
|
||||
"key": {
|
||||
"type": "string",
|
||||
"readOnly": true,
|
||||
"description": "The key of a document that was in the indexing request."
|
||||
},
|
||||
"errorMessage": {
|
||||
"type": "string",
|
||||
"readOnly": true,
|
||||
"description": "The error message explaining why the indexing operation failed for the document identified by the key; null if indexing succeeded."
|
||||
},
|
||||
"status": {
|
||||
"x-ms-client-name": "Succeeded",
|
||||
"type": "boolean",
|
||||
"readOnly": true,
|
||||
"description": "A value indicating whether the indexing operation succeeded for the document identified by the key."
|
||||
},
|
||||
"statusCode": {
|
||||
"type": "integer",
|
||||
"format": "int32",
|
||||
"readOnly": true,
|
||||
"description": "The status code of the indexing operation. Possible values include: 200 for a successful update or delete, 201 for successful document creation, 400 for a malformed input document, 404 for document not found, 409 for a version conflict, 422 when the index is temporarily unavailable, or 503 for when the service is too busy."
|
||||
}
|
||||
},
|
||||
"description": "Status of an indexing operation for a single document.",
|
||||
"x-ms-external": true
|
||||
},
|
||||
"DocumentIndexResult": {
|
||||
"properties": {
|
||||
"value": {
|
||||
"x-ms-client-name": "Results",
|
||||
"type": "array",
|
||||
"readOnly": true,
|
||||
"items": {
|
||||
"$ref": "#/definitions/IndexingResult"
|
||||
},
|
||||
"description": "The list of status information for each document in the indexing request."
|
||||
}
|
||||
},
|
||||
"description": "Response containing the status of operations for all documents in the indexing request."
|
||||
},
|
||||
"IndexActionType": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"upload",
|
||||
"merge",
|
||||
"mergeOrUpload",
|
||||
"delete"
|
||||
],
|
||||
"x-ms-enum": { "name": "IndexActionType" },
|
||||
"description": "Specifies the operation to perform on a document in an indexing batch."
|
||||
},
|
||||
"SearchMode": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"any",
|
||||
"all"
|
||||
],
|
||||
"x-ms-enum": { "name": "SearchMode" },
|
||||
"description": "Specifies whether any or all of the search terms must be matched in order to count the document as a match."
|
||||
},
|
||||
"QueryType": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"simple",
|
||||
"full"
|
||||
],
|
||||
"x-ms-enum": { "name": "QueryType" },
|
||||
"description": "Specifies the syntax of the search query. The default is 'simple'. Use 'full' if your query uses the Lucene query syntax."
|
||||
},
|
||||
"SearchParametersPayload": {
|
||||
"properties": {
|
||||
"count": {
|
||||
"type": "boolean",
|
||||
"description": "A value that specifies whether to fetch the total count of results. Default is false. Setting this value to true may have a performance impact. Note that the count returned is an approximation."
|
||||
},
|
||||
"facets": {
|
||||
"externalDocs": {
|
||||
"url": "https://msdn.microsoft.com/library/azure/dn798927.aspx"
|
||||
},
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"description": "The list of facet expressions to apply to the search query. Each facet expression contains a field name, optionally followed by a comma-separated list of name:value pairs."
|
||||
},
|
||||
"filter": {
|
||||
"externalDocs": {
|
||||
"url": "https://msdn.microsoft.com/library/azure/dn798921.aspx"
|
||||
},
|
||||
"type": "string",
|
||||
"description": "The OData $filter expression to apply to the search query."
|
||||
},
|
||||
"highlight": {
|
||||
"type": "string",
|
||||
"description": "The comma-separated list of field names to use for hit highlights. Only searchable fields can be used for hit highlighting."
|
||||
},
|
||||
"highlightPostTag": {
|
||||
"type": "string",
|
||||
"description": "A string tag that is appended to hit highlights. Must be set with HighlightPreTag. Default is </em>."
|
||||
},
|
||||
"highlightPreTag": {
|
||||
"type": "string",
|
||||
"description": "A string tag that is prepended to hit highlights. Must be set with HighlightPostTag. Default is <em>."
|
||||
},
|
||||
"minimumCoverage": {
|
||||
"type": "number",
|
||||
"format": "double",
|
||||
"description": "A number between 0 and 100 indicating the percentage of the index that must be covered by a search query in order for the query to be reported as a success. This parameter can be useful for ensuring search availability even for services with only one replica. The default is 100."
|
||||
},
|
||||
"orderby": {
|
||||
"x-ms-client-name": "OrderBy",
|
||||
"type": "string",
|
||||
"description": "The comma-separated list of OData $orderby expressions by which to sort the results. Each expression can be either a field name or a call to the geo.distance() function. Each expression can be followed by asc to indicate ascending, and desc to indicate descending. The default is ascending order. Ties will be broken by the match scores of documents. If no OrderBy is specified, the default sort order is descending by document match score. There can be at most 32 Orderby clauses."
|
||||
},
|
||||
"queryType": {
|
||||
"$ref": "#/definitions/QueryType",
|
||||
"description": "Gets or sets a value that specifies the syntax of the search query. The default is 'simple'. Use 'full' if your query uses the Lucene query syntax."
|
||||
},
|
||||
"scoringParameters": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"description": "The list of parameter values to be used in scoring functions (for example, referencePointParameter) using the format name:value. For example, if the scoring profile defines a function with a parameter called 'mylocation' the parameter string would be \"mylocation:-122.2,44.8\"(without the quotes)."
|
||||
},
|
||||
"scoringProfile": {
|
||||
"type": "string",
|
||||
"description": "The name of a scoring profile to evaluate match scores for matching documents in order to sort the results."
|
||||
},
|
||||
"search": {
|
||||
"externalDocs": {
|
||||
"url": "https://msdn.microsoft.com/library/azure/dn798920.aspx"
|
||||
},
|
||||
"type": "string",
|
||||
"description": "A full-text search query expression; Use null or \"*\" to match all documents."
|
||||
},
|
||||
"searchFields": {
|
||||
"type": "string",
|
||||
"description": "The comma-separated list of field names to include in the full-text search."
|
||||
},
|
||||
"searchMode": {
|
||||
"$ref": "#/definitions/SearchMode",
|
||||
"description": "A value that specifies whether any or all of the search terms must be matched in order to count the document as a match."
|
||||
},
|
||||
"select": {
|
||||
"type": "string",
|
||||
"description": "The comma-separated list of fields to retrieve. If unspecified, all fields marked as retrievable in the schema are included."
|
||||
},
|
||||
"skip": {
|
||||
"type": "integer",
|
||||
"format": "int32",
|
||||
"description": "The number of search results to skip. This value cannot be greater than 100,000. If you need to scan documents in sequence, but cannot use Skip due to this limitation, consider using OrderBy on a totally-ordered key and Filter with a range query instead."
|
||||
},
|
||||
"top": {
|
||||
"externalDocs": {
|
||||
"url": "https://msdn.microsoft.com/en-us/library/azure/dn798927.aspx"
|
||||
},
|
||||
"type": "integer",
|
||||
"format": "int32",
|
||||
"description": "The number of search results to retrieve. This can be used in conjunction with Skip to implement client-side paging of search results. If results are truncated due to server-side paging, the response will include a continuation token that can be passed to ContinueSearch to retrieve the next page of results. See DocumentSearchResponse.ContinuationToken for more information."
|
||||
}
|
||||
},
|
||||
"description": "Parameters for filtering, sorting, faceting, paging, and other search query behaviors."
|
||||
},
|
||||
"SuggestParametersPayload": {
|
||||
"properties": {
|
||||
"filter": {
|
||||
"externalDocs": {
|
||||
"url": "https://msdn.microsoft.com/library/azure/dn798921.aspx"
|
||||
},
|
||||
"type": "string",
|
||||
"description": "The OData $filter expression to apply to the suggestions query."
|
||||
},
|
||||
"fuzzy": {
|
||||
"type": "boolean",
|
||||
"description": "A value indicating whether to use fuzzy matching for the suggestion query. Default is false. when set to true, the query will find suggestions even if there's a substituted or missing character in the search text. While this provides a better experience in some scenarios it comes at a performance cost as fuzzy suggestion searches are slower and consume more resources."
|
||||
},
|
||||
"highlightPostTag": {
|
||||
"type": "string",
|
||||
"description": "A string tag that is appended to hit highlights. Must be set with HighlightPreTag. If omitted, hit highlighting of suggestions is disabled."
|
||||
},
|
||||
"highlightPreTag": {
|
||||
"type": "string",
|
||||
"description": "A string tag that is prepended to hit highlights. Must be set with HighlightPostTag. If omitted, hit highlighting of suggestions is disabled."
|
||||
},
|
||||
"minimumCoverage": {
|
||||
"type": "number",
|
||||
"format": "double",
|
||||
"description": "A number between 0 and 100 indicating the percentage of the index that must be covered by a suggestion query in order for the query to be reported as a success. This parameter can be useful for ensuring search availability even for services with only one replica. The default is 80."
|
||||
},
|
||||
"orderby": {
|
||||
"x-ms-client-name": "OrderBy",
|
||||
"type": "string",
|
||||
"description": "The comma-separated list of OData $orderby expressions by which to sort the results. Each expression can be either a field name or a call to the geo.distance() function. Each expression can be followed by asc to indicate ascending, and desc to indicate descending. The default is ascending order. Ties will be broken by the match scores of documents. If no OrderBy is specified, the default sort order is descending by document match score. There can be at most 32 Orderby clauses."
|
||||
},
|
||||
"search": {
|
||||
"type": "string",
|
||||
"description": "The search text on which to base suggestions."
|
||||
},
|
||||
"searchFields": {
|
||||
"type": "string",
|
||||
"description": "The comma-separated list of field names to consider when querying for suggestions."
|
||||
},
|
||||
"select": {
|
||||
"type": "string",
|
||||
"description": "The comma-separated list of fields to retrieve. If unspecified, all fields marked as retrievable in the schema are included."
|
||||
},
|
||||
"suggesterName": {
|
||||
"type": "string",
|
||||
"description": "The name of the suggester as specified in the suggesters collection that's part of the index definition."
|
||||
},
|
||||
"top": {
|
||||
"type": "integer",
|
||||
"format": "int32",
|
||||
"description": "The number of suggestions to retrieve. This must be a value between 1 and 100. The default is to 5."
|
||||
}
|
||||
},
|
||||
"description": "Parameters for filtering, sorting, fuzzy matching, and other suggestions query behaviors."
|
||||
}
|
||||
},
|
||||
"parameters": {
|
||||
"ApiVersionParameter": {
|
||||
"name": "api-version",
|
||||
"in": "query",
|
||||
"required": true,
|
||||
"type": "string",
|
||||
"description": "Client Api Version."
|
||||
}
|
||||
}
|
||||
}
|
Разница между файлами не показана из-за своего большого размера
Загрузить разницу
|
@ -1,269 +0,0 @@
|
|||
{
|
||||
"swagger": "2.0",
|
||||
"info": {
|
||||
"title": "SearchIndexClient",
|
||||
"description": "Client that can be used to query an Azure Search index and upload, merge, or delete documents.",
|
||||
"version": "2015-02-28"
|
||||
},
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"paths": {
|
||||
"/docs/$count": {
|
||||
"get": {
|
||||
"tags": [
|
||||
"Documents"
|
||||
],
|
||||
"operationId": "Documents_Count",
|
||||
"description": "Queries the number of documents in the Azure Search index.",
|
||||
"externalDocs": {
|
||||
"url": "https://msdn.microsoft.com/library/azure/dn798924.aspx"
|
||||
},
|
||||
"parameters": [
|
||||
{
|
||||
"name": "client-request-id",
|
||||
"in": "header",
|
||||
"required": false,
|
||||
"type": "string",
|
||||
"format": "uuid",
|
||||
"description": "Tracking ID sent with the request to help with debugging.",
|
||||
"x-ms-client-request-id": true,
|
||||
"x-ms-parameter-grouping": { "name": "search-request-options" }
|
||||
},
|
||||
{
|
||||
"$ref": "#/parameters/ApiVersionParameter"
|
||||
}
|
||||
],
|
||||
"x-ms-request-id": "request-id",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "",
|
||||
"schema": {
|
||||
"type": "integer",
|
||||
"format": "int64"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"definitions": {
|
||||
"IndexingResult": {
|
||||
"properties": {
|
||||
"key": {
|
||||
"type": "string",
|
||||
"readOnly": true,
|
||||
"description": "Gets the key of a document that was in the indexing request."
|
||||
},
|
||||
"errorMessage": {
|
||||
"type": "string",
|
||||
"readOnly": true,
|
||||
"description": "Gets the error message explaining why the indexing operation failed for the document identified by Key; null if Succeeded is true."
|
||||
}
|
||||
},
|
||||
"description": "Status of an indexing operation for a single document.",
|
||||
"x-ms-external": true
|
||||
},
|
||||
"DocumentIndexResult": {
|
||||
"properties": {
|
||||
"value": {
|
||||
"type": "array",
|
||||
"readOnly": true,
|
||||
"items": {
|
||||
"$ref": "#/definitions/IndexingResult"
|
||||
},
|
||||
"description": "Gets the list of status information for each document in the indexing request."
|
||||
}
|
||||
},
|
||||
"description": "Response containing the status of operations for all documents in the indexing request.",
|
||||
"x-ms-external": true
|
||||
},
|
||||
"IndexActionType": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"upload",
|
||||
"merge",
|
||||
"mergeOrUpload",
|
||||
"delete"
|
||||
],
|
||||
"x-ms-enum": { "name": "IndexActionType" },
|
||||
"description": "Specifies the operation to perform on a document in an indexing batch."
|
||||
},
|
||||
"SearchMode": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"any",
|
||||
"all"
|
||||
],
|
||||
"x-ms-enum": { "name": "SearchMode" },
|
||||
"description": "Specifies whether any or all of the search terms must be matched in order to count the document as a match."
|
||||
},
|
||||
"QueryType": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"simple",
|
||||
"full"
|
||||
],
|
||||
"x-ms-enum": { "name": "QueryType" },
|
||||
"description": "Specifies the syntax of the search query. The default is 'simple'. Use 'full' if your query uses the Lucene query syntax."
|
||||
},
|
||||
"SearchParameters": {
|
||||
"properties": {
|
||||
"filter": {
|
||||
"externalDocs": {
|
||||
"url": "https://msdn.microsoft.com/library/azure/dn798921.aspx"
|
||||
},
|
||||
"type": "string",
|
||||
"description": "Gets or sets the OData $filter expression to apply to the search query."
|
||||
},
|
||||
"highlightFields": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"description": "Gets or sets the list of field names to use for hit highlights. Only searchable fields can be used for hit highlighting."
|
||||
},
|
||||
"highlightPostTag": {
|
||||
"type": "string",
|
||||
"description": "Gets or sets a string tag that is appended to hit highlights. Must be set with HighlightPreTag. Default is </em>."
|
||||
},
|
||||
"highlightPreTag": {
|
||||
"type": "string",
|
||||
"description": "Gets or sets a string tag that is prepended to hit highlights. Must be set with HighlightPostTag. Default is <em>."
|
||||
},
|
||||
"includeTotalResultCount": {
|
||||
"type": "boolean",
|
||||
"description": "Gets or sets a value that specifies whether to fetch the total count of results. Default is false. Setting this value to true may have a performance impact. Note that the count returned is an approximation."
|
||||
},
|
||||
"minimumCoverage": {
|
||||
"type": "number",
|
||||
"format": "double",
|
||||
"description": "Gets or sets a number between 0 and 100 indicating the percentage of the index that must be covered by a search query in order for the query to be reported as a success. This parameter can be useful for ensuring search availability even for services with only one replica. The default is 100."
|
||||
},
|
||||
"orderBy": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"description": "Gets or sets the list of OData $orderby expressions by which to sort the results. Each expression can be either a field name or a call to the geo.distance() function. Each expression can be followed by asc to indicate ascending, and desc to indicate descending. The default is ascending order. Ties will be broken by the match scores of documents. If no OrderBy is specified, the default sort order is descending by document match score. There can be at most 32 Orderby clauses."
|
||||
},
|
||||
"queryType": {
|
||||
"$ref": "#/definitions/QueryType",
|
||||
"description": "Gets or sets a value that specifies the syntax of the search query. The default is 'simple'. Use 'full' if your query uses the Lucene query syntax."
|
||||
},
|
||||
"scoringParameters": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"description": "Gets or sets the list of parameter values to be used in scoring functions (for example, referencePointParameter) using the format name:value. For example, if the scoring profile defines a function with a parameter called 'mylocation' the parameter string would be \"mylocation:-122.2,44.8\"(without the quotes)."
|
||||
},
|
||||
"scoringProfile": {
|
||||
"type": "string",
|
||||
"description": "Gets or sets the name of a scoring profile to evaluate match scores for matching documents in order to sort the results."
|
||||
},
|
||||
"searchFields": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"description": "Gets or sets the list of field names to include in the full-text search."
|
||||
},
|
||||
"searchMode": {
|
||||
"$ref": "#/definitions/SearchMode",
|
||||
"description": "Gets or sets a value that specifies whether any or all of the search terms must be matched in order to count the document as a match."
|
||||
},
|
||||
"select": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"description": "Gets or sets the list of fields to retrieve. If unspecified, all fields marked as retrievable in the schema are included."
|
||||
},
|
||||
"skip": {
|
||||
"type": "integer",
|
||||
"format": "int32",
|
||||
"description": "Gets or sets the number of search results to skip. This value cannot be greater than 100,000. If you need to scan documents in sequence, but cannot use Skip due to this limitation, consider using OrderBy on a totally-ordered key and Filter with a range query instead."
|
||||
},
|
||||
"top": {
|
||||
"externalDocs": {
|
||||
"url": "https://msdn.microsoft.com/en-us/library/azure/dn798927.aspx"
|
||||
},
|
||||
"type": "integer",
|
||||
"format": "int32",
|
||||
"description": "Gets or sets the number of search results to retrieve. This can be used in conjunction with Skip to implement client-side paging of search results. If results are truncated due to server-side paging, the response will include a continuation token that can be passed to ContinueSearch to retrieve the next page of results. See DocumentSearchResponse.ContinuationToken for more information."
|
||||
}
|
||||
},
|
||||
"description": "Parameters for filtering, sorting, faceting, paging, and other search query behaviors.",
|
||||
"x-ms-external": true
|
||||
},
|
||||
"SuggestParameters": {
|
||||
"properties": {
|
||||
"filter": {
|
||||
"externalDocs": {
|
||||
"url": "https://msdn.microsoft.com/library/azure/dn798921.aspx"
|
||||
},
|
||||
"type": "string",
|
||||
"description": "Gets or sets the OData $filter expression to apply to the suggestions query."
|
||||
},
|
||||
"highlightPostTag": {
|
||||
"type": "string",
|
||||
"description": "Gets or sets a string tag that is appended to hit highlights. Must be set with HighlightPreTag. If omitted, hit highlighting of suggestions is disabled."
|
||||
},
|
||||
"highlightPreTag": {
|
||||
"type": "string",
|
||||
"description": "Gets or sets a string tag that is prepended to hit highlights. Must be set with HighlightPostTag. If omitted, hit highlighting of suggestions is disabled."
|
||||
},
|
||||
"minimumCoverage": {
|
||||
"type": "number",
|
||||
"format": "double",
|
||||
"description": "Gets or sets a number between 0 and 100 indicating the percentage of the index that must be covered by a suggestion query in order for the query to be reported as a success. This parameter can be useful for ensuring search availability even for services with only one replica. The default is 80."
|
||||
},
|
||||
"orderBy": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"description": "Gets or sets the list of OData $orderby expressions by which to sort the results. Each expression can be either a field name or a call to the geo.distance() function. Each expression can be followed by asc to indicate ascending, and desc to indicate descending. The default is ascending order. Ties will be broken by the match scores of documents. If no OrderBy is specified, the default sort order is descending by document match score. There can be at most 32 Orderby clauses."
|
||||
},
|
||||
"searchFields": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"description": "Gets or sets the list of field names to consider when querying for suggestions."
|
||||
},
|
||||
"select": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"description": "Gets or sets the list of fields to retrieve. If unspecified, all fields marked as retrievable in the schema are included."
|
||||
},
|
||||
"top": {
|
||||
"type": "integer",
|
||||
"format": "int32",
|
||||
"description": "Gets or sets the number of suggestions to retrieve. This must be a value between 1 and 100. The default is to 5."
|
||||
},
|
||||
"useFuzzyMatching": {
|
||||
"type": "boolean",
|
||||
"description": "Gets or sets a value indicating whether to use fuzzy matching for the suggestion query. Default is false. when set to true, the query will find suggestions even if there's a substituted or missing character in the search text. While this provides a better experience in some scenarios it comes at a performance cost as fuzzy suggestion searches are slower and consume more resources."
|
||||
}
|
||||
},
|
||||
"description": "Parameters for filtering, sorting, fuzzy matching, and other suggestions query behaviors.",
|
||||
"x-ms-external": true
|
||||
}
|
||||
},
|
||||
"parameters": {
|
||||
"ApiVersionParameter": {
|
||||
"name": "api-version",
|
||||
"in": "query",
|
||||
"required": true,
|
||||
"type": "string",
|
||||
"description": "Client Api Version."
|
||||
}
|
||||
}
|
||||
}
|
Разница между файлами не показана из-за своего большого размера
Загрузить разницу
|
@ -0,0 +1,63 @@
|
|||
// 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 glob = require('glob'),
|
||||
path = require('path'),
|
||||
SpecValidator = require('./lib/specValidator'),
|
||||
RefParser = require('json-schema-ref-parser'),
|
||||
swt = require('swagger-tools').specs.v2,
|
||||
finalValidationResult = {};
|
||||
|
||||
var cmd = process.argv[2];
|
||||
exports.printUsage = function printUsage() {
|
||||
console.log('');
|
||||
console.log('Usage: node validate.js <command> <spec-path> [--json]\n\n');
|
||||
console.log('Commands:\n');
|
||||
console.log(' - spec <raw-github-url OR local file-path to the swagger spec> [--json] | Description: Performs semantic validation of the spec.\n')
|
||||
console.log(' - example <raw-github-url OR local file-path to the swagger spec> [--json] | Description: Performs validation of x-ms-examples and examples present in the spec.\n')
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
if (cmd === '-h' || cmd === '--help' || cmd === 'help') {
|
||||
exports.printUsage();
|
||||
}
|
||||
|
||||
var specPath = process.argv[3];
|
||||
var jsonOutput = process.argv[4];
|
||||
if (cmd !== 'spec' && cmd !== 'example') {
|
||||
if (cmd) console.error(`${cmd} is not a valid command.`)
|
||||
exports.printUsage();
|
||||
}
|
||||
if (!specPath) {
|
||||
console.error(`<spec-path> (raw-github url or a local file path to the swagger spec) is required.`);
|
||||
exports.printUsage();
|
||||
}
|
||||
|
||||
function printResult(validator) {
|
||||
if (jsonOutput && jsonOutput === '--json') {
|
||||
console.log('\n> Final Validation Result object:\n')
|
||||
console.dir(finalValidationResult, { depth: null, colors: true });
|
||||
}
|
||||
console.log('\n> Validation Complete.');
|
||||
}
|
||||
|
||||
var validator;
|
||||
if (cmd === 'example') {
|
||||
console.log(`\n> Validating "examples" and "x-ms-examples" in ${specPath}:\n`);
|
||||
validator = new SpecValidator(specPath);
|
||||
finalValidationResult[specPath] = validator.specValidationResult;
|
||||
validator.validateDataModels(function (err, result) {
|
||||
return printResult(validator);
|
||||
});
|
||||
} else if (cmd === 'spec') {
|
||||
console.log(`\n> Semantically validating ${specPath}:\n`);
|
||||
validator = new SpecValidator(specPath);
|
||||
finalValidationResult[specPath] = validator.specValidationResult;
|
||||
validator.validateSpec(function (err, result) {
|
||||
return printResult(validator);
|
||||
});
|
||||
}
|
||||
|
||||
exports = module.exports;
|
Загрузка…
Ссылка в новой задаче