Skip to content

Commit f442263

Browse files
committed
more readiness for new standards
1 parent 7f79fe6 commit f442263

File tree

4 files changed

+241
-40
lines changed

4 files changed

+241
-40
lines changed

.github/copilot-instructions.md

Lines changed: 184 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,184 @@
1+
# Copilot Instructions for CBValidation
2+
3+
## Project Overview
4+
5+
CBValidation is a server-side validation engine for ColdBox applications providing unified object, struct, and form validation. The module implements a comprehensive validator pattern with extensible constraint rules, localization support via cbi18n, and deep integration with ColdBox's WireBox dependency injection container.
6+
7+
## Architecture & Core Components
8+
9+
### Validation Engine Architecture
10+
- **ValidationManager** (`models/ValidationManager.cfc`): Central orchestrator handling constraint discovery, rule processing, and validator delegation
11+
- **Validators** (`models/validators/`): Individual constraint validators extending BaseValidator with specific validation logic
12+
- **ValidationResult** (`models/result/ValidationResult.cfc`): Immutable result container with error aggregation and i18n message formatting
13+
- **GenericObject** (`models/GenericObject.cfc`): Wrapper for validating simple structures/forms as objects
14+
15+
### Constraint System Patterns
16+
```javascript
17+
// Object-based constraints (preferred approach)
18+
component {
19+
this.constraints = {
20+
email: { required: true, type: "email" },
21+
age: { required: true, min: 18, max: 65 }
22+
};
23+
24+
// Profile-based field selection for targeted validation
25+
this.constraintProfiles = {
26+
registration: "email,password,confirmPassword",
27+
update: "email,firstName,lastName"
28+
};
29+
}
30+
31+
// Shared constraints via module settings (global reuse)
32+
moduleSettings = {
33+
cbValidation = {
34+
sharedConstraints = {
35+
userRegistration = {
36+
email: { required: true, type: "email" }
37+
}
38+
}
39+
}
40+
};
41+
```
42+
43+
### Validator Discovery & Extension
44+
- **Auto-Discovery**: ValidationManager scans `models/validators/` and builds wirebox mapping for `*Validator.cfc` files
45+
- **Validator Aliases**: `items``arrayItem`, `constraints``nestedConstraints` for shorthand usage
46+
- **Custom Validators**: Implement IValidator interface, register via `validator: "path.to.CustomValidator"` or wirebox ID
47+
48+
## Key Implementation Patterns
49+
50+
### Validation Workflow
51+
1. **Constraint Resolution**: `determineConstraintsDefinition()` - object properties → shared constraints → inline structs
52+
2. **Profile Processing**: Constraint profiles expand to `includeFields` for targeted validation scenarios
53+
3. **Rule Processing**: `processRules()` iterates constraints, delegating to specific validators
54+
4. **Result Aggregation**: ValidationResult accumulates errors with i18n message formatting
55+
56+
### Mixin Integration Pattern
57+
The module injects validation methods globally via `helpers/Mixins.cfm`:
58+
```javascript
59+
// In any ColdBox component (handlers, views, layouts, interceptors)
60+
var result = validate(target=user, constraints="userValidation");
61+
if (result.hasErrors()) {
62+
// Handle validation errors
63+
}
64+
65+
// Throws ValidationException on failure with JSON error details
66+
var validUser = validateOrFail(target=user, profiles="registration");
67+
```
68+
69+
### Advanced Constraint Features
70+
```javascript
71+
// Nested object validation with dot notation
72+
this.constraints = {
73+
"address.street": { required: true, size: "5..100" },
74+
"preferences.*": { type: "string" } // Array item validation
75+
};
76+
77+
// Conditional validation
78+
email: {
79+
requiredIf: { userType: "premium" },
80+
requiredUnless: { authProvider: "oauth" }
81+
}
82+
83+
// Custom validation methods
84+
password: {
85+
method: "validatePasswordStrength", // Call this.validatePasswordStrength()
86+
udf: variables.customValidator // Direct function reference
87+
}
88+
```
89+
90+
## Developer Workflows
91+
92+
### Testing Strategy
93+
- **Test Harness**: Full ColdBox application in `test-harness/` for integration testing
94+
- **Unit Tests**: `test-harness/tests/specs/` - focus on ValidationManager and individual validators
95+
- **Test Execution**: `box testbox run` from module root, leverages test-harness Application.cfc
96+
97+
### Build & Release Process
98+
- **Build Script**: `build/Build.cfc` handles testing, docs generation, and packaging
99+
- **Commands**: `box task run build/Build.cfc` for full build pipeline
100+
- **API Docs**: Generated for `models/` directory only (public API surface)
101+
102+
### Development Environment Setup
103+
```bash
104+
# Install dependencies and start development
105+
box install
106+
box server start [email protected]
107+
108+
# Run tests during development
109+
box testbox run
110+
111+
# Format code to standards
112+
box run-script format
113+
114+
# Build and package module
115+
box task run build/Build.cfc
116+
```
117+
118+
## Dependency Integration
119+
120+
### CBi18n Integration (Required Dependency)
121+
- **Message Localization**: ValidationResult uses ResourceService for translating error messages
122+
- **Constraint Messages**: Support `{propertyName}Message` keys for field-specific i18n overrides
123+
- **Locale Support**: Validation methods accept `locale` parameter for multilingual applications
124+
125+
### WireBox Registration Patterns
126+
```javascript
127+
// Module auto-registers core services
128+
"ValidationManager@cbvalidation" // Primary validation engine
129+
"validationManager@cbvalidation" // Alias for convenience
130+
131+
// Custom validation manager override
132+
moduleSettings = {
133+
cbValidation = {
134+
manager: "path.to.CustomValidationManager" // Replace default engine
135+
}
136+
};
137+
```
138+
139+
## Integration Examples
140+
141+
### Form Validation in Handlers
142+
```javascript
143+
function saveUser(event, rc, prc) {
144+
var result = validate(target=rc, constraints="userRegistration");
145+
if (result.hasErrors()) {
146+
prc.errors = result.getAllErrors();
147+
return event.setView("users/registration");
148+
}
149+
// Process valid data
150+
}
151+
```
152+
153+
### API Validation with Exception Handling
154+
```javascript
155+
function apiCreateUser(event, rc, prc) {
156+
try {
157+
var validData = validateOrFail(target=rc, profiles="api");
158+
var user = userService.create(validData);
159+
return event.renderData(data=user);
160+
} catch(ValidationException e) {
161+
return event.renderData(
162+
statusCode=422,
163+
data={errors: deserializeJSON(e.extendedInfo)}
164+
);
165+
}
166+
}
167+
```
168+
169+
## Common Conventions
170+
171+
### Constraint Naming
172+
- Use descriptive field names matching object properties exactly
173+
- Leverage profiles for context-specific validation (registration, update, etc.)
174+
- Group related constraints in shared constraint structures for reuse
175+
176+
### Error Handling Patterns
177+
- Prefer `validate()` for form scenarios where you display errors inline
178+
- Use `validateOrFail()` for API endpoints requiring immediate exception handling
179+
- Always check `result.hasErrors()` before proceeding with business logic
180+
181+
### Testing Validators
182+
- Extend `coldbox.system.testing.BaseTestCase` for integration tests
183+
- Mock ValidationResult for unit testing individual validators
184+
- Test constraint edge cases: null values, empty strings, boundary conditions

build/Build.cfc

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,11 @@ component {
6565
buildID = createUUID(),
6666
branch = "development"
6767
){
68+
// If branch == development, then we are building a snapshot
69+
if ( branch == "development" ) {
70+
arguments.version = arguments.version & "-snapshot";
71+
}
72+
6873
// Create project mapping
6974
fileSystemUtil.createMapping( arguments.projectName, variables.cwd );
7075

@@ -167,7 +172,7 @@ component {
167172
.params(
168173
path = "/#variables.projectBuildDir#/**",
169174
token = ( arguments.branch == "master" ? "@build.number@" : "[email protected]@" ),
170-
replacement = ( arguments.branch == "master" ? arguments.buildID : "-snapshot" )
175+
replacement = ( arguments.branch == "master" ? arguments.buildID : "" )
171176
)
172177
.run();
173178

test-harness/box.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
"dependencies":{
88
"cbi18n":"^3.0.0",
99
"testbox":"*",
10-
"coldbox":"^6"
10+
"coldbox":"be"
1111
},
1212
"installPaths":{
1313
"testbox":"testbox/",

test-harness/tests/Application.cfc

Lines changed: 50 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -1,56 +1,69 @@
11
/**
2-
* Copyright 2005-2007 ColdBox Framework by Luis Majano and Ortus Solutions, Corp
3-
* www.ortussolutions.com
4-
* ---
5-
*/
6-
component {
2+
* Copyright 2005-2007 ColdBox Framework by Luis Majano and Ortus Solutions, Corp
3+
* www.ortussolutions.com
4+
* ---
5+
*/
6+
component{
77

8-
// UPDATE THE NAME OF THE MODULE IN TESTING BELOW
9-
request.MODULE_NAME = "cbvalidation";
10-
request.MODULE_PATH = "cbvalidation";
8+
// The name of the module used in cfmappings ,etc
9+
request.MODULE_NAME = "@MODULE_NAME@";
10+
// The directory name of the module on disk. Usually, it's the same as the module name
11+
request.MODULE_PATH = "@MODULE_NAME@";
1112

1213
// APPLICATION CFC PROPERTIES
13-
this.name = "ColdBoxTestingSuite";
14-
this.sessionManagement = true;
15-
this.setClientCookies = true;
16-
this.sessionTimeout = createTimespan( 0, 0, 15, 0 );
17-
this.applicationTimeout = createTimespan( 0, 0, 15, 0 );
14+
this.name = "#request.MODULE_NAME# Testing Suite";
15+
this.sessionManagement = true;
16+
this.sessionTimeout = createTimeSpan( 0, 0, 15, 0 );
17+
this.applicationTimeout = createTimeSpan( 0, 0, 15, 0 );
18+
this.setClientCookies = true;
1819
// Turn on/off white space management
1920
this.whiteSpaceManagement = "smart";
20-
this.enableNullSupport = shouldEnableFullNullSupport();
21+
this.enableNullSupport = shouldEnableFullNullSupport();
2122

2223
// Create testing mapping
2324
this.mappings[ "/tests" ] = getDirectoryFromPath( getCurrentTemplatePath() );
2425

2526
// The application root
26-
rootPath = reReplaceNoCase( this.mappings[ "/tests" ], "tests(\\|/)", "" );
27-
this.mappings[ "/root" ] = rootPath;
28-
this.mappings[ "/cbi18n" ] = rootPath & "modules/cbi18n";
27+
rootPath = REReplaceNoCase( this.mappings[ "/tests" ], "tests(\\|/)", "" );
28+
this.mappings[ "/root" ] = rootPath;
2929

3030
// The module root path
31-
moduleRootPath = reReplaceNoCase(
32-
this.mappings[ "/root" ],
33-
"#request.module_name#(\\|/)test-harness(\\|/)",
34-
""
35-
);
36-
this.mappings[ "/moduleroot" ] = moduleRootPath;
37-
this.mappings[ "/#request.MODULE_NAME#" ] = moduleRootPath & "#request.MODULE_PATH#";
31+
moduleRootPath = REReplaceNoCase( rootPath, "#request.MODULE_PATH#(\\|/)test-harness(\\|/)", "" );
32+
this.mappings[ "/moduleroot" ] = moduleRootPath;
33+
this.mappings[ "/#request.MODULE_NAME#" ] = moduleRootPath & "#request.MODULE_PATH#";
34+
35+
// ORM Definitions
36+
/**
37+
this.datasource = "coolblog";
38+
this.ormEnabled = "true";
39+
this.ormSettings = {
40+
cfclocation = [ "/root/models" ],
41+
logSQL = true,
42+
dbcreate = "update",
43+
secondarycacheenabled = false,
44+
cacheProvider = "ehcache",
45+
flushAtRequestEnd = false,
46+
eventhandling = true,
47+
eventHandler = "cborm.models.EventHandler",
48+
skipcfcWithError = false
49+
};
50+
**/
51+
52+
function onRequestStart( required targetPage ){
3853

39-
// request start
40-
public boolean function onRequestStart( String targetPage ){
4154
// Set a high timeout for long running tests
42-
setting requestTimeout ="9999";
55+
setting requestTimeout="9999";
4356
// New ColdBox Virtual Application Starter
44-
request.coldBoxVirtualApp= new coldbox.system.testing.VirtualApp( appMapping = "/root" );
57+
request.coldBoxVirtualApp = new coldbox.system.testing.VirtualApp( appMapping = "/root" );
4558

4659
// If hitting the runner or specs, prep our virtual app
4760
if ( getBaseTemplatePath().replace( expandPath( "/tests" ), "" ).reFindNoCase( "(runner|specs)" ) ) {
48-
request.coldBoxVirtualApp.startup();
61+
request.coldBoxVirtualApp.startup( true );
4962
}
5063

5164
// ORM Reload for fresh results
52-
if ( structKeyExists( url, "fwreinit" ) ) {
53-
if ( structKeyExists( server, "lucee" ) ) {
65+
if( structKeyExists( url, "fwreinit" ) ){
66+
if( structKeyExists( server, "lucee" ) ){
5467
pagePoolClear();
5568
}
5669
// ormReload();
@@ -60,14 +73,13 @@ component {
6073
return true;
6174
}
6275

63-
public void function onRequestEnd( required targetPage ){
76+
public void function onRequestEnd( required targetPage ) {
6477
request.coldBoxVirtualApp.shutdown();
6578
}
6679

67-
private boolean function shouldEnableFullNullSupport(){
68-
var system = createObject( "java", "java.lang.System" );
69-
var value = system.getEnv( "FULL_NULL" );
70-
return isNull( value ) ? false : !!value;
71-
}
72-
80+
private boolean function shouldEnableFullNullSupport() {
81+
var system = createObject( "java", "java.lang.System" );
82+
var value = system.getEnv( "FULL_NULL" );
83+
return isNull( value ) ? false : !!value;
84+
}
7385
}

0 commit comments

Comments
 (0)