зеркало из https://github.com/Azure/sway.git
Added back the semantic validation various path/path parameter checks
* All path parameter declarations need matching path parameter definitions * All path parameter definitions need matching path parameter declarations * All path strings must be equivalently different
This commit is contained in:
Родитель
bc4615e529
Коммит
9dd52d0277
Различия файлов скрыты, потому что одна или несколько строк слишком длинны
|
@ -168,7 +168,7 @@ module.exports={
|
|||
"use strict";module.exports.supportedHttpMethods=["get","put","post","delete","options","head","patch"];
|
||||
|
||||
},{}],8:[function(require,module,exports){
|
||||
"use strict";function getParameterSchema(e){var r;return _.isUndefined(e.schema)?(r={},_.each(parameterSchemaProperties,function(t){_.isUndefined(e[t])||(r[t]=e[t])})):r=e.schema,r}var _=require("lodash-compat"),JsonRefs=require("json-refs"),formatGenerators=require("./format-generators"),helpers=require("../../helpers"),types=require("../../types"),validators=require("./validators"),vHelpers=require("./helpers"),docsUrl="https://github.com/swagger-api/swagger-spec/blob/master/versions/2.0.md",mocker=helpers.createJSONSchemaMocker({formatGenerators:formatGenerators}),parameterSchemaProperties=["default","description","enum","exclusiveMaximum","exclusiveMinimum","format","items","maxItems","maxLength","maximum","minItems","minLength","minimum","multipleOf","pattern","type","uniqueItems"],version="2.0";module.exports.documentation=docsUrl,module.exports.supportedHttpMethods=vHelpers.supportedHttpMethods,module.exports.version=version,module.exports.canProcess=function(e){return e.swagger===version},module.exports.createSwaggerApi=function(e,r){return new Promise(function(t,o){JsonRefs.resolveRefs(e,r.loaderOptions||{},function(n,a,s){var i;n?o(n):(i=new types.SwaggerApi(module.exports,e,a,s,r),t(i))})})},module.exports.getJSONSchemaValidator=function(){return validators.jsonSchemaValidator},module.exports.getOperations=function(e){var r=[];return _.forEach(e.resolved.paths,function(t,o){var n=["paths",o],a=_.reduce(t.parameters||{},function(e,r,t){return e[r.name+":"+r["in"]]={path:n.concat(["parameters",t.toString()]),definition:r},e},{});_.forEach(t,function(t,s){if(-1!==_.indexOf(vHelpers.supportedHttpMethods,s)){var i=_.cloneDeep(t),m={},p=n.concat(s);_.forEach(a,function(e,r){m[r]=e}),_.forEach(t.parameters,function(e,r){m[e.name+":"+e["in"]]={path:p.concat(["parameters",r.toString()]),definition:e}}),i.parameters=_.map(_.values(m),function(e){return e.definition.$$$ptr$$$=JsonRefs.pathToPointer(e.path),e.definition}),_.isUndefined(i.security)&&(i.security=e.resolved.security),r.push(new types.Operation(e,o,s,JsonRefs.pathToPointer(p),i))}})}),r},module.exports.getOperationParameters=function(e){return _.map(e.parameters,function(r){var t=r.$$$ptr$$$;return delete r.$$$ptr$$$,new types.Parameter(e,t,r,getParameterSchema(r))})},module.exports.getSample=function(e){return mocker(e)},module.exports.getSemanticValidators=function(){return validators.semanticValidators};
|
||||
"use strict";function getParameterSchema(e){var r;return _.isUndefined(e.schema)?(r={},_.each(parameterSchemaProperties,function(t){_.isUndefined(e[t])||(r[t]=e[t])})):r=e.schema,r}var _=require("lodash-compat"),JsonRefs=require("json-refs"),formatGenerators=require("./format-generators"),helpers=require("../../helpers"),types=require("../../types"),validators=require("./validators"),vHelpers=require("./helpers"),docsUrl="https://github.com/swagger-api/swagger-spec/blob/master/versions/2.0.md",mocker=helpers.createJSONSchemaMocker({formatGenerators:formatGenerators}),parameterSchemaProperties=["default","description","enum","exclusiveMaximum","exclusiveMinimum","format","items","maxItems","maxLength","maximum","minItems","minLength","minimum","multipleOf","pattern","type","uniqueItems"],version="2.0";module.exports.documentation=docsUrl,module.exports.supportedHttpMethods=vHelpers.supportedHttpMethods,module.exports.version=version,module.exports.canProcess=function(e){return e.swagger===version},module.exports.createSwaggerApi=function(e,r){return new Promise(function(t,o){JsonRefs.resolveRefs(e,r.loaderOptions||{},function(n,a,s){var i;n?o(n):(i=new types.SwaggerApi(module.exports,e,a,s,r),t(i))})})},module.exports.getJSONSchemaValidator=function(){return validators.jsonSchemaValidator},module.exports.getOperations=function(e){var r=[];return _.forEach(e.resolved.paths,function(t,o){var n=["paths",o],a=_.reduce(t.parameters||[],function(e,r,t){return e[r.name+":"+r["in"]]={path:n.concat(["parameters",t.toString()]),definition:r},e},{});_.forEach(t,function(t,s){if(-1!==_.indexOf(vHelpers.supportedHttpMethods,s)){var i=_.cloneDeep(t),m={},p=n.concat(s);_.forEach(a,function(e,r){m[r]=e}),_.forEach(t.parameters,function(e,r){m[e.name+":"+e["in"]]={path:p.concat(["parameters",r.toString()]),definition:e}}),i.parameters=_.map(_.values(m),function(e){return e.definition.$$$ptr$$$=JsonRefs.pathToPointer(e.path),e.definition}),_.isUndefined(i.security)&&(i.security=e.resolved.security),r.push(new types.Operation(e,o,s,JsonRefs.pathToPointer(p),i))}})}),r},module.exports.getOperationParameters=function(e){return _.map(e.parameters,function(r){var t=r.$$$ptr$$$;return delete r.$$$ptr$$$,new types.Parameter(e,t,r,getParameterSchema(r))})},module.exports.getSample=function(e){return mocker(e)},module.exports.getSemanticValidators=function(){return validators.semanticValidators};
|
||||
|
||||
},{"../../helpers":2,"../../types":4,"./format-generators":5,"./helpers":7,"./validators":10,"json-refs":52,"lodash-compat":138}],9:[function(require,module,exports){
|
||||
module.exports={
|
||||
|
@ -1667,7 +1667,7 @@ module.exports={
|
|||
}
|
||||
}
|
||||
},{}],10:[function(require,module,exports){
|
||||
"use strict";function walkSchema(e,a,r,t){function s(a){return _.indexOf(e,JsonRefs.pathToPointer(a))>-1}function o(a,r){s(r)||(_.forEach(a,function(a,s){_.isNumber(s)&&(s=s.toString()),walkSchema(e,a,r.concat(s),t)}),t(a,r))}var n=a.type||"object";s(r)||(_.isUndefined(a.schema)?"array"!==n||_.isUndefined(a.items)?"object"===n&&(_.isUndefined(a.additionalProperties)||walkSchema(e,a.additionalProperties,r.concat("additionalProperties"),t),_.forEach(["allOf","properties"],function(e){_.isUndefined(a[e])||o(a[e],r.concat(e))})):o(a.items,r.concat("items")):walkSchema(e,a.schema,r.concat("schema"),t),t(a,r))}function validateStructure(e){return helpers.validateAgainstSchema(helpers.createJSONValidator({formatValidators:customFormatValidators}),swaggerSchema,e.resolved)}function validateArrayItems(e){function a(e,a){"array"===e.type&&_.isUndefined(e.items)&&o.errors.push({code:"OBJECT_MISSING_REQUIRED_PROPERTY",message:"Missing required property: items",path:a})}function r(e,r){_.forEach(e,function(e,t){_.isNumber(t)&&(t=t.toString()),walkSchema(s,e,r.concat(t),a)})}function t(e,r){_.forEach(e,function(e,t){var o=r.concat(t);_.forEach(e.headers,function(e,r){walkSchema(s,e,o.concat(["headers",r]),a)}),_.isUndefined(e.schema)||walkSchema(s,e.schema,o.concat("schema"),a)})}var s=_.reduce(e.references,function(e,a,r){var t=JsonRefs.pathFromPointer(r);return t.pop(),e.push(JsonRefs.pathToPointer(t)),e},[]),o={errors:[],warnings:[]};return _.forEach(e.resolved.definitions,function(e,r){walkSchema(s,e,["definitions",r],a)}),r(e.resolved.parameters,["parameters"]),t(e.resolved.responses,["responses"]),_.forEach(e.resolved.paths,function(e,a){var s=["paths",a];r(e.parameters,s.concat("parameters")),_.forEach(e,function(e,a){var o=s.concat(a);-1!==_.indexOf(vHelpers.supportedHttpMethods,a)&&(r(e.parameters,o.concat("parameters")),t(e.responses,o.concat("responses")))})}),o}var _=require("lodash-compat"),customFormatValidators=require("./format-validators"),helpers=require("../../helpers"),JsonRefs=require("json-refs"),swaggerSchema=require("./schema.json"),vHelpers=require("./helpers");module.exports={jsonSchemaValidator:validateStructure,semanticValidators:[validateArrayItems]};
|
||||
"use strict";function walkSchema(e,r,a,t){function s(r){return _.indexOf(e,JsonRefs.pathToPointer(r))>-1}function n(r,a){s(a)||(_.forEach(r,function(r,s){_.isNumber(s)&&(s=s.toString()),walkSchema(e,r,a.concat(s),t)}),t(r,a))}var o=r.type||"object";s(a)||(_.isUndefined(r.schema)?"array"!==o||_.isUndefined(r.items)?"object"===o&&(_.isUndefined(r.additionalProperties)||walkSchema(e,r.additionalProperties,a.concat("additionalProperties"),t),_.forEach(["allOf","properties"],function(e){_.isUndefined(r[e])||n(r[e],a.concat(e))})):n(r.items,a.concat("items")):walkSchema(e,r.schema,a.concat("schema"),t),t(r,a))}function validateStructure(e){return helpers.validateAgainstSchema(helpers.createJSONValidator({formatValidators:customFormatValidators}),swaggerSchema,e.resolved)}function arrayTypeRequiresItemsProperty(e){function r(e,r){"array"===e.type&&_.isUndefined(e.items)&&n.errors.push({code:"OBJECT_MISSING_REQUIRED_PROPERTY",message:"Missing required property: items",path:r})}function a(e,a){_.forEach(e,function(e,t){_.isNumber(t)&&(t=t.toString()),walkSchema(s,e,a.concat(t),r)})}function t(e,a){_.forEach(e,function(e,t){var n=a.concat(t);_.forEach(e.headers,function(e,a){walkSchema(s,e,n.concat(["headers",a]),r)}),_.isUndefined(e.schema)||walkSchema(s,e.schema,n.concat("schema"),r)})}var s=_.reduce(e.references,function(e,r,a){var t=JsonRefs.pathFromPointer(a);return t.pop(),e.push(JsonRefs.pathToPointer(t)),e},[]),n={errors:[],warnings:[]};return _.forEach(e.resolved.definitions,function(e,a){walkSchema(s,e,["definitions",a],r)}),a(e.resolved.parameters,["parameters"]),t(e.resolved.responses,["responses"]),_.forEach(e.resolved.paths,function(e,r){var s=["paths",r];a(e.parameters,s.concat("parameters")),_.forEach(e,function(e,r){var n=s.concat(r);-1!==_.indexOf(vHelpers.supportedHttpMethods,r)&&(a(e.parameters,n.concat("parameters")),t(e.responses,n.concat("responses")))})}),n}function validatePathsAndPathParameters(e){var r={errors:[],warnings:[]};return _.reduce(e.resolved.paths,function(a,t,s){var n=[],o=s,i=["paths",s];return _.forEach(s.match(/\{(.*?)\}/g),function(e,r){n.push(e.replace(/[{}]/g,"")),o=o.replace(e,"arg"+r)}),_.indexOf(a,o)>-1?r.errors.push({code:"EQUIVALENT_PATH",message:"Equivalent path already exists: "+s,path:i}):a.push(o),_.forEach(t,function(a,t){var o,c;-1!==_.indexOf(vHelpers.supportedHttpMethods,t)&&(c=e.getOperation(s,t).getParameters(),o=_.reduce(c,function(e,r){return"path"===r["in"]&&(e[r.name]=r.ptr),e},{}),_.forEach(_.difference(n,_.keys(o)),function(e){r.errors.push({code:"MISSING_PATH_PARAMETER_DEFINITION",message:"Path parameter is declared but is not defined: "+e,path:i.concat(t)})}),_.forEach(_.difference(_.keys(o),n),function(e){r.errors.push({code:"MISSING_PATH_PARAMETER_DECLARATION",message:"Path parameter is defined but is not declared: "+e,path:JsonRefs.pathFromPointer(o[e])})}))}),a},[]),r}var _=require("lodash-compat"),customFormatValidators=require("./format-validators"),helpers=require("../../helpers"),JsonRefs=require("json-refs"),swaggerSchema=require("./schema.json"),vHelpers=require("./helpers");module.exports={jsonSchemaValidator:validateStructure,semanticValidators:[arrayTypeRequiresItemsProperty,validatePathsAndPathParameters]};
|
||||
|
||||
},{"../../helpers":2,"./format-validators":6,"./helpers":7,"./schema.json":9,"json-refs":52,"lodash-compat":138}],11:[function(require,module,exports){
|
||||
|
||||
|
|
Различия файлов скрыты, потому что одна или несколько строк слишком длинны
Различия файлов скрыты, потому что одна или несколько строк слишком длинны
|
@ -10,6 +10,9 @@ Swagger 2.0 as well.
|
|||
|
||||
| Description | Type |
|
||||
| :---------: | :---: |
|
||||
| All path parameters declared in the path string need matching parameter definitions *(Either at the path-level or the operation)* | Error |
|
||||
| All path parameters definition *(Either at the path-level or the operation)* need matching paramater declarations | Error |
|
||||
| All path strings must be *(equivalently)* different *(Example: `/pet/{petId}` and `/pet/{petId2}` are equivalently the same and would generate an error)* | Error |
|
||||
| All places where a [Schema Object][schema-object] can be, and primitive parameters, the `items` property is required when `type` is set to `array` but this is **not** enforced in the JSON Schema. _(See [swagger-api/swagger-spec/issues/174](https://github.com/swagger-api/swagger-spec/issues/174))_ | Error |
|
||||
|
||||
[schema-object]: https://github.com/swagger-api/swagger-spec/blob/master/versions/2.0.md#schemaObject
|
||||
|
|
|
@ -143,7 +143,7 @@ module.exports.getOperations = function (api) {
|
|||
|
||||
_.forEach(api.resolved.paths, function (pathDef, path) {
|
||||
var pPath = ['paths', path];
|
||||
var pParams = _.reduce(pathDef.parameters || {}, function (parameters, paramDef, index) {
|
||||
var pParams = _.reduce(pathDef.parameters || [], function (parameters, paramDef, index) {
|
||||
parameters[paramDef.name + ':' + paramDef.in] = {
|
||||
path: pPath.concat(['parameters', index.toString()]),
|
||||
definition: paramDef
|
||||
|
|
|
@ -93,7 +93,7 @@ function validateStructure (api) {
|
|||
}
|
||||
|
||||
/**
|
||||
* Validates that all arrays have their required items property.
|
||||
* Validates that all array types have their required items property.
|
||||
*
|
||||
* @see {@link https://github.com/swagger-api/swagger-spec/issues/174}
|
||||
*
|
||||
|
@ -101,7 +101,7 @@ function validateStructure (api) {
|
|||
*
|
||||
* @returns {object} Object containing the errors and warnings of the validation
|
||||
*/
|
||||
function validateArrayItems (api) {
|
||||
function arrayTypeRequiresItemsProperty (api) {
|
||||
// Build a blacklist to avoid cascading errors/warnings
|
||||
var blacklist = _.reduce(api.references, function (list, metadata, ptr) {
|
||||
var refPath = JsonRefs.pathFromPointer(ptr);
|
||||
|
@ -189,9 +189,96 @@ function validateArrayItems (api) {
|
|||
return response;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates paths and path paramters (Written as one validator to avoid multiple passes)
|
||||
*
|
||||
* * Ensure that path parameters are defined for each path parameter declaration
|
||||
* * Ensure that defined path parameters match a declared path parameter
|
||||
* * Ensure that paths are functionally different
|
||||
*
|
||||
* @param {SwaggerApi} api - The SwaggerApi object
|
||||
*
|
||||
* @returns {object} Object containing the errors and warnings of the validation
|
||||
*/
|
||||
function validatePathsAndPathParameters (api) {
|
||||
var response = {
|
||||
errors: [],
|
||||
warnings: []
|
||||
};
|
||||
|
||||
_.reduce(api.resolved.paths, function (paths, pathDef, path) {
|
||||
var declaredPathParameters = [];
|
||||
var normalizedPath = path;
|
||||
var pPath = ['paths', path];
|
||||
|
||||
_.forEach(path.match(/\{(.*?)\}/g), function (arg, index) {
|
||||
// Record the path parameter name
|
||||
declaredPathParameters.push(arg.replace(/[{}]/g, ''));
|
||||
|
||||
// Update the normalized path
|
||||
normalizedPath = normalizedPath.replace(arg, 'arg' + index);
|
||||
});
|
||||
|
||||
// Idenfity paths that are functionally the same
|
||||
if (_.indexOf(paths, normalizedPath) > -1) {
|
||||
response.errors.push({
|
||||
code: 'EQUIVALENT_PATH',
|
||||
message: 'Equivalent path already exists: ' + path,
|
||||
path: pPath
|
||||
});
|
||||
} else {
|
||||
paths.push(normalizedPath);
|
||||
}
|
||||
|
||||
_.forEach(pathDef, function (operationDef, method) {
|
||||
var definedPathParameters;
|
||||
var parameters;
|
||||
|
||||
// Do not process non-operations
|
||||
if (_.indexOf(vHelpers.supportedHttpMethods, method) === -1) {
|
||||
return;
|
||||
}
|
||||
|
||||
parameters = api.getOperation(path, method).getParameters();
|
||||
|
||||
// Use SwaggerApi#getOperation to avoid having to consolidate parameters
|
||||
definedPathParameters = _.reduce(parameters, function (pathParameters, parameter) {
|
||||
if (parameter.in === 'path') {
|
||||
pathParameters[parameter.name] = parameter.ptr;
|
||||
}
|
||||
|
||||
return pathParameters;
|
||||
}, {});
|
||||
|
||||
// Identify undefined path parameters
|
||||
_.forEach(_.difference(declaredPathParameters, _.keys(definedPathParameters)), function (name) {
|
||||
response.errors.push({
|
||||
code: 'MISSING_PATH_PARAMETER_DEFINITION',
|
||||
message: 'Path parameter is declared but is not defined: ' + name,
|
||||
path: pPath.concat(method)
|
||||
});
|
||||
});
|
||||
|
||||
// Identify undeclared path parameters
|
||||
_.forEach(_.difference(_.keys(definedPathParameters), declaredPathParameters), function (name) {
|
||||
response.errors.push({
|
||||
code: 'MISSING_PATH_PARAMETER_DECLARATION',
|
||||
message: 'Path parameter is defined but is not declared: ' + name,
|
||||
path: JsonRefs.pathFromPointer(definedPathParameters[name])
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
return paths;
|
||||
}, []);
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
jsonSchemaValidator: validateStructure,
|
||||
semanticValidators: [
|
||||
validateArrayItems
|
||||
arrayTypeRequiresItemsProperty,
|
||||
validatePathsAndPathParameters
|
||||
]
|
||||
};
|
||||
|
|
|
@ -1102,6 +1102,96 @@ describe('swagger-core-api (Swagger 2.0)', function () {
|
|||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('equivalent paths', function (done) {
|
||||
var cSwagger = _.cloneDeep(swaggerDoc);
|
||||
|
||||
cSwagger.paths['/pet/{notPetId}'] = {};
|
||||
|
||||
swaggerApi.create({
|
||||
definition: cSwagger
|
||||
})
|
||||
.then(function (api) {
|
||||
var result = api.validate();
|
||||
|
||||
assert.ok(result === false);
|
||||
assert.deepEqual([], api.getLastWarnings());
|
||||
assert.deepEqual([
|
||||
{
|
||||
code: 'EQUIVALENT_PATH',
|
||||
message: 'Equivalent path already exists: /pet/{notPetId}',
|
||||
path: ['paths', '/pet/{notPetId}']
|
||||
}
|
||||
], api.getLastErrors());
|
||||
})
|
||||
.then(done, done);
|
||||
});
|
||||
|
||||
it('missing path parameter declaration', function (done) {
|
||||
var cSwagger = _.cloneDeep(swaggerDoc);
|
||||
|
||||
cSwagger.paths['/pet/{petId}'].get.parameters = [
|
||||
{
|
||||
description: 'Superfluous path parameter',
|
||||
in: 'path',
|
||||
name: 'petId2',
|
||||
required: true,
|
||||
type: 'string'
|
||||
}
|
||||
];
|
||||
|
||||
swaggerApi.create({
|
||||
definition: cSwagger
|
||||
})
|
||||
.then(function (api) {
|
||||
var result = api.validate();
|
||||
|
||||
assert.ok(result === false);
|
||||
assert.deepEqual([], api.getLastWarnings());
|
||||
assert.deepEqual([
|
||||
{
|
||||
code: 'MISSING_PATH_PARAMETER_DECLARATION',
|
||||
message: 'Path parameter is defined but is not declared: petId2',
|
||||
path: ['paths', '/pet/{petId}', 'get', 'parameters', '0']
|
||||
}
|
||||
], api.getLastErrors());
|
||||
})
|
||||
.then(done, done);
|
||||
});
|
||||
|
||||
it('missing path parameter definition', function (done) {
|
||||
var cSwagger = _.cloneDeep(swaggerDoc);
|
||||
|
||||
cSwagger.paths['/pet/{petId}'].parameters = [];
|
||||
|
||||
swaggerApi.create({
|
||||
definition: cSwagger
|
||||
})
|
||||
.then(function (api) {
|
||||
var result = api.validate();
|
||||
|
||||
assert.ok(result === false);
|
||||
assert.deepEqual([], api.getLastWarnings());
|
||||
assert.deepEqual([
|
||||
{
|
||||
code: 'MISSING_PATH_PARAMETER_DEFINITION',
|
||||
message: 'Path parameter is declared but is not defined: petId',
|
||||
path: ['paths', '/pet/{petId}', 'get']
|
||||
},
|
||||
{
|
||||
code: 'MISSING_PATH_PARAMETER_DEFINITION',
|
||||
message: 'Path parameter is declared but is not defined: petId',
|
||||
path: ['paths', '/pet/{petId}', 'post']
|
||||
},
|
||||
{
|
||||
code: 'MISSING_PATH_PARAMETER_DEFINITION',
|
||||
message: 'Path parameter is declared but is not defined: petId',
|
||||
path: ['paths', '/pet/{petId}', 'delete']
|
||||
}
|
||||
], api.getLastErrors());
|
||||
})
|
||||
.then(done, done);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
Загрузка…
Ссылка в новой задаче