diff --git a/README.md b/README.md index 2a3f531ae5..d620e61d92 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,19 @@ +This is a fork from https://github.com/apigee-127/swagger-tools + +I have added support in swagger-validator to plugin custom formats + +## Usage +``` + const formatValidators = { + uuid: function(value) { + let valid = /*do some validation*/ + return valid; + } + }; + const swaggerValidator = middleware.swaggerValidator({formatValidators}); +``` + + The project provides various tools for integrating and interacting with Swagger. This project is in its infancy but what is within the repository should be fully tested and reusable. Please visit the [issue tracker][project-issues] to see what issues we are aware of and what features/enhancements we are working on. Otherwise, feel free to review the diff --git a/index.js b/index.js index 7d6ad9134a..da989f0717 100644 --- a/index.js +++ b/index.js @@ -87,7 +87,7 @@ var initializeMiddleware = function initializeMiddleware (rlOrSO, resources, cal swaggerMetadata: function () { var swaggerMetadata = require('./middleware/swagger-metadata'); - return swaggerMetadata.apply(undefined, args.slice(0, args.length - 1)); + return swaggerMetadata.apply(undefined, args.slice(0, args.length - 1).concat(Array.from(arguments))); }, swaggerRouter: require('./middleware/swagger-router'), swaggerSecurity: require('./middleware/swagger-security'), diff --git a/lib/helpers.js b/lib/helpers.js index ccbe0c3645..80c8bd518b 100644 --- a/lib/helpers.js +++ b/lib/helpers.js @@ -47,7 +47,8 @@ module.exports.registerCustomFormats = function (json) { }); }; -module.exports.createJsonValidator = function (schemas) { +module.exports.createJsonValidator = function (schemas, formatValidators) { + formatValidators = formatValidators || {} var validator = new ZSchema({ breakOnFirstError: false, reportPathAsArray: true @@ -64,6 +65,10 @@ module.exports.createJsonValidator = function (schemas) { }); }); + for (let format in formatValidators) { + ZSchema.registerFormat(format, formatValidators[format]); + } + // Compile and validate the schemas if (!_.isUndefined(schemas)) { result = validator.compileSchema(schemas); diff --git a/lib/validators.js b/lib/validators.js index e858ee37dd..1d0bae5d4f 100644 --- a/lib/validators.js +++ b/lib/validators.js @@ -121,7 +121,8 @@ var throwErrorWithCode = function (code, msg) { throw err; }; -module.exports.validateAgainstSchema = function (schemaOrName, data, validator) { +module.exports.validateAgainstSchema = function (schemaOrName, data, validator, formatValidators) { + formatValidators = formatValidators || {}; 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 ? @@ -161,7 +162,7 @@ module.exports.validateAgainstSchema = function (schemaOrName, data, validator) // 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]); + validator = helpers.createJsonValidator([schema], formatValidators); } var valid = validator.validate(data, schema); @@ -460,11 +461,14 @@ module.exports.validateRequiredness = function (val, required) { * @param {string} type - The parameter type * @param {string} format - The parameter format * @param {boolean} [skipError=false] - Whether or not to skip throwing an error (Useful for validating arrays) + * @param {object} [formatValidators] * * @throws Error if the value is not the proper type or format */ var validateTypeAndFormat = module.exports.validateTypeAndFormat = - function validateTypeAndFormat (version, val, type, format, allowEmptyValue, skipError) { + function validateTypeAndFormat (version, val, type, format, allowEmptyValue, skipError, formatValidators) { + formatValidators = formatValidators || {}; + var validator = formatValidators[format]; var result = true; var oVal = val; @@ -512,12 +516,17 @@ var validateTypeAndFormat = module.exports.validateTypeAndFormat = case 'string': if (!_.isUndefined(format)) { switch (format) { - case 'date': - result = isValidDate(val); - break; - case 'date-time': - result = isValidDateTime(val); - break; + case 'date': + result = validator ? validator(val) : isValidDate(val); + break; + case 'date-time': + result = validator ? validator(val) : isValidDateTime(val); + break; + default: + if(validator) { + result = validator(val); + } + break; } } break; @@ -561,7 +570,8 @@ var validateUniqueItems = module.exports.validateUniqueItems = function (val, is * * @throws Error if any validation failes */ -var validateSchemaConstraints = module.exports.validateSchemaConstraints = function (version, schema, path, val) { +var validateSchemaConstraints = module.exports.validateSchemaConstraints = function (version, schema, path, val, formatValidators) { + formatValidators = formatValidators || {}; var resolveSchema = function (schema) { var resolved = schema; @@ -613,7 +623,7 @@ var validateSchemaConstraints = module.exports.validateSchemaConstraints = funct if (type === 'array') { _.each(val, function (val, index) { try { - validateSchemaConstraints(version, schema.items || {}, path.concat(index.toString()), val); + validateSchemaConstraints(version, schema.items || {}, path.concat(index.toString()), val, formatValidators); } catch (err) { err.message = 'Value at index ' + index + ' ' + (err.code === 'INVALID_TYPE' ? 'is ' : '') + err.message.charAt(0).toLowerCase() + err.message.substring(1); @@ -622,7 +632,7 @@ var validateSchemaConstraints = module.exports.validateSchemaConstraints = funct } }); } else { - validateTypeAndFormat(version, val, type, schema.format, allowEmptyValue); + validateTypeAndFormat(version, val, type, schema.format, allowEmptyValue, false, formatValidators); } // Validate enum diff --git a/middleware/swagger-metadata.js b/middleware/swagger-metadata.js index 1a7521aab4..2e76b20a4f 100644 --- a/middleware/swagger-metadata.js +++ b/middleware/swagger-metadata.js @@ -38,9 +38,6 @@ var pathToRegexp = require('path-to-regexp'); var bodyParserOptions = { extended: false }; -var multerOptions = { - storage: multer.memoryStorage() -}; var textBodyParserOptions = { type: '*/*' }; @@ -76,7 +73,21 @@ var bodyParser = function (req, res, next) { next(); } }; -var realMultiPartParser = multer(multerOptions); + +function imageFilter(req, file, cb){ + // accept only image files. + if (!file.originalname.match(/\.(jpg|jpeg|png)$/)) { + return cb(new Error('Only image files are allowed'), false); + } else if (!file.mimetype.match(/image\/.*/)) { + return cb(new Error('Only image files are allowed'), false); + } + cb(null, true); +}; + +var realMultiPartParser; +var imageMultiPartParser; +var handleFileUpload; + var makeMultiPartParser = function (parser) { return function (req, res, next) { if (_.isUndefined(req.files)) { @@ -172,7 +183,7 @@ var processOperationParameters = function (swaggerMetadata, pathKeys, pathMatch, case 'formData': if (paramType.toLowerCase() === 'file') { // Swagger spec does not allow array of files, so maxCount should be 1 - fields.push({name: paramName, maxCount: 1}); + fields.push({name: paramName, maxCount: 1, format: parameter.schema.format}); } break; } @@ -181,12 +192,31 @@ var processOperationParameters = function (swaggerMetadata, pathKeys, pathMatch, }, []); var contentType = req.headers['content-type']; - if (multiPartFields.length) { - // If there are files, use multer#fields - parsers.push(makeMultiPartParser(realMultiPartParser.fields(multiPartFields))); - } else if (contentType && contentType.split(';')[0] === 'multipart/form-data') { - // If no files but multipart form, use empty multer#array for text fields - parsers.push(makeMultiPartParser(realMultiPartParser.array())); + + if (handleFileUpload) { + + if (multiPartFields.length) { + // If there are files, use multer#fields + + let imageFiles = multiPartFields.filter(field => field.format === 'image'); + + if (imageFiles.length === 0) { + parsers.push(makeMultiPartParser(realMultiPartParser.fields(multiPartFields))); + } else if (imageFiles.length === multiPartFields.length) { + parsers.push(makeMultiPartParser(imageMultiPartParser.fields(multiPartFields))); + } else { + multiPartFields.forEach(field => { + if (field.format === 'image') { + parsers.push(makeMultiPartParser(imageMultiPartParser.fields([field]))); + } else { + } parsers.push(makeMultiPartParser(realMultiPartParser.fields([field]))); + }); + } + + } else if (contentType && contentType.split(';')[0] === 'multipart/form-data') { + // If no files but multipart form, use empty multer#array for text fields + parsers.push(makeMultiPartParser(realMultiPartParser.array())); + } } async.map(parsers, function (parser, callback) { @@ -358,6 +388,8 @@ var processSwaggerDocuments = function (rlOrSO, apiDeclarations) { return apiCache; }; +let apiCache; + /** * Middleware for providing Swagger information to downstream middleware and request handlers. For all requests that * match a Swagger path, 'req.swagger' will be provided with pertinent Swagger details. Since Swagger 1.2 and 2.0 @@ -370,10 +402,22 @@ var processSwaggerDocuments = function (rlOrSO, apiDeclarations) { * * @returns the middleware function */ -exports = module.exports = function (rlOrSO, apiDeclarations) { +exports = module.exports = function (rlOrSO, options) { + options = options || {}; + handleFileUpload = options.handleFileUpload !== false; + let apiDeclarations = undefined; + + if(handleFileUpload) { + let multerOptions = options.multer || { + storage: multer.memoryStorage() + }; + realMultiPartParser = multer(multerOptions); + imageMultiPartParser = multer(Object.assign({}, multerOptions, {imageFilter})); + } + debug('Initializing swagger-metadata middleware'); - var apiCache = processSwaggerDocuments(rlOrSO, apiDeclarations); + apiCache = processSwaggerDocuments(rlOrSO, apiDeclarations); var swaggerVersion = cHelpers.getSwaggerVersion(rlOrSO); if (_.isUndefined(rlOrSO)) { @@ -389,18 +433,13 @@ exports = module.exports = function (rlOrSO, apiDeclarations) { throw new TypeError('apiDeclarations must be an array'); } } - - return function swaggerMetadata (req, res, next) { + + return function swaggerMetadata(req, res, next) { var method = req.method.toLowerCase(); var path = parseurl(req).pathname; - var cacheEntry; - var match; var metadata; - cacheEntry = apiCache[path] || _.find(apiCache, function (metadata) { - match = metadata.re.exec(path); - return _.isArray(match); - }); + let {cacheEntry, match} = getCacheEntry(path); debug('%s %s', req.method, req.url); debug(' Is a Swagger path: %s', !_.isUndefined(cacheEntry)); @@ -452,3 +491,18 @@ exports = module.exports = function (rlOrSO, apiDeclarations) { } }; }; + +exports.getCacheEntry = getCacheEntry; + +function getCacheEntry(path) { + let match; + + let cacheEntry = apiCache[path] || _.find(apiCache, function (metadata) { + match = metadata.re.exec(path); + return _.isArray(match); + }); + return { + match, + cacheEntry + } +} diff --git a/middleware/swagger-validator.js b/middleware/swagger-validator.js index c968cdcdb2..fec6c5777c 100644 --- a/middleware/swagger-validator.js +++ b/middleware/swagger-validator.js @@ -107,7 +107,7 @@ var send400 = function (req, res, next, err) { return next(err); }; -var validateValue = function (req, schema, path, val, location, callback) { +var validateValue = function (req, schema, path, val, location, callback, formatValidators) { var document = req.swagger.apiDeclaration || req.swagger.swaggerObject; var version = req.swagger.apiDeclaration ? '1.2' : '2.0'; var isModel = mHelpers.isModelParameter(version, schema); @@ -116,7 +116,7 @@ var validateValue = function (req, schema, path, val, location, callback) { val = mHelpers.convertValue(val, schema, mHelpers.getParameterType(schema), location); try { - validators.validateSchemaConstraints(version, schema, path, val); + validators.validateSchemaConstraints(version, schema, path, val, formatValidators); } catch (err) { return callback(err); } @@ -140,7 +140,7 @@ var validateValue = function (req, schema, path, val, location, callback) { schema.type), aVal, oCallback); } else { try { - validators.validateAgainstSchema(schema.schema ? schema.schema : schema, val); + validators.validateAgainstSchema(schema.schema ? schema.schema : schema, val, undefined, formatValidators); oCallback(); } catch (err) { @@ -311,7 +311,7 @@ var wrapEnd = function (req, res, next) { * * @param {object} [options] - The middleware options * @param {boolean} [options.validateResponse=false] - Whether or not to validate responses - * + * @param {object} [optinos.formatValidators] * @returns the middleware function */ exports = module.exports = function (options) { @@ -320,6 +320,8 @@ exports = module.exports = function (options) { if (_.isUndefined(options)) { options = {}; } + + var formatValidators = (options && options.formatValidators || {}); debug(' Response validation: %s', options.validateResponse === true ? 'enabled' : 'disabled'); @@ -376,7 +378,7 @@ exports = module.exports = function (options) { return oCallback(); } - validateValue(req, schema, paramPath, val, pLocation, oCallback); + validateValue(req, schema, paramPath, val, pLocation, oCallback, formatValidators); paramIndex++; }, function (err) { diff --git a/package.json b/package.json index 5b5f80af41..43b5dad9b2 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { - "name": "swagger-tools", - "version": "0.10.3", + "name": "x-swagger-tools", + "version": "0.10.12", "description": "Various tools for using and integrating with Swagger.", "main": "index.js", "scripts": {