Skip to content

Commit babc56a

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 d871691 commit babc56a

10 files changed

Lines changed: 2106 additions & 2017 deletions

mdl/executor/cmd_microflows_format_action.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -587,6 +587,8 @@ func formatAction(
587587
if len(parts) >= 3 {
588588
attrPath = varName + "/" + parts[len(parts)-1]
589589
}
590+
} else if a.AssociationName != "" {
591+
attrPath = varName + "/" + a.AssociationName
590592
}
591593
return fmt.Sprintf("validation feedback %s message %s;", attrPath, msgText)
592594

mdl/executor/cmd_microflows_format_action_test.go

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -507,6 +507,37 @@ func TestFormatAction_ValidationFeedback(t *testing.T) {
507507
}
508508
}
509509

510+
func TestFormatAction_ValidationFeedback_ObjectOnlyTarget(t *testing.T) {
511+
e := newTestExecutor()
512+
action := &microflows.ValidationFeedbackAction{
513+
ObjectVariable: "Customer",
514+
Template: &model.Text{
515+
Translations: map[string]string{"en_US": "Select a customer"},
516+
},
517+
}
518+
got := e.formatAction(action, nil, nil)
519+
want := "validation feedback $Customer message 'Select a customer';"
520+
if got != want {
521+
t.Errorf("got %q, want %q", got, want)
522+
}
523+
}
524+
525+
func TestFormatAction_ValidationFeedback_AssociationTarget(t *testing.T) {
526+
e := newTestExecutor()
527+
action := &microflows.ValidationFeedbackAction{
528+
ObjectVariable: "OrderForm",
529+
AssociationName: "Sales.OrderForm_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 $OrderForm/Sales.OrderForm_Customer message 'Select a customer';"
536+
if got != want {
537+
t.Errorf("got %q, want %q", got, want)
538+
}
539+
}
540+
510541
func TestFormatAction_LogMessage(t *testing.T) {
511542
e := newTestExecutor()
512543
action := &microflows.LogMessageAction{

mdl/grammar/MDLParser.g4

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1321,7 +1321,7 @@ changeObjectStatement
13211321
;
13221322

13231323
attributePath
1324-
: VARIABLE ((SLASH | DOT) (IDENTIFIER | qualifiedName))+
1324+
: VARIABLE ((SLASH | DOT) qualifiedName)+
13251325
;
13261326

13271327
// COMMIT $Product; or COMMIT $Product WITH EVENTS; or COMMIT $Product REFRESH;
@@ -1565,7 +1565,7 @@ throwStatement
15651565
// VALIDATION FEEDBACK $Product/Code MESSAGE 'Product code cannot be empty';
15661566
// VALIDATION FEEDBACK $Product/Code MESSAGE '{1}' OBJECTS [$Var1, $Var2];
15671567
validationFeedbackStatement
1568-
: VALIDATION FEEDBACK attributePath MESSAGE expression (OBJECTS LBRACKET expressionList RBRACKET)?
1568+
: VALIDATION FEEDBACK (attributePath | VARIABLE) MESSAGE expression (OBJECTS LBRACKET expressionList RBRACKET)?
15691569
;
15701570

15711571
// =============================================================================

mdl/grammar/parser/MDLParser.interp

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

mdl/grammar/parser/mdl_lexer.go

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

mdl/grammar/parser/mdl_parser.go

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

mdl/grammar/parser/mdlparser_base_listener.go

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

mdl/grammar/parser/mdlparser_listener.go

Lines changed: 1 addition & 1 deletion
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
@@ -826,6 +826,10 @@ func buildValidationFeedbackStatement(ctx parser.IValidationFeedbackStatementCon
826826
// Build attribute path
827827
if attrPath := vfCtx.AttributePath(); attrPath != nil {
828828
stmt.AttributePath = buildAttributePathFromContext(attrPath)
829+
} else if variable := vfCtx.VARIABLE(); variable != nil {
830+
stmt.AttributePath = &ast.AttributePathExpr{
831+
Variable: strings.TrimPrefix(variable.GetText(), "$"),
832+
}
829833
}
830834

831835
// 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)