Skip to content

Commit

Permalink
Fix embedded explicit view (#3567)
Browse files Browse the repository at this point in the history
* Clean up

* Project explicit views during finalization

This fixes issues with result types embedded in user types that declared inline in method results.

* Fix linter issues

* Fix linter issues
  • Loading branch information
raphael authored Jul 28, 2024
1 parent 4d06dd6 commit 687dc0b
Show file tree
Hide file tree
Showing 15 changed files with 699 additions and 705 deletions.
6 changes: 3 additions & 3 deletions codegen/service/example_svc.go
Original file line number Diff line number Diff line change
Expand Up @@ -120,9 +120,9 @@ func basicEndpointSection(m *expr.MethodExpr, svcData *Data) *codegen.SectionTem
ed.ResultFullRef = svcData.Scope.GoFullTypeRef(m.Result, svcData.PkgName)
ed.ResultIsStruct = expr.IsObject(m.Result.Type)
if md.ViewedResult != nil {
view := "default"
if v, ok := m.Result.Meta["view"]; ok {
view = v[0]
view := expr.DefaultView
if v, ok := m.Result.Meta.Last(expr.ViewMetaKey); ok {
view = v
}
ed.ResultView = view
}
Expand Down
42 changes: 20 additions & 22 deletions codegen/service/service_data.go
Original file line number Diff line number Diff line change
Expand Up @@ -612,20 +612,17 @@ func (d ServicesData) analyze(service *expr.ServiceExpr) *Data {
collectUserTypes(m.Payload)
collectUserTypes(m.StreamingPayload)
collectUserTypes(m.Result)
if _, ok := m.Result.Type.(*expr.ResultTypeExpr); ok {
// collect projected types for the corresponding result type
projected := expr.DupAtt(m.Result)
types, umeths := collectProjectedTypes(projected, m.Result, viewspkg, scope, viewScope, seenProj)
projTypes = append(projTypes, types...)
viewedUnionMeths = append(viewedUnionMeths, umeths...)
}
// Collect projected types
types, umeths := collectProjectedTypes(expr.DupAtt(m.Result), m.Result, viewspkg, scope, viewScope, seenProj)
projTypes = append(projTypes, types...)
viewedUnionMeths = append(viewedUnionMeths, umeths...)
for _, er := range m.Errors {
recordError(er)
}
}

// A function to convert raw object type to user type.
makeUserType := func(att *expr.AttributeExpr, name, id string) {
wrapObject := func(att *expr.AttributeExpr, name, id string) {
if _, ok := att.Type.(*expr.Object); ok {
att.Type = &expr.UserTypeExpr{
AttributeExpr: expr.DupAtt(att),
Expand All @@ -638,15 +635,16 @@ func (d ServicesData) analyze(service *expr.ServiceExpr) *Data {
}
}

for _, e := range service.Methods {
name := codegen.Goify(e.Name, true)
for _, m := range service.Methods {
name := codegen.Goify(m.Name, true)
// Create user type for raw object payloads
makeUserType(e.Payload, name+"Payload", service.Name+"#"+name+"Payload")
wrapObject(m.Payload, name+"Payload", service.Name+"#"+name+"Payload")
// Create user type for raw object streaming payloads
makeUserType(e.StreamingPayload, name+"StreamingPayload", service.Name+"#"+name+"StreamingPayload")
wrapObject(m.StreamingPayload, name+"StreamingPayload", service.Name+"#"+name+"StreamingPayload")
// Create user type for raw object results
makeUserType(e.Result, name+"Result", service.Name+"#"+name+"Result")
wrapObject(m.Result, name+"Result", service.Name+"#"+name+"Result")
}

}

// Add forced types
Expand Down Expand Up @@ -687,8 +685,8 @@ func (d ServicesData) analyze(service *expr.ServiceExpr) *Data {
continue
}
var view string
if v, ok := e.Result.Meta["view"]; ok {
view = v[0]
if v, ok := e.Result.Meta.Last(expr.ViewMetaKey); ok {
view = v
}
if vrt, ok := seenViewed[m.Result+"::"+view]; ok {
m.ViewedResult = vrt
Expand Down Expand Up @@ -1328,8 +1326,8 @@ func buildViewedResultType(att, projected *expr.AttributeExpr, viewspkg string,
if !rt.HasMultipleViews() {
viewName = expr.DefaultView
}
if v, ok := att.Meta["view"]; ok && len(v) > 0 {
viewName = v[0]
if v, ok := att.Meta.Last(expr.ViewMetaKey); ok {
viewName = v
}
views = buildViews(rt, viewScope)
}
Expand Down Expand Up @@ -1666,7 +1664,7 @@ func buildValidations(projected *expr.AttributeExpr, scope *codegen.NameScope) [
)
{
name = "Validate" + tname
if view.Name != "default" {
if view.Name != expr.DefaultView {
vn = codegen.Goify(view.Name, true)
name += vn
}
Expand All @@ -1689,8 +1687,8 @@ func buildValidations(projected *expr.AttributeExpr, scope *codegen.NameScope) [
// use explicitly specified view (if any) for the attribute,
// otherwise use default
vw := ""
if v, ok := vatt.Meta["view"]; ok && len(v) > 0 && v[0] != expr.DefaultView {
vw = v[0]
if v, ok := vatt.Meta.Last(expr.ViewMetaKey); ok && v != expr.DefaultView {
vw = v
}
fields = append(fields, map[string]any{
"Name": name,
Expand Down Expand Up @@ -1807,9 +1805,9 @@ func buildConstructorCode(src, tgt *expr.AttributeExpr, sourceVar, targetVar str
if view != "" {
v := ""
if vatt := rt.View(view).AttributeExpr.Find(nat.Name); vatt != nil {
if attv, ok := vatt.Meta["view"]; ok && len(attv) > 0 && attv[0] != expr.DefaultView {
if attv, ok := vatt.Meta.Last(expr.ViewMetaKey); ok && attv != expr.DefaultView {
// view is explicitly set for the result type on the attribute
v = attv[0]
v = attv
}
}
finit += codegen.Goify(v, true)
Expand Down
22 changes: 22 additions & 0 deletions codegen/service/testdata/service_code.go
Original file line number Diff line number Diff line change
Expand Up @@ -1449,6 +1449,28 @@ func newApplicationDashedTypeView(res *ApplicationDashedType) *resultwithdashedm
}
return vres
}
// newApplicationDashedTypeCollection converts projected type
// ApplicationDashedTypeCollection to service type
// ApplicationDashedTypeCollection.
func newApplicationDashedTypeCollection(vres resultwithdashedmimetypeviews.ApplicationDashedTypeCollectionView) ApplicationDashedTypeCollection {
res := make(ApplicationDashedTypeCollection, len(vres))
for i, n := range vres {
res[i] = newApplicationDashedType(n)
}
return res
}
// newApplicationDashedTypeCollectionView projects result type
// ApplicationDashedTypeCollection to projected type
// ApplicationDashedTypeCollectionView using the "default" view.
func newApplicationDashedTypeCollectionView(res ApplicationDashedTypeCollection) resultwithdashedmimetypeviews.ApplicationDashedTypeCollectionView {
vres := make(resultwithdashedmimetypeviews.ApplicationDashedTypeCollectionView, len(res))
for i, n := range res {
vres[i] = newApplicationDashedTypeView(n)
}
return vres
}
`

const ResultWithOneOfTypeMethod = `
Expand Down
77 changes: 42 additions & 35 deletions dsl/result_type.go
Original file line number Diff line number Diff line change
Expand Up @@ -198,44 +198,51 @@ func TypeName(name string) {
// })
// })
func View(name string, adsl ...func()) {
switch e := eval.Current().(type) {
case *expr.ResultTypeExpr:
if e.View(name) != nil {
eval.ReportError("view %q is defined multiple times in result type %q", name, e.TypeName)
return
if adsl == nil {
switch e := eval.Current().(type) {
case *expr.ResultTypeExpr:
e.AddMeta(expr.ViewMetaKey, name)
case *expr.AttributeExpr:
e.AddMeta(expr.ViewMetaKey, name)
default:
eval.IncompatibleDSL()
}
at := &expr.AttributeExpr{}
ok := false
if len(adsl) > 0 {
ok = eval.Execute(adsl[0], at)
} else if a, ok := e.Type.(*expr.Array); ok {
// inherit view from collection element if present
if elem := a.ElemType; elem != nil {
if pa, ok2 := elem.Type.(*expr.ResultTypeExpr); ok2 {
if v := pa.View(name); v != nil {
at = v.AttributeExpr
e = pa
} else {
eval.ReportError("unknown view %#v", name)
return
}
return
}
rt, ok := eval.Current().(*expr.ResultTypeExpr)
if !ok {
eval.IncompatibleDSL()
return
}
if rt.View(name) != nil {
eval.ReportError("view %q is defined multiple times in result type %q", name, rt.TypeName)
return
}
at := &expr.AttributeExpr{}
ok = false
if len(adsl) > 0 {
ok = eval.Execute(adsl[0], at)
} else if a, ok := rt.Type.(*expr.Array); ok {
// inherit view from collection element if present
if elem := a.ElemType; elem != nil {
if pa, ok2 := elem.Type.(*expr.ResultTypeExpr); ok2 {
if v := pa.View(name); v != nil {
at = v.AttributeExpr
rt = pa
} else {
eval.ReportError("unknown view %#v", name)
return
}
}
}
if ok {
view, err := buildView(name, e, at)
if err != nil {
eval.ReportError(err.Error())
return
}
e.Views = append(e.Views, view)
}
if ok {
view, err := buildView(name, rt, at)
if err != nil {
eval.ReportError(err.Error())
return
}

case *expr.AttributeExpr:
e.AddMeta("view", name)

default:
eval.IncompatibleDSL()
rt.Views = append(rt.Views, view)
}
}

Expand Down Expand Up @@ -519,8 +526,8 @@ func buildView(name string, mt *expr.ResultTypeExpr, at *expr.AttributeExpr) (*e
cat := nat.Attribute
if existing := mt.Find(n); existing != nil {
dup := expr.DupAtt(existing)
if _, ok := cat.Meta["view"]; ok {
dup.AddMeta("view", cat.Meta["view"]...)
if v, ok := cat.Meta.Last(expr.ViewMetaKey); ok {
dup.AddMeta("view", v)
}
o.Set(n, dup)
} else if n != "links" {
Expand Down
20 changes: 9 additions & 11 deletions expr/attribute.go
Original file line number Diff line number Diff line change
Expand Up @@ -243,21 +243,21 @@ func (a *AttributeExpr) Validate(ctx string, parent eval.Expression) *eval.Valid
}
}

if views, ok := a.Meta["view"]; ok {
if view, ok := a.Meta.Last(ViewMetaKey); ok {
rt, ok := a.Type.(*ResultTypeExpr)
if !ok {
verr.Add(parent, "%s uses view %q but %q is not a result type", ctx, views[0], a.Type.Name())
verr.Add(parent, "%s uses view %q but %q is not a result type", ctx, view, a.Type.Name())
}
if name := views[0]; name != "default" && rt != nil {
if view != DefaultView && rt != nil {
found := false
for _, v := range rt.Views {
if v.Name == name {
if v.Name == view {
found = true
break
}
}
if !found {
verr.Add(parent, "%s: type %q does not define view %q", ctx, a.Type.Name(), name)
verr.Add(parent, "%s: type %q does not define view %q", ctx, a.Type.Name(), view)
}
}
}
Expand Down Expand Up @@ -297,8 +297,12 @@ func (a *AttributeExpr) Finalize() {
return // Avoid infinite recursion.
}
a.finalized = true
var pkgPath string
if ut, ok := a.Type.(UserType); ok {
ut.Finalize()
if meta, ok := ut.Attribute().Meta["struct:pkg:path"]; ok {
pkgPath = meta[0]
}
}
switch {
case IsObject(a.Type):
Expand All @@ -316,12 +320,6 @@ func (a *AttributeExpr) Finalize() {
}
a.Merge(ru.Attribute())
}
var pkgPath string
if ut, ok := a.Type.(UserType); ok {
if meta, ok := ut.Attribute().Meta["struct:pkg:path"]; ok {
pkgPath = meta[0]
}
}
for _, nat := range *AsObject(a.Type) {
if pkgPath != "" {
if u := AsUnion(nat.Attribute.Type); u != nil {
Expand Down
12 changes: 5 additions & 7 deletions expr/http_response.go
Original file line number Diff line number Diff line change
Expand Up @@ -152,8 +152,8 @@ func (r *HTTPResponseExpr) Validate(e *HTTPEndpointExpr) *eval.ValidationErrors
return nil
}
if isrt {
if v, ok := e.MethodExpr.Result.Meta["view"]; ok {
v := rt.View(v[0])
if view, ok := e.MethodExpr.Result.Meta.Last(ViewMetaKey); ok {
v := rt.View(view)
if v == nil {
return nil
}
Expand Down Expand Up @@ -346,11 +346,9 @@ func (r *HTTPResponseExpr) mapUnmappedAttrs(svcAtt *AttributeExpr) {
// not mapped explicitly.

var originAttr string
{
if r.Body != nil {
if o, ok := r.Body.Meta["origin:attribute"]; ok {
originAttr = o[0]
}
if r.Body != nil {
if o, ok := r.Body.Meta["origin:attribute"]; ok {
originAttr = o[0]
}
}
// if response body was mapped explicitly using Body(<attribute name>) then
Expand Down
6 changes: 3 additions & 3 deletions expr/http_service.go
Original file line number Diff line number Diff line change
Expand Up @@ -82,12 +82,12 @@ func (svc *HTTPServiceExpr) EndpointFor(name string, m *MethodExpr) *HTTPEndpoin
if a := svc.Endpoint(name); a != nil {
return a
}
a := &HTTPEndpointExpr{
httpEndpoint := &HTTPEndpointExpr{
MethodExpr: m,
Service: svc,
}
svc.HTTPEndpoints = append(svc.HTTPEndpoints, a)
return a
svc.HTTPEndpoints = append(svc.HTTPEndpoints, httpEndpoint)
return httpEndpoint
}

// CanonicalEndpoint returns the canonical endpoint of the service if any.
Expand Down
Loading

0 comments on commit 687dc0b

Please sign in to comment.