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:
Jeremy Whitlock 2015-06-26 09:14:19 -06:00
Родитель bc4615e529
Коммит 9dd52d0277
8 изменённых файлов: 372 добавлений и 18 удалений

4
browser/swagger-core-api-min.js поставляемый

Различия файлов скрыты, потому что одна или несколько строк слишком длинны

4
browser/swagger-core-api-standalone-min.js поставляемый
Просмотреть файл

@ -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);
});
});
});
});