Extending Koa Validation

Koa Validation allows for new rules, filters or actions to be added to the library. Koa Validation returns the rules, filters and actions classes from the plugin and extending the module is simply adding new methods to the required classes.

In order to extend the Koa Validation classes, check out the code below:

//Field Rules Class
let Rules = require('koa-validation').Rules;

//Field Filters Class
let Filters = require('koa-validation').Filters;

//Required Rules Class
let RequiredRules = require('koa-validation').RequiredRules;

//File Rules Class
let FileRules = require('koa-validation').FileRules;

//File Actions Class
let FileActions = require('koa-validation').FileActions;

Extending Rules

To extend a rule directly add to its parent class using the class prototype. Also make sure that the extended function is a generator.

Each Field Rule function takes the following arguments:

  • field - the field name
  • value - the value of the field
  • args - This field is optional and contains and arguments provided to the rule
  • messages - A custom message to the validation if provided

Each File Rule function takes the following arguments:

  • field - the field name
  • file - the file object created by koa-better-body
  • deleteOnFail - Boolean indicating whether the temp file needs to be deleted if validation fails
  • args - This field is optional and contains and arguments provided to the rule
  • messages - A custom message to the validation if provided
var app = require('koa')();
var router = (new require('koa-router'))();
var koaBody = require('koa-better-body');

var validate = require('koa-validation');

app.use(koaBody({
    'multipart': true
}));

let isGzip = require('is_gzip');
let fs = require('fs');

//Field Rules Class
let Rules = require('koa-validation').Rules;

//Required Rules Class
let RequiredRules = require('koa-validation').RequiredRules;

//File Rules Class
let FileRules = require('koa-validation').FileRules;

Rules.prototype.array = function *(field, value, message){
	if(typeof value === 'object' && Array.isArray(value)){
    return true;
	}else{
		this.validator.addError(field, 'rule', 'array', message || 'The value of the field needs to be an array');
            return false;
  }
}

RequiredRules.prototype.requredIfFooIs = function *(field, value, args, message){
	if(typeof this.validator.fields['foo'] !== 'undefined' && this.validator.fields['name'] == args[0]){
  	return true;
  }else{
    this.validator.addError(field, 'rule', message || 'This field is mandatory as name exists');
  	return false;
  }
}

FileRules.prototype.isGzipped = function *(field, file, deleteOnFail, message){
	if(isGzip(fs.readFileSync(file.path))){
  	return true;
  }else{
    if(deleteOnFail){
      if(file.path && (yield fs.exists(file.path))){
        yield fs.remove(file.path);
      }
    }
    
  	this.validator.addError(field, 'rule', message || 'This field is mandatory as name exists');
  	return false;
  }
}

app.use(validate());

app.use(function *(){
	yield this.validateBody({
  	hobbies: 'array|requiredIfFooIs:bar',
    foo: 'equals:bar'
  });
  
  this.validateFiles({
  	compressed: 'isGzipped'
  });
  
  if (this.validationErrors) {
    this.status = 422;
    this.body = this.validationErrors;
  } else {
    this.status = 200;
    this.body = { success: true }
  }
})

Extending Filters

var app = require('koa')();
var router = (new require('koa-router'))();
var koaBody = require('koa-better-body');

var validate = require('koa-validation');

app.use(koaBody({
    'multipart': true
}));


//Field Filters Class
let Filters = require('koa-validation').Filters;

Filters.prototype.convertToFoo = function *(field, value){
	return 'foo';
}

app.use(validate());

app.use(function *(){
	yield this.validateBody({},{},{
  	before: {
    	field1: 'convertToFoo'
    },
   	after: {
    	field2: 'convertToFoo'
    }
  })
})

All newly added filters need to be generator functions and each function takes the arguments field and value. The field contains the name of the field and the value field contains the value of the field.

Extending File Actions

As seen in the example below, create a new file action using a generator. The arguments to the field include the

  • field - the field name
  • file - the file object created by koa-better-body
  • deleteOnFail - Boolean indicating whether the temp file needs to be deleted if validation fails
  • args - Any arguments provided for the action
  • callback - a generator function that needs to fire after the action has successfully completed.
var app = require('koa')();
var router = (new require('koa-router'))();
var koaBody = require('koa-better-body');

var validate = require('koa-validation');

app.use(koaBody({
    'multipart': true
}));


//File Actions Class
let FileActions = require('koa-validation').FileActions;

FileActions.prototype.moveToMyFolder = function *(field, file, deleteOnFail, args, callback){
	try{
            yield fs.move(file.path, '/my/super/secret/destination/secretFile', { clobber: true });
            if(callback){
                if(yield callback(this.validator, file, '/my/super/secret/destination/secretFile')){
                    return true;
                }else{
                    if(deleteOnFail){
                        if(file.path && (yield fs.exists(file.path))){
                            yield fs.remove(file.path);
                        }
                    }

                    return false;
                }
            }else{
                return true;
            }
        } catch (e){
            this.validator.addError(field, 'action', 'move', 'The file could not be moved to the destination provided');
            if(deleteOnFail){
                if(file.path && (yield fs.exists(file.path))){
                    yield fs.remove(file.path);
                }
            }

            return false;
        }
}

app.use(validate());

app.use(function *(){
	yield this.validateFiles({},true, {},{
  	classified: {
    	action: 'moveToMyFolder'
    }
  })
})

The File Actions are a bit complex and may need some help constructing. You can reach out me directly in case it is not very clear.