Skip to content
This repository has been archived by the owner on Aug 4, 2023. It is now read-only.

add support for adding custom format validations #610

Open
wants to merge 16 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -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
Expand Down
2 changes: 1 addition & 1 deletion index.js
Original file line number Diff line number Diff line change
Expand Up @@ -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'),
Expand Down
7 changes: 6 additions & 1 deletion lib/helpers.js
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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);
Expand Down
34 changes: 22 additions & 12 deletions lib/validators.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 ?
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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;

Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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;

Expand Down Expand Up @@ -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);
Expand All @@ -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
Expand Down
96 changes: 75 additions & 21 deletions middleware/swagger-metadata.js
Original file line number Diff line number Diff line change
Expand Up @@ -38,9 +38,6 @@ var pathToRegexp = require('path-to-regexp');
var bodyParserOptions = {
extended: false
};
var multerOptions = {
storage: multer.memoryStorage()
};
var textBodyParserOptions = {
type: '*/*'
};
Expand Down Expand Up @@ -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)) {
Expand Down Expand Up @@ -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;
}
Expand All @@ -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) {
Expand Down Expand Up @@ -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
Expand All @@ -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)) {
Expand All @@ -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));
Expand Down Expand Up @@ -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
}
}
12 changes: 7 additions & 5 deletions middleware/swagger-validator.js
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand All @@ -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);
}
Expand All @@ -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) {
Expand Down Expand Up @@ -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) {
Expand All @@ -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');

Expand Down Expand Up @@ -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) {
Expand Down
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -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": {
Expand Down