Skip to content

Commit

Permalink
* feature : Added constraintProfiles to allow you to define which…
Browse files Browse the repository at this point in the history
… fields to validate according to defined profiles: #37
  • Loading branch information
lmajano committed Feb 4, 2020
1 parent a618ed7 commit 899e7b7
Show file tree
Hide file tree
Showing 13 changed files with 321 additions and 141 deletions.
7 changes: 4 additions & 3 deletions changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,10 @@

## 2.1.0

* `feature` : Added `constraintProfiles` to allow you to define which fields to validate according to defined profiles: https://github.com/coldbox-modules/cbvalidation/issues/37
* `feature` : Updated `RequiredUnless` and `RequiredIf` to use struct literal notation instead of the weird parsing we did.
* `feature` : Added the `Unique` validator thanks to @elpete!
* `improvement` : Added `null` support for the `RequiredIf,RequiredUnless` validators
* `improvement` : Added `null` support for the `RequiredIf,RequiredUnless` validator values

## 2.0.0

Expand All @@ -13,8 +14,8 @@
* No more manual discovery of validators, automated registration and lookup process, cleaned lots of code on this one!
* New Validator: `Accepted` - The field under validation must be yes, on, 1, or true. This is useful for validating "Terms of Service" acceptance.
* New Validator: `Alpha` - Only allows alphabetic characters
* New Validator: `RequiredUnless` with validation data: `anotherField:value,...` - The field under validation must be present and not empty unless the `anotherfield` field is equal to the passed `value`.
* New Validator: `RequiredIf` with validation data: `anotherField:value,...` - The field under validation must be present and not empty if the `anotherfield` field is equal to the passed `value`.
* New Validator: `RequiredUnless` with validation data as a struct literal `{ anotherField:value, ... }` - The field under validation must be present and not empty unless the `anotherfield` field is equal to the passed `value`.
* New Validator: `RequiredIf` with validation data as a struct literal `{ anotherField:value, ... }` - The field under validation must be present and not empty if the `anotherfield` field is equal to the passed `value`.
* Accelerated validation by removing type checks. ACF chokes on interface checks

### Improvements
Expand Down
2 changes: 2 additions & 0 deletions models/Mixins.cfm
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
* @locale The i18n locale to use for validation messages
* @excludeFields The fields to exclude from the validation
* @includeFields The fields to include in the validation
* @profiles If passed, a list of profile names to use for validation constraints
*
* @return cbvalidation.model.result.IValidationResult
*/
Expand All @@ -26,6 +27,7 @@ function validate(){
* @locale The i18n locale to use for validation messages
* @excludeFields The fields to exclude from the validation
* @includeFields The fields to include in the validation
* @profiles If passed, a list of profile names to use for validation constraints
*
* @return The validated object or the structure fields that where validated
* @throws ValidationException
Expand Down
38 changes: 35 additions & 3 deletions models/ValidationManager.cfc
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,7 @@ component accessors="true" serialize="false" singleton {
* @locale An optional locale to use for i18n messages
* @excludeFields An optional list of fields to exclude from the validation.
* @IncludeFields An optional list of fields to include in the validation.
* @profiles If passed, a list of profile names to use for validation constraints
*
* @return IValidationResult
*/
Expand All @@ -126,7 +127,8 @@ component accessors="true" serialize="false" singleton {
any constraints = "",
string locale = "",
string excludeFields = "",
string includeFields = ""
string includeFields = "",
string profiles = ""
){
var targetName = "";

Expand All @@ -152,10 +154,38 @@ component accessors="true" serialize="false" singleton {
locale : arguments.locale,
targetName : targetName,
resourceService : resourceService,
constraints : allConstraints
constraints : allConstraints,
profiles : arguments.profiles
}
);

// Discover profiles, and update the includeFields list from it
if( len( arguments.profiles ) ){
arguments.includeFields = arguments.profiles
.listToArray()
// Check if profiles defined in target and iterated one exists
.filter( function( profileKey ){
return structKeyExists( target, "constraintProfiles" ) && structKeyExists( target.constraintProfiles, profileKey );
} )
// Incorporate fields from each profile
.map( function( profileKey ){
// iterate all declared profile fields and incorporate into the includeFields
return target.constraintProfiles
.find( arguments.profileKey )
.listToArray();
} )
// Reduce all fields into a single hashset to do a distinct collection
.reduce( function( result, item ){
item
.each( function( thisField ){
result.add( thisField );
} );
return result;
}, createObject( "java", "java.util.HashSet" ) )
.toArray();
arguments.includeFields = arrayToList( arguments.includeFields );
}

// iterate over constraints defined
for ( var thisField in allConstraints ) {
var validateField = true;
Expand Down Expand Up @@ -193,6 +223,7 @@ component accessors="true" serialize="false" singleton {
* @locale An optional locale to use for i18n messages
* @excludeFields An optional list of fields to exclude from the validation.
* @IncludeFields An optional list of fields to include in the validation.
* @profiles If passed, a list of profile names to use for validation constraints
*
* @throws ValidationException
* @return any,struct: The target object that was validated, or the structure fields that where validated.
Expand All @@ -203,7 +234,8 @@ component accessors="true" serialize="false" singleton {
any constraints = "",
string locale = "",
string excludeFields = "",
string includeFields = ""
string includeFields = "",
string profiles = ""
){
var vResults = this.validate( argumentCollection = arguments );

Expand Down
37 changes: 22 additions & 15 deletions models/result/ValidationResult.cfc
Original file line number Diff line number Diff line change
Expand Up @@ -7,43 +7,49 @@
component accessors="true" {

/**
* A collection of error objects represented in this result object
*/
* A collection of error objects represented in this result object
*/
property name="errors" type="array";

/**
* Extra metadata you can store in the results object
*/
* Extra metadata you can store in the results object
*/
property name="resultMetadata" type="struct";

/**
* The locale this result validation is using
*/
* The locale this result validation is using
*/
property name="locale" type="string";

/**
* The name of the target object
*/
* The name of the target object
*/
property name="targetName" type="string";

/**
* The constraints evaluated in the validation process
*/
* The constraints evaluated in the validation process
*/
property name="constraints" type="struct";

/**
* The resource bundle object
*/
* The resource bundle object
*/
property name="resourceService";

/**
* Constructor
*/
* The profiles used in the validation
*/
property name="profiles" type="string";

/**
* Constructor
*/
ValidationResult function init(
string locale = "",
string targetName = "",
any resourceService = "",
struct constraints = structNew()
struct constraints = structNew(),
string profiles = ""
){
variables.errors = [];
variables.resultMetadata = {};
Expand All @@ -53,6 +59,7 @@ component accessors="true" {
variables.targetName = arguments.targetName;
variables.resourceService = arguments.resourceService;
variables.constraints = arguments.constraints;
variables.profiles = arguments.profiles;
return this;
}

Expand Down
17 changes: 15 additions & 2 deletions test-harness/handlers/Main.cfc
Original file line number Diff line number Diff line change
Expand Up @@ -57,8 +57,8 @@ component{
}

/**
* validateOrFailWithObject
*/
* validateOrFailWithObject
*/
function validateOrFailWithObject( event, rc, prc ){

var oModel = populateModel( "User" );
Expand All @@ -69,6 +69,19 @@ component{
return "Validated";
}

/**
* validateOrFailWithObjectProfiles
*/
function validateOrFailWithProfiles( event, rc, prc ){

var oModel = populateModel( "User" );

// validate
prc.object = validateOrFail( target=oModel, profiles=rc._profiles );

return "Validated";
}


// Run on first init
any function onAppInit( event, rc, prc ){
Expand Down
14 changes: 10 additions & 4 deletions test-harness/models/User.cfc
Original file line number Diff line number Diff line change
@@ -1,15 +1,21 @@
component accessors="true"{
component accessors="true" {

property name="username" default="";
property name="password" default="";
property name="email" default="";

this.constraintProfiles = {
new : "username,password,email",
update : "username,email"
};
this.constraints = {
username = {required=true, size="2..20"},
password = {required=true, size="2..20"}
username : { required : true, size : "2..20" },
password : { required : true, size : "2..20" },
email : { required : true, type : "email" }
};

function init(){
return this;
}

}
}
59 changes: 59 additions & 0 deletions test-harness/tests/specs/ValidationIntegrations.cfc
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ component extends="coldbox.system.testing.BaseTestCase" appMapping="/root" {
function run(){
describe( "Integrations Specs", function(){
beforeEach( function( currentSpec ){
structDelete( application, "cbController" );
structDelete( application, "wirebox" );
// Setup as a new ColdBox request, VERY IMPORTANT. ELSE EVERYTHING LOOKS LIKE THE SAME REQUEST.
setup();
} );
Expand Down Expand Up @@ -71,6 +73,7 @@ component extends="coldbox.system.testing.BaseTestCase" appMapping="/root" {
params = {
username : "luis",
password : "luis",
email : "[email protected]",
bogus : now(),
anotherBogus : now()
},
Expand All @@ -83,7 +86,63 @@ component extends="coldbox.system.testing.BaseTestCase" appMapping="/root" {
} );
} );
} );


story( "I want to Validate with contraint profiles", function(){
given( "a single profile", function(){
then( "it must validate it with only those fields in that profile", function(){
var e = this.request(
route = "/main/validateOrFailWithProfiles",
params = {
username : "luis",
email : "[email protected]",
bogus : now(),
anotherBogus : now(),
_profiles : "update"
},
method = "post"
);

var object = e.getPrivateValue( "object" );
debug( object );
expect( object ).toBeComponent();
});
});
given( "a multiple profiles", function(){
then( "it must validate it with only distinct fields in those profiles", function(){
var e = this.request(
route = "/main/validateOrFailWithProfiles",
params = {
username : "luis",
email : "[email protected]",
password : "luis",
anotherBogus : now(),
_profiles : "update,new,bogus"
},
method = "post"
);

var object = e.getPrivateValue( "object" );
debug( object );
expect( object ).toBeComponent();
});
});
given( "a single profile and invalid data", function(){
then( "then throw the exception", function(){

expect( function(){
this.request( route = "/main/validateOrFailWithProfiles", params = {
username : "luis"
}, method = "post" );
} ).toThrow();

});
});
});


} );

}

}
Loading

0 comments on commit 899e7b7

Please sign in to comment.