Skip to content

Commit 3f0c44d

Browse files
committed
fix: preserve validation feedback targets
Validation feedback actions can target an object or an association path, but the MDL grammar only accepted attribute paths and the formatter only emitted attribute targets. Round-tripping those actions either failed to parse object-only targets or dropped the association name. The parser now accepts object-only validation feedback targets and treats each path segment as a qualified name so Module.Association stays a single association segment. The formatter emits AssociationName when AttributeName is absent, preserving association targets from existing models. Tests cover object-only and association formatter output, parser coverage for both target shapes, and the full build/lint/test validation passed locally.
1 parent 6f4534f commit 3f0c44d

7 files changed

Lines changed: 1085 additions & 996 deletions

File tree

mdl/executor/cmd_microflows_format_action.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -692,6 +692,8 @@ func formatAction(
692692
if len(parts) >= 3 {
693693
attrPath = varName + "/" + parts[len(parts)-1]
694694
}
695+
} else if a.AssociationName != "" {
696+
attrPath = varName + "/" + a.AssociationName
695697
}
696698
return fmt.Sprintf("validation feedback %s message %s;", attrPath, msgText)
697699

mdl/executor/cmd_microflows_format_action_test.go

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -523,6 +523,37 @@ func TestFormatAction_ValidationFeedback(t *testing.T) {
523523
}
524524
}
525525

526+
func TestFormatAction_ValidationFeedback_ObjectOnlyTarget(t *testing.T) {
527+
e := newTestExecutor()
528+
action := &microflows.ValidationFeedbackAction{
529+
ObjectVariable: "Customer",
530+
Template: &model.Text{
531+
Translations: map[string]string{"en_US": "Select a customer"},
532+
},
533+
}
534+
got := e.formatAction(action, nil, nil)
535+
want := "validation feedback $Customer message 'Select a customer';"
536+
if got != want {
537+
t.Errorf("got %q, want %q", got, want)
538+
}
539+
}
540+
541+
func TestFormatAction_ValidationFeedback_AssociationTarget(t *testing.T) {
542+
e := newTestExecutor()
543+
action := &microflows.ValidationFeedbackAction{
544+
ObjectVariable: "OrderForm",
545+
AssociationName: "Sales.OrderForm_Customer",
546+
Template: &model.Text{
547+
Translations: map[string]string{"en_US": "Select a customer"},
548+
},
549+
}
550+
got := e.formatAction(action, nil, nil)
551+
want := "validation feedback $OrderForm/Sales.OrderForm_Customer message 'Select a customer';"
552+
if got != want {
553+
t.Errorf("got %q, want %q", got, want)
554+
}
555+
}
556+
526557
func TestFormatAction_LogMessage(t *testing.T) {
527558
e := newTestExecutor()
528559
action := &microflows.LogMessageAction{

mdl/grammar/MDLParser.g4

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1364,7 +1364,7 @@ changeObjectStatement
13641364
;
13651365

13661366
attributePath
1367-
: VARIABLE ((SLASH | DOT) (IDENTIFIER | qualifiedName))+
1367+
: VARIABLE ((SLASH | DOT) qualifiedName)+
13681368
;
13691369

13701370
// COMMIT $Product; or COMMIT $Product WITH EVENTS; or COMMIT $Product REFRESH;
@@ -1621,7 +1621,7 @@ throwStatement
16211621
// VALIDATION FEEDBACK $Product/Code MESSAGE 'Product code cannot be empty';
16221622
// VALIDATION FEEDBACK $Product/Code MESSAGE '{1}' OBJECTS [$Var1, $Var2];
16231623
validationFeedbackStatement
1624-
: VALIDATION FEEDBACK attributePath MESSAGE expression (OBJECTS LBRACKET expressionList RBRACKET)?
1624+
: VALIDATION FEEDBACK (attributePath | VARIABLE) MESSAGE expression (OBJECTS LBRACKET expressionList RBRACKET)?
16251625
;
16261626

16271627
// =============================================================================

mdl/grammar/parser/MDLParser.interp

Lines changed: 1 addition & 1 deletion
Large diffs are not rendered by default.

mdl/grammar/parser/mdl_parser.go

Lines changed: 988 additions & 993 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

mdl/visitor/visitor_microflow_actions.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -909,6 +909,10 @@ func buildValidationFeedbackStatement(ctx parser.IValidationFeedbackStatementCon
909909
// Build attribute path
910910
if attrPath := vfCtx.AttributePath(); attrPath != nil {
911911
stmt.AttributePath = buildAttributePathFromContext(attrPath)
912+
} else if variable := vfCtx.VARIABLE(); variable != nil {
913+
stmt.AttributePath = &ast.AttributePathExpr{
914+
Variable: strings.TrimPrefix(variable.GetText(), "$"),
915+
}
912916
}
913917

914918
// Build message expression

mdl/visitor/visitor_test.go

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -605,6 +605,63 @@ END;`
605605
t.Log("VALIDATION FEEDBACK inside IF block parsed correctly")
606606
}
607607

608+
func TestValidationFeedbackObjectAndAssociationTargets(t *testing.T) {
609+
input := `CREATE MICROFLOW Sales.ValidateOrder ($OrderForm: Sales.OrderForm)
610+
RETURNS Boolean
611+
BEGIN
612+
VALIDATION FEEDBACK $OrderForm MESSAGE 'Select a value';
613+
VALIDATION FEEDBACK $OrderForm/Sales.OrderForm_Customer MESSAGE 'Select a customer';
614+
RETURN false;
615+
END;`
616+
617+
prog, errs := Build(input)
618+
if len(errs) > 0 {
619+
for _, err := range errs {
620+
t.Errorf("Parse error: %v", err)
621+
}
622+
return
623+
}
624+
625+
stmt := prog.Statements[0].(*ast.CreateMicroflowStmt)
626+
if len(stmt.Body) < 2 {
627+
t.Fatalf("Expected at least two body statements, got %d", len(stmt.Body))
628+
}
629+
630+
objectFeedback, ok := stmt.Body[0].(*ast.ValidationFeedbackStmt)
631+
if !ok {
632+
t.Fatalf("Expected object-only validation feedback, got %T", stmt.Body[0])
633+
}
634+
if objectFeedback.AttributePath == nil {
635+
t.Fatal("Expected object-only AttributePath to be set")
636+
}
637+
if objectFeedback.AttributePath.Variable != "OrderForm" {
638+
t.Errorf("Expected object-only variable 'OrderForm', got %q", objectFeedback.AttributePath.Variable)
639+
}
640+
if len(objectFeedback.AttributePath.Path) != 0 || len(objectFeedback.AttributePath.Segments) != 0 {
641+
t.Errorf("Expected object-only validation feedback to have no path, got path=%v segments=%v",
642+
objectFeedback.AttributePath.Path, objectFeedback.AttributePath.Segments)
643+
}
644+
645+
associationFeedback, ok := stmt.Body[1].(*ast.ValidationFeedbackStmt)
646+
if !ok {
647+
t.Fatalf("Expected association validation feedback, got %T", stmt.Body[1])
648+
}
649+
if associationFeedback.AttributePath == nil {
650+
t.Fatal("Expected association AttributePath to be set")
651+
}
652+
if associationFeedback.AttributePath.Variable != "OrderForm" {
653+
t.Errorf("Expected association variable 'OrderForm', got %q", associationFeedback.AttributePath.Variable)
654+
}
655+
if len(associationFeedback.AttributePath.Path) != 1 || associationFeedback.AttributePath.Path[0] != "Sales.OrderForm_Customer" {
656+
t.Errorf("Expected association path Sales.OrderForm_Customer, got %v", associationFeedback.AttributePath.Path)
657+
}
658+
if len(associationFeedback.AttributePath.Segments) != 1 ||
659+
associationFeedback.AttributePath.Segments[0].Separator != "/" ||
660+
associationFeedback.AttributePath.Segments[0].Name != "Sales.OrderForm_Customer" {
661+
t.Errorf("Expected slash-qualified association segment, got %v", associationFeedback.AttributePath.Segments)
662+
}
663+
}
664+
608665
// TestRollbackStatement verifies the ROLLBACK statement parses correctly.
609666
func TestRollbackStatement(t *testing.T) {
610667
input := `CREATE MICROFLOW Test.TestRollback ($Order: Test.Order)

0 commit comments

Comments
 (0)