Repository: goadesign/goa
Pull request #3683 (2025-03-26 20:45:06)

Validate example compatibility after DSL evaluation
The type may not have been fully computed when the example DSL function is first run (e.g. `CollectionOf`).


Patch:
diff --git a/dsl/attribute.go b/dsl/attribute.go
index ba4adce7..0576fbe5 100644
--- a/dsl/attribute.go
+++ b/dsl/attribute.go
@@ -338,11 +338,6 @@ func Example(args ...any) {
 		eval.ReportError("example value is missing")
 		return
 	}
-	if a.Type != nil && !a.Type.IsCompatible(ex.Value) {
-		eval.ReportError("example value %#v is incompatible with attribute of type %s",
-			ex.Value, a.Type.Name())
-		return
-	}
 	a.UserExamples = append(a.UserExamples, ex)
 }
 
diff --git a/expr/attribute.go b/expr/attribute.go
index 0742c85f..5193a291 100644
--- a/expr/attribute.go
+++ b/expr/attribute.go
@@ -212,6 +212,7 @@ func (a *AttributeExpr) Validate(ctx string, parent eval.Expression) *eval.Valid
 	if v := a.Validation; v != nil {
 		verr.Merge(v.Validate(ctx, parent))
 	}
+	verr.Merge(a.validateExamples(ctx, parent))
 	if o := AsObject(a.Type); o != nil {
 		for _, n := range a.AllRequired() {
 			if a.Find(n) == nil {
@@ -734,6 +735,18 @@ func (a *AttributeExpr) validateEnumDefault(ctx string, parent eval.Expression)
 	return verr
 }
 
+// validateExamples makes sure that the attribute example values are compatible
+// with the attribute type.
+func (a *AttributeExpr) validateExamples(ctx string, parent eval.Expression) *eval.ValidationErrors {
+	verr := new(eval.ValidationErrors)
+	for _, ex := range a.UserExamples {
+		if !a.Type.IsCompatible(ex.Value) { // DSL ensures ex.Value is not nil
+			verr.Add(parent, "%sexample value %#v is incompatible with type %s", ctx, ex.Value, a.Type.Name())
+		}
+	}
+	return verr
+}
+
 func (a *AttributeExpr) inheritRecursive(parent *AttributeExpr, seen map[*AttributeExpr]struct{}) {
 	if !a.shouldInherit(parent) {
 		return


Test patch:
diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
index 061261fa..b5fa9ea1 100644
--- a/.github/workflows/test.yml
+++ b/.github/workflows/test.yml
@@ -13,7 +13,7 @@ jobs:
     strategy:
       fail-fast: true
       matrix:
-        go: ['1.21', '1.22', '1.23']
+        go: ['1.21', '1.22', '1.23', '1.24']
         os: ['ubuntu-latest', 'windows-latest']
     runs-on: ${{ matrix.os }}
 
diff --git a/expr/example_test.go b/expr/example_test.go
index 09edc55b..01bcf218 100644
--- a/expr/example_test.go
+++ b/expr/example_test.go
@@ -62,7 +62,7 @@ func TestExample(t *testing.T) {
 		{"with-multiple-examples", testdata.WithMultipleExamplesDSL, 100, ""},
 		{"overriding-example", testdata.OverridingExampleDSL, map[string]any{"name": "overridden"}, ""},
 		{"with-extend", testdata.WithExtendExampleDSL, map[string]any{"name": "example"}, ""},
-		{"invalid-example-type", testdata.InvalidExampleTypeDSL, nil, "example value map[int]int{1:1} is incompatible with attribute of type map in attribute"},
+		{"invalid-example-type", testdata.InvalidExampleTypeDSL, nil, "service \"InvalidExampleType\" method \"Method\": payload - example value map[int]int{1:1} is incompatible with type map"},
 		{"empty-example", testdata.EmptyExampleDSL, nil, "too few arguments given to Example in attribute"},
 		{"hiding-example", testdata.HidingExampleDSL, nil, ""},
 		{"overriding-hidden-examples", testdata.OverridingHiddenExamplesDSL, "example", ""},