Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 9 additions & 3 deletions expr/result_type.go
Original file line number Diff line number Diff line change
Expand Up @@ -363,7 +363,7 @@ func projectCollection(rt *ResultTypeExpr, view string, seen map[string]*Attribu
}

func projectRecursive(at *AttributeExpr, vat *NamedAttributeExpr, view string, seen map[string]*AttributeExpr) (*AttributeExpr, error) {
if att, ok := seen[hashAttrAndView(at, view)]; ok {
if att, ok := seen[hashAttrViewField(at, view, vat.Name)]; ok {
return att, nil
}
at = DupAtt(at)
Expand All @@ -378,7 +378,7 @@ func projectRecursive(at *AttributeExpr, vat *NamedAttributeExpr, view string, s
view = DefaultView
}
}
seen[hashAttrAndView(at, view)] = at
seen[hashAttrViewField(at, view, vat.Name)] = at
pr, err := project(rt, view, seen)
if err != nil {
return nil, fmt.Errorf("view %#v on field %#v cannot be computed: %w", view, vat.Name, err)
Expand All @@ -388,7 +388,7 @@ func projectRecursive(at *AttributeExpr, vat *NamedAttributeExpr, view string, s
}

if _, ok := at.Type.(*UserTypeExpr); ok {
seen[hashAttrAndView(at, view)] = at
seen[hashAttrViewField(at, view, vat.Name)] = at
}

if obj := AsObject(at.Type); obj != nil {
Expand Down Expand Up @@ -462,3 +462,9 @@ func (v *ViewExpr) EvalName() string {
func hashAttrAndView(att *AttributeExpr, view string) string {
return Hash(att.Type, false, false, false) + "::" + view
}

// hashAttrViewField computes the projection cache key for an attribute, view,
// and field name.
func hashAttrViewField(att *AttributeExpr, view, field string) string {
return hashAttrAndView(att, view) + "::" + field
}
2 changes: 2 additions & 0 deletions http/codegen/server_types_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ func TestServerTypes(t *testing.T) {
{"server-result-type-validate", testdata.ResultTypeValidateDSL},
{"server-with-result-collection", testdata.ResultWithResultCollectionDSL},
{"server-with-result-view", testdata.ResultWithResultViewDSL},
{"server-with-result-sibling-user-type-fields", testdata.ResultTypeSiblingUserTypeFieldsDSL},
{"server-with-result-collection-sibling-user-type-fields", testdata.ResultTypeCollectionSiblingUserTypeFieldsDSL},
{"server-empty-error-response-body", testdata.EmptyErrorResponseBodyDSL},
{"server-with-error-custom-pkg", testdata.WithErrorCustomPkgDSL},
{"server-body-custom-name", testdata.PayloadBodyCustomNameDSL},
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
// ResulttypesiblingcollectionResponseCollection is the type of the
// "ServiceResultCollectionUserTypeSibling" service
// "MethodResultCollectionUserTypeSibling" endpoint HTTP response body.
type ResulttypesiblingcollectionResponseCollection []*ResulttypesiblingcollectionResponse

// ResulttypesiblingcollectionResponse is used to define fields on response
// body types.
type ResulttypesiblingcollectionResponse struct {
// Attribute A
A *UserTypeResponse `json:"a"`
// Attribute B
B *UserTypeResponse `json:"b"`
}

// UserTypeResponse is used to define fields on response body types.
type UserTypeResponse struct {
U *int `form:"u,omitempty" json:"u,omitempty" xml:"u,omitempty"`
}

// NewResulttypesiblingcollectionResponseCollection builds the HTTP response
// body from the result of the "MethodResultCollectionUserTypeSibling" endpoint
// of the "ServiceResultCollectionUserTypeSibling" service.
func NewResulttypesiblingcollectionResponseCollection(res serviceresultcollectionusertypesiblingviews.ResulttypesiblingcollectionCollectionView) ResulttypesiblingcollectionResponseCollection {
body := make([]*ResulttypesiblingcollectionResponse, len(res))
for i, val := range res {
if val == nil {
body[i] = nil
continue
}
body[i] = marshalServiceresultcollectionusertypesiblingviewsResulttypesiblingcollectionViewToResulttypesiblingcollectionResponse(val)
}
return body
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
// MethodResultUserTypeSiblingResponseBody is the type of the
// "ServiceResultUserTypeSibling" service "MethodResultUserTypeSibling"
// endpoint HTTP response body.
type MethodResultUserTypeSiblingResponseBody struct {
// Attribute A
A *UserTypeResponseBody `json:"a"`
// Attribute B
B *UserTypeResponseBody `json:"b"`
}

// UserTypeResponseBody is used to define fields on response body types.
type UserTypeResponseBody struct {
U *int `form:"u,omitempty" json:"u,omitempty" xml:"u,omitempty"`
}

// NewMethodResultUserTypeSiblingResponseBody builds the HTTP response body
// from the result of the "MethodResultUserTypeSibling" endpoint of the
// "ServiceResultUserTypeSibling" service.
func NewMethodResultUserTypeSiblingResponseBody(res *serviceresultusertypesiblingviews.ResulttypesiblingView) *MethodResultUserTypeSiblingResponseBody {
body := &MethodResultUserTypeSiblingResponseBody{}
if res.A != nil {
body.A = marshalServiceresultusertypesiblingviewsUserTypeViewToUserTypeResponseBody(res.A)
}
if res.B != nil {
body.B = marshalServiceresultusertypesiblingviewsUserTypeViewToUserTypeResponseBody(res.B)
}
return body
}
56 changes: 56 additions & 0 deletions http/codegen/testdata/result_dsls.go
Original file line number Diff line number Diff line change
Expand Up @@ -761,6 +761,62 @@ var ResultWithResultCollectionDSL = func() {
})
}

// ResultTypeSiblingUserTypeFieldsDSL defines a result type with sibling fields (a, b)
// that both reference the same named type (UserType). This tests the projection cache
// fix for a bug where sibling fields were sharing the same AttributeExpr pointer,
// causing metadata (descriptions and JSON tags) to leak between them.
var ResultTypeSiblingUserTypeFieldsDSL = func() {
var UserType = Type("UserType", func() {
Attribute("u", Int)
})

var RT = ResultType("ResultTypeSibling", func() {
Attribute("a", UserType, "Attribute A", func() {
Meta("struct:tag:json", "a")
})
Attribute("b", UserType, "Attribute B", func() {
Meta("struct:tag:json", "b")
})
})

Service("ServiceResultUserTypeSibling", func() {
Method("MethodResultUserTypeSibling", func() {
Result(RT)
HTTP(func() {
GET("/")
})
})
})
}

// ResultTypeCollectionSiblingUserTypeFieldsDSL defines a result type collection with
// sibling fields (a, b) that both reference the same named type (UserType). This tests
// the fix for a bug where sibling fields were sharing the same AttributeExpr pointer,
// causing metadata (descriptions and JSON tags) to leak between them.
var ResultTypeCollectionSiblingUserTypeFieldsDSL = func() {
var UserType = Type("UserType", func() {
Attribute("u", Int)
})

var RT = ResultType("ResultTypeSiblingCollection", func() {
Attribute("a", UserType, "Attribute A", func() {
Meta("struct:tag:json", "a")
})
Attribute("b", UserType, "Attribute B", func() {
Meta("struct:tag:json", "b")
})
})

Service("ServiceResultCollectionUserTypeSibling", func() {
Method("MethodResultCollectionUserTypeSibling", func() {
Result(CollectionOf(RT))
HTTP(func() {
GET("/")
})
})
})
}

var ResultWithCustomPkgTypeDSL = func() {
var Foo = Type("Foo", func() {
Meta("struct:pkg:path", "foo")
Expand Down
Loading