From 2afbaad8d3ca5751f4945b3ef2a302bcf5449a6e Mon Sep 17 00:00:00 2001 From: Gajendra Malviya Date: Wed, 29 Jul 2026 15:16:58 +0530 Subject: [PATCH] Fix broken request wrappers in generated gRPC code MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The request-wrapper template ranges over .Requests ([]ServiceRequest) but referenced the loop variable directly as `{{ $request }}`. text/template falls back to fmt's struct format, so it rendered `{GetThingRequest GetThingRequest}` — producing `type {GetThingRequest GetThingRequest}Wrapper` and `*{GetThingRequest GetThingRequest}`, which do not compile ("syntax error: unexpected {, expected name/type"). Use `{{ $request.Request }}` at every site in the request-wrapper block (type, embedded field, all receiver methods, and the Bind reflect call). Adds the first test for the wrap package, asserting the request wrappers render as `Wrapper` for multiple request types and that no struct literal leaks into the output. Fixes #75 --- wrap/template.go | 18 ++++++------- wrap/template_test.go | 60 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 69 insertions(+), 9 deletions(-) create mode 100644 wrap/template_test.go diff --git a/wrap/template.go b/wrap/template.go index 4e08aa4..7b18670 100644 --- a/wrap/template.go +++ b/wrap/template.go @@ -328,30 +328,30 @@ import ( // Request Wrappers {{- range $request := .Requests }} -type {{ $request }}Wrapper struct { +type {{ $request.Request }}Wrapper struct { ctx context.Context - *{{ $request }} + *{{ $request.Request }} } -func (h *{{ $request }}Wrapper) Context() context.Context { +func (h *{{ $request.Request }}Wrapper) Context() context.Context { return h.ctx } -func (h *{{ $request }}Wrapper) Param(s string) string { +func (h *{{ $request.Request }}Wrapper) Param(s string) string { return "" } -func (h *{{ $request }}Wrapper) PathParam(s string) string { +func (h *{{ $request.Request }}Wrapper) PathParam(s string) string { return "" } -func (h *{{ $request }}Wrapper) Bind(p interface{}) error { +func (h *{{ $request.Request }}Wrapper) Bind(p interface{}) error { ptr := reflect.ValueOf(p) if ptr.Kind() != reflect.Ptr { return fmt.Errorf("expected a pointer, got %T", p) } - hValue := reflect.ValueOf(h.{{ $request }}).Elem() + hValue := reflect.ValueOf(h.{{ $request.Request }}).Elem() ptrValue := ptr.Elem() for i := 0; i < hValue.NumField(); i++ { @@ -368,11 +368,11 @@ func (h *{{ $request }}Wrapper) Bind(p interface{}) error { return nil } -func (h *{{ $request }}Wrapper) HostName() string { +func (h *{{ $request.Request }}Wrapper) HostName() string { return "" } -func (h *{{ $request }}Wrapper) Params(s string) []string { +func (h *{{ $request.Request }}Wrapper) Params(s string) []string { return nil } {{- end }}` diff --git a/wrap/template_test.go b/wrap/template_test.go new file mode 100644 index 0000000..bc40bf9 --- /dev/null +++ b/wrap/template_test.go @@ -0,0 +1,60 @@ +package wrap + +import ( + "strings" + "testing" + + "gofr.dev/pkg/gofr" + "gofr.dev/pkg/gofr/cmd" + gofrConfig "gofr.dev/pkg/gofr/config" + "gofr.dev/pkg/gofr/container" + "gofr.dev/pkg/gofr/logging" +) + +// createTestContext creates a test gofr.Context for CMD applications. +func createTestContext() *gofr.Context { + c := container.NewContainer(gofrConfig.NewEnvFile("", logging.NewMockLogger(logging.DEBUG))) + req := cmd.NewRequest([]string{}) + + return &gofr.Context{ + Context: req.Context(), + Request: req, + Container: c, + } +} + +// The request-wrapper template ranges over []ServiceRequest, so it must render +// the request type's Name, not the whole struct. Regression test for #75: +// `{{ $request }}` printed the struct as `{GetThingRequest GetThingRequest}`, +// producing `type {GetThingRequest GetThingRequest}Wrapper` that would not +// compile. +func TestGenerateGoFrRequestWrapper_UsesRequestTypeName(t *testing.T) { + out := generateGoFrRequestWrapper(createTestContext(), &WrapperData{ + Package: "example", + Source: "example.proto", + Requests: []ServiceRequest{ + {Request: "GetThingRequest"}, + {Request: "CreateThingRequest"}, + }, + }) + + // Every request type renders correctly, for both entries. + want := []string{ + "type GetThingRequestWrapper struct {", + "*GetThingRequest", + "func (h *GetThingRequestWrapper) Context() context.Context {", + "func (h *GetThingRequestWrapper) Bind(p interface{}) error {", + "reflect.ValueOf(h.GetThingRequest).Elem()", + "type CreateThingRequestWrapper struct {", + } + for _, s := range want { + if !strings.Contains(out, s) { + t.Errorf("generated request wrapper missing %q\n---\n%s", s, out) + } + } + + // The Go struct-literal formatting must not leak into the output. + if strings.Contains(out, "{GetThingRequest") || strings.Contains(out, "{CreateThingRequest") { + t.Errorf("request wrapper leaked a struct literal into the output:\n%s", out) + } +}