function initializeMiddleware(rlOrSO, resources, callback) {
var args;
var spec;
debug('Initializing middleware');
if (_.isUndefined(rlOrSO)) {
throw new Error('rlOrSO is required');
} else if (!_.isPlainObject(rlOrSO)) {
throw new TypeError('rlOrSO must be an object');
}
args = [rlOrSO];
spec = helpers.getSpec(helpers.getSwaggerVersion(rlOrSO), true);
debug(' Identified Swagger version: %s', spec.version);
if (spec.version === '1.2') {
if (_.isUndefined(resources)) {
throw new Error('resources is required');
} else if (!_.isArray(resources)) {
throw new TypeError('resources must be an array');
}
debug(' Number of API Declarations: %d', resources.length);
args.push(resources);
} else {
callback = arguments[1];
}
if (_.isUndefined(callback)) {
throw new Error('callback is required');
} else if (!_.isFunction(callback)) {
throw new TypeError('callback must be a function');
}
args.push(function (err, results) {
if (results && results.errors.length + _.reduce(results.apiDeclarations || [], function (count, apiDeclaration) {
return count += (apiDeclaration ? apiDeclaration.errors.length : 0);
}, 0) > 0) {
err = new Error('Swagger document(s) failed validation so the server cannot start');
err.failedValidation = true;
err.results = results;
}
debug(' Validation: %s', err ? 'failed' : 'succeeded');
try {
if (err) {
throw err;
}
callback({
// Create a wrapper to avoid having to pass the non-optional arguments back to the swaggerMetadata middleware
swaggerMetadata: function () {
var swaggerMetadata = require('./middleware/swagger-metadata');
return swaggerMetadata.apply(undefined, args.slice(0, args.length - 1));
},
swaggerRouter: require('./middleware/swagger-router'),
swaggerSecurity: require('./middleware/swagger-security'),
// Create a wrapper to avoid having to pass the non-optional arguments back to the swaggerUi middleware
swaggerUi: function (options) {
var swaggerUi = require('./middleware/swagger-ui');
var suArgs = [rlOrSO];
if (spec.version === '1.2') {
suArgs.push(_.reduce(resources, function (map, resource) {
map[resource.resourcePath] = resource;
return map;
}, {}));
}
suArgs.push(options || {});
return swaggerUi.apply(undefined, suArgs);
},
swaggerValidator: require('./middleware/swagger-validator')
});
} catch (err) {
if (process.env.RUNNING_SWAGGER_TOOLS_TESTS === 'true') {
// When running the swagger-tools test suite, we want to return an error instead of exiting the process. This
// does not mean that this function is an error-first callback but due to json-refs using Promises, we have to
// return the error to avoid the error being swallowed.
callback(err);
} else {
if (err.failedValidation === true) {
helpers.printValidationResults(spec.version, rlOrSO, resources, results, true);
} else {
console.error('Error initializing middleware');
console.error(err.stack);
}
process.exit(1);
}
}
});
spec.validate.apply(spec, args);
}n/a
createJsonValidator = function (schemas) {
var validator = new ZSchema({
breakOnFirstError: false,
reportPathAsArray: true
});
var result;
// Add the draft-04 spec
validator.setRemoteReference(draft04Url, draft04Json);
// Swagger uses some unsupported/invalid formats so just make them all pass
_.each(customJsonSchemaFormats, function (format) {
ZSchema.registerFormat(format, function () {
return true;
});
});
// Compile and validate the schemas
if (!_.isUndefined(schemas)) {
result = validator.compileSchema(schemas);
// If there is an error, it's unrecoverable so just blow the eff up
if (result === false) {
console.error('JSON Schema file' + (schemas.length > 1 ? 's are' : ' is') + ' invalid:');
_.each(validator.getLastErrors(), function (err) {
console.error(' ' + (_.isArray(err.path) ? JsonRefs.pathToPtr(err.path) : err.path) + ': ' + err.message);
});
throw new Error('Unable to create validator due to invalid JSON Schema');
}
}
return validator;
}...
.catch(callback);
} else {
callback();
}
};
var validateAgainstSchema = function (spec, schemaOrName, data, callback) {
var validator = _.isString(schemaOrName) ? spec.validators[schemaOrName] : helpers.createJsonValidator
();
helpers.registerCustomFormats(data);
try {
validators.validateAgainstSchema(schemaOrName, data, validator);
} catch (err) {
if (err.failedValidation) {
...formatResults = function (results) {
if (results) {
// Update the results based on its content to indicate success/failure accordingly
results = (results.errors.length + results.warnings.length +
_.reduce(results.apiDeclarations, function (count, aResult) {
if (aResult) {
count += aResult.errors.length + aResult.warnings.length;
}
return count;
}, 0) > 0) ? results : undefined;
}
return results;
}...
validateDefinitions(documentMetadata, results);
callback(undefined, results);
};
var validateSemantically = function (spec, rlOrSO, apiDeclarations, callback) {
var cbWrapper = function (err, results) {
callback(err, helpers.formatResults(results));
};
if (spec.version === '1.2') {
validateSwagger1_2(spec, rlOrSO, apiDeclarations, cbWrapper); // jshint ignore:line
} else {
validateSwagger2_0(spec, rlOrSO, cbWrapper); // jshint ignore:line
}
};
...getErrorCount = function (results) {
var errors = 0;
if (results) {
errors = results.errors.length;
_.each(results.apiDeclarations, function (adResults) {
if (adResults) {
errors += adResults.errors.length;
}
});
}
return errors;
}...
Specification.prototype.composeModel = function (apiDOrSO, modelIdOrRef, callback) {
var swaggerVersion = helpers.getSwaggerVersion(apiDOrSO);
var doComposition = function (err, results) {
var documentMetadata;
if (err) {
return callback(err);
} else if (helpers.getErrorCount(results) > 0) {
return handleValidationError(results, callback);
}
documentMetadata = getDocumentCache(apiDOrSO);
results = {
errors: [],
warnings: []
...getSpec = function (version, throwError) {
var spec;
version = coerceVersion(version);
spec = specCache[version];
if (_.isUndefined(spec)) {
switch (version) {
case '1.2':
spec = require('../lib/specs').v1_2; // jshint ignore:line
break;
case '2.0':
spec = require('../lib/specs').v2_0; // jshint ignore:line
break;
default:
if (throwError === true) {
throw new Error('Unsupported Swagger version: ' + version);
}
}
}
return spec;
}...
if (_.isUndefined(rlOrSO)) {
throw new Error('rlOrSO is required');
} else if (!_.isPlainObject(rlOrSO)) {
throw new TypeError('rlOrSO must be an object');
}
args = [rlOrSO];
spec = helpers.getSpec(helpers.getSwaggerVersion(rlOrSO), true);
debug(' Identified Swagger version: %s', spec.version);
if (spec.version === '1.2') {
if (_.isUndefined(resources)) {
throw new Error('resources is required');
} else if (!_.isArray(resources)) {
...getSwaggerVersion = function (document) {
return _.isPlainObject(document) ? coerceVersion(document.swaggerVersion || document.swagger) : undefined;
}...
if (_.isUndefined(rlOrSO)) {
throw new Error('rlOrSO is required');
} else if (!_.isPlainObject(rlOrSO)) {
throw new TypeError('rlOrSO must be an object');
}
args = [rlOrSO];
spec = helpers.getSpec(helpers.getSwaggerVersion(rlOrSO), true);
debug(' Identified Swagger version: %s', spec.version);
if (spec.version === '1.2') {
if (_.isUndefined(resources)) {
throw new Error('resources is required');
} else if (!_.isArray(resources)) {
...printValidationResults = function (version, apiDOrSO, apiDeclarations, results, printSummary) {
var hasErrors = getErrorCount(results) > 0;
var stream = hasErrors ? console.error : console.log;
var pluralize = function (string, count) {
return count === 1 ? string : string + 's';
};
var printErrorsOrWarnings = function (header, entries, indent) {
if (header) {
stream(header + ':');
stream();
}
_.each(entries, function (entry) {
stream(new Array(indent + 1).join(' ') + JsonRefs.pathToPtr(entry.path) + ': ' + entry.message);
if (entry.inner) {
printErrorsOrWarnings (undefined, entry.inner, indent + 2);
}
});
if (header) {
stream();
}
};
var errorCount = 0;
var warningCount = 0;
stream();
if (results.errors.length > 0) {
errorCount += results.errors.length;
printErrorsOrWarnings('API Errors', results.errors, 2);
}
if (results.warnings.length > 0) {
warningCount += results.warnings.length;
printErrorsOrWarnings('API Warnings', results.warnings, 2);
}
if (results.apiDeclarations) {
results.apiDeclarations.forEach(function (adResult, index) {
if (!adResult) {
return;
}
var name = apiDeclarations[index].resourcePath || index;
if (adResult.errors.length > 0) {
errorCount += adResult.errors.length;
printErrorsOrWarnings(' API Declaration (' + name + ') Errors', adResult.errors, 4);
}
if (adResult.warnings.length > 0) {
warningCount += adResult.warnings.length;
printErrorsOrWarnings(' API Declaration (' + name + ') Warnings', adResult.warnings, 4);
}
});
}
if (printSummary) {
if (errorCount > 0) {
stream(errorCount + ' ' + pluralize('error', errorCount) + ' and ' + warningCount + ' ' +
pluralize('warning', warningCount));
} else {
stream('Validation succeeded but with ' + warningCount + ' ' + pluralize('warning', warningCount));
}
}
stream();
}...
if (process.env.RUNNING_SWAGGER_TOOLS_TESTS === 'true') {
// When running the swagger-tools test suite, we want to return an error instead of exiting the process. This
// does not mean that this function is an error-first callback but due to json-refs using Promises, we have to
// return the error to avoid the error being swallowed.
callback(err);
} else {
if (err.failedValidation === true) {
helpers.printValidationResults(spec.version, rlOrSO, resources, results, true);
} else {
console.error('Error initializing middleware');
console.error(err.stack);
}
process.exit(1);
}
...registerCustomFormats = function (json) {
traverse(json).forEach(function () {
var name = this.key;
var format = this.node;
if (name === 'format' && _.indexOf(ZSchema.getRegisteredFormats(), format) === -1) {
ZSchema.registerFormat(format, function () {
return true;
});
}
});
}...
callback();
}
};
var validateAgainstSchema = function (spec, schemaOrName, data, callback) {
var validator = _.isString(schemaOrName) ? spec.validators[schemaOrName] : helpers.createJsonValidator();
helpers.registerCustomFormats(data);
try {
validators.validateAgainstSchema(schemaOrName, data, validator);
} catch (err) {
if (err.failedValidation) {
return callback(undefined, err.results);
} else {
...validateAgainstSchema = function (schemaOrName, data, validator) {
var sanitizeError = function (obj) {
// Make anyOf/oneOf errors more human readable (Issue 200)
var defType = ['additionalProperties', 'items'].indexOf(obj.path[obj.path.length - 1]) > -1 ?
'schema' :
obj.path[obj.path.length - 2];
if (['ANY_OF_MISSING', 'ONE_OF_MISSING'].indexOf(obj.code) > -1) {
switch (defType) {
case 'parameters':
defType = 'parameter';
break;
case 'responses':
defType = 'response';
break;
case 'schema':
defType += ' ' + obj.path[obj.path.length - 1];
// no default
}
obj.message = 'Not a valid ' + defType + ' definition';
}
// Remove the params portion of the error
delete obj.params;
delete obj.schemaId;
if (obj.inner) {
_.each(obj.inner, function (nObj) {
sanitizeError(nObj);
});
}
};
var schema = _.isPlainObject(schemaOrName) ? _.cloneDeep(schemaOrName) : schemaOrName;
// We don't check this due to internal usage but if validator is not provided, schemaOrName must be a schema
if (_.isUndefined(validator)) {
validator = helpers.createJsonValidator([schema]);
}
var valid = validator.validate(data, schema);
if (!valid) {
try {
throwErrorWithCode('SCHEMA_VALIDATION_FAILED', 'Failed schema validation');
} catch (err) {
err.results = {
errors: _.map(validator.getLastErrors(), function (err) {
sanitizeError(err);
return err;
}),
warnings: []
};
throw err;
}
}
}...
var validateAgainstSchema = function (spec, schemaOrName, data, callback) {
var validator = _.isString(schemaOrName) ? spec.validators[schemaOrName] : helpers.createJsonValidator();
helpers.registerCustomFormats(data);
try {
validators.validateAgainstSchema(schemaOrName, data, validator);
} catch (err) {
if (err.failedValidation) {
return callback(undefined, err.results);
} else {
return callback(err);
}
}
...validateArrayType = function (schema) {
// We have to do this manually for now
if (schema.type === 'array' && _.isUndefined(schema.items)) {
throwErrorWithCode('OBJECT_MISSING_REQUIRED_PROPERTY', 'Missing required property: items');
}
}n/a
validateContentType = function (gPOrC, oPOrC, reqOrRes) {
// http://www.w3.org/Protocols/rfc2616/rfc2616-sec7.html#sec7.2.1
var isResponse = typeof reqOrRes.end === 'function';
var contentType = isResponse ? reqOrRes.getHeader('content-type') : reqOrRes.headers['content-type'];
var pOrC = _.map(_.union(gPOrC, oPOrC), function (contentType) {
return contentType.split(';')[0];
});
if (!contentType) {
if (isResponse) {
contentType = 'text/plain';
} else {
contentType = 'application/octet-stream';
}
}
contentType = contentType.split(';')[0];
if (pOrC.length > 0 && (isResponse ?
true :
['POST', 'PUT'].indexOf(reqOrRes.method) !== -1) && pOrC.indexOf(contentType) === -1) {
throw new Error('Invalid content type (' + contentType + '). These are valid: ' + pOrC.join(', '));
}
}...
sendData(swaggerVersion, res, data, encoding, true);
return; // do NOT call next() here, doing so would execute remaining middleware chain twice
}
try {
// Validate the content type
try {
validators.validateContentType(req.swagger.apiDeclaration ?
req.swagger.apiDeclaration.produces :
req.swagger.swaggerObject.produces,
operation.produces, res);
} catch (err) {
err.failedValidation = true;
throw err;
...validateEnum = function (val, allowed) {
if (!_.isUndefined(allowed) && !_.isUndefined(val) && allowed.indexOf(val) === -1) {
throwErrorWithCode('ENUM_MISMATCH', 'Not an allowable value (' + allowed.join(', ') + '): ' + val);
}
}n/a
validateMaxItems = function (val, maxItems) {
if (!_.isUndefined(maxItems) && val.length > maxItems) {
throwErrorWithCode('ARRAY_LENGTH_LONG', 'Array is too long (' + val.length + '), maximum ' + maxItems);
}
}n/a
validateMaxLength = function (val, maxLength) {
if (!_.isUndefined(maxLength) && val.length > maxLength) {
throwErrorWithCode('MAX_LENGTH', 'String is too long (' + val.length + ' chars), maximum ' + maxLength);
}
}n/a
validateMaxProperties = function (val, maxProperties) {
var propCount = _.isPlainObject(val) ? Object.keys(val).length : 0;
if (!_.isUndefined(maxProperties) && propCount > maxProperties) {
throwErrorWithCode('MAX_PROPERTIES',
'Number of properties is too many (' + propCount + ' properties), maximum ' + maxProperties);
}
}n/a
validateMaximum = function (val, maximum, type, exclusive) {
var code = exclusive === true ? 'MAXIMUM_EXCLUSIVE' : 'MAXIMUM';
var testMax;
var testVal;
if (_.isUndefined(exclusive)) {
exclusive = false;
}
if (type === 'integer') {
testVal = parseInt(val, 10);
} else if (type === 'number') {
testVal = parseFloat(val);
}
if (!_.isUndefined(maximum)) {
testMax = parseFloat(maximum);
if (exclusive && testVal >= testMax) {
throwErrorWithCode(code, 'Greater than or equal to the configured maximum (' + maximum + '): ' + val);
} else if (testVal > testMax) {
throwErrorWithCode(code, 'Greater than the configured maximum (' + maximum + '): ' + val);
}
}
}n/a
validateMinItems = function (val, minItems) {
if (!_.isUndefined(minItems) && val.length < minItems) {
throwErrorWithCode('ARRAY_LENGTH_SHORT', 'Array is too short (' + val.length + '), minimum ' + minItems);
}
}n/a
validateMinLength = function (val, minLength) {
if (!_.isUndefined(minLength) && val.length < minLength) {
throwErrorWithCode('MIN_LENGTH', 'String is too short (' + val.length + ' chars), minimum ' + minLength);
}
}n/a
validateMinProperties = function (val, minProperties) {
var propCount = _.isPlainObject(val) ? Object.keys(val).length : 0;
if (!_.isUndefined(minProperties) && propCount < minProperties) {
throwErrorWithCode('MIN_PROPERTIES',
'Number of properties is too few (' + propCount + ' properties), minimum ' + minProperties);
}
}n/a
validateMinimum = function (val, minimum, type, exclusive) {
var code = exclusive === true ? 'MINIMUM_EXCLUSIVE' : 'MINIMUM';
var testMin;
var testVal;
if (_.isUndefined(exclusive)) {
exclusive = false;
}
if (type === 'integer') {
testVal = parseInt(val, 10);
} else if (type === 'number') {
testVal = parseFloat(val);
}
if (!_.isUndefined(minimum)) {
testMin = parseFloat(minimum);
if (exclusive && testVal <= testMin) {
throwErrorWithCode(code, 'Less than or equal to the configured minimum (' + minimum + '): ' + val);
} else if (testVal < testMin) {
throwErrorWithCode(code, 'Less than the configured minimum (' + minimum + '): ' + val);
}
}
}n/a
validateMultipleOf = function (val, multipleOf) {
if (!_.isUndefined(multipleOf) && val % multipleOf !== 0) {
throwErrorWithCode('MULTIPLE_OF', 'Not a multiple of ' + multipleOf);
}
}n/a
validatePattern = function (val, pattern) {
if (!_.isUndefined(pattern) && _.isNull(val.match(new RegExp(pattern)))) {
throwErrorWithCode('PATTERN', 'Does not match required pattern: ' + pattern);
}
}n/a
validateRequiredness = function (val, required) {
if (!_.isUndefined(required) && required === true && _.isUndefined(val)) {
throwErrorWithCode('REQUIRED', 'Is required');
}
}...
paramName = schema.name;
paramPath = swaggerVersion === '1.2' ?
req.swagger.operationPath.concat(['params', paramIndex.toString()]) :
parameter.path;
val = req.swagger.params[paramName].value;
// Validate requiredness
validators.validateRequiredness(val, schema.required);
// Quick return if the value is not present
if (_.isUndefined(val)) {
return oCallback();
}
validateValue(req, schema, paramPath, val, oCallback);
...validateSchemaConstraints = function (version, schema, path, val) {
var resolveSchema = function (schema) {
var resolved = schema;
if (resolved.schema) {
path = path.concat(['schema']);
resolved = resolveSchema(resolved.schema);
}
return resolved;
};
var type = schema.type;
var allowEmptyValue;
if (!type) {
if (!schema.schema) {
if (path[path.length - 2] === 'responses') {
type = 'void';
} else {
type = 'object';
}
} else {
schema = resolveSchema(schema);
type = schema.type || 'object';
}
}
allowEmptyValue = schema ? schema.allowEmptyValue === true : false;
try {
// Always perform this check even if there is no value
if (type === 'array') {
validateArrayType(schema);
}
// Default to default value if necessary
if (_.isUndefined(val)) {
val = version === '1.2' ? schema.defaultValue : schema.default;
path = path.concat([version === '1.2' ? 'defaultValue' : 'default']);
}
// If there is no explicit default value, return as all validations will fail
if (_.isUndefined(val)) {
return;
}
if (type === 'array') {
_.each(val, function (val, index) {
try {
validateSchemaConstraints(version, schema.items || {}, path.concat(index.toString()), val);
} catch (err) {
err.message = 'Value at index ' + index + ' ' + (err.code === 'INVALID_TYPE' ? 'is ' : '') +
err.message.charAt(0).toLowerCase() + err.message.substring(1);
throw err;
}
});
} else {
validateTypeAndFormat(version, val, type, schema.format, allowEmptyValue);
}
// Validate enum
validateEnum(val, schema.enum);
// Validate maximum
validateMaximum(val, schema.maximum, type, schema.exclusiveMaximum);
// Validate maxItems (Swagger 2.0+)
validateMaxItems(val, schema.maxItems);
// Validate maxLength (Swagger 2.0+)
validateMaxLength(val, schema.maxLength);
// Validate maxProperties (Swagger 2.0+)
validateMaxProperties(val, schema.maxProperties);
// Validate minimum
validateMinimum(val, schema.minimum, type, schema.exclusiveMinimum);
// Validate minItems
validateMinItems(val, schema.minItems);
// Validate minLength (Swagger 2.0+)
validateMinLength(val, schema.minLength);
// Validate minProperties (Swagger 2.0+)
validateMinProperties(val, schema.minProperties);
// Validate multipleOf (Swagger 2.0+)
validateMultipleOf(val, schema.multipleOf);
// Validate pattern (Swagger 2.0+)
validatePattern(val, schema.pattern);
// Validate uniqueItems
validateUniqueItems(val, schema.uniqueItems);
} catch (err) {
err.path = path;
throw err;
}
}...
if (!_.isUndefined(data) && data.indexOf(val) > -1) {
createErrorOrWarning('DUPLICATE_' + codeSuffix, msgPrefix + ' already defined: ' + val, path, dest);
}
};
var validateSchemaConstraints = function (documentMetadata, schema, path, results, skip) {
try {
validators.validateSchemaConstraints(documentMetadata.swaggerVersion, schema, path
, undefined);
} catch (err) {
if (!skip) {
createErrorOrWarning(err.code, err.message, err.path, results.errors);
}
}
};
...function validateTypeAndFormat(version, val, type, format, allowEmptyValue, skipError) {
var result = true;
var oVal = val;
// If there is an empty value and we allow empty values, the value is always valid
if (allowEmptyValue === true && val === '') {
return;
}
if (_.isArray(val)) {
_.each(val, function (aVal, index) {
if (!validateTypeAndFormat(version, aVal, type, format, allowEmptyValue, true)) {
throwErrorWithCode('INVALID_TYPE', 'Value at index ' + index + ' is not a valid ' + type + ': ' + aVal);
}
});
} else {
switch (type) {
case 'boolean':
// Coerce the value only for Swagger 1.2
if (version === '1.2' && _.isString(val)) {
if (val === 'false') {
val = false;
} else if (val === 'true') {
val = true;
}
}
result = _.isBoolean(val);
break;
case 'integer':
// Coerce the value only for Swagger 1.2
if (version === '1.2' && _.isString(val)) {
val = Number(val);
}
result = _.isFinite(val) && (Math.round(val) === val);
break;
case 'number':
// Coerce the value only for Swagger 1.2
if (version === '1.2' && _.isString(val)) {
val = Number(val);
}
result = _.isFinite(val);
break;
case 'string':
if (!_.isUndefined(format)) {
switch (format) {
case 'date':
result = isValidDate(val);
break;
case 'date-time':
result = isValidDateTime(val);
break;
}
}
break;
case 'void':
result = _.isUndefined(val);
break;
}
}
if (skipError) {
return result;
} else if (!result) {
throwErrorWithCode('INVALID_TYPE',
type !== 'void' ?
'Not a valid ' + (_.isUndefined(format) ? '' : format + ' ') + type + ': ' + oVal :
'Void does not allow a value');
}
}n/a
validateUniqueItems = function (val, isUnique) {
if (!_.isUndefined(isUnique) && _.uniq(val).length !== val.length) {
throwErrorWithCode('ARRAY_UNIQUE', 'Does not allow duplicate values: ' + val.join(', '));
}
}n/a