validation-microflows · diff
git:20260917.5351edc to git:20260922.4954f84
4 added, 0 removed. Audit A to A.
---
name: validation-microflows
description: "Validation microflows for new/edit pages — attribute checks, feedback messages, and conditional validation chains. Use when a form needs input validation with messages shown against the fields."
---
# Validation Microflows Skill
This skill provides guidance for creating validation microflows in MDL that validate user input on NewEdit pages and provide feedback to users.
## When to Use This Skill
Use this skill when:
- Creating validation logic for NewEdit pages
- Implementing attribute validation with feedback messages
- Building conditional validation chains
- Creating action microflows that call validation microflows
> **Nanoflow validation:** The same patterns apply to nanoflows (`create nanoflow` instead of `create microflow`). Use nanoflows for client-side validation when server roundtrips are unnecessary — validation feedback renders instantly without a network call.
## The Validation Pattern
Mendix validation follows a two-microflow pattern:
1. **VAL_Entity_Action** - The validation microflow that:
- Takes an entity object as parameter
- Validates each required field
- Shows validation feedback on invalid fields
- Returns a Boolean indicating overall validity
2. **ACT_Entity_Action** - The action microflow that:
- Calls the validation microflow
- Only proceeds with save/commit if validation passes
- Closes the page on success
## MDL Syntax
### VALIDATION FEEDBACK Statement
```mdl
validation feedback $VariableName/attributename message 'Error message';
```
With template arguments (for dynamic messages):
```mdl
validation feedback $VariableName/attributename message '{1}' objects [$MessageVariable];
```
### CLOSE PAGE Statement
```mdl
close page;
```
Or to close multiple pages:
```mdl
close page 2;
```
## Complete Example
### Validation Microflow (VAL_Car_NewEdit)
```mdl
/**
* Validates a Car entity for NewEdit operations
*
* Performs validation on all required fields and displays
* appropriate error messages to the user.
*
* @param $Car The Car entity to validate
* @returns Boolean indicating if all validations passed
*/
create microflow MdlTemplates.VAL_Car_NewEdit (
$Car: MdlTemplates.Car
)
returns boolean as $IsValid
folder 'OverviewPages'
begin
-- Initialize validation flag
declare $IsValid boolean = true;
-- Validate Brand (required text field)
if trim($Car/Brand) = '' then
set $IsValid = false;
validation feedback $Car/Brand message 'Brand is required';
end if;
-- Validate Model (required text field)
if trim($Car/model) = '' then
set $IsValid = false;
validation feedback $Car/model message 'Model is required';
end if;
-- Validate Price (required, must be positive)
if $Car/Price = empty then
set $IsValid = false;
validation feedback $Car/Price message 'Price is required';
else
if $Car/Price <= 0 then
set $IsValid = false;
validation feedback $Car/Price message 'Price must be greater than 0';
end if;
end if;
-- Validate enumeration (required)
if $Car/CarType = empty then
set $IsValid = false;
validation feedback $Car/CarType message 'Car type is required';
end if;
return $IsValid;
end;
/
```
### Action Microflow (ACT_Car_NewEdit)
```mdl
/**
* Handles the Save action for Car NewEdit page
*
* Validates the Car, commits it if valid, and closes the page.
*
* @param $Car The Car entity to save
* @returns Boolean indicating success
*/
create microflow MdlTemplates.ACT_Car_NewEdit (
$Car: MdlTemplates.Car
)
returns boolean as $IsValid
folder 'OverviewPages'
begin
-- Call validation microflow
$IsValid = call microflow MdlTemplates.VAL_Car_NewEdit($param = $Car);
-- Only save if validation passed
if $IsValid then
commit $Car;
close page;
end if;
return $IsValid;
end;
/
```
## Validation Patterns
### Simple Required Field Validation
```mdl
if trim($entity/TextField) = '' then
set $IsValid = false;
validation feedback $entity/TextField message 'This field is required';
end if;
```
### Numeric Range Validation
```mdl
if $entity/Amount != empty then
if $entity/Amount < 0 or $entity/Amount > 1000 then
set $IsValid = false;
validation feedback $entity/Amount message 'Amount must be between 0 and 1000';
end if;
end if;
```
### Enumeration Required Validation
```mdl
if $entity/status = empty then
set $IsValid = false;
validation feedback $entity/status message 'Status is required';
end if;
```
### Enumeration Value Comparison
**IMPORTANT**: When comparing enumeration values, use the fully qualified enumeration value, NOT a string literal.
```mdl
-- CORRECT: Use fully qualified enumeration value
if $task/TaskStatus = Module.TaskStatus.Completed then
-- Task is completed
end if;
if $task/TaskStatus != Module.TaskStatus.Cancelled then
-- Task is not cancelled
end if;
-- WRONG: Do NOT use string literals for enumeration comparison
-- IF $Task/TaskStatus = 'Completed' THEN -- This is incorrect!
```
### Conditional Validation Based on Enumeration
```mdl
-- Validate CompletedDate only when status is Completed
if $task/TaskStatus = Module.TaskStatus.Completed then
if $task/CompletedDate = empty then
set $IsValid = false;
validation feedback $task/CompletedDate message 'Completed date is required';
end if;
end if;
-- Validate DueDate for active (non-completed, non-cancelled) tasks
if $task/TaskStatus != Module.TaskStatus.Completed and $task/TaskStatus != Module.TaskStatus.Cancelled then
if $task/DueDate = empty then
set $IsValid = false;
validation feedback $task/DueDate message 'Due date is required for active tasks';
end if;
end if;
```
### Date Validation
```mdl
if $entity/StartDate != empty and $entity/EndDate != empty then
if $entity/EndDate < $entity/StartDate then
set $IsValid = false;
validation feedback $entity/EndDate message 'End date must be after start date';
end if;
end if;
```
### Dynamic Message with Template Arguments
```mdl
-- Build validation message with dynamic content
declare $ValidationMessage string = '';
if $entity/value < $entity/MinValue then
set $ValidationMessage = 'Value must be at least ' + toString($entity/MinValue);
end if;
if $entity/value > $entity/MaxValue then
set $ValidationMessage = if trim($ValidationMessage) = ''
then 'Value must be at most ' + toString($entity/MaxValue)
else $ValidationMessage + '. Value must be at most ' + toString($entity/MaxValue);
end if;
if trim($ValidationMessage) != '' then
set $IsValid = false;
validation feedback $entity/value message '{1}' objects [$ValidationMessage];
end if;
```
### Association Validation
```mdl
-- Validate that an association is set
if $Order/Order_Customer = empty then
set $IsValid = false;
validation feedback $Order/Module.Order_Customer message 'Customer is required';
end if;
```
## Implementation Checklist
When implementing validation microflows:
1. **Initialize the validation flag**: Always start with `declare $IsValid boolean = true;`
2. **Declare all variables before using SET**: You must use `declare` before `set` for primitive variables. Parameters are automatically available but local variables require declaration.
3. **Validate all required fields**: Check each attribute that needs validation
4. **Set flag to false on error**: `set $IsValid = false;` before showing feedback
5. **Show clear error messages**: Use `validation feedback` with descriptive messages
6. **Return the validation flag**: End with `return $IsValid;`
7. **Handle nullable fields**: Check for `empty` before validating nullable fields
8. **Use appropriate validation order**: Validate presence before other constraints
**Important**: The script executor validates that all variables used with `set` are declared. If you use `set $Var = ...` without a prior `declare $Var type = ...`, you will receive an error like:
```
variable '$Var' is not declared. use declare $Var: <type> before using set
```
## Files Modified
This feature is implemented in:
- `mdl/grammar/MDL.g4` - ANTLR4 grammar with VALIDATION FEEDBACK tokens
- `mdl/ast/ast_microflow.go` - AST type definitions (MfValidationFeedbackStmt)
- `mdl/visitor/visitor_microflow_statements.go` - ANTLR listener to build AST
- `mdl/executor/cmd_microflows_builder.go` - Flow builder with variable validation
- `mdl/executor/cmd_microflows_show.go` - DESCRIBE formatter for MDL output
- `mdl/backend/modelsdk/microflow_write.go` - BSON serialization for ValidationFeedbackAction
- `sdk/microflows/microflows_actions.go` - ValidationFeedbackAction struct
+ ## Validation rules (CREATE VALIDATION RULE)
+
+ binds a **regex** or a **range** to one attribute — `create validation rule for Mod.Entity.Attr regex Mod.Pattern feedback '…'`. The rule is anonymous and entity-scoped, so the statement names the attribute; re-running it replaces the rule of the same type and leaves the attribute's others alone. Unlocked by a `STORAGE-NAME OVERRIDE` in `modelsdk/gen`: it bound `RegularExpression` where Studio Pro stores `RegExIdentifier`, and the control (same script, key reverted) fails **CE0135 "No regular expression specified"** while the fixed one is 0 errors on mxbuild 11.13 with `RegExIdentifier` on disk. Range bounds are inclusive and map to Mendix's only three kinds (`from X to Y`/`from X`/`to Y` → Between/GreaterThanOrEqualTo/SmallerThanOrEqualTo); there is no strict `<`/`>`, and the old grammar's forms for it — plus an EXPRESSION rule type Mendix does not have and an inline regex literal — were removed, having never had a visitor or handler. Required/Unique stay attribute constraints (`not null error '…'` / `unique error '…'`), not a second spelling here. Rewriting an entity carrying **MaxLength or EqualsTo** is **refused** on both engines rather than silently downgraded to Required; that round trip was lossy and `mx check` stayed green, because a Required rule is valid. Both engines carry each rule's payload on READ (`ruleInfoFromGen` / `parseValidationRuleInfo`), which is what makes the refusal narrow instead of covering all of RegEx and Range — and what lets a **range bounded by another attribute** survive a rewrite even though MDL cannot author one (`describe entity` marks it with a comment rather than rendering it wrong). A rule whose payload did not survive the read is refused as firmly as an unknown type: a bare RuleInfo of the right `$Type` constrains nothing, which is the same silent downgrade wearing the right name
+