Skip to content

Commit 0a16993

Browse files
committed
Merge branch 'development'
2 parents e3481c6 + 00b9ce9 commit 0a16993

22 files changed

+569
-223
lines changed

box.json

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"name":"ColdBox Validation",
33
"author":"Ortus Solutions <[email protected]>",
4-
"version":"2.0.0",
4+
"version":"2.1.0",
55
"location":"https://downloads.ortussolutions.com/ortussolutions/coldbox-modules/cbvalidation/@build.version@/[email protected]@.zip",
66
"slug":"cbvalidation",
77
"type":"modules",
@@ -31,9 +31,9 @@
3131
".git*"
3232
],
3333
"scripts":{
34-
"toMaster":"recipe build/toMaster.boxr",
35-
"format":"cfformat run models/**/*.cfc,ModuleConfig.cfc,tests/specs/**/*.cfc,*.cfc --overwrite",
36-
"format:check":"cfformat run models/**/*.cfc,ModuleConfig.cfc,tests/specs/**/*.cfc,*.cfc --check",
37-
"lint":"cflint models/**.cfc --text --html --json --!exitOnError --suppress"
34+
"toMaster":"recipe build/toMaster.boxr",
35+
"format":"cfformat run models/**/*.cfc,ModuleConfig.cfc,tests/specs/**/*.cfc,*.cfc --overwrite",
36+
"format:check":"cfformat run models/**/*.cfc,ModuleConfig.cfc,tests/specs/**/*.cfc,*.cfc --check",
37+
"lint":"cflint models/**.cfc --text --html --json --!exitOnError --suppress"
3838
}
3939
}

changelog.md

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,21 @@
11
# CHANGELOG
22

3+
## 2.1.0
4+
5+
* `feature` : Added `constraintProfiles` to allow you to define which fields to validate according to defined profiles: https://github.com/coldbox-modules/cbvalidation/issues/37
6+
* `feature` : Updated `RequiredUnless` and `RequiredIf` to use struct literal notation instead of the weird parsing we did.
7+
* `feature` : Added the `Unique` validator thanks to @elpete!
8+
* `improvement` : Added `null` support for the `RequiredIf,RequiredUnless` validator values
9+
310
## 2.0.0
411

512
### Features
613

714
* No more manual discovery of validators, automated registration and lookup process, cleaned lots of code on this one!
815
* New Validator: `Accepted` - The field under validation must be yes, on, 1, or true. This is useful for validating "Terms of Service" acceptance.
916
* New Validator: `Alpha` - Only allows alphabetic characters
10-
* 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`.
11-
* 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`.
17+
* 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`.
18+
* 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`.
1219
* Accelerated validation by removing type checks. ACF chokes on interface checks
1320

1421
### Improvements

models/Mixins.cfm

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
* @locale The i18n locale to use for validation messages
1010
* @excludeFields The fields to exclude from the validation
1111
* @includeFields The fields to include in the validation
12+
* @profiles If passed, a list of profile names to use for validation constraints
1213
*
1314
* @return cbvalidation.model.result.IValidationResult
1415
*/
@@ -26,6 +27,7 @@ function validate(){
2627
* @locale The i18n locale to use for validation messages
2728
* @excludeFields The fields to exclude from the validation
2829
* @includeFields The fields to include in the validation
30+
* @profiles If passed, a list of profile names to use for validation constraints
2931
*
3032
* @return The validated object or the structure fields that where validated
3133
* @throws ValidationException

models/ValidationManager.cfc

Lines changed: 35 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,7 @@ component accessors="true" serialize="false" singleton {
117117
* @locale An optional locale to use for i18n messages
118118
* @excludeFields An optional list of fields to exclude from the validation.
119119
* @IncludeFields An optional list of fields to include in the validation.
120+
* @profiles If passed, a list of profile names to use for validation constraints
120121
*
121122
* @return IValidationResult
122123
*/
@@ -126,7 +127,8 @@ component accessors="true" serialize="false" singleton {
126127
any constraints = "",
127128
string locale = "",
128129
string excludeFields = "",
129-
string includeFields = ""
130+
string includeFields = "",
131+
string profiles = ""
130132
){
131133
var targetName = "";
132134

@@ -152,10 +154,38 @@ component accessors="true" serialize="false" singleton {
152154
locale : arguments.locale,
153155
targetName : targetName,
154156
resourceService : resourceService,
155-
constraints : allConstraints
157+
constraints : allConstraints,
158+
profiles : arguments.profiles
156159
}
157160
);
158161

162+
// Discover profiles, and update the includeFields list from it
163+
if( len( arguments.profiles ) ){
164+
arguments.includeFields = arguments.profiles
165+
.listToArray()
166+
// Check if profiles defined in target and iterated one exists
167+
.filter( function( profileKey ){
168+
return structKeyExists( target, "constraintProfiles" ) && structKeyExists( target.constraintProfiles, profileKey );
169+
} )
170+
// Incorporate fields from each profile
171+
.map( function( profileKey ){
172+
// iterate all declared profile fields and incorporate into the includeFields
173+
return target.constraintProfiles
174+
.find( arguments.profileKey )
175+
.listToArray();
176+
} )
177+
// Reduce all fields into a single hashset to do a distinct collection
178+
.reduce( function( result, item ){
179+
item
180+
.each( function( thisField ){
181+
result.add( thisField );
182+
} );
183+
return result;
184+
}, createObject( "java", "java.util.HashSet" ) )
185+
.toArray();
186+
arguments.includeFields = arrayToList( arguments.includeFields );
187+
}
188+
159189
// iterate over constraints defined
160190
for ( var thisField in allConstraints ) {
161191
var validateField = true;
@@ -193,6 +223,7 @@ component accessors="true" serialize="false" singleton {
193223
* @locale An optional locale to use for i18n messages
194224
* @excludeFields An optional list of fields to exclude from the validation.
195225
* @IncludeFields An optional list of fields to include in the validation.
226+
* @profiles If passed, a list of profile names to use for validation constraints
196227
*
197228
* @throws ValidationException
198229
* @return any,struct: The target object that was validated, or the structure fields that where validated.
@@ -203,7 +234,8 @@ component accessors="true" serialize="false" singleton {
203234
any constraints = "",
204235
string locale = "",
205236
string excludeFields = "",
206-
string includeFields = ""
237+
string includeFields = "",
238+
string profiles = ""
207239
){
208240
var vResults = this.validate( argumentCollection = arguments );
209241

models/result/ValidationResult.cfc

Lines changed: 22 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -7,43 +7,49 @@
77
component accessors="true" {
88

99
/**
10-
* A collection of error objects represented in this result object
11-
*/
10+
* A collection of error objects represented in this result object
11+
*/
1212
property name="errors" type="array";
1313

1414
/**
15-
* Extra metadata you can store in the results object
16-
*/
15+
* Extra metadata you can store in the results object
16+
*/
1717
property name="resultMetadata" type="struct";
1818

1919
/**
20-
* The locale this result validation is using
21-
*/
20+
* The locale this result validation is using
21+
*/
2222
property name="locale" type="string";
2323

2424
/**
25-
* The name of the target object
26-
*/
25+
* The name of the target object
26+
*/
2727
property name="targetName" type="string";
2828

2929
/**
30-
* The constraints evaluated in the validation process
31-
*/
30+
* The constraints evaluated in the validation process
31+
*/
3232
property name="constraints" type="struct";
3333

3434
/**
35-
* The resource bundle object
36-
*/
35+
* The resource bundle object
36+
*/
3737
property name="resourceService";
3838

3939
/**
40-
* Constructor
41-
*/
40+
* The profiles used in the validation
41+
*/
42+
property name="profiles" type="string";
43+
44+
/**
45+
* Constructor
46+
*/
4247
ValidationResult function init(
4348
string locale = "",
4449
string targetName = "",
4550
any resourceService = "",
46-
struct constraints = structNew()
51+
struct constraints = structNew(),
52+
string profiles = ""
4753
){
4854
variables.errors = [];
4955
variables.resultMetadata = {};
@@ -53,6 +59,7 @@ component accessors="true" {
5359
variables.targetName = arguments.targetName;
5460
variables.resourceService = arguments.resourceService;
5561
variables.constraints = arguments.constraints;
62+
variables.profiles = arguments.profiles;
5663
return this;
5764
}
5865

models/validators/AcceptedValidator.cfc

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -18,11 +18,12 @@ component accessors="true" singleton {
1818

1919
/**
2020
* Will check if an incoming value validates
21-
* @validationResultThe result object of the validation
22-
* @targetThe target object to validate on
23-
* @fieldThe field on the target object to validate on
24-
* @targetValueThe target value to validate
25-
* @validationDataThe validation data the validator was created with
21+
*
22+
* @validationResult The result object of the validation
23+
* @target The target object to validate on
24+
* @field The field on the target object to validate on
25+
* @targetValue The target value to validate
26+
* @validationData The validation data the validator was created with
2627
*/
2728
boolean function validate(
2829
required any validationResult,

models/validators/RequiredIfValidator.cfc

Lines changed: 24 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,8 @@
22
* Copyright since 2020 by Ortus Solutions, Corp
33
* www.ortussolutions.com
44
* ---
5-
* This validator checks if a field has value and not null
5+
* This validator checks a struct of key-value pairs passed in the validation data.
6+
* If those key-value pairs are equal then the target field will be required
67
*/
78
component accessors="true" extends="RequiredValidator" singleton {
89

@@ -18,11 +19,12 @@ component accessors="true" extends="RequiredValidator" singleton {
1819

1920
/**
2021
* Will check if an incoming value validates
21-
* @validationResultThe result object of the validation
22-
* @targetThe target object to validate on
23-
* @fieldThe field on the target object to validate on
24-
* @targetValueThe target value to validate
25-
* @validationDataThe validation data the validator was created with
22+
*
23+
* @validationResult The result object of the validation
24+
* @target The target object to validate on
25+
* @field The field on the target object to validate on
26+
* @targetValue The target value to validate
27+
* @validationData The validation data the validator was created with
2628
*/
2729
boolean function validate(
2830
required any validationResult,
@@ -31,24 +33,29 @@ component accessors="true" extends="RequiredValidator" singleton {
3133
any targetValue,
3234
any validationData
3335
){
34-
// Validation Data Format: property:value,...
35-
var validationArray = arguments.validationData.listToArray();
36+
// If you passed in simple data, conver it to a struct, simple values are not evaluated
37+
if( isSimpleValue( arguments.validationData ) ){
38+
arguments.validationData = {};
39+
}
40+
3641
// Inflate to array to test multiple properties
37-
var isRequired = validationArray
38-
.map( function( item ){
42+
var isRequired = arguments.validationData
43+
.map( function( key, value ){
3944
// Get comparison values
40-
var compareProperty = getToken( arguments.item, 1, ":" );
41-
var compareValue = getToken( arguments.item, 2, ":" );
42-
var comparePropertyValue = invoke( target, "get#compareProperty#" );
45+
var comparePropertyValue = invoke( target, "get#key#" );
46+
// Null checks
47+
if( isNull( comparePropertyValue ) ){
48+
return isNull( arguments.value );
49+
}
4350
// Check if the compareValue is the same as the defined one
44-
return ( compareValue == comparePropertyValue ? true : false );
51+
return ( arguments.value == comparePropertyValue ? true : false );
4552
} )
4653
// AND them all for a single result
47-
.reduce( function( result, item ){
48-
return arguments.result && arguments.item;
54+
.reduce( function( result, key, value ){
55+
return ( arguments.value && arguments.result );
4956
}, true );
5057

51-
if( !validationArray.len() || !isRequired ){
58+
if( !arguments.validationData.count() || !isRequired ){
5259
return true;
5360
}
5461

models/validators/RequiredUnlessValidator.cfc

Lines changed: 26 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,8 @@
22
* Copyright since 2020 by Ortus Solutions, Corp
33
* www.ortussolutions.com
44
* ---
5-
* This validator checks if a field has value and not null
5+
* This validator checks a struct of key-value pairs passed in the validation data.
6+
* If those key-value pairs are equal then the target field will NOT be required
67
*/
78
component accessors="true" extends="RequiredValidator" singleton {
89

@@ -18,11 +19,12 @@ component accessors="true" extends="RequiredValidator" singleton {
1819

1920
/**
2021
* Will check if an incoming value validates
21-
* @validationResultThe result object of the validation
22-
* @targetThe target object to validate on
23-
* @fieldThe field on the target object to validate on
24-
* @targetValueThe target value to validate
25-
* @validationDataThe validation data the validator was created with
22+
*
23+
* @validationResult The result object of the validation
24+
* @target The target object to validate on
25+
* @field The field on the target object to validate on
26+
* @targetValue The target value to validate
27+
* @validationData The validation data the validator was created with
2628
*/
2729
boolean function validate(
2830
required any validationResult,
@@ -31,24 +33,30 @@ component accessors="true" extends="RequiredValidator" singleton {
3133
any targetValue,
3234
any validationData
3335
){
34-
// Validation Data Format: property:value,...
35-
var validationArray = arguments.validationData.listToArray();
36-
// Inflate to array to test multiple properties
37-
var isOptional = validationArray
38-
.map( function( item ){
36+
// If you passed in simple data, conver it to a struct, simple values are not evaluated
37+
if( isSimpleValue( arguments.validationData ) ){
38+
arguments.validationData = {};
39+
}
40+
41+
// Test the data
42+
var isOptional = arguments.validationData
43+
.map( function( key, value ){
3944
// Get comparison values
40-
var compareProperty = getToken( arguments.item, 1, ":" );
41-
var compareValue = getToken( arguments.item, 2, ":" );
42-
var comparePropertyValue = invoke( target, "get#compareProperty#" );
45+
var comparePropertyValue = invoke( target, "get#key#" );
46+
// Null checks
47+
if( isNull( comparePropertyValue ) ){
48+
return isNull( arguments.value );
49+
}
4350
// Check if the compareValue is the same as the defined one
44-
return ( compareValue == comparePropertyValue ? true : false );
51+
return ( arguments.value == comparePropertyValue ? true : false );
4552
} )
4653
// AND them all for a single result
47-
.reduce( function( result, item ){
48-
return ( arguments.item && arguments.result );
54+
.reduce( function( result, key, value ){
55+
return ( arguments.value && arguments.result );
4956
}, true );
5057

51-
if( validationArray.len() && isOptional ){
58+
// If we have data, then test the optional
59+
if( arguments.validationData.count() && isOptional ){
5260
return true;
5361
}
5462

models/validators/UDFValidator.cfc

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -31,21 +31,21 @@ component accessors="true" singleton {
3131
any targetValue,
3232
any validationData
3333
){
34-
// return true if no data to check, type needs a data element to be checked.
35-
if ( isNull( arguments.targetValue ) || ( isSimpleValue( arguments.targetValue ) && !len( arguments.targetValue ) ) ) {
36-
return true;
37-
}
34+
// Validate against the UDF/closure
35+
var passed = arguments.validationData(
36+
isNull( arguments.targetValue ) ? javacast( "null", "" ) : arguments.targetValue,
37+
arguments.target
38+
);
3839

39-
// Validate against the UDF/closure
40-
if ( arguments.validationData( arguments.targetValue, arguments.target ) ) {
40+
if ( passed ) {
4141
return true;
4242
}
4343

4444
var args = {
4545
message : "The '#arguments.field#' value does not validate",
4646
field : arguments.field,
4747
validationType : getName(),
48-
rejectedValue : ( isSimpleValue( arguments.targetValue ) ? arguments.targetValue : "" ),
48+
rejectedValue : !isNull( arguments.targetValue ) && isSimpleValue( arguments.targetValue ) ? arguments.targetValue : "",
4949
validationData : arguments.validationData
5050
};
5151

0 commit comments

Comments
 (0)