[coldbox:3.5] new ColdBox validation framework is here: ValidBox!

Thanks to Dan Vega’s incredible work with Hyrule and inspirations from Grails, rails, django, and form daddy. We now have a full fledged server side object validation framework. It requires CF9 or above, or Railo 3.1 or above.

Our documentation will be out soon but here are a few pointers. We have based it on several intefaces, so you can use any validation framework in your ColdBox applications as long as they implement the interfaces. Hyrule is the easiest to port. We have also allowed for you to declare constraints in your configuration file so you can reuse validation constraints or as we call them shared constraints.

Todo:

  • More wide testing
  • i18n for messages. Right now it uses generic messages.
  • Custom messages. Ability to declare a custom message constraint.

Configuration
In your ColdBox.cfc you can now declare the following:

validation = {
manager = “class path”,
sharedConstraints = {
sharedName = { constraints }
}
}

The manager is the class path of the validation manager you woud like to use. By default you do not set it as it defaults to ‘coldbox.system.validation.ValidationManager’

Then you can optional register shared constraints. This means you register them with a unique sharedName of your choice and it’s value is a collection of constraints for properties in your objects. Here is an example:

sharedConstraints = {
user = {
fName = {required=true},
lname = {required=true},
age = {required=true, max=18 }
metadata = {required=false, type=“json”}
}
}

As you can see, our constraints definition describes the set of rules for a property. Once your application loads, the shared constraints and your validation manager are configured.

Event Handlers
You now have the ability to use the following functions:

getValidationManager() or controller.getValidationManager()
validateModel(target, [fields], [constraints] )

You pass in your target object, an optional list of fields or properties to validate only (by default it does all of them), an an optional constraints argument which can be the shared name or an actual constraints structure a-la-carte. If no constraints are passed, then we will look for the constraints in the target object as a public property called ‘constraints’.

component{

this.constraints = {
fName = {required=true},
lname = {required=true},
age = {required=true, max=18 }
metadata = {required=false, type=“json”}
}

}

The validateModel() method returns coldbox.system.validation.result.IValidationResult type object, which you can use methods like:

/**

  • Determine if the results had error or not
  • @field.hint The field to count on (optional)
    */
    boolean function hasErrors(string field);

/**

  • Clear All errors
    */
    coldbox.system.validation.result.IValidationResult function clearErrors();

/**

  • Get how many errors you have
  • @field.hint The field to count on (optional)
    */
    numeric function getErrorCount(string field);

/**

  • Get the Errors Array, which is an array of error messages (strings)
  • @field.hint The field to use to filter the error messages on (optional)
    */
    array function getAllErrors(string field);

/**

  • Get an error object for a specific field that failed. Throws exception if the field does not exist
  • @field.hint The field to return error objects on
    */
    coldbox.system.validation.result.IValidationError[] function getFieldErrors(required string field);

/**

  • Get a collection of metadata about the validation results
    */
    struct function getResultMetadata();

/**

  • Set a collection of metadata into the results object
    */
    coldbox.system.validation.result.IValidationResult function setResultMetadata(required struct data);

The Error Interface: coldbox.system.validation.result.IValidationError is also very simple and has these useful methods for building your error responses:

/**

  • Get the error message
    */
    string function getMessage();

/**

  • Get the error field
    */
    string function getField();

/**

  • Get the rejected value
    */
    any function getRejectedValue();

/**

  • Get the error representation
    */
    struct function getMemento();

So you can do:

prc.validationResults = validateModel( obj );
if( prc.validationResults.hasErrors() ){
// do something here
getPlugin(“MessageBox”).error( prc.validationResults.getAllErrors() );
}

You can even use the results object in your views to get specific field errors, messagesbox, etc.

WireBox DSL
You can use now → coldbox:validationManager to get a reference to the validation manager.

Available Constraints
The available constraints are:

propertyName = {
// required field or not, includes null values
required : boolean [false],

// specific type constraint, one in the list.
type : (ssn,email,url,alpha,boolean,date,usdate,eurodate,numeric,GUID,UUID,integer,string,telephone,zipcode,ipaddress,creditcard,binary,component,query,struct,json,xml),

// size or length of the value which can be a (struct,string,array,query)
size : numeric or range, eg: 10 or 6…8

// range is a range of values the property value should exist in
range : eg: 1…10 or 5…-5

// regex validation
regex : valid no case regex

// same as another property
sameAs : propertyName

// same as but with no case
sameAsNoCase : propertyName

// value in list
inList : list

// discrete math modifiers
discrete : (gt,gte,lt,lte,eq,neq):value

// UDF to use for validation, must return boolean accept the incoming value and target object, validate(value,target):boolean
udf = variables.UDF or this.UDF or a closure.

// Validation method to use in the target object must return boolean accept the incoming value and target object
method : methodName

// Custom validator, must implement coldbox.system.validation.validators.IValidator
validator : path or wirebox id, example: ‘mypath.MyValidator’ or ‘id:MyValidator’

// min value
min : value

// max value
max : value
}

Custom Validators
As you can see, you can build also your own validators by implementing our interface coldbox.system.validation.validators.IValidator, then you can put the path or a registered wirebox id.

/**

  • Will check if an incoming value validates
  • @validationResult.hint The result object of the validation
  • @target.hint The target object to validate on
  • @field.hint The field on the target object to validate on
  • @targetValue.hint The target value to validate
    */
    boolean function validate(required coldbox.system.validation.result.IValidationResult validationResult, required any target, required string field, any targetValue, string validationData);

/**

  • Get the name of the validator
    */
    string function getName();

http://bit.ly/yxLMwm Documentation is now up, still in progress, but now there.

Luis F. Majano
CEO
Ortus Solutions, Corp
www.ortussolutions.com

ColdBox Platform: http://www.coldbox.org
Linked In: http://www.linkedin.com/pub/3/731/483
Blog: http://www.luismajano.com
IECFUG Manager: http://www.iecfug.com

Social: twitter.com/lmajano facebook.com/lmajano

Very cool. Does the capability exist to provide a context for validation with different rule sets?

Jason Durham

All sounds good, dont see it in the development branch yet though :slight_smile:

This is awesome, Luis. Can’t wait to try this out.

Yea you can choose which fields and also have multiple rules

Super cool Luis, I'll start using it right away.

First question: Say I have a person with many addresses. I would
like to validate the person object which would only validate if each
address was also valid. How would I handle cascading validations with
ValidBox?

.brett

You receive the target object in the validator, so you can iterate over the relationships and validate those as well.

i18n has just been finalized into the framework.

You can now pass a ‘locale’ argument into the validateLocale() method. The messages will be pulled from the applications resource bundle in the following convention format:

[CFCName].[Field].[ValidatorType]=My localized message.

Example: User.name.required

You also have {} message format qualifiers to do replacements in your localized string. Valid {} variables are

  • {targetName} - The name of the target object
  • {validationType} - The nameof the validation type like “required” “inList”, etc
  • {validationData} - The value of the validation type so if you have inList=“1,2,3” the value is 1,2,3
  • {field} - The name of the property or field
  • {rejectedValue} - The value of the rejected value

Enjoy!

So I'd have to write a custom validator?

Yes

Aldo all validators are created by wirebox so you can do full di

FYI theres a typo on each documentation i’ve found, its missing a comma which gets a server error, took me a while to find

correct code is below, missing comma in red/highlighted:

sharedConstraints = {
user = {
fName = {required=true},
lname = {required=true},
age = {required=true, max=18 },
metadata = {required=false, type=“json”}
}
}

I used the following constraint:

this.constraints = {
name = {required = true, range=‘3-20’}
,beschreibung = {required = true, size=‘1-50’}
};

If I now enter only 3 characters in the form field for NAME it says that it doesn’t meet the rule above, but I would suggest that the minimum and maximum number must be within the accepted range.

What do you think? GTE vs GT! I’m for GTE and LTE :wink:

I would tend to agree with you on this one.

Curt

Markus,

I looked into this, but it was already setup that way, the issue is with how you have the range specified in your constraints. It should be name = {required = true, range=‘3…20’}. Notice the … instead of the –

I think that will work for you.

Curt

Wouldn0t it be nice to have the following as well?

required=false, range=‘2-20’

That if no value is entered, it does not check further for the range but when a value is coming in, it checks it!

Put the required in kind of an OR relation if FALSE?

Markus

I second Markus’ suggestion. If a field is not marked as required, it should not be validated further for range, type etc. if it is left blank by the user. So, for example, if postal code is a form field with the following constraint and the user leaves it blank, form submission should not stop with “Postal code entered is not valid.” error message.

postalCode = {regex="[A-Z]{1,2}\d[A-Z\d]?\s?\d[ABD-HJLNP-UW-Z]{2}", regexMessage=“Postal code entered is not valid.”}

Parul Bali