swagger-tools
Advanced tools
Comparing version 0.5.0 to 0.5.1
652
lib/specs.js
@@ -32,2 +32,3 @@ /* | ||
var jjve = require('jjve'); | ||
var validators = require('./validators'); | ||
@@ -70,2 +71,257 @@ var draft04Json = require('../schemas/json-schema-draft-04.json'); | ||
var createErrorOrWarning = function createErrorOrWarning (code, message, data, path, dest) { | ||
dest.push({ | ||
code: code, | ||
message: message, | ||
data: data, | ||
path: path | ||
}); | ||
}; | ||
var createUnusedErrorOrWarning = function createUnusedErrorOrWarning (data, val, codeSuffix, msgPrefix, path, dest) { | ||
createErrorOrWarning('UNUSED_' + codeSuffix, msgPrefix + ' is defined but is not used: ' + val, data, path, dest); | ||
}; | ||
var validateExist = function validateExist (data, val, codeSuffix, msgPrefix, path, dest) { | ||
if (!_.isUndefined(data) && data.indexOf(val) === -1) { | ||
createErrorOrWarning('UNRESOLVABLE_' + codeSuffix, msgPrefix + ' could not be resolved: ' + val, val, path, dest); | ||
} | ||
}; | ||
var validateNoExist = function validateNoExist (data, val, codeSuffix, msgPrefix, path, dest) { | ||
if (!_.isUndefined(data) && data.indexOf(val) > -1) { | ||
createErrorOrWarning('DUPLICATE_' + codeSuffix, msgPrefix + ' already defined: ' + val, val, path, dest); | ||
} | ||
}; | ||
var validateNoDuplicates = function validateNoDuplicates (data, codeSuffix, msgPrefix, path, dest) { | ||
var name = path[path.length - 1]; | ||
if (!_.isUndefined(data) && data.length !== _.uniq(data).length) { | ||
createErrorOrWarning('DUPLICATE_' + codeSuffix, msgPrefix + ' ' + name + ' has duplicate items', data, path, dest); | ||
} | ||
}; | ||
// TODO: Move this to a helper | ||
var identifyModelInheritanceIssues = function identifyModelInheritanceIssues (modelDeps, models, result) { | ||
var circular = {}; | ||
var composed = {}; | ||
var resolved = {}; | ||
var unresolved = {}; | ||
var addModelProps = function addModelProps (parentModel, modelName) { | ||
var model = models[modelName]; | ||
if (model) { | ||
_.each(model.properties, function (prop, propName) { | ||
if (composed[propName]) { | ||
createErrorOrWarning('CHILD_MODEL_REDECLARES_PROPERTY', | ||
'Child model declares property already declared by ancestor: ' + propName, prop, | ||
['models', parentModel, 'properties', propName], result.errors); | ||
} else { | ||
composed[propName] = propName; | ||
} | ||
}); | ||
} | ||
}; | ||
var getPath = function getPath (parent, unresolved) { | ||
var parentVisited = false; | ||
return Object.keys(unresolved).filter(function (dep) { | ||
if (dep === parent) { | ||
parentVisited = true; | ||
} | ||
return parentVisited && unresolved[dep]; | ||
}); | ||
}; | ||
var resolver = function resolver (id, deps, circular, resolved, unresolved) { | ||
var model = models[id]; | ||
var modelDeps = deps[id]; | ||
unresolved[id] = true; | ||
if (modelDeps) { | ||
if (modelDeps.length > 1) { | ||
createErrorOrWarning('MULTIPLE_MODEL_INHERITANCE', | ||
'Child model is sub type of multiple models: ' + modelDeps.join(' && '), model, | ||
['models', id], result.errors); | ||
} | ||
modelDeps.forEach(function (dep) { | ||
if (!resolved[dep]) { | ||
if (unresolved[dep]) { | ||
circular[id] = getPath(dep, unresolved); | ||
createErrorOrWarning('CYCLICAL_MODEL_INHERITANCE', | ||
'Model has a circular inheritance: ' + id + ' -> ' + circular[id].join(' -> '), | ||
model.subTypes, | ||
['models', id, 'subTypes'], result.errors); | ||
return; | ||
} | ||
addModelProps(id, dep); | ||
resolver(dep, deps, circular, resolved, unresolved); | ||
} | ||
}); | ||
} | ||
resolved[id] = true; | ||
unresolved[id] = false; | ||
}; | ||
Object.keys(modelDeps).forEach(function (modelName) { | ||
composed = {}; | ||
addModelProps(modelName, modelName); | ||
resolver(modelName, modelDeps, circular, resolved, unresolved); | ||
}); | ||
}; | ||
// TODO: Move this to a helper | ||
var validateParameterConstraints = function validateParameterConstraints (spec, parameter, val, path, dest) { | ||
switch (spec.version) { | ||
case '1.2': | ||
// TODO: Make this work with parameters that have references | ||
// Validate the value type/format | ||
try { | ||
validators.validateTypeAndFormat(parameter.name, val, | ||
parameter.type === 'array' ? parameter.items.type : parameter.type, | ||
parameter.type === 'array' && parameter.items.format ? | ||
parameter.items.format : | ||
parameter.format); | ||
} catch (err) { | ||
// TODO: Update to notify of 'INVALID_FORMAT' | ||
createErrorOrWarning ('INVALID_TYPE', err.message, val, path, dest); | ||
return; | ||
} | ||
// Validate enum | ||
try { | ||
validators.validateEnum(parameter.name, val, parameter.enum); | ||
} catch (err) { | ||
createErrorOrWarning ('ENUM_MISMATCH', err.message, val, path, dest); | ||
return; | ||
} | ||
// Validate maximum | ||
try { | ||
validators.validateMaximum(parameter.name, val, parameter.maximum, parameter.type); | ||
} catch (err) { | ||
createErrorOrWarning ('MAXIMUM', err.message, val, path, dest); | ||
return; | ||
} | ||
// Validate minimum | ||
try { | ||
validators.validateMinimum(parameter.name, val, parameter.minimum, parameter.type); | ||
} catch (err) { | ||
createErrorOrWarning ('MINIMUM', err.message, val, path, dest); | ||
return; | ||
} | ||
// Validate uniqueItems | ||
try { | ||
validators.validateUniqueItems(parameter.name, val, parameter.uniqueItems); | ||
} catch (err) { | ||
createErrorOrWarning ('ARRAY_UNIQUE', err.message, val, path, dest); | ||
return; | ||
} | ||
break; | ||
case '2.0': | ||
// TODO: Make this work with parameters that have schemas/references | ||
// Validate the value type/format | ||
try { | ||
validators.validateTypeAndFormat(parameter.name, val, | ||
parameter.type === 'array' ? parameter.items.type : parameter.type, | ||
parameter.type === 'array' && parameter.items.format ? | ||
parameter.items.format : | ||
parameter.format); | ||
} catch (err) { | ||
// TODO: Update to notify of 'INVALID_FORMAT' | ||
createErrorOrWarning('INVALID_TYPE', err.message, val, path, dest); | ||
return; | ||
} | ||
// Validate enum | ||
try { | ||
validators.validateEnum(parameter.name, val, parameter.enum); | ||
} catch (err) { | ||
createErrorOrWarning('ENUM_MISMATCH', err.message, val, path, dest); | ||
return; | ||
} | ||
// Validate maximum | ||
try { | ||
validators.validateMaximum(parameter.name, val, parameter.maximum, parameter.type, parameter.exclusiveMaximum); | ||
} catch (err) { | ||
createErrorOrWarning(parameter.exclusiveMaximum === true ? 'MAXIMUM_EXCLUSIVE' : 'MAXIMUM', err.message, val, | ||
path, dest); | ||
return; | ||
} | ||
// Validate maximum items | ||
try { | ||
validators.validateMaxItems(parameter.name, val, parameter.maxItems); | ||
} catch (err) { | ||
createErrorOrWarning('ARRAY_LENGTH_LONG', err.message, val, path, dest); | ||
return; | ||
} | ||
// Validate maximum length | ||
try { | ||
validators.validateMaxLength(parameter.name, val, parameter.maxLength); | ||
} catch (err) { | ||
createErrorOrWarning('MAX_LENGTH', err.message, val, path, dest); | ||
return; | ||
} | ||
// Validate minimum | ||
try { | ||
validators.validateMinimum(parameter.name, val, parameter.minimum, parameter.type, parameter.exclusiveMinimum); | ||
} catch (err) { | ||
createErrorOrWarning(parameter.exclusiveMinimum === 'true' ? 'MINIMUM_EXCLUSIVE' : 'MINIMUM', err.message, val, | ||
path, dest); | ||
return; | ||
} | ||
// Validate minimum items | ||
try { | ||
validators.validateMinItems(parameter.name, val, parameter.minItems); | ||
} catch (err) { | ||
createErrorOrWarning('ARRAY_LENGTH_SHORT', err.message, val, path, dest); | ||
return; | ||
} | ||
// Validate minimum length | ||
try { | ||
validators.validateMinLength(parameter.name, val, parameter.minLength); | ||
} catch (err) { | ||
createErrorOrWarning('MIN_LENGTH', err.message, val, path, dest); | ||
return; | ||
} | ||
// Validate pattern | ||
try { | ||
validators.validatePattern(parameter.name, val, parameter.pattern); | ||
} catch (err) { | ||
createErrorOrWarning('PATTERN', err.message, val, path, dest); | ||
return; | ||
} | ||
// Validate uniqueItems | ||
try { | ||
validators.validateUniqueItems(parameter.name, val, parameter.uniqueItems); | ||
} catch (err) { | ||
createErrorOrWarning('ARRAY_UNIQUE', err.message, val, path, dest); | ||
return; | ||
} | ||
break; | ||
} | ||
}; | ||
/** | ||
@@ -166,2 +422,379 @@ * Creates a new Swagger specification object. | ||
var validateContent = function validateContent (spec, rlOrSO, apiDeclarations) { | ||
var response = { | ||
errors: [], | ||
warnings: [] | ||
}; | ||
var authDefs = {}; // (1.2) | ||
var authRefs = {}; // (1.2) | ||
var pathDefs = []; // (1.2) | ||
var pathRefs = []; // (1.2) | ||
switch (spec.version) { | ||
case '1.2': | ||
// Build path model | ||
_.each(rlOrSO.apis, function (api, index) { | ||
// Validate duplicate resource paths | ||
validateNoExist(pathDefs, api.path, 'RESOURCE_PATH', 'Resource path', ['apis', index.toString(), 'path'], | ||
response.errors); | ||
if (pathDefs.indexOf(api.path) === -1) { | ||
pathDefs.push(api.path); | ||
} | ||
}); | ||
if (response.errors.length === 0) { | ||
// Build the authorization model | ||
_.each(rlOrSO.authorizations, function (authorization, name) { | ||
authDefs[name] = _.map(authorization.scopes, function (scope) { | ||
return scope.scope; | ||
}); | ||
}, {}); | ||
response.apiDeclarations = []; | ||
// Validate the API declarations | ||
_.each(apiDeclarations, function (apiDeclaration, index) { | ||
var result = response.apiDeclarations[index] = { | ||
errors: [], | ||
warnings: [] | ||
}; | ||
var apiAuthDefs = {}; | ||
var apiAuthRefs = {}; | ||
var modelDeps = {}; | ||
var modelRefs = {}; | ||
var addModelRef = function addModelRef (modelId, modelRef) { | ||
if (_.isUndefined(modelRefs[modelId])) { | ||
modelRefs[modelId] = []; | ||
} | ||
modelRefs[modelId].push(modelRef); | ||
}; | ||
var addScopeRef = function addScopeRef (authId, scopeId) { | ||
var auth; | ||
if (!_.isUndefined(apiAuthDefs[authId])) { | ||
// Local auth definition | ||
auth = apiAuthRefs[authId]; | ||
if (_.isUndefined(auth)) { | ||
auth = apiAuthRefs[authId] = []; | ||
} | ||
} else { | ||
// Global (Or missing in which case we'll assume global) | ||
auth = authRefs[authId]; | ||
if (_.isUndefined(auth)) { | ||
auth = authRefs[authId] = []; | ||
} | ||
} | ||
if (auth.indexOf(scopeId) === -1) { | ||
auth.push(scopeId); | ||
} | ||
}; | ||
var modelsById = {}; | ||
// Build the authorization model | ||
_.each(apiDeclaration.authorizations, function (authorization, name) { | ||
apiAuthDefs[name] = _.map(authorization.scopes, function (scope) { | ||
return scope.scope; | ||
}); | ||
}, {}); | ||
// Validate duplicate resource path | ||
validateNoExist(pathRefs, apiDeclaration.resourcePath, 'RESOURCE_PATH', 'Resource path', ['resourcePath'], | ||
result.errors); | ||
// Validate missing resource path definition | ||
validateExist(pathDefs, apiDeclaration.resourcePath, 'RESOURCE_PATH', 'Resource path', ['resourcePath'], | ||
result.errors); | ||
// Keep track of the seen paths | ||
if (pathRefs.indexOf(apiDeclaration.resourcePath) === -1) { | ||
pathRefs.push(apiDeclaration.resourcePath); | ||
} | ||
// Validate consumes/produces uniqueness | ||
_.each(['consumes', 'produces'], function (name) { | ||
validateNoDuplicates(apiDeclaration[name], 'API_' + name.toUpperCase(), 'API', [name], | ||
result.warnings); | ||
}); | ||
// Valdate APIs | ||
_.reduce(apiDeclaration.apis, function (seenApiPaths, api, index) { | ||
var aPath = ['apis', index.toString()]; | ||
// Validate operations | ||
_.reduce(api.operations, function (seenMethods, operation, index) { | ||
var oPath = aPath.concat(['operations', index.toString()]); | ||
// Validate consumes/produces uniqueness | ||
_.each(['consumes', 'produces'], function (name) { | ||
validateNoDuplicates(operation[name], 'OPERATION_' + name.toUpperCase(), 'Operation', | ||
oPath.concat(name), result.warnings); | ||
}); | ||
// Validate unique method | ||
validateNoExist(seenMethods, operation.method, 'OPERATION_METHOD', 'Operation method', | ||
oPath.concat('method'), result.errors); | ||
// Validate authorizations | ||
_.each(operation.authorizations, function (scopes, name) { | ||
// Validate missing authorization | ||
validateExist(_.uniq(Object.keys(apiAuthDefs).concat(Object.keys(authDefs))), name, 'AUTHORIZATION', | ||
'Authorization', oPath.concat(['authorizations', name]), result.errors); | ||
// Validate missing authorization scopes (Only when the authorization is not missing) | ||
_.each(scopes, function (scope, index) { | ||
if (!_.isUndefined(apiAuthDefs[name]) || !_.isUndefined(authDefs[name])) { | ||
// Validate missing authorization scope | ||
validateExist(_.uniq((apiAuthDefs[name] || []).concat(authDefs[name] || [])), scope.scope, | ||
'AUTHORIZATION_SCOPE', 'Authorization scope', | ||
oPath.concat(['authorizations', name, index.toString(), 'scope']), result.errors); | ||
} | ||
addScopeRef(name, scope.scope); | ||
}); | ||
}); | ||
// Validate parameters | ||
_.each(operation.parameters, function (parameter, index) { | ||
// Add model references from parameter type/items | ||
if (spec.primitives.indexOf(parameter.type) === -1) { | ||
addModelRef(parameter.type, oPath.concat(['parameters', index.toString(), 'type'])); | ||
} else if (parameter.type === 'array' && parameter.items.$ref) { | ||
addModelRef(parameter.items.$ref, oPath.concat(['parameters', index.toString(), 'items', '$ref'])); | ||
} | ||
if (_.isUndefined(parameter.defaultValue)) { | ||
return; | ||
} | ||
// Validate default value against constraints | ||
validateParameterConstraints(spec, parameter, parameter.defaultValue, | ||
oPath.concat('parameters', index.toString(), 'defaultValue'), | ||
result.errors); | ||
}); | ||
// Validate duplicate resource path | ||
validateNoExist(seenApiPaths, api.path, 'API_PATH', 'API path', aPath.concat('path'), result.errors); | ||
// Validate unique response code | ||
_.reduce(operation.responseMessages, function (seenResponseCodes, responseMessage, index) { | ||
validateNoExist(seenResponseCodes, responseMessage.code, 'RESPONSE_MESSAGE_CODE', 'Response message code', | ||
oPath.concat(['responseMessages', index.toString(), 'code']), result.errors); | ||
// Add model references from responseMessages responseModel | ||
if (responseMessage.responseModel) { | ||
addModelRef(responseMessage.responseModel, | ||
oPath.concat(['responseMessages', index.toString(), 'responseModel'])); | ||
} | ||
return seenResponseCodes.concat(responseMessage.code); | ||
}, []); | ||
// Add model references from type/items | ||
if (operation.type === 'array' && operation.items.$ref) { | ||
addModelRef(operation.items.$ref, oPath.concat(['items', '$ref'])); | ||
} else if (spec.primitives.indexOf(operation.type) === -1) { | ||
addModelRef(operation.type, oPath.concat(['type'])); | ||
} | ||
return seenMethods.concat(operation.method); | ||
}, []); | ||
return seenApiPaths.concat(api.path); | ||
}, []); | ||
// Validate models | ||
_.each(apiDeclaration.models, function (model, name) { | ||
var mPath = ['models', name]; | ||
var modelId = model.id; | ||
// Validate the model is not already defined (by id) | ||
validateNoExist(Object.keys(modelsById), modelId, 'MODEL_DEFINITION', 'Model', | ||
mPath.concat('id'), result.errors); | ||
// Add model references from properties and validate the default values | ||
_.each(model.properties, function (property, name) { | ||
var pPath = mPath.concat('properties', name); | ||
// Keep track of the model references | ||
if (property.$ref) { | ||
addModelRef(property.$ref, pPath.concat(['$ref'])); | ||
} else if (property.type === 'array' && property.items.$ref) { | ||
addModelRef(property.items.$ref, pPath.concat(['items', '$ref'])); | ||
} | ||
// Validate the default value against constraints | ||
if (!_.isUndefined(property.defaultValue)) { | ||
validateParameterConstraints(spec, property, property.defaultValue, pPath.concat('defaultValue'), | ||
result.errors); | ||
} | ||
}); | ||
// Validate required properties | ||
if (!_.isUndefined(model.required)) { | ||
var props = model.properties || {}; | ||
_.each(model.required, function (propName, index) { | ||
if (_.isUndefined(props[propName])) { | ||
createErrorOrWarning('MISSING_REQUIRED_MODEL_PROPERTY', | ||
'Model requires property but it is not defined: ' + propName, propName, | ||
mPath.concat(['required', index.toString()]), result.errors); | ||
} | ||
}); | ||
} | ||
// Keep track of model references in subTypes | ||
_.each(_.uniq(model.subTypes), function (subType, index) { | ||
var deps = modelDeps[subType]; | ||
addModelRef(subType, mPath.concat(['subTypes', index.toString()])); | ||
if (deps) { | ||
modelDeps[subType].push(name); | ||
} else { | ||
modelDeps[subType] = [name]; | ||
} | ||
}); | ||
// Keep track of the seen model ids | ||
modelsById[modelId] = model; | ||
}, []); | ||
// Validate unused authorizations | ||
_.each(_.difference(Object.keys(apiAuthDefs), Object.keys(apiAuthRefs)), function (unused) { | ||
createUnusedErrorOrWarning(apiDeclaration.authorizations[unused], unused, 'AUTHORIZATION', 'Authorization', | ||
['authorizations', unused], result.warnings); | ||
}); | ||
// Validate unused authorization scopes | ||
_.each(apiAuthDefs, function (scopes, name) { | ||
var path = ['authorizations', name]; | ||
var authDef = apiDeclaration.authorizations[name]; | ||
_.each(_.difference(scopes, apiAuthRefs[name] || []), function (scope, index) { | ||
var sIndex = scopes.indexOf(scope); | ||
createUnusedErrorOrWarning(authDef.scopes[sIndex], scope, 'AUTHORIZATION_SCOPE', | ||
'Authorization scope', path.concat(['scopes', sIndex.toString()]), | ||
result.warnings); | ||
}); | ||
}); | ||
// Identify missing models (referenced but not declared) | ||
_.each(_.difference(Object.keys(modelRefs), Object.keys(modelsById)), function (missing) { | ||
modelRefs[missing].forEach(function (modelRef) { | ||
createErrorOrWarning('UNRESOLVABLE_MODEL', 'Model could not be resolved: ' + missing, | ||
missing, modelRef, result.errors); | ||
}); | ||
}); | ||
// Identify unused models (declared but not referenced) | ||
_.each(_.difference(Object.keys(modelsById), Object.keys(modelRefs)), function (unused) { | ||
var modelName = Object.keys(apiDeclaration.models)[Object.keys(modelsById).indexOf(unused)]; | ||
createUnusedErrorOrWarning(modelsById[unused], unused, 'MODEL', 'Model', ['models', modelName], | ||
result.warnings); | ||
}); | ||
// Validate model inheritance issues | ||
identifyModelInheritanceIssues(modelDeps, apiDeclaration.models, result); | ||
}); | ||
// Validate unused resources | ||
_.each(_.difference(pathDefs, pathRefs), function (unused) { | ||
var index = _.map(rlOrSO.apis, function (api) { return api.path; }).indexOf(unused); | ||
createUnusedErrorOrWarning(rlOrSO.apis[index].path, unused, 'RESOURCE_PATH', 'Resource path', | ||
['apis', index.toString(), 'path'], response.errors); | ||
}); | ||
// Validate unused authorizations | ||
_.each(_.difference(Object.keys(authDefs), Object.keys(authRefs)), function (unused) { | ||
createUnusedErrorOrWarning(rlOrSO.authorizations[unused], unused, 'AUTHORIZATION', 'Authorization', | ||
['authorizations', unused], response.warnings); | ||
}); | ||
// Validate unused authorization scopes | ||
_.each(authRefs, function (scopes, name) { | ||
var path = ['authorizations', name]; | ||
_.each(_.difference(scopes, authRefs[name]), function (unused) { | ||
var index = scopes.indexOf(unused); | ||
createUnusedErrorOrWarning(rlOrSO.authorizations[name].scopes[index], unused, 'AUTHORIZATION_SCOPE', | ||
'Authorization scope', path.concat(['scopes', index.toString()]), | ||
response.warnings); | ||
}); | ||
}); | ||
} | ||
break; | ||
case '2.0': | ||
// Validate (for now) unique consumes/produces/schemes | ||
_.each(['consumes', 'produces', 'schemes'], function (name) { | ||
validateNoDuplicates(rlOrSO[name], 'API_' + name.toUpperCase(), 'API', [name], response.warnings); | ||
}); | ||
if (response.errors.length === 0 && response.warnings.length === 0) { | ||
// Validate the Paths | ||
_.each(rlOrSO.paths, function (path, name) { | ||
var aPath = ['paths', name]; | ||
// Validate parameter constraints | ||
_.each(path.parameters, function (parameter, index) { | ||
if (!_.isUndefined(parameter.schema)) { | ||
parameter = parameter.schema; | ||
validateParameterConstraints(spec, parameter, parameter.default, | ||
aPath.concat('parameters', index.toString(), 'schema', 'default'), | ||
response.errors); | ||
} | ||
}); | ||
// Validate the Operations | ||
_.each(path, function (operation, method) { | ||
var oPath = aPath.concat(method); | ||
if (method === 'parameters') { | ||
return; | ||
} | ||
// Validate (for now) consumes/produces/schemes uniqueness | ||
_.each(['consumes', 'produces', 'schemes'], function (name) { | ||
validateNoDuplicates(operation[name], 'OPERATION_' + name.toUpperCase(), 'Operation', | ||
oPath.concat(name), response.warnings); | ||
}); | ||
// Validate parameter constraints | ||
_.each(path.parameters, function (parameter, index) { | ||
if (!_.isUndefined(parameter.schema)) { | ||
parameter = parameter.schema; | ||
if (_.isUndefined(parameter.default)) { | ||
return; | ||
} | ||
validateParameterConstraints(spec, parameter, parameter.default, | ||
oPath.concat('parameters', index.toString(), 'schema', 'default'), | ||
response.errors); | ||
} | ||
}); | ||
}); | ||
// TODO: Validate the definitions | ||
// TODO: Validate definition references | ||
}); | ||
} | ||
break; | ||
} | ||
return response; | ||
}; | ||
/** | ||
@@ -171,3 +804,3 @@ * Returns the result of the validation of the Swagger document(s). | ||
* @param {object} rlOrSO - The Swagger Resource Listing (1.2) or Swagger Object (2.0) | ||
* @param {object[]} apiDeclarations - The array of Swagger API Declarations (1.2) | ||
* @param {object[]} [apiDeclarations] - The array of Swagger API Declarations (1.2) | ||
* | ||
@@ -210,6 +843,16 @@ * @returns undefined if validation passes or an object containing errors and/or warnings | ||
response.apiDeclarations[index] = validateWithSchema(this, 'apiDeclaration.json', apiDeclaration); | ||
if (response.apiDeclarations[index].errors.length > 0) { | ||
skipRemaining = true; | ||
// Skip the remaining validation | ||
return false; | ||
} | ||
}.bind(this)); | ||
} | ||
// TODO: Validate semantically | ||
// Validate semantically | ||
if (!skipRemaining) { | ||
response = validateContent(this, rlOrSO, apiDeclarations); | ||
} | ||
@@ -241,3 +884,6 @@ // Set the response | ||
// TODO: Validate semantically | ||
// Validate semantically | ||
if (!skipRemaining) { | ||
response = validateContent(this, rlOrSO); | ||
} | ||
@@ -244,0 +890,0 @@ // Set the response |
@@ -49,2 +49,8 @@ /* | ||
* @param {object} [options] - The middleware options | ||
* @param {(string|object|string[]) [options.controllers=./controllers] - If this is a string or string array, this is | ||
* the path, or paths, to find the controllers | ||
* in. If it's an object, the keys are the | ||
* controller "name" (as described above) and the | ||
* value is a function. | ||
* @param {boolean} [options.useStubs=false] - Whether or not to stub missing controllers and methods | ||
* | ||
@@ -74,5 +80,10 @@ * @returns the middleware function | ||
return function swaggerRouter (req, res, next) { | ||
var operation = req.swagger ? req.swagger.operation : undefined; | ||
var handler; | ||
var operation; | ||
if (req.swagger) { | ||
operation = req.swagger.operation; | ||
req.swagger.useStubs = options.useStubs; | ||
} | ||
if (!_.isUndefined(operation)) { | ||
@@ -79,0 +90,0 @@ handler = handlerCache[operation.nickname]; |
@@ -28,3 +28,3 @@ /* | ||
var _ = require('lodash'); | ||
var validators = require('../validators'); | ||
var validators = require('../../lib/validators'); | ||
@@ -31,0 +31,0 @@ /** |
@@ -51,2 +51,8 @@ /* | ||
* @param {object} [options] - The middleware options | ||
* @param {(string|object|string[]) [options.controllers=./controllers] - If this is a string or string array, this is | ||
* the path, or paths, to find the controllers | ||
* in. If it's an object, the keys are the | ||
* controller "name" (as described above) and the | ||
* value is a function. | ||
* @param {boolean} [options.useStubs=false] - Whether or not to stub missing controllers and methods | ||
* | ||
@@ -76,6 +82,11 @@ * @returns the middleware function | ||
return function swaggerRouter (req, res, next) { | ||
var operation = req.swagger ? req.swagger.operation : undefined; | ||
var handler; | ||
var handlerName; | ||
var operation; | ||
if (req.swagger) { | ||
operation = req.swagger.operation; | ||
req.swagger.useStubs = options.useStubs; | ||
} | ||
if (!_.isUndefined(operation)) { | ||
@@ -82,0 +93,0 @@ handlerName = (operation['x-swagger-router-controller'] ? |
@@ -28,3 +28,3 @@ /* | ||
var _ = require('lodash'); | ||
var validators = require('../validators'); | ||
var validators = require('../../lib/validators'); | ||
@@ -31,0 +31,0 @@ /** |
@@ -32,23 +32,34 @@ /* | ||
module.exports.handlerCacheFromDir = function handlerCacheFromDir (dir) { | ||
module.exports.handlerCacheFromDir = function handlerCacheFromDir (dirOrDirs) { | ||
var handlerCache = {}; | ||
var jsFileRegex = /\.js$/; | ||
var dirs = []; | ||
_.each(fs.readdirSync(dir), function (file) { | ||
var controllerName = file.replace(jsFileRegex, ''); | ||
var controller; | ||
if (_.isArray(dirOrDirs)) { | ||
dirs = dirOrDirs; | ||
} else { | ||
dirs.push(dirOrDirs); | ||
} | ||
if (file.match(jsFileRegex)) { | ||
controller = require(path.resolve(path.join(dir, controllerName))); | ||
_.each(dirs, function (dir) { | ||
_.each(fs.readdirSync(dir), function (file) { | ||
var controllerName = file.replace(jsFileRegex, ''); | ||
var controller; | ||
if (!_.isPlainObject(controller)) { | ||
throw new Error('Controller module expected to export an object: ' + path.join(dir, file)); | ||
} | ||
if (file.match(jsFileRegex)) { | ||
controller = require(path.resolve(path.join(dir, controllerName))); | ||
_.each(controller, function (value, name) { | ||
if (_.isFunction(value)) { | ||
handlerCache[controllerName + '_' + name] = value; | ||
if (_.isPlainObject(controller)) { | ||
_.each(controller, function (value, name) { | ||
var handlerId = controllerName + '_' + name; | ||
// TODO: Log this situation | ||
if (_.isFunction(value) && !handlerCache[handlerId]) { | ||
handlerCache[handlerId] = value; | ||
} | ||
}); | ||
} | ||
}); | ||
} | ||
} | ||
}); | ||
}); | ||
@@ -55,0 +66,0 @@ |
{ | ||
"name": "swagger-tools", | ||
"version": "0.5.0", | ||
"version": "0.5.1", | ||
"description": "Various tools for using and integrating with Swagger.", | ||
@@ -15,9 +15,9 @@ "main": "index.js", | ||
"bugs": { | ||
"url": "https://github.com/apigee/connect-swagger/issues" | ||
"url": "https://github.com/apigee-127/swagger-tools/issues" | ||
}, | ||
"homepage": "https://github.com/apigee/connect-swagger", | ||
"homepage": "https://github.com/apigee-127/swagger-tools", | ||
"license": "MIT", | ||
"repository": { | ||
"type": "git", | ||
"url": "git://github.com/apigee/connect-swagger.git" | ||
"url": "git://github.com/apigee-127/swagger-tools.git" | ||
}, | ||
@@ -24,0 +24,0 @@ "keywords": [ |
@@ -11,3 +11,3 @@ The project provides various tools for integrating and interacting with Swagger. This project is in its infancy but | ||
* Downloads: [![NPM Downloads Per Month](http://img.shields.io/npm/dm/swagger-tools.svg)](https://www.npmjs.org/package/swagger-tools) | ||
* License: [![License](http://img.shields.io/badge/license-MIT-blue.svg)](https://github.com/apigee-127/swagger-tools/blob/master/LICENSE) | ||
* License: [![License](http://img.shields.io/npm/l/swagger-tools.svg)](https://github.com/apigee-127/swagger-tools/blob/master/LICENSE) | ||
* Version: [![NPM Version](http://img.shields.io/npm/v/swagger-tools.svg)](https://www.npmjs.org/package/swagger-tools) | ||
@@ -24,3 +24,3 @@ | ||
based on the [JSON Schema][json-schema] associated with that version of the specification | ||
* ~~Semantic validation: Validates Swagger files above and beyond the structure of the file~~ _(Coming back shortly)_ | ||
* Semantic validation: Validates Swagger files above and beyond the structure of the file | ||
* Connect middleware for adding pertinent Swagger information to your requests (swagger-metadata) | ||
@@ -48,3 +48,3 @@ * Connect middleware for wiring request handlers to requests based on Swagger documentation (swagger-router) | ||
* `validate`: This is a function used to validate your Swagger document(s) based on the schema(s) for that | ||
specifications schemas ~~and semantically~~ _(Coming back soon)_ | ||
specifications schemas and semantically | ||
@@ -51,0 +51,0 @@ Here is an example showing how to use both versions of the `validate` function *(For more details, the sources are |
@@ -9,2 +9,16 @@ { | ||
"definitions": { | ||
"externalDocs": { | ||
"type": "object", | ||
"description": "information about external documentation", | ||
"required": [ "url" ], | ||
"properties": { | ||
"description": { | ||
"type": "string" | ||
}, | ||
"url": { | ||
"type": "string", | ||
"format": "uri" | ||
} | ||
} | ||
}, | ||
"info": { | ||
@@ -112,6 +126,4 @@ "type": "object", | ||
}, | ||
"docsUrl": { | ||
"type": "string", | ||
"format": "uri", | ||
"description": "Location of external documentation." | ||
"externalDocs": { | ||
"$ref": "#/definitions/externalDocs" | ||
}, | ||
@@ -157,5 +169,49 @@ "operationId": { | ||
} | ||
}, | ||
"security": { | ||
"$ref": "#/definitions/security" | ||
} | ||
} | ||
}, | ||
"pathItem": { | ||
"type": "object", | ||
"additionalProperties": false, | ||
"patternProperties": { | ||
"^x-": { | ||
"$ref": "#/definitions/vendorExtension" | ||
} | ||
}, | ||
"properties": { | ||
"$ref": { | ||
"type": "string" | ||
}, | ||
"get": { | ||
"$ref": "#/definitions/operation" | ||
}, | ||
"put": { | ||
"$ref": "#/definitions/operation" | ||
}, | ||
"post": { | ||
"$ref": "#/definitions/operation" | ||
}, | ||
"delete": { | ||
"$ref": "#/definitions/operation" | ||
}, | ||
"options": { | ||
"$ref": "#/definitions/operation" | ||
}, | ||
"head": { | ||
"$ref": "#/definitions/operation" | ||
}, | ||
"patch": { | ||
"$ref": "#/definitions/operation" | ||
}, | ||
"parameters": { | ||
"type": "array", | ||
"items": { | ||
"$ref": "#/definitions/parameter" | ||
} | ||
} | ||
} | ||
}, | ||
"responses": { | ||
@@ -237,4 +293,3 @@ "type": "object", | ||
"description": "Determines the location of the parameter.", | ||
"enum": [ "query", "header", "path", "formData" ], | ||
"default": "query" | ||
"enum": [ "query", "header", "path", "formData" ] | ||
}, | ||
@@ -251,3 +306,3 @@ "description": { | ||
"type": "string", | ||
"enum": [ "string", "number", "boolean", "integer", "array" ] | ||
"enum": [ "string", "number", "boolean", "integer", "array", "file" ] | ||
}, | ||
@@ -280,4 +335,3 @@ "format": { | ||
"description": "Determines the location of the parameter.", | ||
"enum": [ "body" ], | ||
"default": "body" | ||
"enum": [ "body" ] | ||
}, | ||
@@ -341,2 +395,3 @@ "description": { | ||
"required": { "$ref": "http://json-schema.org/draft-04/schema#/definitions/stringArray" }, | ||
"externalDocs": { "$ref": "#/definitions/externalDocs" }, | ||
"definitions": { | ||
@@ -354,2 +409,5 @@ "type": "object", | ||
"type": { "$ref": "http://json-schema.org/draft-04/schema#/properties/type" }, | ||
"example": { | ||
}, | ||
"allOf": { | ||
@@ -362,2 +420,6 @@ "type": "array", | ||
}, | ||
"security": { | ||
"type": "array", | ||
"description": "defines security requirements" | ||
}, | ||
"xml": { | ||
@@ -372,2 +434,16 @@ "properties": { | ||
"additionalProperties": false | ||
}, | ||
"tag": { | ||
"type": "object", | ||
"properties": { | ||
"externalDocs": { "$ref": "#/definitions/externalDocs" } | ||
}, | ||
"patternProperties": { | ||
"^x-": { | ||
"$ref": "#/definitions/vendorExtension" | ||
}, | ||
"^/.*[^\/]$": { | ||
"type": "string" | ||
} | ||
} | ||
} | ||
@@ -390,2 +466,5 @@ }, | ||
}, | ||
"externalDocs": { | ||
"$ref": "#/definitions/externalDocs" | ||
}, | ||
"host": { | ||
@@ -426,52 +505,12 @@ "type": "string", | ||
"type": "object", | ||
"description": "Relative paths to the individual endpoints. They should be relative to the 'basePath'.", | ||
"description": "Relative paths to the individual endpoints. They must be relative to the 'basePath'.", | ||
"patternProperties": { | ||
"^x-": { | ||
"$ref": "#/definitions/vendorExtension" | ||
}, | ||
"^/.*[^\/]$": { | ||
"$ref": "#/definitions/pathItem" | ||
} | ||
}, | ||
"additionalProperties": { | ||
"type": "object", | ||
"minProperties": 1, | ||
"additionalProperties": false, | ||
"patternProperties": { | ||
"^x-": { | ||
"$ref": "#/definitions/vendorExtension" | ||
} | ||
}, | ||
"properties": { | ||
"$ref": { | ||
"type": "string" | ||
}, | ||
"get": { | ||
"$ref": "#/definitions/operation" | ||
}, | ||
"put": { | ||
"$ref": "#/definitions/operation" | ||
}, | ||
"post": { | ||
"$ref": "#/definitions/operation" | ||
}, | ||
"delete": { | ||
"$ref": "#/definitions/operation" | ||
}, | ||
"options": { | ||
"$ref": "#/definitions/operation" | ||
}, | ||
"head": { | ||
"$ref": "#/definitions/operation" | ||
}, | ||
"patch": { | ||
"$ref": "#/definitions/operation" | ||
}, | ||
"parameters": { | ||
"type": "array", | ||
"items": { | ||
"$ref": "#/definitions/parameter" | ||
} | ||
} | ||
} | ||
} | ||
"additionalProperties": false | ||
}, | ||
@@ -486,5 +525,11 @@ "definitions": { | ||
"security": { | ||
"type": "array" | ||
"$ref": "#/definitions/security" | ||
}, | ||
"tags": { | ||
"type": "array", | ||
"items": { | ||
"$ref": "#/definitions/tag" | ||
} | ||
} | ||
} | ||
} |
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
No bug tracker
MaintenancePackage does not have a linked bug tracker in package.json.
Found 1 instance in 1 package
No website
QualityPackage does not have a website.
Found 1 instance in 1 package
125539
3064
1
1