Skip to content
This repository was archived by the owner on Aug 4, 2023. It is now read-only.
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
38 changes: 35 additions & 3 deletions docs/Middleware.md
Original file line number Diff line number Diff line change
Expand Up @@ -406,10 +406,10 @@ configuration. _(Note: This limitation, the need to manually create your `contr
issues #219 and #221 are completed.)_

A new option (since 0.8.4) is the addition of the `x-swagger-router-handle-subpaths` extension to the Swagger path
component. By setting this property to `true`, it indicates to Swagger Router that it should match and route all
requests to not only the specified path, but also any undeclared subpaths requested that do not match an explicitly
component. By setting this property to `true`, it indicates to Swagger Router that it should match and route all
requests to not only the specified path, but also any undeclared subpaths requested that do not match an explicitly
defined path in the Swagger. While you cannot specify wildcards in Swagger, this would be the spiritual equivalent
of wildcarding the end of the path something like `/pets/**`. For example, the following Swagger would cause
of wildcarding the end of the path something like `/pets/**`. For example, the following Swagger would cause
Swagger Router to match and route `/pets`, `/pets/1`, or even `/pets/this/is/an/arbitrary/route` to the `Pets`
controller:

Expand Down Expand Up @@ -530,9 +530,41 @@ suppose to return `application/x-yaml` but it returns `application/json`, it wil

* **options:** `object` The middleware options
* **options.validateResponse:** `[boolean=false]` Whether or not to validate responses
* **options.schemaValidator**: `[Object]` A custom validator to use instead of the built in validator. See below for details

**Returns**

### Custom JSON Schema Validator
The Swagger Validator Middleware provides built-in validation using [z-schema](https://github.com/zaggino/z-schema). This should
be sufficient for all cases using the official Swagger specification.

If you want to use a [Vendor Extension](http://swagger.io/specification/#vendorExtensions) in the validation of
[Schema Objects](http://swagger.io/specification/#schema-object-80), you can supply your own schema validator
to the swagger validator middleware in `options.schemaValidator`.

**WARNING:** This ONLY applies to **_schema objects_** in the body of a request or response. All other validation
continues to be handled by the _internal_ validators, including validation of the swagger definition itself,
the validation of path and query parameters.

A custom validator _MUST_ provide the following 2 functions in a compatible manner to z-schema's implementation:
- `validate(json, schema)`
- - Arguments:
- - - **json**: `String` The JSON to be validated
- - - **schema**: `String|Object` The JSON schema to validate against
- - Returns:
- - - `boolean` returns true on successful validation, false on error
- `getLastErrors()`
- - Returns:
- - - `[object[]]` An array of objects describing the error or `undefined` if no errors.

A custom validator _SHOULD_ include custom format functions to support the `format`s specified in the
[Swagger Specification](http://swagger.io/specification/#data-types-12) (i.e. `int32`, `int64`, etc.)
as these are not included in JSON Schema and are thus not in most validators by default.

Any custom `vendor extension`s (or `custom keywords`) added to the the validators MUST start with `"x-"`
to comply with the swagger specification. So `"x-example-keyword"` is accceptable, but `"exampleKeyword"`
is not. This does not apply to custom `format`s which DO NOT need to start with `"x-"`.

## Complete Example

Here is a complete example for using all middlewares documented above:
Expand Down
36 changes: 19 additions & 17 deletions lib/validators.js
Original file line number Diff line number Diff line change
Expand Up @@ -140,27 +140,29 @@ var throwErrorWithCode = function (code, msg) {
module.exports.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;
if (!_.isUndefined(obj.path) && !_.isUndefined(obj.code)) {
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 'responses':
defType = 'response';
break;

case 'schema':
defType += ' ' + obj.path[obj.path.length - 1];
case 'schema':
defType += ' ' + obj.path[obj.path.length - 1];

// no default
}
// no default
}

obj.message = 'Not a valid ' + defType + ' definition';
obj.message = 'Not a valid ' + defType + ' definition';
}
}

// Remove the params portion of the error
Expand Down
92 changes: 59 additions & 33 deletions middleware/swagger-validator.js
Original file line number Diff line number Diff line change
Expand Up @@ -64,38 +64,40 @@ var send400 = function (req, res, next, err) {
currentMessage = err.message;
validationMessage = 'Parameter (' + err.paramName + ') ';

switch (err.code) {
case 'ENUM_MISMATCH':
case 'MAXIMUM':
case 'MAXIMUM_EXCLUSIVE':
case 'MINIMUM':
case 'MINIMUM_EXCLUSIVE':
case 'MULTIPLE_OF':
case 'INVALID_TYPE':
if (err.code === 'INVALID_TYPE' && err.message.split(' ')[0] === 'Value') {
validationMessage += err.message.split(' ').slice(1).join(' ');
} else {
validationMessage += 'is ' + err.message.charAt(0).toLowerCase() + err.message.substring(1);
}
if (!_.isUndefined(err.code)) {
switch (err.code) {
case 'ENUM_MISMATCH':
case 'MAXIMUM':
case 'MAXIMUM_EXCLUSIVE':
case 'MINIMUM':
case 'MINIMUM_EXCLUSIVE':
case 'MULTIPLE_OF':
case 'INVALID_TYPE':
if (err.code === 'INVALID_TYPE' && err.message.split(' ')[0] === 'Value') {
validationMessage += err.message.split(' ').slice(1).join(' ');
} else {
validationMessage += 'is ' + err.message.charAt(0).toLowerCase() + err.message.substring(1);
}

break;
break;

case 'ARRAY_LENGTH_LONG':
case 'ARRAY_LENGTH_SHORT':
case 'MAX_LENGTH':
case 'MIN_LENGTH':
validationMessage += err.message.split(' ').slice(1).join(' ');
case 'ARRAY_LENGTH_LONG':
case 'ARRAY_LENGTH_SHORT':
case 'MAX_LENGTH':
case 'MIN_LENGTH':
validationMessage += err.message.split(' ').slice(1).join(' ');

break;
break;

case 'MAX_PROPERTIES':
case 'MIN_PROPERTIES':
validationMessage += 'properties are ' + err.message.split(' ').slice(4).join(' ');
case 'MAX_PROPERTIES':
case 'MIN_PROPERTIES':
validationMessage += 'properties are ' + err.message.split(' ').slice(4).join(' ');

break;
break;

default:
validationMessage += err.message.charAt(0).toLowerCase() + err.message.substring(1);
default:
validationMessage += err.message.charAt(0).toLowerCase() + err.message.substring(1);
}
}

// Replace the message
Expand All @@ -107,7 +109,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, schemaValidator, callback) {
var document = req.swagger.apiDeclaration || req.swagger.swaggerObject;
var version = req.swagger.apiDeclaration ? '1.2' : '2.0';
var isModel = mHelpers.isModelParameter(version, schema);
Expand Down Expand Up @@ -140,7 +142,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, schemaValidator);

oCallback();
} catch (err) {
Expand Down Expand Up @@ -169,7 +171,16 @@ var validateValue = function (req, schema, path, val, location, callback) {
callback();
}
};
var wrapEnd = function (req, res, next) {

/**
* Wraps res.end() so we can validate responses body etc.
*
* @param {object} options - the validator middleware options
* @param {object} req - the request
* @param {object} res - the response
* @param {callback} next - the callback
*/
var wrapEnd = function(options, req, res, next) {
var operation = req.swagger.operation;
var originalEnd = res.end;
var vPath = _.cloneDeep(req.swagger.operationPath);
Expand Down Expand Up @@ -281,7 +292,7 @@ var wrapEnd = function (req, res, next) {
if (_.isUndefined(schema)) {
sendData(swaggerVersion, res, val, encoding, true);
} else {
validateValue(req, schema, vPath, val, 'body', function (err) {
validateValue(req, schema, vPath, val, 'body', options.schemaValidator, function (err) {
if (err) {
throw err;
}
Expand Down Expand Up @@ -311,8 +322,9 @@ 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} [options.schemaValidator] - Provide a custom JSON Schema Validator to override the built-in one
*
* @returns the middleware function
* @returns {function} the middleware function
*/
exports = module.exports = function (options) {
debug('Initializing swagger-validator middleware');
Expand All @@ -321,7 +333,21 @@ exports = module.exports = function (options) {
options = {};
}

/**
* Check that any provided validator has the correct API.
* If it doesn't just throw an exception so the developer can catch it quickly.
*/
if (!_.isUndefined(options.schemaValidator)) {
if (
!_.isFunction(options.schemaValidator.validate) ||
!_.isFunction(options.schemaValidator.getLastErrors)
) {
throw new Error('Custom validator must provide z-schema compatible validate() and getLastErrors() functions');
}
}

debug(' Response validation: %s', options.validateResponse === true ? 'enabled' : 'disabled');
debug(' Customer validator: %s', _.isUndefined(options.schemaValidator) ? 'no' : 'yes');

return function swaggerValidator (req, res, next) {
var operation = req.swagger ? req.swagger.operation : undefined;
Expand All @@ -336,7 +362,7 @@ exports = module.exports = function (options) {
if (!_.isUndefined(operation)) {
// If necessary, override 'res.end'
if (options.validateResponse === true) {
wrapEnd(req, res, next);
wrapEnd(options, req, res, next);
}

debug(' Request validation:');
Expand Down Expand Up @@ -376,7 +402,7 @@ exports = module.exports = function (options) {
return oCallback();
}

validateValue(req, schema, paramPath, val, pLocation, oCallback);
validateValue(req, schema, paramPath, val, pLocation, options.schemaValidator, oCallback);

paramIndex++;
}, function (err) {
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
"schemas"
],
"devDependencies": {
"ajv": "^4.10.3",
"browserify": "^13.0.0",
"connect": "^3.4.0",
"del": "^2.2.0",
Expand Down
Loading