Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ jobs:
strategy:
fail-fast: false
matrix:
cfengine: [ "boxlang@1", "boxlang-cfml@1", "lucee@5", "lucee@6", "adobe@2023", "adobe@2025" ]
cfengine: [ "boxlang-cfml@1", "lucee@5", "lucee@6", "adobe@2023", "adobe@2025" ]
coldboxVersion: [ "^7.0.0" ]
experimental: [ false ]
# Experimental: ColdBox BE vs All Engines
Expand Down
61 changes: 58 additions & 3 deletions models/ValidationManager.cfc
Original file line number Diff line number Diff line change
Expand Up @@ -302,9 +302,64 @@ component accessors="true" serialize="false" singleton {
}

// Return validated keys
return arguments.target.filter( function( key ){
return constraints.keyExists( key );
} );
return filterTargetForConstraints( arguments.target, constraints );
}

/**
* Recursively filters the given target structure or object according to the provided constraints.
*
* This method processes the target and returns a new structure containing only the keys that exist in the constraints.
* It handles nested constraints (via "constraints" or "nestedConstraints" keys) and array item constraints (via "items" or "arrayItem" keys)
* by recursively filtering nested objects and arrays as needed.
*
* with nested structures and arrays filtered recursively as specified by the constraints.
*
* @target The target structure or object to filter. Can be a struct or an object containing fields to validate.
* @constraints The structure of constraints to use for filtering the target. Keys correspond to fields in the target.
*
* @return struct: A new structure containing only the fields from the target that match the provided constraints,
*/
private any function filterTargetForConstraints( required any target, required struct constraints ){
var filteredTarget = {};
for ( var key in arguments.target ) {
if ( !arguments.constraints.keyExists( key ) ) {
continue;
}

var constraint = arguments.constraints[ key ];
if ( constraint.keyExists( "items" ) || constraint.keyExists( "arrayItem" ) ) {
var filteredArray = [];
var arrayConstraints = ( constraint.keyExists( "items" ) ? constraint.items : constraint.arrayItem );
if ( arrayConstraints.keyExists( "constraints" ) || arrayConstraints.keyExists( "nestedConstraints" ) ) {
for ( var item in arguments.target[ key ] ) {
if ( isStruct( item ) ) {
arrayAppend(
filteredArray,
filterTargetForConstraints(
target = item,
constraints = arrayConstraints.keyExists( "constraints" ) ? arrayConstraints.constraints : arrayConstraints.nestedConstraints
)
);
} else {
arrayAppend( filteredArray, item );
}
}
} else {
filteredArray = arguments.target[ key ];
}
filteredTarget[ key ] = filteredArray;
} else if ( constraint.keyExists( "constraints" ) || constraint.keyExists( "nestedConstraints" ) ) {
filteredTarget[ key ] = filterTargetForConstraints(
target = arguments.target[ key ],
constraints = (
constraint.keyExists( "constraints" ) ? constraint.constraints : constraint.nestedConstraints
)
);
} else {
filteredTarget[ key ] = arguments.target[ key ];
}
}
return filteredTarget;
}

/**
Expand Down
118 changes: 69 additions & 49 deletions test-harness/handlers/Main.cfc
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,14 @@ component {

// Index
any function index( event, rc, prc ){

// Test Mixins
log.info( "validateHasValue #validateHasValue( "true" )# has passed!" );
log.info( "validateIsNullOrEmpty #validateIsNullOrEmpty( "true" )# has passed!" );
assert( true );
try{
try {
assert( false, "bogus line" );
} catch( AssertException e ){}
catch( any e ){
} catch ( AssertException e ) {
} catch ( any e ) {
rethrow;
}

Expand All @@ -26,39 +25,29 @@ component {
password : { required : true, size : "6..20" }
};
// validation
validate(
target = rc,
constraints = constraints
).onError( function( results ){
flash.put(
"notice",
arguments.results.getAllErrors().tostring()
);
return index( event, rc, prc );
})
.onSuccess( function( results ){
flash.put( "notice", "User info validated!" );
relocate( "main" );
} )
validate( target = rc, constraints = constraints )
.onError( function( results ){
flash.put( "notice", arguments.results.getAllErrors().tostring() );
return index( event, rc, prc );
} )
.onSuccess( function( results ){
flash.put( "notice", "User info validated!" );
relocate( "main" );
} )
;
}

any function saveShared( event, rc, prc ){
// validation
validate(
target = rc,
constraints = "sharedUser"
).onError( function( results ){
flash.put(
"notice",
results.getAllErrors().tostring()
);
return index( event, rc, prc );
})
.onSuccess( function( results ){
flash.put( "User info validated!" );
setNextEvent( "main" );
} );
validate( target = rc, constraints = "sharedUser" )
.onError( function( results ){
flash.put( "notice", results.getAllErrors().tostring() );
return index( event, rc, prc );
} )
.onSuccess( function( results ){
flash.put( "User info validated!" );
setNextEvent( "main" );
} );
}

/**
Expand All @@ -71,10 +60,46 @@ component {
};

// validate
prc.keys = validateOrFail(
target = rc,
constraints = constraints
);
prc.keys = validateOrFail( target = rc, constraints = constraints );

return prc.keys;
}

/**
* validateOrFailWithNestedKeys
*/
function validateOrFailWithNestedKeys( event, rc, prc ){
var constraints = {
"keep0" : { "required" : true, "type" : "string" },
"keepNested0" : {
"required" : true,
"type" : "struct",
"constraints" : {
"keepNested1" : {
"required" : true,
"type" : "struct",
"constraints" : { "keep2" : { "required" : true, "type" : "string" } }
},
"keepArray1" : {
"required" : true,
"type" : "array",
"items" : {
"type" : "struct",
"constraints" : { "keepNested3" : { "required" : true, "type" : "string" } }
}
},
"keepArray1B" : {
"required" : true,
"type" : "array",
"items" : { "type" : "array", "arrayItem" : { "type" : "string" } }
}
}
},
"keepNested0B.keep1B" : { "required" : true, "type" : "string" }
};

// validate
prc.keys = validateOrFail( target = rc, constraints = constraints );

return prc.keys;
}
Expand All @@ -98,27 +123,22 @@ component {
var oModel = populateModel( "User" );

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

return "Validated";
}
/**
}


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

var oModel = populateModel( "User" );
function validateOnly( event, rc, prc ){
var oModel = populateModel( "User" );

// validate
prc.result = validate( oModel );
prc.result = validate( oModel );

return "Validated";

}


Expand Down
77 changes: 77 additions & 0 deletions test-harness/tests/specs/ValidationIntegrations.cfc
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,83 @@ component extends="coldbox.system.testing.BaseTestCase" appMapping="/root" {
.notToHaveKey( "anotherBogus" );
} );
} );

given( "valid nested data", function(){
then( "it should give you back only the validated keys including in nested structs", function(){
var e = this.request(
route = "/main/validateOrFailWithNestedKeys",
params = {
"keepNested0" : {
"keepNested1" : { "keep2" : "foo", "remove2" : "foo" },
"keepArray1" : [
{ "keepNested3" : "foo", "removeNested3" : "foo" },
{ "keepNested3" : "bar", "removeNested3" : "bar" }
],
"keepArray1B" : [ [ "foo", "bar" ], [ "baz", "qux" ] ],
"removeNested1" : { "foo" : "bar" },
"remove1" : "foo"
},
"keepNested0B" : { "keep1B" : "foo", "remove1B" : "foo" },
"keep0" : "foo",
"remove0" : "foo"
},
method = "post"
);

var keys = e.getPrivateValue( "keys" );
debug( keys );
expect( keys ).toBeStruct();
expect( keys ).toHaveKey( "keepNested0" );
expect( keys ).toHaveKey( "keepNested0B" );
expect( keys ).toHaveKey( "keep0" );
expect( keys ).notToHaveKey( "remove0" );

var nested0 = keys.keepNested0;
expect( nested0 ).toBeStruct();
expect( nested0 ).toHaveKey( "keepNested1" );
expect( nested0 ).toHaveKey( "keepArray1" );
expect( nested0 ).toHaveKey( "keepArray1B" );
expect( nested0 ).notToHaveKey( "remove1" );
expect( nested0 ).notToHaveKey( "removeNested1" );

var nested1 = nested0.keepNested1;
expect( nested1 ).toBeStruct();
expect( nested1 ).toHaveKey( "keep2" );
expect( nested1 ).notToHaveKey( "remove2" );

var array1 = nested0.keepArray1;
expect( array1 ).toBeArray();
expect( array1 ).toHaveLength( 2 );
expect( array1[ 1 ] ).toBeStruct();
expect( array1[ 1 ] ).toHaveKey( "keepNested3" );
expect( array1[ 1 ] ).notToHaveKey( "removeNested3" );
expect( array1[ 2 ] ).toBeStruct();
expect( array1[ 2 ] ).toHaveKey( "keepNested3" );
expect( array1[ 2 ] ).notToHaveKey( "removeNested3" );

var array1B = nested0.keepArray1B;
expect( array1B ).toBeArray();
expect( array1B ).toHaveLength( 2 );
expect( array1B[ 1 ] ).toBeArray();
expect( array1B[ 1 ] ).toHaveLength( 2 );
expect( array1B[ 1 ][ 1 ] ).toBeString();
expect( array1B[ 1 ][ 1 ] ).toBe( "foo" );
expect( array1B[ 1 ][ 2 ] ).toBeString();
expect( array1B[ 1 ][ 2 ] ).toBe( "bar" );

expect( array1B[ 2 ] ).toBeArray();
expect( array1B[ 2 ] ).toHaveLength( 2 );
expect( array1B[ 2 ][ 1 ] ).toBeString();
expect( array1B[ 2 ][ 1 ] ).toBe( "baz" );
expect( array1B[ 2 ][ 2 ] ).toBeString();
expect( array1B[ 2 ][ 2 ] ).toBe( "qux" );

var nested0B = keys.keepNested0B;
expect( nested0B ).toBeStruct();
expect( nested0B ).toHaveKey( "keep1B" );
expect( nested0B ).notToHaveKey( "remove1B" );
} );
} );
} );

story( "validate or fail with objects", function(){
Expand Down