Skip to content

Commit 2b2e314

Browse files
authored
Nested constraints, array and struct shorthand syntax, and validator aliases
* Formatting with cfformat * Add nested constraints validator * wip: passing but changing the field name might be problematic later on * Show me all failing tests on PRs * Apply cfformat changes * Fix CI testing * Allow for validator aliases * Updates for array item validator to handle arrays of simple values as well
1 parent cce5166 commit 2b2e314

17 files changed

+600
-75
lines changed

.github/workflows/ci.yml

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -50,13 +50,16 @@ jobs:
5050
5151
- name: Start ${{ matrix.cfengine }} Server
5252
working-directory: ./test-harness
53-
run: |
54-
box server start serverConfigFile="server-${{ matrix.cfengine }}.json" --noSaveSettings --debug
55-
# Install Adobe 2021 cfpm modules
56-
if [[ "${{ matrix.cfengine }}" == "adobe@2021" ]] ; then
57-
box run-script install:2021
58-
fi
59-
curl http://127.0.0.1:60299
53+
run: box server start serverConfigFile="server-${{ matrix.cfengine }}.json" --noSaveSettings --debug
54+
55+
- name: CFPM Setup
56+
if: ${{ matrix.cfengine == 'adobe@2021' }}
57+
working-directory: ./test-harness
58+
run: box run-script install:2021
59+
60+
- name: Prime ${{ matrix.cfengine }} server
61+
working-directory: ./test-harness
62+
run: curl http://127.0.0.1:60299
6063

6164
- name: Run Tests
6265
working-directory: ./test-harness

.github/workflows/pr.yml

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ jobs:
1818
DB_USER: root
1919
DB_PASSWORD: root
2020
strategy:
21-
fail-fast: true
21+
fail-fast: false
2222
matrix:
2323
cfengine: [ "lucee@5", "adobe@2016", "adobe@2018", "adobe@2021" ]
2424
steps:
@@ -41,9 +41,16 @@ jobs:
4141
4242
- name: Start ${{ matrix.cfengine }} Server
4343
working-directory: ./test-harness
44-
run: |
45-
box server start serverConfigFile="server-${{ matrix.cfengine }}.json" --noSaveSettings --debug
46-
curl http://127.0.0.1:60299
44+
run: box server start serverConfigFile="server-${{ matrix.cfengine }}.json" --noSaveSettings --debug
45+
46+
- name: CFPM Setup
47+
if: ${{ matrix.cfengine == 'adobe@2021' }}
48+
working-directory: ./test-harness
49+
run: box run-script install:2021
50+
51+
- name: Prime ${{ matrix.cfengine }} server
52+
working-directory: ./test-harness
53+
run: curl http://127.0.0.1:60299
4754

4855
- name: Run Tests
4956
working-directory: ./test-harness

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33
.artifacts/**
44
.tmp/**
55

6+
modules/
7+
68
test-harness/.engine/**
79
test-harness/.env
810
test-harness/coldbox/**

models/ValidationManager.cfc

Lines changed: 84 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -87,15 +87,24 @@ component
8787
* @sharedConstraints A structure of shared constraints
8888
*/
8989
ValidationManager function init( struct sharedConstraints = {} ){
90-
// valid validator registrations
91-
variables.validValidators = "required,type,size,range,regex,sameAs,sameAsNoCase,inList,discrete,udf,method,validator,min,max";
9290
// store shared constraints if passed
9391
variables.sharedConstraints = arguments.sharedConstraints;
9492
// Validators Path
9593
variables.validatorsPath = getDirectoryFromPath( getMetadata( this ).path ) & "validators";
9694
// Register validators
97-
variables.registeredValidators = directoryList(
98-
variables.validatorsPath,
95+
variables.registeredValidators = discoverValidators( variables.validatorsPath );
96+
// Register aliases
97+
variables.validatorAliases = {
98+
"items" : "arrayItem",
99+
"constraints" : "nestedConstraints"
100+
};
101+
102+
return this;
103+
}
104+
105+
public struct function discoverValidators( required string path ){
106+
return directoryList(
107+
arguments.path,
99108
false,
100109
"name",
101110
"*.cfc"
@@ -113,8 +122,6 @@ component
113122
result[ item.replaceNoCase( "Validator", "" ) ] = "cbvalidation.models.validators.#item#";
114123
return result;
115124
}, {} );
116-
117-
return this;
118125
}
119126

120127
/**
@@ -162,6 +169,9 @@ component
162169
arguments.constraints
163170
);
164171

172+
expandConstraintShortcuts( allConstraints );
173+
// writeDump( var = allConstraints );
174+
165175
// create new result object
166176
var results = wirebox.getInstance(
167177
name = "cbvalidation.models.result.ValidationResult",
@@ -328,6 +338,16 @@ component
328338
required string validatorType,
329339
required any validationData
330340
){
341+
// Are we an alias?
342+
if (
343+
structKeyExists(
344+
variables.validatorAliases,
345+
arguments.validatorType
346+
)
347+
) {
348+
arguments.validatorType = variables.validatorAliases[ arguments.validatorType ];
349+
}
350+
331351
// Are we a core validator?
332352
if (
333353
structKeyExists(
@@ -459,4 +479,62 @@ component
459479
return ( structKeyExists( arguments.target, "constraints" ) ? arguments.target.constraints : {} );
460480
}
461481

482+
private void function expandConstraintShortcuts( required struct constraints ){
483+
for ( var key in arguments.constraints ) {
484+
if ( listLen( key, "." ) > 1 ) {
485+
// is an object or an array shortcut
486+
expandNestedConstraint(
487+
constraintSlice = arguments.constraints,
488+
constraints = arguments.constraints[ key ],
489+
currentKey = listFirst( key, "." ),
490+
nestedKeys = listRest( key, "." )
491+
);
492+
structDelete( arguments.constraints, key );
493+
}
494+
}
495+
}
496+
497+
private void function expandNestedConstraint(
498+
required struct constraintSlice,
499+
required struct constraints,
500+
required string currentKey,
501+
string nestedKeys = ""
502+
){
503+
if ( arguments.nestedKeys == "" ) {
504+
arguments.constraintSlice[ currentKey ] = arguments.constraints;
505+
return;
506+
}
507+
508+
if ( !arguments.constraintSlice.keyExists( currentKey ) ) {
509+
arguments.constraintSlice[ currentKey ] = {};
510+
}
511+
512+
var nextKey = listFirst( arguments.nestedKeys, "." );
513+
var nextSlice = {};
514+
var nextKeys = listRest( arguments.nestedKeys, "." );
515+
if ( nextKey == "*" ) {
516+
nextKey = listFirst( nextKeys, "." );
517+
nextKeys = listRest( nextKeys, "." );
518+
if ( nextKey == "" ) {
519+
arguments.constraintSlice[ currentKey ][ "arrayItem" ] = arguments.constraints;
520+
return;
521+
}
522+
if ( !arguments.constraintSlice[ currentKey ].keyExists( "arrayItem" ) ) {
523+
arguments.constraintSlice[ currentKey ][ "arrayItem" ] = { "constraints" : {} };
524+
}
525+
nextSlice = arguments.constraintSlice[ currentKey ][ "arrayItem" ][ "constraints" ];
526+
} else {
527+
if ( !arguments.constraintSlice[ currentKey ].keyExists( "constraints" ) ) {
528+
arguments.constraintSlice[ currentKey ][ "constraints" ] = {};
529+
}
530+
nextSlice = arguments.constraintSlice[ currentKey ][ "constraints" ];
531+
}
532+
expandNestedConstraint(
533+
constraintSlice = nextSlice,
534+
constraints = arguments.constraints,
535+
currentKey = nextKey,
536+
nestedKeys = nextKeys
537+
);
538+
}
539+
462540
}

models/validators/AfterOrEqualValidator.cfc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ component
5858
"get#arguments.validationData#"
5959
);
6060
}
61-
61+
6262
// return true if no value to compare against
6363
if ( isNull( compareValue ) || isNullOrEmpty( compareValue ) ) {
6464
return true;

models/validators/AfterValidator.cfc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ component
5858
"get#arguments.validationData#"
5959
);
6060
}
61-
61+
6262
// return true if no value to compare against
6363
if ( isNull( compareValue ) || isNullOrEmpty( compareValue ) ) {
6464
return true;

models/validators/ArrayItemValidator.cfc

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -62,17 +62,24 @@ component
6262
// Iterate and dance baby!
6363
var itemValidationResults = arguments.targetValue.filter( function( thisItem, thisIndex ){
6464
var vResult = variables.validationManager.validate(
65-
target = arguments.thisItem,
66-
constraints = validationData
65+
target = { "item" : arguments.thisItem },
66+
constraints = { "item" : validationData }
6767
);
6868
// Process errors into validation result
6969
vResult
7070
.getErrors()
7171
.each( function( error ){
72-
arguments.error.setMessage(
73-
"Validation failed for the [#field#] array at index [#thisIndex#]. " &
74-
arguments.error.getMessage()
72+
var newField = "#field#[#thisIndex#]";
73+
var nestedField = reReplace(
74+
arguments.error.getField(),
75+
"^item\.?",
76+
"",
77+
"one"
7578
);
79+
if ( nestedField != "" ) {
80+
newField = newField & "." & nestedField;
81+
}
82+
arguments.error.setField( newField );
7683
validationResult.addError( arguments.error );
7784
} );
7885
// Filter out valid items

models/validators/BeforeOrEqualValidator.cfc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ component
5858
"get#arguments.validationData#"
5959
);
6060
}
61-
61+
6262
// return true if no value to compare against
6363
if ( isNull( compareValue ) || isNullOrEmpty( compareValue ) ) {
6464
return true;

models/validators/BeforeValidator.cfc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ component
5858
"get#arguments.validationData#"
5959
);
6060
}
61-
61+
6262
// return true if no value to compare against
6363
if ( isNull( compareValue ) || isNullOrEmpty( compareValue ) ) {
6464
return true;

models/validators/DateEqualsValidator.cfc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ component
5858
"get#arguments.validationData#"
5959
);
6060
}
61-
61+
6262
// return true if no value to compare against
6363
if ( isNull( compareValue ) || isNullOrEmpty( compareValue ) ) {
6464
return true;

0 commit comments

Comments
 (0)