Repository: goadesign/goa
Pull request #3578 (2024-08-14 03:00:42)

Write response headers with SkipEncodeDecodeResponseBody using a custom writer


Patch:
diff --git a/cmd/goa/gen.go b/cmd/goa/gen.go
index e52bcec6..3a56485c 100644
--- a/cmd/goa/gen.go
+++ b/cmd/goa/gen.go
@@ -44,7 +44,7 @@ type Generator struct {
 }
 
 // NewGenerator creates a Generator.
-func NewGenerator(cmd string, path, output string) *Generator {
+func NewGenerator(cmd, path, output string) *Generator {
 	bin := "goa"
 	if runtime.GOOS == "windows" {
 		bin += ".exe"
@@ -247,7 +247,7 @@ func (g *Generator) runGoCmd(args ...string) error {
 	out, err := c.CombinedOutput()
 	if err != nil {
 		if len(out) > 0 {
-			return fmt.Errorf(string(out))
+			return fmt.Errorf("%s", out)
 		}
 		return fmt.Errorf("failed to compile generator: %w", err)
 	}
diff --git a/expr/http_response.go b/expr/http_response.go
index c150cd05..f18a49c4 100644
--- a/expr/http_response.go
+++ b/expr/http_response.go
@@ -139,10 +139,10 @@ func (r *HTTPResponseExpr) Validate(e *HTTPEndpointExpr) *eval.ValidationErrors
 	// an explicit conflict with the content-type and response.
 	if (r.ContentType == "text/html" || r.ContentType == "text/plain") && !e.SkipRequestBodyEncodeDecode {
 		if e.MethodExpr.Result.Type != nil && e.MethodExpr.Result.Type != String && e.MethodExpr.Result.Type != Bytes && r.Body == nil {
-			verr.Add(r, fmt.Sprintf("Result type must be String or Bytes when ContentType is '%s'", r.ContentType))
+			verr.Add(r, "Result type must be String or Bytes when ContentType is '%s'", r.ContentType)
 		}
 		if r.Body != nil && r.Body.Type != String && r.Body.Type != Bytes {
-			verr.Add(r, fmt.Sprintf("Result type must be String or Bytes when ContentType is '%s'", r.ContentType))
+			verr.Add(r, "Result type must be String or Bytes when ContentType is '%s'", r.ContentType)
 		}
 	}
 
diff --git a/grpc/error.go b/grpc/error.go
index a4a6d1ff..a144db1a 100644
--- a/grpc/error.go
+++ b/grpc/error.go
@@ -49,7 +49,7 @@ func NewErrorResponse(err error) *goapb.ErrorResponse {
 			Fault:     gerr.Fault,
 		}
 	}
-	return NewErrorResponse(goa.Fault(err.Error()))
+	return NewErrorResponse(goa.Fault("%s", err.Error()))
 }
 
 // NewServiceError returns a goa ServiceError type for the given ErrorResponse
@@ -92,18 +92,15 @@ func EncodeError(err error) error {
 	if errors.As(err, &gerr) {
 		// goa service error type. Compute the status code from the service error
 		// characteristics and create a new detailed gRPC status error.
-		var code codes.Code
-		{
-			code = codes.Unknown
-			if gerr.Fault {
-				code = codes.Internal
-			}
-			if gerr.Timeout {
-				code = codes.DeadlineExceeded
-			}
-			if gerr.Temporary {
-				code = codes.Unavailable
-			}
+		code := codes.Unknown
+		if gerr.Fault {
+			code = codes.Internal
+		}
+		if gerr.Timeout {
+			code = codes.DeadlineExceeded
+		}
+		if gerr.Temporary {
+			code = codes.Unavailable
 		}
 		return NewStatusError(code, err, NewErrorResponse(err))
 	}
diff --git a/http/codegen/templates/server_handler_init.go.tpl b/http/codegen/templates/server_handler_init.go.tpl
index 00fb7094..fdd3a452 100644
--- a/http/codegen/templates/server_handler_init.go.tpl
+++ b/http/codegen/templates/server_handler_init.go.tpl
@@ -85,6 +85,12 @@ func {{ .HandlerInit }}(
 		o := res.(*{{ .ServicePkgName }}.{{ .Method.ResponseStruct }})
 		defer o.Body.Close()
 		if wt, ok := o.Body.(io.WriterTo); ok {
+			{{- if not (or .Redirect (isWebSocketEndpoint .)) }}
+			if err := encodeResponse(ctx, w, {{ if and .Method.SkipResponseBodyEncodeDecode .Result.Ref }}o.Result{{ else }}res{{ end }}); err != nil {
+				errhandler(ctx, w, err)
+				return
+			}
+			{{- end }}
 			n, err := wt.WriteTo(w)
 			if err != nil {
 				if n == 0 {
diff --git a/http/error.go b/http/error.go
index 07da1021..409f2b3d 100644
--- a/http/error.go
+++ b/http/error.go
@@ -55,7 +55,7 @@ func NewErrorResponse(ctx context.Context, err error) Statuser {
 			Fault:     gerr.Fault,
 		}
 	}
-	return NewErrorResponse(ctx, goa.Fault(err.Error()))
+	return NewErrorResponse(ctx, goa.Fault("%s", err.Error()))
 }
 
 func (resp *ErrorResponse) MarshalXML(e *xml.Encoder, _ xml.StartElement) error {
diff --git a/pkg/error.go b/pkg/error.go
index d24dd108..7c115614 100644
--- a/pkg/error.go
+++ b/pkg/error.go
@@ -116,7 +116,7 @@ func MissingPayloadError() error {
 // DecodePayloadError is the error produced by the generated code when a request
 // body cannot be decoded successfully.
 func DecodePayloadError(msg string) error {
-	return PermanentError("decode_payload", msg)
+	return PermanentError("decode_payload", "%s", msg)
 }
 
 // UnsupportedMediaTypeError is the error produced by the Goa decoder when the
@@ -162,7 +162,7 @@ func InvalidFormatError(name, target string, format Format, formatError error) e
 // InvalidPatternError is the error produced by the generated code when the
 // value of a payload field does not match the pattern validation defined in the
 // design.
-func InvalidPatternError(name, target string, pattern string) error {
+func InvalidPatternError(name, target, pattern string) error {
 	return withField(name, PermanentError(
 		InvalidPattern, "%s must match the regexp %q but got value %q", name, pattern, target))
 }
@@ -170,7 +170,7 @@ func InvalidPatternError(name, target string, pattern string) error {
 // InvalidRangeError is the error produced by the generated code when the value
 // of a payload field does not match the range validation defined in the design.
 // value may be an int or a float64.
-func InvalidRangeError(name string, target any, value any, min bool) error {
+func InvalidRangeError(name string, target, value any, min bool) error {
 	comp := "greater or equal"
 	if !min {
 		comp = "lesser or equal"


Test patch:
diff --git a/expr/grpc_endpoint_test.go b/expr/grpc_endpoint_test.go
index 8851c191..98f8c781 100644
--- a/expr/grpc_endpoint_test.go
+++ b/expr/grpc_endpoint_test.go
@@ -47,7 +47,7 @@ service "Service" gRPC endpoint "Method": field number 2 in attribute "key_dup_i
 	}
 	for name, c := range cases {
 		t.Run(name, func(t *testing.T) {
-			if c.Errors == nil || len(c.Errors) == 0 {
+			if len(c.Errors) == 0 {
 				expr.RunDSL(t, c.DSL)
 			} else {
 				var errs []error
diff --git a/http/codegen/testdata/handler_init_functions.go b/http/codegen/testdata/handler_init_functions.go
index 5450611a..b6532c39 100644
--- a/http/codegen/testdata/handler_init_functions.go
+++ b/http/codegen/testdata/handler_init_functions.go
@@ -272,6 +272,10 @@ func NewMethodSkipResponseBodyEncodeDecodeHandler(
 		o := res.(*serviceskipresponsebodyencodedecode.MethodSkipResponseBodyEncodeDecodeResponseData)
 		defer o.Body.Close()
 		if wt, ok := o.Body.(io.WriterTo); ok {
+			if err := encodeResponse(ctx, w, res); err != nil {
+				errhandler(ctx, w, err)
+				return
+			}
 			n, err := wt.WriteTo(w)
 			if err != nil {
 				if n == 0 {