A few weeks ago I blogged about the advantages of validation your request scope vs validating your model. Actually, it is even better to validate your request scope, populate your model with the results of your validateOrFail function and now also validate the model , this time including business logic in your validations.

As explained in this previous post, validateOrFail acts as a kind of filter if you validate a struct. Input of this filter is your request collection( a struct) or a user-defined struct. The nice thing here is: validateOrFail will only return your validated fields, and this way you get rid of all kind of unwanted other fields in your request scope which can help secure your input. At least, that’s what the docs indicate

* @return The validated object or the structure fields that where validated

https://coldbox-validation.ortusbooks.com/overview/validating-constraints

I wouldn’t write this post if this was 100% valid, so let’s see what’s going on based on my simple use case.

I had some validation requirements for a DnsRecord object. I will not explain how many different validations we need for all kind of dns entries. In this case it is enough to know most dns entries are lower case, and we don’t want to bother our customers with entering the correct case. We just convert to lower case in our model, no matter what they enter, e.g

component{
  property name="id" type="numeric";
  property name="fullName"  type="string";
  property name="type"      type="string";
  property name="content"   type="string";
  property name="ttl" type="numeric";
  property name="prio" type="numeric";

  function setfullName(value){
    // force fullName to lower case
    variables.fullName = lcase(value); 
  }
  this.constraints = {
    "fullName" = { required=true, regex="^([a-z0-9\-_\.\*]){0,62}$"},
    "type" = { required=true, inList="A,AAAA,NS,PTR,MX" },
    "ttl" = { required=true, type="integer" },
    "prio" = { required=false, type="integer"}
  }
}

This example is quite simple (there are more DNS record types, each having their own constraints) but for this scenario it is sufficient. As yo can see, I force my fullname to lower case in the setter, and make sure my object only has lowercase fullname by the regex in the constraint (and some special characters and max length for a hostname). Initially I moved all my validation from model to the request scope, and immediately hit my first roadblock: the fullName was not accepted anymore if my customer doesn’t care about case.
That’s the difference with model validation: no preprocessing to lcase anymore and no business rules. So in this case I had 2 options: generate 2 different sets of constraints for RC and model validation, or just skip some fields from RC validation. Because most rules were quite simple I decided I would do both RC and model validation and just skip the fullName validation in my initial RC validation, something similar to this:

var validationResults = validateOrFail(
  target = rc,
  constraints = DnsRecordservice.getConstraints(),
  locale = getFWLocale(),
  excludeFields="fullName"
)
var oDnsRecord = DnsRecordservice
  .new( validationResults )
  .validateOrFail( 
    constraints = DnsRecordservice.getConstraints(),
    locale = getFWLocale()
  )
  .save();

So as you see I exclude fullName in my initial RC validation. But hey, if there’s no validation in my first step, will fullName be part of the validationResult? If not, it will not populate my oDnsRecord in my second step ( .new( validationResults ) ).

To my surprise fullName was still part of the validationResult. It was part of my constraints, but not of the validation, because I excluded it. In this case it is convenient, because I need it in my population step.

This brings me to a second scenario. I have a prio field which is not required, but if it is there it should be an integer as defined in my constraints. So if a field like fullName will be returned, even if it is not validated, what happens to prio, if it is not in my request scope, but still in my constraints?

In this second case, prio will not be in my ValidationResults. This makes sense, because we didn’t enter it in our rc, so we don’t need it for populating our model in step 2.

Just to find out what validateOrFail() is returning I looked at the sourcecode. ValidateOrFail is just some kind of wrapper around validate and if there are no validation errors it does this processing:

// If object, return it
if ( isObject( arguments.target ) ) {
  return arguments.target;
}
// Return validated keys
return arguments.target.filter( function( key ){
  return constraints.keyExists( key );
} );

So if my validation target is an object, it will return the object itself. If my validation target is a struct it loop over all keys, and only return a key if it is also part of the constraints. So it doesn’t look at excluded validations, only at the constraints. And to answer the second part about the non-existing rc keys: if it is not present in the target rc struct it will not be part of your validationResult. If you are explicitly specify a null value for a key and have full null support enabled it will be returned. There are some rare cases where I want to reset my object key back to a null value (e.g. in some update scenario’s). In that case I absolutely have to set my null value in my rc.

So, my final conclusion: validateOrFail does NOT return all validated fields. It returns ALL fields which are both present as key in your RC (or your target struct) AND part of the constraints even if you skip fields with the excludeFields parameter. Actually this behaviour is what you want if you use your validationResults for population of a model, but it’s not always obvious. Because of this, you still might opt for two different sets of constraints.