Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support multiple custom validators/sanitisers with schemas #552

Closed
gustavohenke opened this issue Mar 29, 2018 · 8 comments · Fixed by #1180
Closed

Support multiple custom validators/sanitisers with schemas #552

gustavohenke opened this issue Mar 29, 2018 · 8 comments · Fixed by #1180

Comments

@gustavohenke
Copy link
Member

We can't use multiple custom validators/sanitisers when we use schemas, as an object can't have multiple keys with the same name.

This makes it impossible to break them into smaller pieces of validation/sanitisation.
Example taken from #550: one that does only email DNS validation, another one that does only email availability validation, etc.

The only way I can imagine for now is to accept an array of custom validators.
Any other suggestions are welcome.

@Tech56
Copy link

Tech56 commented Apr 25, 2018

I would like to be able to have such a possibility as well

@prettymuchbryce
Copy link

I have left an API suggestion in another issue which would tangentially solve this problem. Let me know what you think.

#527 (comment)

@OmgImAlexis
Copy link

For those that need a work around for now this should help.

/**
 * Runs an array of validators over a value
 */
const validationArray = validators => {
	let errors = [];

	return {
		custom: {
			errorMessage: () => {
				return errors.length >= 1 ? errors.join('\n') : 'value is invalid';
			},
			options: (value, opts) => {
				errors = [];
				const results = validators.map((validator, index) => {
					logger.debug(`validating ${value} [${index + 1}/${validators.length}]`);
					const result = validator.validate(value, opts);
					// If validation failed set current Error
					if (result === false) {
						const errorMessage = validator.errorMessage || `${value} is invalid`;
						logger.debug(`setting error to ${errorMessage}`);
						errors.push(errorMessage);
					}
					return result;
				});
				return results.every(value => value === true);
			}
		}
	};
};

To use it pass an array of functions that return a validate field and optionally an errorMessage field.

/**
 * Ensures field value is from provided array
 */
const oneOf = array => ({
	errorMessage: `value must be one of [${array.join(', ').replace(/, ([^,]*)$/, ' or $1')}]`,
	validate: value => array.includes(value)
});

/**
 * Ensures field value is boolean
 */
const isBoolean = {
	validate: value => typeof value === 'boolean'
};

/**
 * Schema
 */
export const schema = {
	type: {
		in: ['params'],
		...validationArray([
            isBoolean,
			oneOf([
				'virtual',
				'ipv4',
				'ipv6'
			])
		])
	}
};

@sobiodarlington
Copy link

You can do multiple validations in the "custom" prop

function isValidPhone() {
	
}

function anotherValidation() {
	
}

Validation.testSchema= {
	'user.phone': {
        in: ['body'],
        custom: {
            options(value, { req, location, path }) {
                if (!isValidPhone(value)) {
                    this.message = 'Invalid phone';
                    return false;
                } else if (!anotherValidation(value)) {
                    this.message = 'Invalid validation';
                    return false;
                }

                return true;
            },
        },
        errorMessage: 'Phone id required',
    },
};

pcraig3 added a commit to cds-snc/cra-claim-tax-benefits that referenced this issue Oct 28, 2019
Previously, we would sanitize the input SIN to remove spaces or
hyphens before validating it.

Unfortunately, sanitizing it would also change mutate the submitted
value, so repopulating the form would show an error but then a
potentially a value that they never typed in.

Like if I submitted "123 456 78", it would come back as "12345678".

So it's telling me I'm wrong, but it also rewrote what I sent in.
That's not really fair.

Changed the SIN validation to _only_ use our custom validation method
and send back the same error messages. The code is uglier but
it has tests now, so should be okay.

Behaviour should be the same as previously.

Also, added an underscore to my internal functions because they're
supposed to be private.

Inspired by @OmgImAlexis' comment 👇

Source:
- express-validator/express-validator#552 (comment)
pcraig3 added a commit to cds-snc/cra-claim-tax-benefits that referenced this issue Oct 28, 2019
Previously, we would sanitize the input SIN to remove spaces or
hyphens before validating it.

Unfortunately, sanitizing it would also mutate the submitted
value, so repopulating the form would show them a validation error
and then a value they probably never typed in.

Like if I submitted "123 456 78", it would come back as "12345678".

So it's telling me I'm wrong, but it also rewrote what I sent in.
That's not really fair.

Changed the SIN validation to _only_ use our custom validation method
and send back the same error messages. The code is uglier but
it has tests now, so should be okay.

Behaviour should be the same as previously.

Also, added an underscore to my internal functions because they're
supposed to be private.

Inspired by @OmgImAlexis' comment 👇

Source:
- express-validator/express-validator#552 (comment)
@sobiodarlington
Copy link

This is a workaround I use.
extend.js

'use strict';

const expressValidator = require('express-validator');
const customValidators = require('./custom-validators');

for (const [name, fn] of Object.entries(customValidators)) {
    if (!expressValidator.validator[name]) {
        expressValidator.validator[name] = fn;
    }
}

Require extend.js before your routes

@tayyabferozi

This comment was marked as off-topic.

@hiranyasarma44
Copy link

Multiple validation can be used by following schema. Also bail can be used to stop validation on error.

import express, { json, urlencoded, Router } from "express";
import { checkSchema, validationResult } from "express-validator";
import mongoose from "mongoose";

const { isValidObjectId } = mongoose

const app = express();

const router = Router();

app.use(json())
app.use(urlencoded({ extended : false }))

const isObjectId = (value) => {
  if (value !== undefined && !isValidObjectId(value)) {
    throw new Error();
  }
  return value;
};
const validationSchema = {
  id: {
    in: ["body"],
    notEmpty: {
      errorMessage: "ID is required",
      bail: true,
    },
    custom: {
      options: (value) => isObjectId(value),
      errorMessage: "ID should be a valid object id.",
      bail: true,
    },
  },
};

router.post("/", checkSchema(validationSchema), (req, res) => {
  res.send(validationResult(req));
});


app.use(router)

app.listen(5000, () => console.log('Server running at http://localhost:5000/'))

My package.json

{
"scripts": {
    "start": "node dist/app.js",
    "dev": "nodemon src/app.js"
  },
  "dependencies": {
    "express": "^4.17.1",
    "express-validator": "^6.12.1",
    "mongoose": "^6.0.7"
  },
  "devDependencies": {
    "nodemon": "^2.0.12"
  }
}

I guess, this is what you all are looking for.

@Sundarasan
Copy link

Workaround to have multiple custom validators with their own error message,

const validationMiddleware = validator.checkSchema({
  titleLabel: {
    in: 'body',
    isString: {
      errorMessage: 'titleLabel name must be a string'
    },
    custom: {
      options: (value, params) => {

        // Custom Validator 1
        if (!isSafe(value)) {
          params.errorMessage = 'titleLabel has malicious content'
          return false
        }

        // Custom Validator 2 
        if (hasRestrictedChars(value)) {
          params.errorMessage = 'titleLabel has restricted characters'
          return false
        }

        return true
      },
      errorMessage: (_, params) => {
        if (params.errorMessage) {
          return params.errorMessage
        }
        return 'Invalid titleLabel'
      }
    }
  }
})

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging a pull request may close this issue.

8 participants