swagger-tools
Advanced tools
Comparing version 0.1.1 to 0.1.2
188
index.js
@@ -25,7 +25,9 @@ /* | ||
var validatorDefaults = { | ||
useDefault: false, | ||
useCoerce: false, | ||
checkRequired: true, | ||
removeAdditional: false | ||
var defaultOptions = { | ||
validator: { | ||
useDefault: false, | ||
useCoerce: false, | ||
checkRequired: true, | ||
removeAdditional: false | ||
} | ||
}; | ||
@@ -41,10 +43,10 @@ | ||
* @param {string} version - The Swagger version | ||
* @param {object} [options] - The specification options (Currently used to pass validator options) | ||
* @param {boolean} [options.useDefault=false] - If true it modifies the object to have the default values for missing | ||
* non-required fields | ||
* @param {boolean} [options.useCoerce=false] - If true it enables type coercion where defined | ||
* @param {boolean} [options.checkRequired=true] - If true it reports missing required properties, otherwise it allows | ||
* missing required properties | ||
* @param {boolean} [options.removeAdditional=false] - If true it removes all attributes of an object which are not | ||
* matched by the schema's specification | ||
* @param {object} [options] - The specification options | ||
* @param {boolean} [options.validator.useDefault=false] - If true it modifies the object to have the default values for | ||
* missing non-required fields | ||
* @param {boolean} [options.validator.useCoerce=false] - If true it enables type coercion where defined | ||
* @param {boolean} [options.validatorcheckRequired=true] - If true it reports missing required properties, otherwise it | ||
* allows missing required properties | ||
* @param {boolean} [options.validator.removeAdditional=false] - If true it removes all attributes of an object which | ||
* are not matched by the schema's specification | ||
* @constructor | ||
@@ -56,3 +58,3 @@ */ | ||
options = _.defaults(options || {}, validatorDefaults); | ||
options = _.defaults(options || {}, defaultOptions); | ||
@@ -91,3 +93,3 @@ switch (version) { | ||
Object.keys(this.schemas).forEach(function (schemaName) { | ||
var validator = jjv(); | ||
var validator = jjv(this.options.validator); | ||
var toCompile = []; | ||
@@ -164,2 +166,128 @@ | ||
var validateModels = function validateModels (spec, resource) { | ||
var modelIds = _.map(resource.models || {}, function (model) { | ||
return model.id; | ||
}); | ||
var modelRefs = {}; | ||
var primitives = _.union(spec.schemas['dataType.json'].definitions.primitiveType.properties.type.enum, | ||
['array', 'void', 'File']); | ||
var addModelRef = function (modelId, modelRef) { | ||
if (Object.keys(modelRefs).indexOf(modelId) === -1) { | ||
modelRefs[modelId] = []; | ||
} | ||
modelRefs[modelId].push(modelRef); | ||
}; | ||
var errors = []; | ||
var warnings = []; | ||
switch (spec.version) { | ||
case '1.2': | ||
// Find references defined in the operations (Validation happens elsewhere but we have to be smart) | ||
if (resource.apis && _.isArray(resource.apis)) { | ||
_.each(resource.apis, function (api, index) { | ||
var apiPath = '$.apis[' + index + ']'; | ||
_.each(api.operations, function (operation, index) { | ||
var operationPath = apiPath + '.operations[' + index + ']'; | ||
// References in operation type | ||
if (operation.type) { | ||
if (operation.type === 'array' && _.isObject(operation.items) && operation.items.$ref) { | ||
addModelRef(operation.items.$ref, operationPath + '.items.$ref'); | ||
} else if (primitives.indexOf(operation.type) === -1) { | ||
addModelRef(operation.type, operationPath + '.type'); | ||
} | ||
} | ||
// References in operation parameters | ||
if (operation.parameters && _.isObject(operation.parameters)) { | ||
_.each(operation.parameters, function (parameter, index) { | ||
if (parameter.type && primitives.indexOf(parameter.type) === -1) { | ||
addModelRef(parameter.type, operationPath + '.parameters[' + index + '].type'); | ||
} else if (parameter.type === 'array' && _.isObject(parameter.items) && parameter.items.$ref) { | ||
addModelRef(parameter.items.$ref, operationPath + '.parameters[' + index + '].items.$ref'); | ||
} | ||
}); | ||
} | ||
// References in response messages | ||
if (operation.responseMessages && _.isArray(operation.responseMessages)) { | ||
_.each(operation.responseMessages, function (message, index) { | ||
if (message.responseModel) { | ||
addModelRef(message.responseModel, operationPath + '.responseMessages[' + index + '].responseModel'); | ||
} | ||
}); | ||
} | ||
}); | ||
}); | ||
} | ||
// Find references defined in the models themselves (Validation happens elsewhere but we have to be smart) | ||
if (resource.models && _.isObject(resource.models)) { | ||
_.each(resource.models, function (model, name) { | ||
var modelPath = '$.models[\'' + name + '\']'; // Always use bracket notation just to be safe | ||
// References in model properties | ||
if (model.properties && _.isObject(model.properties)) { | ||
_.each(model.properties, function (property, name) { | ||
var propPath = modelPath + '.properties[\'' + name + '\']'; // Always use bracket notation just to be safe | ||
if (property.$ref) { | ||
addModelRef(property.$ref, propPath + '.$ref'); | ||
} else if (property.type === 'array' && _.isObject(property.items) && property.items.$ref) { | ||
addModelRef(property.items.$ref, propPath + '.items.$ref'); | ||
} | ||
}); | ||
} | ||
// References in model subTypes | ||
if (model.subTypes && _.isArray(model.subTypes)) { | ||
_.each(model.subTypes, function (name, index) { | ||
addModelRef(name, modelPath + '.subTypes[' + index + ']'); | ||
}); | ||
} | ||
}); | ||
} | ||
break; | ||
default: | ||
throwUnsupportedVersion(spec.version); | ||
} | ||
// Handle missing models | ||
_.difference(Object.keys(modelRefs), modelIds).forEach(function (missing) { | ||
modelRefs[missing].forEach(function (modelRef) { | ||
errors.push({ | ||
code: 'UNRESOLVABLE_MODEL_REFERENCE', | ||
message: 'Model reference could not be resolved: ' + missing, | ||
data: missing, | ||
path: modelRef | ||
}); | ||
}); | ||
}); | ||
// Handle unused models | ||
_.difference(modelIds, Object.keys(modelRefs)).forEach(function (unused) { | ||
warnings.push({ | ||
code: 'UNUSED_MODEL', | ||
message: 'Model is defined but is not used: ' + unused, | ||
data: unused, | ||
path: '$.models[\'' + unused + '\']' | ||
}); | ||
}); | ||
// TODO: Validate subTypes are not cyclical | ||
// TODO: Validate subTypes do not override parent properties | ||
// TODO: Validate subTypes do not include discriminiator | ||
// TODO: Validate discriminitor property exists | ||
// TODO: Validate required properties exist | ||
return { | ||
errors: errors, | ||
warnings: warnings | ||
}; | ||
}; | ||
/** | ||
@@ -180,2 +308,4 @@ * Returns the result of the validation of the Swagger document against its schema. | ||
var errors = []; | ||
var warnings = []; | ||
var schema; | ||
@@ -190,4 +320,2 @@ var validator; | ||
schema = this.schemas[schemaName]; | ||
break; | ||
@@ -198,6 +326,9 @@ default: | ||
schema = this.schemas[schemaName]; | ||
if (!schema) { | ||
throw new Error('dataSchema is not valid. Valid schema names: ' + Object.keys(this.schemas).join(', ')); | ||
throw new Error('schemaName is not valid. Valid schema names: ' + Object.keys(this.schemas).join(', ')); | ||
} | ||
// Do structural (JSON Schema) validation | ||
validator = this.validators[schemaName]; | ||
@@ -207,8 +338,23 @@ result = validator.validate(schema, data); | ||
if (result) { | ||
return validator.je(schema, data, result); | ||
} else { | ||
return undefined; | ||
errors = validator.je(schema, data, result); | ||
} | ||
switch (schemaName) { | ||
case 'apiDeclaration.json': | ||
result = validateModels(this, data); | ||
if (result.errors && _.isArray(result.errors)) { | ||
errors = errors.concat(result.errors); | ||
} | ||
if (result.warnings && _.isArray(result.warnings)) { | ||
warnings = warnings.concat(result.warnings); | ||
} | ||
break; | ||
} | ||
return errors.length === 0 && warnings.length === 0 ? undefined : {errors: errors, warnings: warnings}; | ||
}; | ||
var v1_2 = module.exports.v1_2 = new Specification('1.2'); // jshint ignore:line |
{ | ||
"name": "swagger-tools", | ||
"version": "0.1.1", | ||
"version": "0.1.2", | ||
"description": "Various tools for using and integrating with Swagger.", | ||
@@ -5,0 +5,0 @@ "main": "index.js", |
@@ -56,6 +56,8 @@ /* global describe, it */ | ||
assert.deepEqual(spec.options, { | ||
useDefault: false, | ||
useCoerce: false, | ||
checkRequired: true, | ||
removeAdditional: false | ||
validator: { | ||
useDefault: false, | ||
useCoerce: false, | ||
checkRequired: true, | ||
removeAdditional: false | ||
} | ||
}); | ||
@@ -113,3 +115,3 @@ assert.strictEqual(spec.docsUrl, 'https://github.com/wordnik/swagger-spec/blob/master/versions/1.2.md'); | ||
it('should return false for invalid JSON files', function () { | ||
it('should return errors for structurally invalid JSON files', function () { | ||
var petJson = _.cloneDeep(allSampleFiles['pet.json']); | ||
@@ -163,8 +165,49 @@ var petErrors = [ | ||
assert.deepEqual(spec.validate(petJson), petErrors); | ||
assert.deepEqual(spec.validate(rlJson, 'resourceListing.json'), rlErrors); | ||
assert.deepEqual(spec.validate(storeJson), storeErrors); | ||
assert.deepEqual(spec.validate(userJson), userErrors); | ||
assert.deepEqual(spec.validate(petJson).errors, petErrors); | ||
assert.equal(spec.validate(petJson).warnings, 0); | ||
assert.deepEqual(spec.validate(rlJson, 'resourceListing.json').errors, rlErrors); | ||
assert.equal(spec.validate(rlJson, 'resourceListing.json').warnings, 0); | ||
assert.deepEqual(spec.validate(storeJson).errors, storeErrors); | ||
assert.equal(spec.validate(storeJson).warnings, 0); | ||
assert.deepEqual(spec.validate(userJson).errors, userErrors); | ||
assert.equal(spec.validate(userJson).warnings, 0); | ||
}); | ||
it('should return errors for missing model references in apiDeclaration/resource files', function () { | ||
var json = require('./v1_2-invalid-models.json'); | ||
var result = spec.validate(json); | ||
var expectedMissingModelRefs = { | ||
'MissingParamRef': '$.apis[0].operations[0].parameters[0].type', | ||
'MissingParamItemsRef': '$.apis[0].operations[0].parameters[1].items.$ref', | ||
'MissingResponseMessageRef': '$.apis[0].operations[0].responseMessages[0].responseModel', | ||
'MissingTypeRef': '$.apis[0].operations[0].type', | ||
'MissingTypeItemsRef': '$.apis[1].operations[0].items.$ref', | ||
'MissingPropertyItemsRef': '$.models[\'Animal\'].properties[\'breeds\'].items.$ref', | ||
'MissingSubTypeRef': '$.models[\'Animal\'].subTypes[1]', | ||
'MissingPropertyRef': '$.models[\'Cat\'].properties[\'address\'].$ref' | ||
}; | ||
assert.equal(result.errors.length, Object.keys(expectedMissingModelRefs).length); | ||
result.errors.forEach(function (error) { | ||
assert.equal(error.code, 'UNRESOLVABLE_MODEL_REFERENCE'); | ||
assert.equal(error.message, 'Model reference could not be resolved: ' + error.data); | ||
assert.equal(error.path, expectedMissingModelRefs[error.data]); | ||
}); | ||
}); | ||
it('should return warnings for unused models in apiDeclaration/resource files', function () { | ||
var json = require('./v1_2-invalid-models.json'); | ||
var result = spec.validate(json); | ||
assert.equal(1, result.warnings.length); | ||
assert.deepEqual(result.warnings[0], { | ||
code: 'UNUSED_MODEL', | ||
message: 'Model is defined but is not used: Animal', | ||
data: 'Animal', | ||
path: '$.models[\'Animal\']' | ||
}); | ||
}); | ||
}); | ||
}); |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
80461
25
2134