From 687dc0b1a6f516855726f27cb5355a93b5dc5c47 Mon Sep 17 00:00:00 2001 From: Raphael Simon Date: Sun, 28 Jul 2024 00:08:53 -0700 Subject: [PATCH] Fix embedded explicit view (#3567) * 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 --- codegen/service/example_svc.go | 6 +- codegen/service/service_data.go | 42 +- codegen/service/testdata/service_code.go | 22 + dsl/result_type.go | 77 +- expr/attribute.go | 20 +- expr/http_response.go | 12 +- expr/http_service.go | 6 +- expr/result_type.go | 145 +-- http/codegen/client_body_types_test.go | 6 +- http/codegen/openapi/json_schema.go | 2 +- http/codegen/openapi/v2/builder.go | 4 +- http/codegen/openapi/v3/types.go | 8 +- http/codegen/service_data.go | 1046 ++++++++--------- .../codegen/templates/partial/response.go.tpl | 2 - http/codegen/testdata/streaming_code.go | 6 +- 15 files changed, 699 insertions(+), 705 deletions(-) diff --git a/codegen/service/example_svc.go b/codegen/service/example_svc.go index 17a6eb150d..7f4b156054 100644 --- a/codegen/service/example_svc.go +++ b/codegen/service/example_svc.go @@ -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 } diff --git a/codegen/service/service_data.go b/codegen/service/service_data.go index c2a7cfd37c..a00e30ada1 100644 --- a/codegen/service/service_data.go +++ b/codegen/service/service_data.go @@ -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), @@ -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 @@ -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 @@ -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) } @@ -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 } @@ -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, @@ -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) diff --git a/codegen/service/testdata/service_code.go b/codegen/service/testdata/service_code.go index 26b9a9bd04..f6a9b5bf9a 100644 --- a/codegen/service/testdata/service_code.go +++ b/codegen/service/testdata/service_code.go @@ -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 = ` diff --git a/dsl/result_type.go b/dsl/result_type.go index 21d9245d76..d10eb4b36a 100644 --- a/dsl/result_type.go +++ b/dsl/result_type.go @@ -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) } } @@ -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" { diff --git a/expr/attribute.go b/expr/attribute.go index 1490306a4e..c4a6f0e0c2 100644 --- a/expr/attribute.go +++ b/expr/attribute.go @@ -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) } } } @@ -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): @@ -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 { diff --git a/expr/http_response.go b/expr/http_response.go index 638cf2232b..c150cd0502 100644 --- a/expr/http_response.go +++ b/expr/http_response.go @@ -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 } @@ -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() then diff --git a/expr/http_service.go b/expr/http_service.go index 7a959e5587..1944ed929f 100644 --- a/expr/http_service.go +++ b/expr/http_service.go @@ -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. diff --git a/expr/result_type.go b/expr/result_type.go index e04b09bb7a..8622ab9c34 100644 --- a/expr/result_type.go +++ b/expr/result_type.go @@ -11,6 +11,9 @@ import ( const ( // DefaultView is the name of the default result type view. DefaultView = "default" + + // ViewMetaKey is the key used to store the view name in the attribute meta. + ViewMetaKey = "view" ) type ( @@ -94,7 +97,7 @@ var ( errorResultView = &ViewExpr{ AttributeExpr: &AttributeExpr{Type: errorResultType}, - Name: "default", + Name: DefaultView, } ) @@ -129,25 +132,25 @@ func CanonicalIdentifier(identifier string) string { func (*ResultTypeExpr) Kind() Kind { return ResultTypeKind } // Dup creates a deep copy of the result type given a deep copy of its attribute. -func (m *ResultTypeExpr) Dup(att *AttributeExpr) UserType { +func (rt *ResultTypeExpr) Dup(att *AttributeExpr) UserType { return &ResultTypeExpr{ - UserTypeExpr: m.UserTypeExpr.Dup(att).(*UserTypeExpr), - Identifier: m.Identifier, - Views: m.Views, + UserTypeExpr: rt.UserTypeExpr.Dup(att).(*UserTypeExpr), + Identifier: rt.Identifier, + Views: rt.Views, } } // ID returns the identifier of the result type. -func (m *ResultTypeExpr) ID() string { - return m.Identifier +func (rt *ResultTypeExpr) ID() string { + return rt.Identifier } // Name returns the result type name. -func (m *ResultTypeExpr) Name() string { return m.TypeName } +func (rt *ResultTypeExpr) Name() string { return rt.TypeName } // View returns the view with the given name. -func (m *ResultTypeExpr) View(name string) *ViewExpr { - for _, v := range m.Views { +func (rt *ResultTypeExpr) View(name string) *ViewExpr { + for _, v := range rt.Views { if v.Name == name { return v } @@ -156,14 +159,14 @@ func (m *ResultTypeExpr) View(name string) *ViewExpr { } // HasMultipleViews returns true if the result type has more than one view. -func (m *ResultTypeExpr) HasMultipleViews() bool { - return len(m.Views) > 1 +func (rt *ResultTypeExpr) HasMultipleViews() bool { + return len(rt.Views) > 1 } // ViewHasAttribute returns true if the result type view has the given // attribute. -func (m *ResultTypeExpr) ViewHasAttribute(view, attr string) bool { - v := m.View(view) +func (rt *ResultTypeExpr) ViewHasAttribute(view, attr string) bool { + v := rt.View(view) if v == nil { return false } @@ -172,16 +175,16 @@ func (m *ResultTypeExpr) ViewHasAttribute(view, attr string) bool { // Finalize builds the default view if not explicitly defined and finalizes // the underlying UserTypeExpr. -func (m *ResultTypeExpr) Finalize() { - if m.View("default") == nil { - m.ensureDefaultView() - } - m.UserTypeExpr.Finalize() +func (rt *ResultTypeExpr) Finalize() { + rt.useExplicitView() + rt.ensureDefaultView() + rt.UserTypeExpr.Finalize() seen := make(map[string]struct{}) - walkAttribute(m.AttributeExpr, func(_ string, att *AttributeExpr) error { // nolint: errcheck + walkAttribute(rt.AttributeExpr, func(_ string, att *AttributeExpr) error { // nolint: errcheck if rt, ok := att.Type.(*ResultTypeExpr); ok { if _, ok := seen[rt.Identifier]; !ok { seen[rt.Identifier] = struct{}{} + rt.useExplicitView() rt.ensureDefaultView() } } @@ -189,19 +192,31 @@ func (m *ResultTypeExpr) Finalize() { }) } +// useExplicitView projects the result type using the view explicitly set on the +// attribute if any. +func (rt *ResultTypeExpr) useExplicitView() { + if view, ok := rt.AttributeExpr.Meta.Last(ViewMetaKey); ok { + p, err := Project(rt, view) + if err != nil { + panic(err) // bug - presence of view meta should have been validated before + } + *rt = *p + } +} + // ensureDefaultView builds the default view if not explicitly defined. -func (m *ResultTypeExpr) ensureDefaultView() { - if m.View("default") == nil { - att := DupAtt(m.AttributeExpr) +func (rt *ResultTypeExpr) ensureDefaultView() { + if rt.View(DefaultView) == nil { + att := DupAtt(rt.AttributeExpr) if arr := AsArray(att.Type); arr != nil { att.Type = AsObject(arr.ElemType.Type) } v := &ViewExpr{ AttributeExpr: att, - Name: "default", - Parent: m, + Name: DefaultView, + Parent: rt, } - m.Views = append(m.Views, v) + rt.Views = append(rt.Views, v) } } @@ -216,24 +231,24 @@ func (m *ResultTypeExpr) ensureDefaultView() { // or any result type that makes up its attributes recursively. Note that // individual attributes may use a different view. In this case Project uses // that view and returns an error if it isn't defined on the attribute type. -func Project(m *ResultTypeExpr, view string) (*ResultTypeExpr, error) { - return project(m, view, make(map[string]*AttributeExpr)) +func Project(rt *ResultTypeExpr, view string) (*ResultTypeExpr, error) { + return project(rt, view, make(map[string]*AttributeExpr)) } -func project(m *ResultTypeExpr, view string, seen map[string]*AttributeExpr) (*ResultTypeExpr, error) { - _, params, _ := mime.ParseMediaType(m.Identifier) +func project(rt *ResultTypeExpr, view string, seen map[string]*AttributeExpr) (*ResultTypeExpr, error) { + _, params, _ := mime.ParseMediaType(rt.Identifier) if params["view"] == view { // nothing to do - return m, nil + return rt, nil } - if _, ok := m.Type.(*Array); ok { - return projectCollection(m, view, seen) + if _, ok := rt.Type.(*Array); ok { + return projectCollection(rt, view, seen) } - return projectSingle(m, view, seen) + return projectSingle(rt, view, seen) } -func projectSingle(m *ResultTypeExpr, view string, seen map[string]*AttributeExpr) (*ResultTypeExpr, error) { - v := m.View(view) +func projectSingle(rt *ResultTypeExpr, view string, seen map[string]*AttributeExpr) (*ResultTypeExpr, error) { + v := rt.View(view) if v == nil { return nil, fmt.Errorf("unknown view %#v", view) } @@ -241,32 +256,32 @@ func projectSingle(m *ResultTypeExpr, view string, seen map[string]*AttributeExp // Compute validations - view may not have all fields var val *ValidationExpr - if m.Validation != nil { + if rt.Validation != nil { var required []string - for _, n := range m.Validation.Required { + for _, n := range rt.Validation.Required { if att := viewObj.Attribute(n); att != nil { required = append(required, n) } } - val = m.Validation.Dup() + val = rt.Validation.Dup() val.Required = required } // Compute description - desc := m.Description + desc := rt.Description if desc == "" { - desc = m.TypeName + " result type" + desc = rt.TypeName + " result type" } desc += " (" + view + " view)" // Compute type name - typeName := m.TypeName - if view != "default" { + typeName := rt.TypeName + if view != DefaultView { typeName += Title(view) } var ut *UserTypeExpr - if att, ok := seen[hashAttrAndView(m.Attribute(), view)]; ok { + if att, ok := seen[hashAttrAndView(rt.Attribute(), view)]; ok { if rt, ok2 := att.Type.(*ResultTypeExpr); ok2 { ut = &UserTypeExpr{ AttributeExpr: DupAtt(rt.Attribute()), @@ -274,7 +289,7 @@ func projectSingle(m *ResultTypeExpr, view string, seen map[string]*AttributeExp } } } - id := m.projectIdentifier(view) + id := rt.projectIdentifier(view) if ut == nil { ut = &UserTypeExpr{ AttributeExpr: &AttributeExpr{ @@ -292,13 +307,13 @@ func projectSingle(m *ResultTypeExpr, view string, seen map[string]*AttributeExp UserTypeExpr: ut, } projected.Views = []*ViewExpr{{ - Name: "default", + Name: DefaultView, AttributeExpr: DupAtt(v.AttributeExpr), Parent: projected, }} projectedObj := projected.Type.(*Object) - mtObj := AsObject(m.Type) + mtObj := AsObject(rt.Type) for _, nat := range *viewObj { if at := mtObj.Attribute(nat.Name); at != nil { pat, err := projectRecursive(at, nat, view, seen) @@ -311,30 +326,30 @@ func projectSingle(m *ResultTypeExpr, view string, seen map[string]*AttributeExp return projected, nil } -func projectCollection(m *ResultTypeExpr, view string, seen map[string]*AttributeExpr) (*ResultTypeExpr, error) { +func projectCollection(rt *ResultTypeExpr, view string, seen map[string]*AttributeExpr) (*ResultTypeExpr, error) { // Project the collection element result type - e := m.Type.(*Array).ElemType.Type.(*ResultTypeExpr) // validation checked this cast would work + e := rt.Type.(*Array).ElemType.Type.(*ResultTypeExpr) // validation checked this cast would work pe, err2 := project(e, view, seen) if err2 != nil { return nil, fmt.Errorf("collection element: %w", err2) } // Build the projected collection with the results - id := m.projectIdentifier(view) + id := rt.projectIdentifier(view) proj := &ResultTypeExpr{ Identifier: id, UserTypeExpr: &UserTypeExpr{ AttributeExpr: &AttributeExpr{ - Description: m.TypeName + " is the result type for an array of " + e.TypeName + " (" + view + " view)", + Description: rt.TypeName + " is the result type for an array of " + e.TypeName + " (" + view + " view)", Type: &Array{ElemType: &AttributeExpr{Type: pe}}, - UserExamples: m.UserExamples, + UserExamples: rt.UserExamples, }, TypeName: pe.TypeName + "Collection", UID: id, }, Views: []*ViewExpr{{ - AttributeExpr: DupAtt(pe.View("default").AttributeExpr), - Name: "default", + AttributeExpr: DupAtt(pe.View(DefaultView).AttributeExpr), + Name: DefaultView, Parent: pe, }}, } @@ -355,15 +370,13 @@ func projectRecursive(at *AttributeExpr, vat *NamedAttributeExpr, view string, s if rt, ok := at.Type.(*ResultTypeExpr); ok { vatt := vat.Attribute - var view string - if len(vatt.Meta["view"]) > 0 { - view = vatt.Meta["view"][0] - } - if view == "" && len(at.Meta["view"]) > 0 { - view = at.Meta["view"][0] - } - if view == "" { - view = DefaultView + view, ok := vatt.Meta.Last(ViewMetaKey) + if !ok { + if v, ok := at.Meta.Last(ViewMetaKey); ok { + view = v + } else { + view = DefaultView + } } seen[hashAttrAndView(at, view)] = at pr, err := project(rt, view, seen) @@ -418,10 +431,10 @@ func projectRecursive(at *AttributeExpr, vat *NamedAttributeExpr, view string, s // "view" param. We need the projected result type identifier to be different so // that looking up projected result types from ProjectedResultTypes works // correctly. It's also good for clients. -func (m *ResultTypeExpr) projectIdentifier(view string) string { - base, params, err := mime.ParseMediaType(m.Identifier) +func (rt *ResultTypeExpr) projectIdentifier(view string) string { + base, params, err := mime.ParseMediaType(rt.Identifier) if err != nil { - base = m.Identifier + base = rt.Identifier } if params == nil { params = make(map[string]string) diff --git a/http/codegen/client_body_types_test.go b/http/codegen/client_body_types_test.go index 143d2d6a78..1abad7d8f1 100644 --- a/http/codegen/client_body_types_test.go +++ b/http/codegen/client_body_types_test.go @@ -709,10 +709,10 @@ func ValidateRtResponseBody(body *RtResponseBody) (err error) { } ` -const ResultWithResultViewClientTypesFile = `// MethodResultWithResultViewResponseBody is the type of the +const ResultWithResultViewClientTypesFile = `// MethodResultWithResultViewResponseBodyFull is the type of the // "ServiceResultWithResultView" service "MethodResultWithResultView" endpoint // HTTP response body. -type MethodResultWithResultViewResponseBody struct { +type MethodResultWithResultViewResponseBodyFull struct { Name *string ` + "`" + `form:"name,omitempty" json:"name,omitempty" xml:"name,omitempty"` + "`" + ` Rt *RtResponseBody ` + "`" + `form:"rt,omitempty" json:"rt,omitempty" xml:"rt,omitempty"` + "`" + ` } @@ -725,7 +725,7 @@ type RtResponseBody struct { // NewMethodResultWithResultViewResulttypeOK builds a // "ServiceResultWithResultView" service "MethodResultWithResultView" endpoint // result from a HTTP "OK" response. -func NewMethodResultWithResultViewResulttypeOK(body *MethodResultWithResultViewResponseBody) *serviceresultwithresultviewviews.ResulttypeView { +func NewMethodResultWithResultViewResulttypeOK(body *MethodResultWithResultViewResponseBodyFull) *serviceresultwithresultviewviews.ResulttypeView { v := &serviceresultwithresultviewviews.ResulttypeView{ Name: body.Name, } diff --git a/http/codegen/openapi/json_schema.go b/http/codegen/openapi/json_schema.go index 8c431ed2a7..e51658c3aa 100644 --- a/http/codegen/openapi/json_schema.go +++ b/http/codegen/openapi/json_schema.go @@ -247,7 +247,7 @@ func ResultTypeRefWithPrefix(api *expr.APIExpr, mt *expr.ResultTypeExpr, view, p if metaName != "" { projected.TypeName = metaName } - GenerateResultTypeDefinition(api, projected, "default") + GenerateResultTypeDefinition(api, projected, expr.DefaultView) } return fmt.Sprintf("#/definitions/%s", projected.TypeName) } diff --git a/http/codegen/openapi/v2/builder.go b/http/codegen/openapi/v2/builder.go index 70377d67cd..fd6f2fe040 100644 --- a/http/codegen/openapi/v2/builder.go +++ b/http/codegen/openapi/v2/builder.go @@ -378,8 +378,8 @@ func responseSpecFromExpr(_ *V2, root *expr.RootExpr, r *expr.HTTPResponseExpr, var schema *openapi.Schema if mt, ok := r.Body.Type.(*expr.ResultTypeExpr); ok { view := expr.DefaultView - if v, ok := r.Body.Meta["view"]; ok { - view = v[0] + if v, ok := r.Body.Meta.Last(expr.ViewMetaKey); ok { + view = v } schema = openapi.NewSchema() schema.Ref = openapi.ResultTypeRefWithPrefix(root.API, mt, view, typeNamePrefix) diff --git a/http/codegen/openapi/v3/types.go b/http/codegen/openapi/v3/types.go index 8a4fe51fd0..b96deabc61 100644 --- a/http/codegen/openapi/v3/types.go +++ b/http/codegen/openapi/v3/types.go @@ -117,8 +117,8 @@ func buildBodyTypes(api *expr.APIExpr) (map[string]map[string]*EndpointBodies, m } for _, resp := range resps { var view string - if vs, ok := resp.Body.Meta["view"]; ok { - view = vs[0] + if v, ok := resp.Body.Meta.Last(expr.ViewMetaKey); ok { + view = v } body := resp.Body if view != "" { @@ -432,8 +432,8 @@ func hashAttribute(att *expr.AttributeExpr, h hash.Hash64, seen map[string]*uint // the computation of the hash. rt := t.(*expr.ResultTypeExpr) *res = hashString(rt.Identifier, h) - if view := rt.AttributeExpr.Meta["view"]; len(view) > 0 { - *res = orderedHash(*res, hashString(view[0], h), h) + if view, ok := rt.AttributeExpr.Meta.Last(expr.ViewMetaKey); ok { + *res = orderedHash(*res, hashString(view, h), h) } default: // Primitives or Any diff --git a/http/codegen/service_data.go b/http/codegen/service_data.go index 2f7572ff8b..5881b8cfc7 100644 --- a/http/codegen/service_data.go +++ b/http/codegen/service_data.go @@ -590,8 +590,8 @@ func (svc *ServiceData) Endpoint(name string) *EndpointData { // analyze creates the data necessary to render the code of the given service. // It records the user types needed by the service definition in userTypes. -func (ServicesData) analyze(hs *expr.HTTPServiceExpr) *ServiceData { - svc := service.Services.Get(hs.ServiceExpr.Name) +func (ServicesData) analyze(httpSvc *expr.HTTPServiceExpr) *ServiceData { + svc := service.Services.Get(httpSvc.ServiceExpr.Name) scope := codegen.NewNameScope() scope.Unique("c") // 'c' is reserved as the client's receiver name. scope.Unique("v") // 'v' is reserved as the request builder payload argument name. @@ -608,7 +608,7 @@ func (ServicesData) analyze(hs *expr.HTTPServiceExpr) *ServiceData { Scope: scope, } - for _, s := range hs.FileServers { + for _, s := range httpSvc.FileServers { paths := make([]string, len(s.RequestPaths)) for i, p := range s.RequestPaths { idx := strings.LastIndex(p, "/{") @@ -644,12 +644,12 @@ func (ServicesData) analyze(hs *expr.HTTPServiceExpr) *ServiceData { rd.FileServers = append(rd.FileServers, data) } - for _, a := range hs.HTTPEndpoints { - ep := svc.Method(a.MethodExpr.Name) + for _, httpEndpoint := range httpSvc.HTTPEndpoints { + method := svc.Method(httpEndpoint.MethodExpr.Name) var routes []*RouteData i := 0 - for _, r := range a.Routes { + for _, r := range httpEndpoint.Routes { for _, rpath := range r.FullPaths() { params := expr.ExtractHTTPWildcards(rpath) var ( @@ -657,20 +657,20 @@ func (ServicesData) analyze(hs *expr.HTTPServiceExpr) *ServiceData { ) { initArgs := make([]*InitArgData, len(params)) - pathParamsObj := expr.AsObject(a.PathParams().Type) + pathParamsObj := expr.AsObject(httpEndpoint.PathParams().Type) suffix := "" if i > 0 { suffix = strconv.Itoa(i + 1) } i++ - name := fmt.Sprintf("%s%sPath%s", ep.VarName, svc.StructName, suffix) + name := fmt.Sprintf("%s%sPath%s", method.VarName, svc.StructName, suffix) for j, arg := range params { patt := pathParamsObj.Attribute(arg) att := makeHTTPType(patt) - pointer := a.Params.IsPrimitivePointer(arg, true) - if expr.IsObject(a.MethodExpr.Payload.Type) { + pointer := httpEndpoint.Params.IsPrimitivePointer(arg, true) + if expr.IsObject(httpEndpoint.MethodExpr.Payload.Type) { // Path params may override requiredness, need to check payload. - pointer = a.MethodExpr.Payload.IsPrimitivePointer(arg, true) + pointer = httpEndpoint.MethodExpr.Payload.IsPrimitivePointer(arg, true) } name := rd.Scope.Name(codegen.Goify(arg, false)) var vcode string @@ -709,7 +709,7 @@ func (ServicesData) analyze(hs *expr.HTTPServiceExpr) *ServiceData { } init = &InitData{ Name: name, - Description: fmt.Sprintf("%s returns the URL path to the %s service %s HTTP endpoint. ", name, svc.Name, ep.Name), + Description: fmt.Sprintf("%s returns the URL path to the %s service %s HTTP endpoint. ", name, svc.Name, method.Name), ServerArgs: initArgs, ClientArgs: initArgs, ReturnTypeName: "string", @@ -727,7 +727,7 @@ func (ServicesData) analyze(hs *expr.HTTPServiceExpr) *ServiceData { } } - payload := buildPayloadData(a, rd) + payload := buildPayloadData(httpEndpoint, rd) var ( reqs service.RequirementsData @@ -736,151 +736,144 @@ func (ServicesData) analyze(hs *expr.HTTPServiceExpr) *ServiceData { qsch service.SchemesData basch *service.SchemeData ) - { - - for _, req := range a.Requirements { - var rs service.SchemesData - for _, sch := range req.Schemes { - s := service.BuildSchemeData(sch, a.MethodExpr) - rs = rs.Append(s) - switch s.Type { - case "Basic": - basch = s + for _, req := range httpEndpoint.Requirements { + var rs service.SchemesData + for _, sch := range req.Schemes { + s := service.BuildSchemeData(sch, httpEndpoint.MethodExpr) + rs = rs.Append(s) + switch s.Type { + case "Basic": + basch = s + default: + switch s.In { + case "query": + qsch = qsch.Append(s) + case "header": + hsch = hsch.Append(s) default: - switch s.In { - case "query": - qsch = qsch.Append(s) - case "header": - hsch = hsch.Append(s) - default: - bosch = bosch.Append(s) - } + bosch = bosch.Append(s) } } - reqs = append(reqs, &service.RequirementData{Schemes: rs, Scopes: req.Scopes}) } + reqs = append(reqs, &service.RequirementData{Schemes: rs, Scopes: req.Scopes}) } var requestEncoder string - { - if payload.Request.ClientBody != nil || len(payload.Request.Headers) > 0 || len(payload.Request.QueryParams) > 0 || len(payload.Request.Cookies) > 0 || basch != nil { - requestEncoder = fmt.Sprintf("Encode%sRequest", ep.VarName) - } + if payload.Request.ClientBody != nil || len(payload.Request.Headers) > 0 || len(payload.Request.QueryParams) > 0 || len(payload.Request.Cookies) > 0 || basch != nil { + requestEncoder = fmt.Sprintf("Encode%sRequest", method.VarName) } var requestInit *InitData + var ( + name string + args []*InitArgData + payloadRef string + pkg string + ) { - var ( - name string - args []*InitArgData - payloadRef string - pkg string - ) - { - name = fmt.Sprintf("Build%sRequest", ep.VarName) - s := codegen.NewNameScope() - s.Unique("c") // 'c' is reserved as the client's receiver name. - for _, ca := range routes[0].PathInit.ClientArgs { - if ca.FieldName != "" { - ca.VarName = s.Unique(ca.VarName) - ca.Ref = ca.VarName - args = append(args, ca) - } - } - pkg = pkgWithDefault(ep.PayloadLoc, svc.PkgName) - if len(routes[0].PathInit.ClientArgs) > 0 && a.MethodExpr.Payload.Type != expr.Empty { - payloadRef = svc.Scope.GoFullTypeRef(a.MethodExpr.Payload, pkg) + name = fmt.Sprintf("Build%sRequest", method.VarName) + s := codegen.NewNameScope() + s.Unique("c") // 'c' is reserved as the client's receiver name. + for _, ca := range routes[0].PathInit.ClientArgs { + if ca.FieldName != "" { + ca.VarName = s.Unique(ca.VarName) + ca.Ref = ca.VarName + args = append(args, ca) } } - data := map[string]any{ - "PayloadRef": payloadRef, - "HasFields": expr.IsObject(a.MethodExpr.Payload.Type), - "ServiceName": svc.Name, - "EndpointName": ep.Name, - "Args": args, - "PathInit": routes[0].PathInit, - "Verb": routes[0].Verb, - "IsStreaming": a.MethodExpr.IsStreaming(), - } - if a.SkipRequestBodyEncodeDecode { - data["RequestStruct"] = pkg + "." + ep.RequestStruct - } - var buf bytes.Buffer - if err := requestInitTmpl.Execute(&buf, data); err != nil { - panic(err) // bug - } - clientArgs := []*InitArgData{{Ref: "v", AttributeData: &AttributeData{Name: "payload", VarName: "v", TypeRef: "any"}}} - requestInit = &InitData{ - Name: name, - Description: fmt.Sprintf("%s instantiates a HTTP request object with method and path set to call the %q service %q endpoint", name, svc.Name, ep.Name), - ClientCode: buf.String(), - ClientArgs: clientArgs, + pkg = pkgWithDefault(method.PayloadLoc, svc.PkgName) + if len(routes[0].PathInit.ClientArgs) > 0 && httpEndpoint.MethodExpr.Payload.Type != expr.Empty { + payloadRef = svc.Scope.GoFullTypeRef(httpEndpoint.MethodExpr.Payload, pkg) } } + data := map[string]any{ + "PayloadRef": payloadRef, + "HasFields": expr.IsObject(httpEndpoint.MethodExpr.Payload.Type), + "ServiceName": svc.Name, + "EndpointName": method.Name, + "Args": args, + "PathInit": routes[0].PathInit, + "Verb": routes[0].Verb, + "IsStreaming": httpEndpoint.MethodExpr.IsStreaming(), + } + if httpEndpoint.SkipRequestBodyEncodeDecode { + data["RequestStruct"] = pkg + "." + method.RequestStruct + } + var buf bytes.Buffer + if err := requestInitTmpl.Execute(&buf, data); err != nil { + panic(err) // bug + } + clientArgs := []*InitArgData{{Ref: "v", AttributeData: &AttributeData{Name: "payload", VarName: "v", TypeRef: "any"}}} + requestInit = &InitData{ + Name: name, + Description: fmt.Sprintf("%s instantiates a HTTP request object with method and path set to call the %q service %q endpoint", name, svc.Name, method.Name), + ClientCode: buf.String(), + ClientArgs: clientArgs, + } - ad := &EndpointData{ - Method: ep, + ed := &EndpointData{ + Method: method, ServiceName: svc.Name, ServiceVarName: svc.VarName, ServicePkgName: svc.PkgName, Payload: payload, - Result: buildResultData(a, rd), - Errors: buildErrorsData(a, rd), + Result: buildResultData(httpEndpoint, rd), + Errors: buildErrorsData(httpEndpoint, rd), HeaderSchemes: hsch, BodySchemes: bosch, QuerySchemes: qsch, BasicScheme: basch, Routes: routes, - MountHandler: fmt.Sprintf("Mount%sHandler", ep.VarName), - HandlerInit: fmt.Sprintf("New%sHandler", ep.VarName), - RequestDecoder: fmt.Sprintf("Decode%sRequest", ep.VarName), - ResponseEncoder: fmt.Sprintf("Encode%sResponse", ep.VarName), - ErrorEncoder: fmt.Sprintf("Encode%sError", ep.VarName), + MountHandler: fmt.Sprintf("Mount%sHandler", method.VarName), + HandlerInit: fmt.Sprintf("New%sHandler", method.VarName), + RequestDecoder: fmt.Sprintf("Decode%sRequest", method.VarName), + ResponseEncoder: fmt.Sprintf("Encode%sResponse", method.VarName), + ErrorEncoder: fmt.Sprintf("Encode%sError", method.VarName), ClientStruct: "Client", - EndpointInit: ep.VarName, + EndpointInit: method.VarName, RequestInit: requestInit, RequestEncoder: requestEncoder, - ResponseDecoder: fmt.Sprintf("Decode%sResponse", ep.VarName), + ResponseDecoder: fmt.Sprintf("Decode%sResponse", method.VarName), Requirements: reqs, } - if a.MethodExpr.IsStreaming() { - initWebSocketData(ad, a, rd) + if httpEndpoint.MethodExpr.IsStreaming() { + initWebSocketData(ed, httpEndpoint, rd) } - if a.MultipartRequest { - ad.MultipartRequestDecoder = &MultipartData{ - FuncName: fmt.Sprintf("%s%sDecoderFunc", svc.StructName, ep.VarName), - InitName: fmt.Sprintf("New%s%sDecoder", svc.StructName, ep.VarName), - VarName: fmt.Sprintf("%s%sDecoderFn", svc.VarName, ep.VarName), + if httpEndpoint.MultipartRequest { + ed.MultipartRequestDecoder = &MultipartData{ + FuncName: fmt.Sprintf("%s%sDecoderFunc", svc.StructName, method.VarName), + InitName: fmt.Sprintf("New%s%sDecoder", svc.StructName, method.VarName), + VarName: fmt.Sprintf("%s%sDecoderFn", svc.VarName, method.VarName), ServiceName: svc.Name, - MethodName: ep.Name, - Payload: ad.Payload, + MethodName: method.Name, + Payload: ed.Payload, } - ad.MultipartRequestEncoder = &MultipartData{ - FuncName: fmt.Sprintf("%s%sEncoderFunc", svc.StructName, ep.VarName), - InitName: fmt.Sprintf("New%s%sEncoder", svc.StructName, ep.VarName), - VarName: fmt.Sprintf("%s%sEncoderFn", svc.VarName, ep.VarName), + ed.MultipartRequestEncoder = &MultipartData{ + FuncName: fmt.Sprintf("%s%sEncoderFunc", svc.StructName, method.VarName), + InitName: fmt.Sprintf("New%s%sEncoder", svc.StructName, method.VarName), + VarName: fmt.Sprintf("%s%sEncoderFn", svc.VarName, method.VarName), ServiceName: svc.Name, - MethodName: ep.Name, - Payload: ad.Payload, + MethodName: method.Name, + Payload: ed.Payload, } } - if a.SkipRequestBodyEncodeDecode { - ad.BuildStreamPayload = scope.Unique("Build" + codegen.Goify(ep.Name, true) + "StreamPayload") + if httpEndpoint.SkipRequestBodyEncodeDecode { + ed.BuildStreamPayload = scope.Unique("Build" + codegen.Goify(method.Name, true) + "StreamPayload") } - if a.Redirect != nil { - ad.Redirect = &RedirectData{ - URL: a.Redirect.URL, - StatusCode: statusCodeToHTTPConst(a.Redirect.StatusCode), + if httpEndpoint.Redirect != nil { + ed.Redirect = &RedirectData{ + URL: httpEndpoint.Redirect.URL, + StatusCode: statusCodeToHTTPConst(httpEndpoint.Redirect.StatusCode), } } - rd.Endpoints = append(rd.Endpoints, ad) + rd.Endpoints = append(rd.Endpoints, ed) } - for _, a := range hs.HTTPEndpoints { + for _, a := range httpSvc.HTTPEndpoints { collectUserTypes(a.Body.Type, func(ut expr.UserType) { if d := attributeTypeData(ut, true, true, true, rd); d != nil { rd.ServerBodyAttributeTypes = append(rd.ServerBodyAttributeTypes, d) @@ -1010,86 +1003,84 @@ func buildPayloadData(e *expr.HTTPEndpointExpr, sd *ServiceData) *PayloadData { mustValidate bool mustHaveBody = true ) - { - if e.MapQueryParams != nil { - var ( - fieldName string - name = "query" - required = true - pAtt = payload - ) - if n := *e.MapQueryParams; n != "" { - pAtt = expr.AsObject(payload.Type).Attribute(n) - required = payload.IsRequired(n) - name = n - fieldName = codegen.Goify(name, true) - } - varn := codegen.Goify(name, false) - mapQueryParam = &ParamData{ - MapQueryParams: e.MapQueryParams, - Map: expr.AsMap(payload.Type) != nil, - Element: &Element{ - HTTPName: name, - AttributeData: &AttributeData{ - Name: name, - VarName: varn, - FieldName: fieldName, - FieldType: pAtt.Type, - Required: required, - Type: pAtt.Type, - TypeName: sd.Scope.GoTypeName(pAtt), - TypeRef: sd.Scope.GoTypeRef(pAtt), - Validate: codegen.AttributeValidationCode(pAtt, nil, httpsvrctx, required, expr.IsAlias(pAtt.Type), varn, name), - DefaultValue: pAtt.DefaultValue, - Example: pAtt.Example(expr.Root.API.ExampleGenerator), - }, + if e.MapQueryParams != nil { + var ( + fieldName string + name = "query" + required = true + pAtt = payload + ) + if n := *e.MapQueryParams; n != "" { + pAtt = expr.AsObject(payload.Type).Attribute(n) + required = payload.IsRequired(n) + name = n + fieldName = codegen.Goify(name, true) + } + varn := codegen.Goify(name, false) + mapQueryParam = &ParamData{ + MapQueryParams: e.MapQueryParams, + Map: expr.AsMap(payload.Type) != nil, + Element: &Element{ + HTTPName: name, + AttributeData: &AttributeData{ + Name: name, + VarName: varn, + FieldName: fieldName, + FieldType: pAtt.Type, + Required: required, + Type: pAtt.Type, + TypeName: sd.Scope.GoTypeName(pAtt), + TypeRef: sd.Scope.GoTypeRef(pAtt), + Validate: codegen.AttributeValidationCode(pAtt, nil, httpsvrctx, required, expr.IsAlias(pAtt.Type), varn, name), + DefaultValue: pAtt.DefaultValue, + Example: pAtt.Example(expr.Root.API.ExampleGenerator), }, - } - queryData = append(queryData, mapQueryParam) + }, } - if serverBodyData != nil { - sd.ServerTypeNames[serverBodyData.Name] = false - sd.ClientTypeNames[serverBodyData.Name] = false + queryData = append(queryData, mapQueryParam) + } + if serverBodyData != nil { + sd.ServerTypeNames[serverBodyData.Name] = false + sd.ClientTypeNames[serverBodyData.Name] = false + } + for _, p := range cookiesData { + if p.Required || p.Validate != "" || needConversion(p.Type) { + mustValidate = true + break } - for _, p := range cookiesData { - if p.Required || p.Validate != "" || needConversion(p.Type) { + } + if !mustValidate { + for _, p := range paramsData { + if p.Validate != "" || needConversion(p.Type) { mustValidate = true break } } - if !mustValidate { - for _, p := range paramsData { - if p.Validate != "" || needConversion(p.Type) { - mustValidate = true - break - } - } - } - if !mustValidate { - for _, q := range queryData { - if q.Map || q.Validate != "" || q.Required || needConversion(q.Type) { - mustValidate = true - break - } + } + if !mustValidate { + for _, q := range queryData { + if q.Map || q.Validate != "" || q.Required || needConversion(q.Type) { + mustValidate = true + break } } - if !mustValidate { - for _, h := range headersData { - if h.Validate != "" || h.Required || needConversion(h.Type) { - mustValidate = true - break - } + } + if !mustValidate { + for _, h := range headersData { + if h.Validate != "" || h.Required || needConversion(h.Type) { + mustValidate = true + break } } - if e.Body.Type != expr.Empty { - // If design uses Body("name") syntax we need to use the - // corresponding attribute in the result type for body - // transformation. - if o, ok := e.Body.Meta["origin:attribute"]; ok { - origin = o[0] - if !payload.IsRequired(o[0]) { - mustHaveBody = false - } + } + if e.Body.Type != expr.Empty { + // If design uses Body("name") syntax we need to use the + // corresponding attribute in the result type for body + // transformation. + if o, ok := e.Body.Meta["origin:attribute"]; ok { + origin = o[0] + if !payload.IsRequired(o[0]) { + mustHaveBody = false } } } @@ -1389,21 +1380,19 @@ func buildPayloadData(e *expr.HTTPEndpointExpr, sd *ServiceData) *PayloadData { name string ref string ) - { - if payload.Type != expr.Empty { - name = svc.Scope.GoFullTypeName(payload, pkg) - ref = svc.Scope.GoFullTypeRef(payload, pkg) - } - if init == nil { - if o := expr.AsObject(e.Params.Type); o != nil && len(*o) > 0 { - returnValue = codegen.Goify((*o)[0].Name, false) - } else if o := expr.AsObject(e.Headers.Type); o != nil && len(*o) > 0 { - returnValue = codegen.Goify((*o)[0].Name, false) - } else if o := expr.AsObject(e.Cookies.Type); o != nil && len(*o) > 0 { - returnValue = codegen.Goify((*o)[0].Name, false) - } else if e.MapQueryParams != nil && *e.MapQueryParams == "" { - returnValue = mapQueryParam.VarName - } + if payload.Type != expr.Empty { + name = svc.Scope.GoFullTypeName(payload, pkg) + ref = svc.Scope.GoFullTypeRef(payload, pkg) + } + if init == nil { + if o := expr.AsObject(e.Params.Type); o != nil && len(*o) > 0 { + returnValue = codegen.Goify((*o)[0].Name, false) + } else if o := expr.AsObject(e.Headers.Type); o != nil && len(*o) > 0 { + returnValue = codegen.Goify((*o)[0].Name, false) + } else if o := expr.AsObject(e.Cookies.Type); o != nil && len(*o) > 0 { + returnValue = codegen.Goify((*o)[0].Name, false) + } else if e.MapQueryParams != nil && *e.MapQueryParams == "" { + returnValue = mapQueryParam.VarName } } @@ -1427,15 +1416,13 @@ func buildResultData(e *expr.HTTPEndpointExpr, sd *ServiceData) *ResultData { ref string view string ) - { - view = "default" - if v, ok := result.Meta["view"]; ok { - view = v[0] - } - if result.Type != expr.Empty { - name = svc.Scope.GoFullTypeName(result, pkg) - ref = svc.Scope.GoFullTypeRef(result, pkg) - } + view = expr.DefaultView + if v, ok := result.Meta.Last(expr.ViewMetaKey); ok { + view = v + } + if result.Type != expr.Empty { + name = svc.Scope.GoFullTypeName(result, pkg) + ref = svc.Scope.GoFullTypeRef(result, pkg) } var ( @@ -1528,11 +1515,11 @@ func buildResponses(e *expr.HTTPEndpointExpr, result *expr.AttributeExpr, viewed if sbd := buildResponseBodyType(resp.Body, result, md.ResultLoc, e, true, &vname, sd); sbd != nil { serverBodyData = append(serverBodyData, sbd) } - } else if v, ok := e.MethodExpr.Result.Meta["view"]; ok && len(v) > 0 { + } else if v, ok := e.MethodExpr.Result.Meta.Last(expr.ViewMetaKey); ok { // Design explicitly sets the view to render the result. // We generate only one server body type which will be rendered // using the specified view. - if sbd := buildResponseBodyType(resp.Body, result, md.ResultLoc, e, true, &v[0], sd); sbd != nil { + if sbd := buildResponseBodyType(resp.Body, result, md.ResultLoc, e, true, &v, sd); sbd != nil { serverBodyData = append(serverBodyData, sbd) } } else { @@ -1710,12 +1697,10 @@ func buildResponses(e *expr.HTTPEndpointExpr, result *expr.AttributeExpr, viewed tagVal string tagPtr bool ) - { - if resp.Tag[0] != "" { - tagName = codegen.Goify(resp.Tag[0], true) - tagVal = resp.Tag[1] - tagPtr = viewed || result.IsPrimitivePointer(resp.Tag[0], true) - } + if resp.Tag[0] != "" { + tagName = codegen.Goify(resp.Tag[0], true) + tagVal = resp.Tag[1] + tagPtr = viewed || result.IsPrimitivePointer(resp.Tag[0], true) } responses = append(responses, &ResponseData{ StatusCode: statusCodeToHTTPConst(resp.StatusCode), @@ -1772,53 +1757,51 @@ func buildErrorsData(e *expr.HTTPEndpointExpr, sd *ServiceData) []*ErrorGroupDat isObject bool args []*InitArgData ) - { - name = fmt.Sprintf("New%s%s", codegen.Goify(ep.Name, true), codegen.Goify(v.ErrorExpr.Name, true)) - desc = fmt.Sprintf("%s builds a %s service %s endpoint %s error.", - name, svc.Name, e.Name(), v.ErrorExpr.Name) - if body != expr.Empty { - isObject = expr.IsObject(body) - ref := "body" - if isObject { - ref = "&body" - } - args = []*InitArgData{{ - Ref: ref, - AttributeData: &AttributeData{Name: "body", VarName: "body", TypeRef: sd.Scope.GoTypeRef(v.Response.Body)}, - }} - } - for _, h := range extractHeaders(v.Response.Headers, v.ErrorExpr.AttributeExpr, errctx, sd.Scope) { - args = append(args, &InitArgData{ - Ref: h.VarName, - AttributeData: &AttributeData{ - Name: h.Name, - VarName: h.VarName, - FieldName: h.FieldName, - FieldPointer: false, - FieldType: h.FieldType, - TypeRef: h.TypeRef, - Type: h.Type, - Validate: h.Validate, - Example: h.Example, - }, - }) - } - for _, c := range extractCookies(v.Response.Cookies, v.ErrorExpr.AttributeExpr, errctx, sd.Scope) { - args = append(args, &InitArgData{ - Ref: c.VarName, - AttributeData: &AttributeData{ - Name: c.Name, - VarName: c.VarName, - FieldName: c.FieldName, - FieldPointer: false, - FieldType: c.FieldType, - TypeRef: c.TypeRef, - Type: c.Type, - Validate: c.Validate, - Example: c.Example, - }, - }) + name = fmt.Sprintf("New%s%s", codegen.Goify(ep.Name, true), codegen.Goify(v.ErrorExpr.Name, true)) + desc = fmt.Sprintf("%s builds a %s service %s endpoint %s error.", + name, svc.Name, e.Name(), v.ErrorExpr.Name) + if body != expr.Empty { + isObject = expr.IsObject(body) + ref := "body" + if isObject { + ref = "&body" } + args = []*InitArgData{{ + Ref: ref, + AttributeData: &AttributeData{Name: "body", VarName: "body", TypeRef: sd.Scope.GoTypeRef(v.Response.Body)}, + }} + } + for _, h := range extractHeaders(v.Response.Headers, v.ErrorExpr.AttributeExpr, errctx, sd.Scope) { + args = append(args, &InitArgData{ + Ref: h.VarName, + AttributeData: &AttributeData{ + Name: h.Name, + VarName: h.VarName, + FieldName: h.FieldName, + FieldPointer: false, + FieldType: h.FieldType, + TypeRef: h.TypeRef, + Type: h.Type, + Validate: h.Validate, + Example: h.Example, + }, + }) + } + for _, c := range extractCookies(v.Response.Cookies, v.ErrorExpr.AttributeExpr, errctx, sd.Scope) { + args = append(args, &InitArgData{ + Ref: c.VarName, + AttributeData: &AttributeData{ + Name: c.Name, + VarName: c.VarName, + FieldName: c.FieldName, + FieldPointer: false, + FieldType: c.FieldType, + TypeRef: c.TypeRef, + Type: c.Type, + Validate: c.Validate, + Example: c.Example, + }, + }) } var ( @@ -1826,34 +1809,32 @@ func buildErrorsData(e *expr.HTTPEndpointExpr, sd *ServiceData) []*ErrorGroupDat origin string err error ) - { - if body != expr.Empty { - eAtt := v.ErrorExpr.AttributeExpr - // If design uses Body("name") syntax then need to use payload - // attribute to transform. - if o, ok := v.Response.Body.Meta["origin:attribute"]; ok { - origin = o[0] - eAtt = expr.AsObject(v.ErrorExpr.Type).Attribute(origin) - } + if body != expr.Empty { + eAtt := v.ErrorExpr.AttributeExpr + // If design uses Body("name") syntax then need to use payload + // attribute to transform. + if o, ok := v.Response.Body.Meta["origin:attribute"]; ok { + origin = o[0] + eAtt = expr.AsObject(v.ErrorExpr.Type).Attribute(origin) + } + var helpers []*codegen.TransformFunctionData + code, helpers, err = unmarshal(v.Response.Body, eAtt, "body", "v", httpclictx, errctx) + if err == nil { + sd.ClientTransformHelpers = codegen.AppendHelpers(sd.ClientTransformHelpers, helpers) + } + } else if expr.IsArray(v.ErrorExpr.Type) || expr.IsMap(v.ErrorExpr.Type) { + if params := expr.AsObject(e.QueryParams().Type); len(*params) > 0 { var helpers []*codegen.TransformFunctionData - code, helpers, err = unmarshal(v.Response.Body, eAtt, "body", "v", httpclictx, errctx) + code, helpers, err = unmarshal((*params)[0].Attribute, v.ErrorExpr.AttributeExpr, codegen.Goify((*params)[0].Name, false), "v", httpclictx, errctx) if err == nil { sd.ClientTransformHelpers = codegen.AppendHelpers(sd.ClientTransformHelpers, helpers) } - } else if expr.IsArray(v.ErrorExpr.Type) || expr.IsMap(v.ErrorExpr.Type) { - if params := expr.AsObject(e.QueryParams().Type); len(*params) > 0 { - var helpers []*codegen.TransformFunctionData - code, helpers, err = unmarshal((*params)[0].Attribute, v.ErrorExpr.AttributeExpr, codegen.Goify((*params)[0].Name, false), "v", httpclictx, errctx) - if err == nil { - sd.ClientTransformHelpers = codegen.AppendHelpers(sd.ClientTransformHelpers, helpers) - } - } - } - if err != nil { - fmt.Println(err.Error()) // TBD validate DSL so errors are not possible } } + if err != nil { + fmt.Println(err.Error()) // TBD validate DSL so errors are not possible + } init = &InitData{ Name: name, @@ -1894,18 +1875,16 @@ func buildErrorsData(e *expr.HTTPEndpointExpr, sd *ServiceData) []*ErrorGroupDat headers := extractHeaders(v.Response.Headers, v.ErrorExpr.AttributeExpr, errctx, sd.Scope) cookies := extractCookies(v.Response.Cookies, v.ErrorExpr.AttributeExpr, errctx, sd.Scope) var mustValidate bool - { - for _, h := range headers { - if h.Validate != "" || h.Required || needConversion(h.Type) { - mustValidate = true - break - } + for _, h := range headers { + if h.Validate != "" || h.Required || needConversion(h.Type) { + mustValidate = true + break } - for _, c := range cookies { - if c.Validate != "" || c.Required || needConversion(c.Type) { - mustValidate = true - break - } + } + for _, c := range cookies { + if c.Validate != "" || c.Required || needConversion(c.Type) { + mustValidate = true + break } } var contentType string @@ -1995,91 +1974,87 @@ func buildRequestBodyType(body, att *expr.AttributeExpr, e *expr.HTTPEndpointExp pkg = pkgWithDefault(ep.PayloadLoc, sd.Service.PkgName) svcctx = serviceContext(pkg, sd.Service.Scope) ) - { - name = body.Type.Name() - ref = sd.Scope.GoTypeRef(body) - - AddMarshalTags(body, make(map[string]struct{})) - - if ut, ok := body.Type.(expr.UserType); ok { - varname = codegen.Goify(ut.Name(), true) - def = goTypeDef(sd.Scope, ut.Attribute(), svr, !svr) - desc = fmt.Sprintf("%s is the type of the %q service %q endpoint HTTP request body.", - varname, svc.Name, e.Name()) - if svr { - // generate validation code for unmarshaled type (server-side). - validateDef = codegen.ValidationCode(ut.Attribute(), ut, httpctx, true, expr.IsAlias(ut), false, "body") - if validateDef != "" { - validateRef = fmt.Sprintf("err = Validate%s(&body)", varname) - } - } - } else { - if svr && expr.IsObject(body.Type) { - // Body is an explicit object described in the design and in - // this case the GoTypeRef is an inline struct definition. We - // want to force all attributes to be pointers because we are - // generating the server body type pre-validation. - body.Validation = nil + name = body.Type.Name() + ref = sd.Scope.GoTypeRef(body) + + AddMarshalTags(body, make(map[string]struct{})) + + if ut, ok := body.Type.(expr.UserType); ok { + varname = codegen.Goify(ut.Name(), true) + def = goTypeDef(sd.Scope, ut.Attribute(), svr, !svr) + desc = fmt.Sprintf("%s is the type of the %q service %q endpoint HTTP request body.", + varname, svc.Name, e.Name()) + if svr { + // generate validation code for unmarshaled type (server-side). + validateDef = codegen.ValidationCode(ut.Attribute(), ut, httpctx, true, expr.IsAlias(ut), false, "body") + if validateDef != "" { + validateRef = fmt.Sprintf("err = Validate%s(&body)", varname) } - varname = sd.Scope.GoTypeRef(body) - ctx := codegen.NewAttributeContext(false, false, !svr, "", sd.Scope) - validateRef = codegen.ValidationCode(body, nil, ctx, true, expr.IsAlias(body.Type), false, "body") - desc = body.Description } + } else { + if svr && expr.IsObject(body.Type) { + // Body is an explicit object described in the design and in + // this case the GoTypeRef is an inline struct definition. We + // want to force all attributes to be pointers because we are + // generating the server body type pre-validation. + body.Validation = nil + } + varname = sd.Scope.GoTypeRef(body) + ctx := codegen.NewAttributeContext(false, false, !svr, "", sd.Scope) + validateRef = codegen.ValidationCode(body, nil, ctx, true, expr.IsAlias(body.Type), false, "body") + desc = body.Description } var init *InitData - { - if !svr && att.Type != expr.Empty && needInit(body.Type) { - var ( - name string - desc string - code string - origin string - err error - helpers []*codegen.TransformFunctionData - - sourceVar = "p" - svc = sd.Service - ) - { - name = fmt.Sprintf("New%s", codegen.Goify(sd.Scope.GoTypeName(body), true)) - desc = fmt.Sprintf("%s builds the HTTP request body from the payload of the %q endpoint of the %q service.", - name, e.Name(), svc.Name) - src := sourceVar - srcAtt := att - // If design uses Body("name") syntax then need to use payload attribute - // to transform. - if o, ok := body.Meta["origin:attribute"]; ok { - srcObj := expr.AsObject(att.Type) - origin = o[0] - srcAtt = srcObj.Attribute(origin) - src += "." + codegen.Goify(origin, true) - } - code, helpers, err = marshal(srcAtt, body, src, "body", svcctx, httpctx) - if err != nil { - fmt.Println(err.Error()) // TBD validate DSL so errors are not possible - } - sd.ClientTransformHelpers = codegen.AppendHelpers(sd.ClientTransformHelpers, helpers) - } - arg := InitArgData{ - Ref: sourceVar, - AttributeData: &AttributeData{ - Name: "payload", - VarName: sourceVar, - TypeRef: svc.Scope.GoFullTypeRef(att, pkg), - Type: att.Type, - Validate: validateDef, - Example: att.Example(expr.Root.API.ExampleGenerator), - }, - } - init = &InitData{ - Name: name, - Description: desc, - ReturnTypeRef: sd.Scope.GoTypeRef(body), - ReturnTypeAttribute: codegen.Goify(origin, true), - ClientCode: code, - ClientArgs: []*InitArgData{&arg}, - } + if !svr && att.Type != expr.Empty && needInit(body.Type) { + var ( + name string + desc string + code string + origin string + err error + helpers []*codegen.TransformFunctionData + + sourceVar = "p" + svc = sd.Service + ) + { + name = fmt.Sprintf("New%s", codegen.Goify(sd.Scope.GoTypeName(body), true)) + desc = fmt.Sprintf("%s builds the HTTP request body from the payload of the %q endpoint of the %q service.", + name, e.Name(), svc.Name) + src := sourceVar + srcAtt := att + // If design uses Body("name") syntax then need to use payload attribute + // to transform. + if o, ok := body.Meta["origin:attribute"]; ok { + srcObj := expr.AsObject(att.Type) + origin = o[0] + srcAtt = srcObj.Attribute(origin) + src += "." + codegen.Goify(origin, true) + } + code, helpers, err = marshal(srcAtt, body, src, "body", svcctx, httpctx) + if err != nil { + fmt.Println(err.Error()) // TBD validate DSL so errors are not possible + } + sd.ClientTransformHelpers = codegen.AppendHelpers(sd.ClientTransformHelpers, helpers) + } + arg := InitArgData{ + Ref: sourceVar, + AttributeData: &AttributeData{ + Name: "payload", + VarName: sourceVar, + TypeRef: svc.Scope.GoFullTypeRef(att, pkg), + Type: att.Type, + Validate: validateDef, + Example: att.Example(expr.Root.API.ExampleGenerator), + }, + } + init = &InitData{ + Name: name, + Description: desc, + ReturnTypeRef: sd.Scope.GoTypeRef(body), + ReturnTypeAttribute: codegen.Goify(origin, true), + ClientCode: code, + ClientArgs: []*InitArgData{&arg}, } } return &TypeData{ @@ -2126,64 +2101,62 @@ func buildResponseBodyType(body, att *expr.AttributeExpr, loc *codegen.Location, pkg = pkgWithDefault(loc, sd.Service.PkgName) svcctx = serviceContext(pkg, sd.Service.Scope) ) - { - // For server code, we project the response body type if the type is a result - // type and generate a type for each view in the result type. This makes it - // possible to return only the attributes in the view in the server response. - if svr && view != nil && *view != "" { - viewName = *view - body = expr.DupAtt(body) - if rt, ok := body.Type.(*expr.ResultTypeExpr); ok { - var err error - rt, err = expr.Project(rt, *view) - if err != nil { - panic(err) + // For server code, we project the response body type if the type is a result + // type and generate a type for each view in the result type. This makes it + // possible to return only the attributes in the view in the server response. + if svr && view != nil && *view != "" { + viewName = *view + body = expr.DupAtt(body) + if rt, ok := body.Type.(*expr.ResultTypeExpr); ok { + var err error + rt, err = expr.Project(rt, *view) + if err != nil { + panic(err) + } + body.Type = rt + sd.ServerTypeNames[rt.Name()] = false + } + } + + name = body.Type.Name() + ref = sd.Scope.GoTypeRef(body) + mustInit = att.Type != expr.Empty && needInit(body.Type) + + AddMarshalTags(body, make(map[string]struct{})) + + if ut, ok := body.Type.(expr.UserType); ok { + // response body is a user type. + varname = codegen.Goify(ut.Name(), true) + def = goTypeDef(sd.Scope, ut.Attribute(), !svr, svr) + desc = fmt.Sprintf("%s is the type of the %q service %q endpoint HTTP response body.", + varname, svc.Name, e.Name()) + if !svr && view == nil { + // generate validation code for unmarshaled type (client-side). + validateDef = codegen.ValidationCode(body, ut, httpctx, true, expr.IsAlias(body.Type), false, "body") + if validateDef != "" { + target := "&body" + if expr.IsArray(ut) { + // result type collection + target = "body" } - body.Type = rt - sd.ServerTypeNames[rt.Name()] = false + validateRef = fmt.Sprintf("err = Validate%s(%s)", varname, target) } } - - name = body.Type.Name() - ref = sd.Scope.GoTypeRef(body) - mustInit = att.Type != expr.Empty && needInit(body.Type) - - AddMarshalTags(body, make(map[string]struct{})) - - if ut, ok := body.Type.(expr.UserType); ok { - // response body is a user type. - varname = codegen.Goify(ut.Name(), true) - def = goTypeDef(sd.Scope, ut.Attribute(), !svr, svr) - desc = fmt.Sprintf("%s is the type of the %q service %q endpoint HTTP response body.", - varname, svc.Name, e.Name()) - if !svr && view == nil { - // generate validation code for unmarshaled type (client-side). - validateDef = codegen.ValidationCode(body, ut, httpctx, true, expr.IsAlias(body.Type), false, "body") - if validateDef != "" { - target := "&body" - if expr.IsArray(ut) { - // result type collection - target = "body" - } - validateRef = fmt.Sprintf("err = Validate%s(%s)", varname, target) - } - } - } else if !expr.IsPrimitive(body.Type) && mustInit { - // response body is an array or map type. - name = codegen.Goify(e.Name(), true) + "ResponseBody" - varname = name - desc = fmt.Sprintf("%s is the type of the %q service %q endpoint HTTP response body.", - varname, svc.Name, e.Name()) - def = goTypeDef(sd.Scope, body, !svr, svr) - validateRef = codegen.ValidationCode(body, nil, httpctx, true, expr.IsAlias(body.Type), false, "body") - } else { - // response body is a primitive type. They are used as non-pointers when - // encoding/decoding responses. - httpctx = httpContext("", sd.Scope, false, true) - validateRef = codegen.ValidationCode(body, nil, httpctx, true, expr.IsAlias(body.Type), false, "body") - varname = sd.Scope.GoTypeRef(body) - desc = body.Description - } + } else if !expr.IsPrimitive(body.Type) && mustInit { + // response body is an array or map type. + name = codegen.Goify(e.Name(), true) + "ResponseBody" + varname = name + desc = fmt.Sprintf("%s is the type of the %q service %q endpoint HTTP response body.", + varname, svc.Name, e.Name()) + def = goTypeDef(sd.Scope, body, !svr, svr) + validateRef = codegen.ValidationCode(body, nil, httpctx, true, expr.IsAlias(body.Type), false, "body") + } else { + // response body is a primitive type. They are used as non-pointers when + // encoding/decoding responses. + httpctx = httpContext("", sd.Scope, false, true) + validateRef = codegen.ValidationCode(body, nil, httpctx, true, expr.IsAlias(body.Type), false, "body") + varname = sd.Scope.GoTypeRef(body) + desc = body.Description } if svr { sd.ServerTypeNames[name] = false @@ -2197,82 +2170,79 @@ func buildResponseBodyType(body, att *expr.AttributeExpr, loc *codegen.Location, sd.ServerBodyAttributeTypes = append(sd.ServerBodyAttributeTypes, d) } }) - } var init *InitData - { - if svr && mustInit { - var ( - name string - desc string - rtref string - code string - origin string - err error - helpers []*codegen.TransformFunctionData - - sourceVar = "res" - svc = sd.Service - ) - { - var rtname string - if _, ok := body.Type.(expr.UserType); !ok && !expr.IsPrimitive(body.Type) { - rtname = codegen.Goify(e.Name(), true) + "ResponseBody" - rtref = rtname - } else { - rtname = codegen.Goify(sd.Scope.GoTypeName(body), true) - rtref = sd.Scope.GoTypeRef(body) - } - name = fmt.Sprintf("New%s", rtname) - desc = fmt.Sprintf("%s builds the HTTP response body from the result of the %q endpoint of the %q service.", - name, e.Name(), svc.Name) - if view != nil { - svcctx = viewContext(sd.Service.ViewsPkg, sd.Service.ViewScope) - } - src := sourceVar - srcAtt := att - // If design uses Body("name") syntax then need to use result attribute - // to transform. - if o, ok := body.Meta["origin:attribute"]; ok { - srcObj := expr.AsObject(att.Type) - origin = o[0] - srcAtt = srcObj.Attribute(origin) - src += "." + codegen.Goify(origin, true) - } - code, helpers, err = marshal(srcAtt, body, src, "body", svcctx, httpctx) - if err != nil { - panic(err) // bug - } - sd.ServerTransformHelpers = codegen.AppendHelpers(sd.ServerTransformHelpers, helpers) - } - ref := sourceVar - if view != nil { - ref += ".Projected" + if svr && mustInit { + var ( + name string + desc string + rtref string + code string + origin string + err error + helpers []*codegen.TransformFunctionData + + sourceVar = "res" + svc = sd.Service + ) + { + var rtname string + if _, ok := body.Type.(expr.UserType); !ok && !expr.IsPrimitive(body.Type) { + rtname = codegen.Goify(e.Name(), true) + "ResponseBody" + rtref = rtname + } else { + rtname = codegen.Goify(sd.Scope.GoTypeName(body), true) + rtref = sd.Scope.GoTypeRef(body) } - tref := svc.Scope.GoFullTypeRef(att, pkg) + name = fmt.Sprintf("New%s", rtname) + desc = fmt.Sprintf("%s builds the HTTP response body from the result of the %q endpoint of the %q service.", + name, e.Name(), svc.Name) if view != nil { - tref = svc.ViewScope.GoFullTypeRef(att, svc.ViewsPkg) - } - arg := InitArgData{ - Ref: ref, - AttributeData: &AttributeData{ - Name: "result", - VarName: sourceVar, - TypeRef: tref, - Type: att.Type, - Validate: validateDef, - Example: att.Example(expr.Root.API.ExampleGenerator), - }, + svcctx = viewContext(sd.Service.ViewsPkg, sd.Service.ViewScope) + } + src := sourceVar + srcAtt := att + // If design uses Body("name") syntax then need to use result attribute + // to transform. + if o, ok := body.Meta["origin:attribute"]; ok { + srcObj := expr.AsObject(att.Type) + origin = o[0] + srcAtt = srcObj.Attribute(origin) + src += "." + codegen.Goify(origin, true) } - init = &InitData{ - Name: name, - Description: desc, - ReturnTypeRef: rtref, - ReturnTypeAttribute: codegen.Goify(origin, true), - ServerCode: code, - ServerArgs: []*InitArgData{&arg}, + code, helpers, err = marshal(srcAtt, body, src, "body", svcctx, httpctx) + if err != nil { + panic(err) // bug } + sd.ServerTransformHelpers = codegen.AppendHelpers(sd.ServerTransformHelpers, helpers) + } + ref := sourceVar + if view != nil { + ref += ".Projected" + } + tref := svc.Scope.GoFullTypeRef(att, pkg) + if view != nil { + tref = svc.ViewScope.GoFullTypeRef(att, svc.ViewsPkg) + } + arg := InitArgData{ + Ref: ref, + AttributeData: &AttributeData{ + Name: "result", + VarName: sourceVar, + TypeRef: tref, + Type: att.Type, + Validate: validateDef, + Example: att.Example(expr.Root.API.ExampleGenerator), + }, + } + init = &InitData{ + Name: name, + Description: desc, + ReturnTypeRef: rtref, + ReturnTypeAttribute: codegen.Goify(origin, true), + ServerCode: code, + ServerArgs: []*InitArgData{&arg}, } } return &TypeData{ @@ -2422,14 +2392,12 @@ func extractHeaders(a *expr.MappedAttributeExpr, svcAtt *expr.AttributeExpr, svc } var hattr *expr.AttributeExpr var stringSlice bool - { - // The StringSlice field of ParamData must be false for aliased primitive types - if arr := expr.AsArray(attr.Type); arr != nil { - stringSlice = arr.ElemType.Type.Kind() == expr.StringKind - } - - hattr = makeHTTPType(attr) + // The StringSlice field of ParamData must be false for aliased primitive types + if arr := expr.AsArray(attr.Type); arr != nil { + stringSlice = arr.ElemType.Type.Kind() == expr.StringKind } + + hattr = makeHTTPType(attr) var ( varn = scope.Name(codegen.Goify(name, false)) arr = expr.AsArray(hattr.Type) @@ -2440,15 +2408,13 @@ func extractHeaders(a *expr.MappedAttributeExpr, svcAtt *expr.AttributeExpr, svc pointer bool fptr bool ) - { - pointer = a.IsPrimitivePointer(name, true) - if expr.IsObject(svcAtt.Type) { - fieldName = codegen.GoifyAtt(attr, name, true) - fptr = svcCtx.IsPrimitivePointer(name, svcAtt) - } - if pointer { - typeRef = "*" + typeRef - } + pointer = a.IsPrimitivePointer(name, true) + if expr.IsObject(svcAtt.Type) { + fieldName = codegen.GoifyAtt(attr, name, true) + fptr = svcCtx.IsPrimitivePointer(name, svcAtt) + } + if pointer { + typeRef = "*" + typeRef } headers = append(headers, &HeaderData{ CanonicalName: http.CanonicalHeaderKey(elem), @@ -2484,12 +2450,10 @@ func extractCookies(a *expr.MappedAttributeExpr, svcAtt *expr.AttributeExpr, svc var cookies []*CookieData codegen.WalkMappedAttr(a, func(name, elem string, required bool, _ *expr.AttributeExpr) error { // nolint: errcheck var hattr *expr.AttributeExpr - { - if hattr = svcAtt.Find(name); hattr == nil { - hattr = svcAtt - } - hattr = makeHTTPType(hattr) + if hattr = svcAtt.Find(name); hattr == nil { + hattr = svcAtt } + hattr = makeHTTPType(hattr) var ( varn = scope.Name(codegen.Goify(name, false)) typeRef = scope.GoTypeRef(hattr) @@ -2499,16 +2463,14 @@ func extractCookies(a *expr.MappedAttributeExpr, svcAtt *expr.AttributeExpr, svc pointer bool fptr bool ) - { - pointer = a.IsPrimitivePointer(name, true) - if expr.IsObject(svcAtt.Type) { - fieldName = codegen.GoifyAtt(hattr, name, true) - fptr = svcCtx.IsPrimitivePointer(name, svcAtt) - ft = svcAtt.Find(name).Type - } - if pointer { - typeRef = "*" + typeRef - } + pointer = a.IsPrimitivePointer(name, true) + if expr.IsObject(svcAtt.Type) { + fieldName = codegen.GoifyAtt(hattr, name, true) + fptr = svcCtx.IsPrimitivePointer(name, svcAtt) + ft = svcAtt.Find(name).Type + } + if pointer { + typeRef = "*" + typeRef } c := &CookieData{ Element: &Element{ @@ -2617,21 +2579,19 @@ func attributeTypeData(ut expr.UserType, req, ptr, server bool, rd *ServiceData) att = &expr.AttributeExpr{Type: ut} hctx = httpContext("", rd.Scope, req, server) ) - { - name = rd.Scope.GoTypeName(att) - ctx := "request" - if !req { - ctx = "response" - } - desc = name + " is used to define fields on " + ctx + " body types." - if req || !req && !server { - // generate validations for responses client-side and for - // requests server-side and CLI - validate = codegen.ValidationCode(ut.Attribute(), ut, hctx, true, expr.IsAlias(ut), false, "body") - } - if validate != "" { - validateRef = fmt.Sprintf("err = Validate%s(v)", name) - } + name = rd.Scope.GoTypeName(att) + ctx := "request" + if !req { + ctx = "response" + } + desc = name + " is used to define fields on " + ctx + " body types." + if req || !req && !server { + // generate validations for responses client-side and for + // requests server-side and CLI + validate = codegen.ValidationCode(ut.Attribute(), ut, hctx, true, expr.IsAlias(ut), false, "body") + } + if validate != "" { + validateRef = fmt.Sprintf("err = Validate%s(v)", name) } return &TypeData{ Name: ut.Name(), diff --git a/http/codegen/templates/partial/response.go.tpl b/http/codegen/templates/partial/response.go.tpl index 8aa8f97826..26f8033e4f 100644 --- a/http/codegen/templates/partial/response.go.tpl +++ b/http/codegen/templates/partial/response.go.tpl @@ -1,8 +1,6 @@ {{- $servBodyLen := len .ServerBody }} {{- if gt $servBodyLen 0 }} enc := encoder(ctx, w) - {{- end }} - {{- if gt $servBodyLen 0 }} {{- if and (gt $servBodyLen 1) $.ViewedResult }} var body any switch res.View { diff --git a/http/codegen/testdata/streaming_code.go b/http/codegen/testdata/streaming_code.go index 003bbcd466..e31536f882 100644 --- a/http/codegen/testdata/streaming_code.go +++ b/http/codegen/testdata/streaming_code.go @@ -404,7 +404,7 @@ var StreamingResultWithExplicitViewClientStreamRecvCode = `// Recv reads instanc func (s *StreamingResultWithExplicitViewMethodClientStream) Recv() (*streamingresultwithexplicitviewservice.Usertype, error) { var ( rv *streamingresultwithexplicitviewservice.Usertype - body StreamingResultWithExplicitViewMethodResponseBody + body StreamingResultWithExplicitViewMethodResponseBodyExtended err error ) err = s.conn.ReadJSON(&body) @@ -1423,7 +1423,7 @@ var StreamingPayloadResultWithExplicitViewClientStreamRecvCode = `// CloseAndRec func (s *StreamingPayloadResultWithExplicitViewMethodClientStream) CloseAndRecv() (*streamingpayloadresultwithexplicitviewservice.Usertype, error) { var ( rv *streamingpayloadresultwithexplicitviewservice.Usertype - body StreamingPayloadResultWithExplicitViewMethodResponseBody + body StreamingPayloadResultWithExplicitViewMethodResponseBodyExtended err error ) defer s.conn.Close() @@ -2623,7 +2623,7 @@ var BidirectionalStreamingResultWithExplicitViewClientStreamRecvCode = `// Recv func (s *BidirectionalStreamingResultWithExplicitViewMethodClientStream) Recv() (*bidirectionalstreamingresultwithexplicitviewservice.Usertype, error) { var ( rv *bidirectionalstreamingresultwithexplicitviewservice.Usertype - body BidirectionalStreamingResultWithExplicitViewMethodResponseBody + body BidirectionalStreamingResultWithExplicitViewMethodResponseBodyExtended err error ) err = s.conn.ReadJSON(&body)