Skip to content

Commit f740ef7

Browse files
authored
fix(expr): use AttributeExpr for UserType example generation (#3730)
Previously, UserTypeExpr.recExample() called u.Type.Example() instead of u.AttributeExpr.Example(), which ignored validation rules and custom examples defined on the UserType. This caused UserTypes with format validation (e.g., FormatURI) to generate generic string examples instead of format-appropriate ones, and custom examples were not being used. The fix ensures that when generating examples for UserTypes, the full AttributeExpr (including validation and examples) is used rather than just the underlying Type. Fixes #3716 * Add comprehensive tests for UserType example generation * Test UserTypes with format validation * Test UserTypes with custom examples * Test attribute inheritance from UserTypes
1 parent 4f43169 commit f740ef7

2 files changed

Lines changed: 177 additions & 1 deletion

File tree

expr/user_type.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,7 @@ func (u *UserTypeExpr) recExample(r *ExampleGenerator) *any {
9696
var ex any
9797
pex := &ex
9898
r.HaveSeen(u.ID(), pex)
99-
actual := u.Type.Example(r)
99+
actual := u.AttributeExpr.Example(r)
100100
*pex = actual
101101
return pex
102102
}

expr/user_type_example_test.go

Lines changed: 176 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,176 @@
1+
package expr_test
2+
3+
import (
4+
"testing"
5+
6+
"goa.design/goa/v3/expr"
7+
)
8+
9+
// TestUserTypeWithUserExample tests that when an attribute uses a UserType
10+
// and has a user-defined example, the user example is used.
11+
func TestUserTypeWithUserExample(t *testing.T) {
12+
// Create a UserType "URL" with FormatURI validation
13+
urlType := &expr.UserTypeExpr{
14+
AttributeExpr: &expr.AttributeExpr{
15+
Type: expr.String,
16+
Validation: &expr.ValidationExpr{
17+
Format: expr.FormatURI,
18+
},
19+
},
20+
TypeName: "URL",
21+
}
22+
23+
// Create an attribute that uses the URL type with a custom example
24+
customURL := "http://www.seller.topi.eu/offer/739b63c2-9e58-4964-b38d-4ebb424de242%2Fv1"
25+
attr := &expr.AttributeExpr{
26+
Type: urlType,
27+
UserExamples: []*expr.ExampleExpr{
28+
{
29+
Summary: "default",
30+
Value: customURL,
31+
},
32+
},
33+
}
34+
35+
// Test with both randomizers to ensure user examples always take precedence
36+
t.Run("with faker randomizer", func(t *testing.T) {
37+
exampleGen := expr.NewRandom("test")
38+
example := attr.Example(exampleGen)
39+
if example != customURL {
40+
t.Errorf("Attribute with user example should return %q, got %q", customURL, example)
41+
}
42+
})
43+
44+
t.Run("with deterministic randomizer", func(t *testing.T) {
45+
gen := expr.NewDeterministicRandomizer()
46+
exampleGen := &expr.ExampleGenerator{
47+
Randomizer: gen,
48+
}
49+
example := attr.Example(exampleGen)
50+
if example != customURL {
51+
t.Errorf("Attribute with user example should return %q, got %q", customURL, example)
52+
}
53+
})
54+
}
55+
56+
// TestUserTypeFormatWithCustomExample tests the exact scenario from issue #3716
57+
// where a UserType with FormatURI has a custom example that should be preserved
58+
func TestUserTypeFormatWithCustomExample(t *testing.T) {
59+
// Create a UserType "URL" with FormatURI validation (no example on the type itself)
60+
urlType := &expr.UserTypeExpr{
61+
AttributeExpr: &expr.AttributeExpr{
62+
Type: expr.String,
63+
Validation: &expr.ValidationExpr{
64+
Format: expr.FormatURI,
65+
},
66+
},
67+
TypeName: "URL",
68+
}
69+
70+
// Create an attribute using the URL type with a custom example
71+
// This simulates: Attribute("seller_offer_redirect_url", URL, func() { Example("...") })
72+
customExample := "http://www.seller.topi.eu/offer/739b63c2-9e58-4964-b38d-4ebb424de242%2Fv1"
73+
attr := &expr.AttributeExpr{
74+
Type: urlType,
75+
UserExamples: []*expr.ExampleExpr{
76+
{
77+
Summary: "default",
78+
Value: customExample,
79+
},
80+
},
81+
}
82+
83+
// Test with deterministic randomizer (as reported in the issue)
84+
gen := expr.NewDeterministicRandomizer()
85+
exampleGen := &expr.ExampleGenerator{
86+
Randomizer: gen,
87+
}
88+
89+
// The bug was that this would return "https://example.com/foo" instead of the custom example
90+
example := attr.Example(exampleGen)
91+
92+
if example != customExample {
93+
t.Errorf("Custom example should be preserved. Expected %q but got %q", customExample, example)
94+
}
95+
}
96+
97+
// TestIssue3716Regression tests the specific regression where UserTypes with
98+
// format validation would not generate format-appropriate examples
99+
func TestIssue3716Regression(t *testing.T) {
100+
// This test captures what happens when we need to generate an example
101+
// for an object that contains a field with a UserType that has format validation
102+
103+
// Create the URL UserType
104+
urlType := &expr.UserTypeExpr{
105+
AttributeExpr: &expr.AttributeExpr{
106+
Type: expr.String,
107+
Validation: &expr.ValidationExpr{
108+
Format: expr.FormatURI,
109+
},
110+
},
111+
TypeName: "URL",
112+
}
113+
114+
// Create an object with a field that uses the URL type but no explicit example
115+
obj := &expr.Object{
116+
{"redirect_url", &expr.AttributeExpr{Type: urlType}},
117+
}
118+
119+
// When generating an example for the object
120+
gen := expr.NewDeterministicRandomizer()
121+
exampleGen := &expr.ExampleGenerator{
122+
Randomizer: gen,
123+
}
124+
125+
example := obj.Example(exampleGen)
126+
objExample, ok := example.(map[string]any)
127+
if !ok {
128+
t.Fatalf("Expected map example, got %T", example)
129+
}
130+
131+
// The redirect_url field should have a proper URI example
132+
redirectURL, ok := objExample["redirect_url"].(string)
133+
if !ok {
134+
t.Fatalf("Expected string for redirect_url, got %T", objExample["redirect_url"])
135+
}
136+
137+
// With the bug, this would be "abc123" (from String type)
138+
// With the fix, this should be "https://example.com/foo" (from FormatURI)
139+
if redirectURL != "https://example.com/foo" {
140+
t.Errorf("Expected URI example for UserType with FormatURI, got %q", redirectURL)
141+
}
142+
}
143+
144+
// TestUserTypeWithOwnExample tests that a UserType can have its own example
145+
func TestUserTypeWithOwnExample(t *testing.T) {
146+
// Create a UserType "URL" with FormatURI validation AND its own example
147+
customExample := "https://api.example.com/v1/resources"
148+
urlType := &expr.UserTypeExpr{
149+
AttributeExpr: &expr.AttributeExpr{
150+
Type: expr.String,
151+
Validation: &expr.ValidationExpr{
152+
Format: expr.FormatURI,
153+
},
154+
UserExamples: []*expr.ExampleExpr{
155+
{
156+
Summary: "default",
157+
Value: customExample,
158+
},
159+
},
160+
},
161+
TypeName: "URL",
162+
}
163+
164+
// Use deterministic randomizer
165+
gen := expr.NewDeterministicRandomizer()
166+
exampleGen := &expr.ExampleGenerator{
167+
Randomizer: gen,
168+
}
169+
170+
// The UserType itself should return its custom example
171+
example := urlType.Example(exampleGen)
172+
173+
if example != customExample {
174+
t.Errorf("UserType with custom example should return %q, got %q", customExample, example)
175+
}
176+
}

0 commit comments

Comments
 (0)