Skip to content
Merged
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
48 changes: 45 additions & 3 deletions mains/generate.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@ type srvInfo struct {
HasHTTP bool
HasGRPC bool
ServerName string
// FSCounts maps service name to the number of HTTP file servers.
FSCounts map[string]int
}

// svcT provides template data for each service imported by a server.
Expand All @@ -50,6 +52,9 @@ type svcT struct {
HasWebSocket bool
HasHTTP bool
HasGRPC bool
// FileServerNils is used by the template to emit one trailing
// nil argument per HTTP file server in the service.
FileServerNils []int
}

// Register the plugin for the example phase.
Expand Down Expand Up @@ -110,15 +115,26 @@ func generateExample(genpkg string, roots []eval.Root, files []*codegen.File) ([
}
if len(httpSvcs) == 0 { continue }
var svcs []*service.Data
for _, sd := range httpSvcs { if sd != nil && sd.Service != nil { svcs = append(svcs, sd.Service) } }
fsCounts := map[string]int{}
for _, sd := range httpSvcs {
if sd == nil || sd.Service == nil {
continue
}
svcs = append(svcs, sd.Service)
if sd.FileServers != nil {
fsCounts[sd.Service.Name] = len(sd.FileServers)
}
}
hasWS := httpcodegen.NeedDialer(httpSvcs)
apipkg := apiPkgAlias(genpkg, roots)
if info, ok := srvMap[dir]; ok {
info.HasWS = hasWS
info.HasHTTP = true
if info.APIPkg == "" { info.APIPkg = apipkg }
if info.FSCounts == nil { info.FSCounts = map[string]int{} }
for k, v := range fsCounts { info.FSCounts[k] = v }
} else {
srvMap[dir] = &srvInfo{Dir: dir, APIPkg: apipkg, Services: svcs, HasWS: hasWS, HasHTTP: true}
srvMap[dir] = &srvInfo{Dir: dir, APIPkg: apipkg, Services: svcs, HasWS: hasWS, HasHTTP: true, FSCounts: fsCounts}
}
}
// Detect gRPC servers from grpc.go files
Expand Down Expand Up @@ -228,6 +244,12 @@ func generateExample(genpkg string, roots []eval.Root, files []*codegen.File) ([
hasAnyWS = true
}

// Determine file server count: prefer extracted counts from example
// HTTP files, fallback to design counts when missing.
fsn, ok := info.FSCounts[sd.Name]
if !ok {
fsn = httpFileServerCounts(roots)[sd.Name]
}
svcsData = append(svcsData, svcT{
Name: sd.Name,
StructName: sd.StructName,
Expand All @@ -241,6 +263,7 @@ func generateExample(genpkg string, roots []eval.Root, files []*codegen.File) ([
HasWebSocket: hws,
HasHTTP: hasHTTP,
HasGRPC: hasGRPC,
FileServerNils: func(n int) []int { if n <= 0 { return nil }; s := make([]int, n); for i := range s { s[i] = i }; return s }(fsn),
})
}

Expand Down Expand Up @@ -354,7 +377,7 @@ func httpServicesByName(roots []eval.Root) map[string]bool {
continue
}
for _, svc := range root.API.HTTP.Services {
if len(svc.HTTPEndpoints) > 0 {
if len(svc.HTTPEndpoints) > 0 || len(svc.FileServers) > 0 {
hasHTTP[svc.Name()] = true
}
}
Expand All @@ -378,3 +401,22 @@ func grpcServicesByName(roots []eval.Root) map[string]bool {
}
return hasGRPC
}

// httpFileServerCounts returns a map from service name to the number of
// HTTP Files() endpoints defined for that service.
func httpFileServerCounts(roots []eval.Root) map[string]int {
counts := map[string]int{}
for _, r := range roots {
root, ok := r.(*expr.RootExpr)
if !ok || root.API == nil || root.API.HTTP == nil {
continue
}
for _, svc := range root.API.HTTP.Services {
if svc == nil {
continue
}
counts[svc.Name()] = len(svc.FileServers)
}
}
return counts
}
36 changes: 35 additions & 1 deletion mains/generate_test.go
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
package mains

import (
"bytes"
"regexp"
"testing"

"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"bytes"
"goa.design/goa/v3/codegen"
"goa.design/goa/v3/codegen/example"
"goa.design/goa/v3/codegen/service"
Expand Down Expand Up @@ -94,3 +95,36 @@ func TestWebSocketMainIncludesUpgrader(t *testing.T) {
assert.Contains(t, code, "github.com/gorilla/websocket")
assert.Contains(t, code, "websocket.Upgrader")
}

func TestMainsAddsFileServerNils(t *testing.T) {
root := codegen.RunDSL(t, testdata.FileServerServiceDSL)
svcs := service.NewServicesData(root)
httpSvcs := httpcodegen.NewServicesData(svcs, root.API.HTTP)
files := append(example.ServerFiles("gen", root, svcs), httpcodegen.ExampleServerFiles("gen", httpSvcs)...)

out, err := Generate("gen", []eval.Root{root}, files)
require.NoError(t, err)

// Expect relocated main under services/static/cmd/static/main.go
var mainFile *codegen.File
for _, f := range out {
if f.Path == "services/static/cmd/static/main.go" {
mainFile = f
break
}
}
require.NotNil(t, mainFile)

// Render the mains section and look for exactly 2 (errhandler, formatter)
// + 3 (file servers) nil arguments at the end of the New(...) call.
sections := mainFile.Section("mains-main")
require.Greater(t, len(sections), 0)
var buf bytes.Buffer
require.NoError(t, sections[0].Write(&buf))
code := buf.String()

// Match a New(...) call that ends with five consecutive `, nil` args
// (2 standard + 3 file servers)
re := regexp.MustCompile(`New\([\s\S]*,\s*nil(?:,\s*nil){4}\)`) // total 5 nils
assert.Regexp(t, re, code)
}
5 changes: 2 additions & 3 deletions mains/templates/main.go.tpl
Original file line number Diff line number Diff line change
Expand Up @@ -119,9 +119,9 @@ func main() {
{{- if .HasHTTP }}
// {{ .Name }} HTTP server
{{- if .HasWebSocket }}
{{ .SrvVar }} := {{ .GenHTTPPkg }}.New({{ .EpVar }}, mux, goahttp.RequestDecoder, goahttp.ResponseEncoder, nil, nil, upgrader, nil)
{{ .SrvVar }} := {{ .GenHTTPPkg }}.New({{ .EpVar }}, mux, goahttp.RequestDecoder, goahttp.ResponseEncoder, nil, nil, upgrader, nil{{- range .FileServerNils }}, nil{{- end }})
{{- else }}
{{ .SrvVar }} := {{ .GenHTTPPkg }}.New({{ .EpVar }}, mux, goahttp.RequestDecoder, goahttp.ResponseEncoder, nil, nil)
{{ .SrvVar }} := {{ .GenHTTPPkg }}.New({{ .EpVar }}, mux, goahttp.RequestDecoder, goahttp.ResponseEncoder, nil, nil{{- range .FileServerNils }}, nil{{- end }})
{{- end }}
{{ .GenHTTPPkg }}.Mount(mux, {{ .SrvVar }})
for _, m := range {{ .SrvVar }}.Mounts {
Expand Down Expand Up @@ -234,4 +234,3 @@ func main() {
wg.Wait()
log.Printf(ctx, "exited")
}

17 changes: 17 additions & 0 deletions mains/testdata/dsls.go
Original file line number Diff line number Diff line change
Expand Up @@ -63,3 +63,20 @@ var WebSocketServiceDSL = func() {
})
})
}

// Single service with only Files() endpoints; used to validate mains adds the
// correct number of http.FileSystem arguments to the HTTP server constructor.
var FileServerServiceDSL = func() {
API("fsapi", func() {
Server("edge", func() {
Services("static")
Host("dev", func() { URI("http://localhost:8080") })
})
})
Service("static", func() {
HTTP(func() { Path("/") })
Files("/f1.json", "/assets/f1.json")
Files("/f2.json", "/assets/f2.json")
Files("/f3.json", "/assets/f3.json")
})
}
11 changes: 11 additions & 0 deletions testing/codegen/clients.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,8 @@ type (
HasGRPC bool // Method has gRPC transport
HasJSONRPC bool // Method has JSON-RPC transport (any variant)
IsStreaming bool // Method has streaming
// PkgResultRef is the package-qualified result type reference
PkgResultRef string
}
)

Expand Down Expand Up @@ -123,6 +125,9 @@ func buildClientData(svcData *service.Data, root *expr.RootExpr, svc *expr.Servi
Methods: make([]*clientMethodData, 0, len(svcData.Methods)),
}

// Create a name scope for type reference generation
scope := codegen.NewNameScope()

// Build method data with client-specific extensions
for i, m := range svc.Methods {
md := svcData.Methods[i]
Expand All @@ -137,6 +142,12 @@ func buildClientData(svcData *service.Data, root *expr.RootExpr, svc *expr.Servi
IsStreaming: md.StreamKind != expr.NoStreamKind,
}

// Compute package-qualified result reference using Goa's GoFullTypeRef
// This properly handles arrays, maps, primitives, and user types
if m.Result != nil && m.Result.Type != expr.Empty {
cmd.PkgResultRef = scope.GoFullTypeRef(m.Result, svcData.PkgName)
}

// Analyze targets to determine available transports
for _, target := range targets {
if target.IsGRPC {
Expand Down
8 changes: 4 additions & 4 deletions testing/codegen/templates/client_methods.go.tpl
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
{{- if or $method.ServerStream $method.ClientStream }}
func (c *Client) {{ $method.VarName }}(ctx context.Context{{- if and $method.PayloadRef (not $method.StreamingPayload) }}, p *{{ $.PkgName }}.{{ $method.Payload }}{{- end }}) ({{ $.PkgName }}.{{ $method.ClientStream.Interface }}, error) {
{{- else }}
func (c *Client) {{ $method.VarName }}(ctx context.Context{{- if $method.PayloadRef }}, p *{{ $.PkgName }}.{{ $method.Payload }}{{- end }}) ({{- if $method.ResultRef }}*{{ $.PkgName }}.{{ $method.Result }}, {{ end }}error) {
func (c *Client) {{ $method.VarName }}(ctx context.Context{{- if $method.PayloadRef }}, p *{{ $.PkgName }}.{{ $method.Payload }}{{- end }}) ({{- if $method.PkgResultRef }}{{ $method.PkgResultRef }}, {{ end }}error) {
{{- end }}
// Determine which transport to use
transport := c.transport
Expand Down Expand Up @@ -62,7 +62,7 @@ func (c *Client) {{ $method.VarName }}(ctx context.Context{{- if $method.Payload
if err != nil {
return nil, err
}
return res.(*{{ $.PkgName }}.{{ $method.Result }}), nil
return res.({{ $method.PkgResultRef }}), nil
{{- else }}
_, err := endpoint(ctx, {{- if $method.PayloadRef }}p{{ else }}nil{{ end }})
return err
Expand All @@ -88,7 +88,7 @@ func (c *Client) {{ $method.VarName }}(ctx context.Context{{- if $method.Payload
if err != nil {
return nil, err
}
return res.(*{{ $.PkgName }}.{{ $method.Result }}), nil
return res.({{ $method.PkgResultRef }}), nil
{{- else }}
_, err := endpoint(ctx, {{- if $method.PayloadRef }}p{{ else }}nil{{ end }})
return err
Expand Down Expand Up @@ -129,7 +129,7 @@ func (c *Client) {{ $method.VarName }}(ctx context.Context{{- if $method.Payload
if err != nil {
return nil, err
}
return res.(*{{ $.PkgName }}.{{ $method.Result }}), nil
return res.({{ $method.PkgResultRef }}), nil
{{- else }}
_, err := endpoint(ctx, {{- if $method.PayloadRef }}p{{ else }}nil{{ end }})
return err
Expand Down

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

54 changes: 27 additions & 27 deletions testing/examples/calculator/gen/calculator/calculatortest/errors.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading
Loading