Repository: goadesign/goa
Pull request #3137 (2022-08-30 19:13:12)

Making error formatter accept context.Context parameter
Starting to work on http error formatter in order to be able to accept `context.Context` attribute.

Reference to thread https://gophers.slack.com/archives/C0FK8EV28/p1660978056628579


Patch:
diff --git a/Makefile b/Makefile
index c6e98140..344a9471 100644
--- a/Makefile
+++ b/Makefile
@@ -14,7 +14,7 @@
 #
 MAJOR=3
 MINOR=8
-BUILD=4
+BUILD=5
 
 GOOS=$(shell go env GOOS)
 GO_FILES=$(shell find . -type f -name '*.go')
@@ -64,7 +64,7 @@ depend:
 
 lint:
 ifneq ($(GOOS),windows)
-	@if [ "`staticcheck -checks all ./... | grep -v ".pb.go" | tee /dev/stderr`" ]; then \
+	@if [ "`staticcheck ./... | grep -v ".pb.go" | tee /dev/stderr`" ]; then \
 		echo "^ - staticcheck errors!" && echo && exit 1; \
 	fi
 endif
diff --git a/README.md b/README.md
index 1b272a99..14657ad0 100644
--- a/README.md
+++ b/README.md
@@ -3,7 +3,7 @@
 ## Overview
 
 [![Build Status](https://github.com/goadesign/goa/workflows/build/badge.svg?branch=v3&event=push)](https://github.com/goadesign/goa/actions?query=branch%3Av3+event%3Apush)
-[![GoDev](https://img.shields.io/badge/go.dev-reference-007d9c?logo=go&logoColor=white&style=flat-square)](https://pkg.go.dev/goa.design/goa/v3@v3.8.4/dsl?tab=doc)
+[![GoDev](https://img.shields.io/badge/go.dev-reference-007d9c?logo=go&logoColor=white&style=flat-square)](https://pkg.go.dev/goa.design/goa/v3@v3.8.5/dsl?tab=doc)
 [![Go Report Card](https://goreportcard.com/badge/goadesign/goa)](https://goreportcard.com/report/goadesign/goa)
 [![Slack](https://img.shields.io/badge/join-us%20on%20slack-gray.svg?longCache=true&logo=slack&colorB=red)](https://gophers.slack.com/messages/goa)
 [![Twitter](https://img.shields.io/twitter/url/https/twitter.com/goadesign.svg?style=social&label=Follow%20%40goadesign)](https://twitter.com/goadesign)
@@ -49,7 +49,7 @@ go install goa.design/goa/v3/cmd/goa@v3
 
 >Note: Goa requires the use of Go modules.
 
-Current Release: `v3.8.4`
+Current Release: `v3.8.5`
 
 ## Teaser
 
@@ -315,7 +315,7 @@ In particular the page
 explains how to leverage the generated code to implement an HTTP or gRPC
 service.
 
-The [![DSL GoDoc](https://img.shields.io/badge/godoc-DSL-blue)](https://pkg.go.dev/goa.design/goa/v3@v3.8.4/dsl?tab=doc)
+The [![DSL GoDoc](https://img.shields.io/badge/godoc-DSL-blue)](https://pkg.go.dev/goa.design/goa/v3@v3.8.5/dsl?tab=doc)
 contains a fully documented reference of all the DSL functions.
 
 ### Instrumentation and System Example
diff --git a/dsl/payload.go b/dsl/payload.go
index 7509be80..eea7ca37 100644
--- a/dsl/payload.go
+++ b/dsl/payload.go
@@ -18,57 +18,56 @@ import (
 //
 // The valid usage for Payload are thus:
 //
-//    Payload(Type)
+//	Payload(Type)
 //
-//    Payload(func())
+//	Payload(func())
 //
-//    Payload(Type, "description")
+//	Payload(Type, "description")
 //
-//    Payload(Type, func())
+//	Payload(Type, func())
 //
-//    Payload(Type, "description", func())
+//	Payload(Type, "description", func())
 //
 // Examples:
 //
-//    Method("upper", func() {
-//        // Use primitive type.
-//        Payload(String)
-//    })
-//
-//    Method("upper", func() {
-//        // Use primitive type.and description
-//        Payload(String, "string to convert to uppercase")
-//    })
-//
-//    Method("upper", func() {
-//        // Use primitive type, description and validations
-//        Payload(String, "string to convert to uppercase", func() {
-//            Pattern("^[a-z]")
-//        })
-//    })
-//
-//    Method("add", func() {
-//        // Define payload data structure inline
-//        Payload(func() {
-//            Description("Left and right operands to add")
-//            Attribute("left", Int32, "Left operand")
-//            Attribute("right", Int32, "Left operand")
-//            Required("left", "right")
-//        })
-//    })
-//
-//    Method("add", func() {
-//        // Define payload type by reference to user type
-//        Payload(Operands)
-//    })
-//
-//    Method("divide", func() {
-//        // Specify additional required attributes on user type.
-//        Payload(Operands, func() {
-//            Required("left", "right")
-//        })
-//    })
-//
+//	Method("upper", func() {
+//	    // Use primitive type.
+//	    Payload(String)
+//	})
+//
+//	Method("upper", func() {
+//	    // Use primitive type.and description
+//	    Payload(String, "string to convert to uppercase")
+//	})
+//
+//	Method("upper", func() {
+//	    // Use primitive type, description and validations
+//	    Payload(String, "string to convert to uppercase", func() {
+//	        Pattern("^[a-z]")
+//	    })
+//	})
+//
+//	Method("add", func() {
+//	    // Define payload data structure inline
+//	    Payload(func() {
+//	        Description("Left and right operands to add")
+//	        Attribute("left", Int32, "Left operand")
+//	        Attribute("right", Int32, "Left operand")
+//	        Required("left", "right")
+//	    })
+//	})
+//
+//	Method("add", func() {
+//	    // Define payload type by reference to user type
+//	    Payload(Operands)
+//	})
+//
+//	Method("divide", func() {
+//	    // Specify additional required attributes on user type.
+//	    Payload(Operands, func() {
+//	        Required("left", "right")
+//	    })
+//	})
 func Payload(val interface{}, args ...interface{}) {
 	if len(args) > 2 {
 		eval.ReportError("too many arguments")
@@ -78,7 +77,7 @@ func Payload(val interface{}, args ...interface{}) {
 		eval.IncompatibleDSL()
 		return
 	}
-	e.Payload = methodDSL("Payload", val, args...)
+	e.Payload = methodDSL(e, "Payload", val, args...)
 }
 
 // StreamingPayload defines a method that accepts a stream of instances of the
@@ -90,40 +89,39 @@ func Payload(val interface{}, args ...interface{}) {
 //
 // Examples:
 //
-//    // Method payload is the JWT token and the method streaming payload is a
-//    // stream of strings.
-//    Method("upper", func() {
-//        Payload(func() {
-//            Token("token", String, func() {
-//                Description("JWT used for authentication")
-//            })
-//        })
-//        StreamingPayload(String)
-//    })
-//
-//    // Method streaming payload is a stream of string with validation set
-//    // on each
-//    Method("upper"), func() {
-//        StreamingPayload(String, "string to convert to uppercase", func() {
-//            Pattern("^[a-z]")
-//        })
-//    }
-//
-//    // Method payload is a stream of objects defined inline
-//    Method("add", func() {
-//        StreamingPayload(func() {
-//            Description("Left and right operands to add")
-//            Attribute("left", Int32, "Left operand")
-//            Attribute("right", Int32, "Left operand")
-//            Required("left", "right")
-//        })
-//    })
-//
-//    // Method payload is a stream of user type
-//    Method("add", func() {
-//        StreamingPayload(Operands)
-//    })
-//
+//	// Method payload is the JWT token and the method streaming payload is a
+//	// stream of strings.
+//	Method("upper", func() {
+//	    Payload(func() {
+//	        Token("token", String, func() {
+//	            Description("JWT used for authentication")
+//	        })
+//	    })
+//	    StreamingPayload(String)
+//	})
+//
+//	// Method streaming payload is a stream of string with validation set
+//	// on each
+//	Method("upper"), func() {
+//	    StreamingPayload(String, "string to convert to uppercase", func() {
+//	        Pattern("^[a-z]")
+//	    })
+//	}
+//
+//	// Method payload is a stream of objects defined inline
+//	Method("add", func() {
+//	    StreamingPayload(func() {
+//	        Description("Left and right operands to add")
+//	        Attribute("left", Int32, "Left operand")
+//	        Attribute("right", Int32, "Left operand")
+//	        Required("left", "right")
+//	    })
+//	})
+//
+//	// Method payload is a stream of user type
+//	Method("add", func() {
+//	    StreamingPayload(Operands)
+//	})
 func StreamingPayload(val interface{}, args ...interface{}) {
 	if len(args) > 2 {
 		eval.ReportError("too many arguments")
@@ -133,7 +131,7 @@ func StreamingPayload(val interface{}, args ...interface{}) {
 		eval.IncompatibleDSL()
 		return
 	}
-	e.StreamingPayload = methodDSL("StreamingPayload", val, args...)
+	e.StreamingPayload = methodDSL(e, "StreamingPayload", val, args...)
 	if e.Stream == expr.ServerStreamKind {
 		e.Stream = expr.BidirectionalStreamKind
 	} else {
@@ -141,7 +139,7 @@ func StreamingPayload(val interface{}, args ...interface{}) {
 	}
 }
 
-func methodDSL(suffix string, p interface{}, args ...interface{}) *expr.AttributeExpr {
+func methodDSL(m *expr.MethodExpr, suffix string, p interface{}, args ...interface{}) *expr.AttributeExpr {
 	var (
 		att *expr.AttributeExpr
 		fn  func()
@@ -155,7 +153,24 @@ func methodDSL(suffix string, p interface{}, args ...interface{}) *expr.Attribut
 			// Do not duplicate type if it is not customized
 			return &expr.AttributeExpr{Type: actual}
 		}
-		att = &expr.AttributeExpr{Type: expr.Dup(actual)}
+		dupped := expr.Dup(actual)
+		att = &expr.AttributeExpr{Type: dupped}
+		if f, ok := args[len(args)-1].(func()); ok {
+			numreqs := 0
+			if att.Validation != nil {
+				numreqs = len(att.Validation.Required)
+			}
+			eval.Execute(f, att)
+			if att.Validation != nil && len(att.Validation.Required) != numreqs {
+				// If the DSL modifies the type attributes "requiredness"
+				// then rename the type to avoid collisions.
+				if renamer, ok := dupped.(interface {
+					Rename(string)
+				}); ok {
+					renamer.Rename(actual.Name() + "_" + m.Name + "_" + suffix)
+				}
+			}
+		}
 	case expr.DataType:
 		att = &expr.AttributeExpr{Type: actual}
 	default:
diff --git a/dsl/result.go b/dsl/result.go
index 829a3358..02cc4c9e 100644
--- a/dsl/result.go
+++ b/dsl/result.go
@@ -20,57 +20,56 @@ import (
 //
 // The valid syntax for Result is thus:
 //
-//    Result(Type)
+//	Result(Type)
 //
-//    Result(func())
+//	Result(func())
 //
-//    Result(Type, "description")
+//	Result(Type, "description")
 //
-//    Result(Type, func())
+//	Result(Type, func())
 //
-//    Result(Type, "description", func())
+//	Result(Type, "description", func())
 //
 // Examples:
 //
-//    // Define result using primitive type
-//    Method("add", func() {
-//        Result(Int32)
-//    })
-//
-//    // Define result using primitive type and description
-//    Method("add", func() {
-//        Result(Int32, "Resulting sum")
-//    })
-//
-//    // Define result using primitive type, description and validations.
-//    Method("add", func() {
-//        Result(Int32, "Resulting sum", func() {
-//            Minimum(0)
-//        })
-//    })
-//
-//    // Define result using object defined inline
-//    Method("add", func() {
-//        Result(func() {
-//            Description("Result defines a single field which is the sum.")
-//            Attribute("value", Int32, "Resulting sum")
-//            Required("value")
-//        })
-//    })
-//
-//    // Define result type using user type
-//    Method("add", func() {
-//        Result(Sum)
-//    })
-//
-//    // Specify view and required attributes on result type
-//    Method("add", func() {
-//        Result(Sum, func() {
-//            View("default")
-//            Required("value")
-//        })
-//    })
-//
+//	// Define result using primitive type
+//	Method("add", func() {
+//	    Result(Int32)
+//	})
+//
+//	// Define result using primitive type and description
+//	Method("add", func() {
+//	    Result(Int32, "Resulting sum")
+//	})
+//
+//	// Define result using primitive type, description and validations.
+//	Method("add", func() {
+//	    Result(Int32, "Resulting sum", func() {
+//	        Minimum(0)
+//	    })
+//	})
+//
+//	// Define result using object defined inline
+//	Method("add", func() {
+//	    Result(func() {
+//	        Description("Result defines a single field which is the sum.")
+//	        Attribute("value", Int32, "Resulting sum")
+//	        Required("value")
+//	    })
+//	})
+//
+//	// Define result type using user type
+//	Method("add", func() {
+//	    Result(Sum)
+//	})
+//
+//	// Specify view and required attributes on result type
+//	Method("add", func() {
+//	    Result(Sum, func() {
+//	        View("default")
+//	        Required("value")
+//	    })
+//	})
 func Result(val interface{}, args ...interface{}) {
 	if len(args) > 2 {
 		eval.ReportError("too many arguments")
@@ -81,7 +80,7 @@ func Result(val interface{}, args ...interface{}) {
 		eval.IncompatibleDSL()
 		return
 	}
-	e.Result = methodDSL("Result", val, args...)
+	e.Result = methodDSL(e, "Result", val, args...)
 }
 
 // StreamingResult defines a method that streams instances of the given type.
@@ -92,44 +91,43 @@ func Result(val interface{}, args ...interface{}) {
 //
 // Examples:
 //
-//    // Method result is a stream of integers
-//    Method("add", func() {
-//        StreamingResult(Int32)
-//    })
-//
-//    Method("add", func() {
-//        StreamingResult(Int32, "Resulting sum")
-//    })
-//
-//    // Method result is a stream of integers with validation set on each
-//    Method("add", func() {
-//        StreamingResult(Int32, "Resulting sum", func() {
-//            Minimum(0)
-//        })
-//    })
-//
-//    // Method result is a stream of objects defined inline
-//    Method("add", func() {
-//        StreamingResult(func() {
-//            Description("Result defines a single field which is the sum.")
-//            Attribute("value", Int32, "Resulting sum")
-//            Required("value")
-//        })
-//    })
-//
-//    // Method result is a stream of user type
-//    Method("add", func() {
-//        StreamingResult(Sum)
-//    })
-//
-//    // Method result is a stream of result type with a view
-//    Method("add", func() {
-//        StreamingResult(Sum, func() {
-//            View("default")
-//            Required("value")
-//        })
-//    })
-//
+//	// Method result is a stream of integers
+//	Method("add", func() {
+//	    StreamingResult(Int32)
+//	})
+//
+//	Method("add", func() {
+//	    StreamingResult(Int32, "Resulting sum")
+//	})
+//
+//	// Method result is a stream of integers with validation set on each
+//	Method("add", func() {
+//	    StreamingResult(Int32, "Resulting sum", func() {
+//	        Minimum(0)
+//	    })
+//	})
+//
+//	// Method result is a stream of objects defined inline
+//	Method("add", func() {
+//	    StreamingResult(func() {
+//	        Description("Result defines a single field which is the sum.")
+//	        Attribute("value", Int32, "Resulting sum")
+//	        Required("value")
+//	    })
+//	})
+//
+//	// Method result is a stream of user type
+//	Method("add", func() {
+//	    StreamingResult(Sum)
+//	})
+//
+//	// Method result is a stream of result type with a view
+//	Method("add", func() {
+//	    StreamingResult(Sum, func() {
+//	        View("default")
+//	        Required("value")
+//	    })
+//	})
 func StreamingResult(val interface{}, args ...interface{}) {
 	if len(args) > 2 {
 		eval.ReportError("too many arguments")
@@ -140,7 +138,7 @@ func StreamingResult(val interface{}, args ...interface{}) {
 		eval.IncompatibleDSL()
 		return
 	}
-	e.Result = methodDSL("Result", val, args...)
+	e.Result = methodDSL(e, "Result", val, args...)
 	if e.Stream == expr.ClientStreamKind {
 		e.Stream = expr.BidirectionalStreamKind
 	} else {
diff --git a/go.mod b/go.mod
index f55e3497..bedfbeb1 100644
--- a/go.mod
+++ b/go.mod
@@ -5,7 +5,7 @@ go 1.19
 require (
 	github.com/dimfeld/httppath v0.0.0-20170720192232-ee938bf73598
 	github.com/dimfeld/httptreemux/v5 v5.4.0
-	github.com/getkin/kin-openapi v0.98.0
+	github.com/getkin/kin-openapi v0.102.0
 	github.com/google/uuid v1.3.0
 	github.com/gorilla/websocket v1.5.0
 	github.com/hashicorp/go-multierror v1.1.1
diff --git a/go.sum b/go.sum
index 58ed6bc1..308e2f3a 100644
--- a/go.sum
+++ b/go.sum
@@ -6,8 +6,8 @@ github.com/dimfeld/httppath v0.0.0-20170720192232-ee938bf73598 h1:MGKhKyiYrvMDZs
 github.com/dimfeld/httppath v0.0.0-20170720192232-ee938bf73598/go.mod h1:0FpDmbrt36utu8jEmeU05dPC9AB5tsLYVVi+ZHfyuwI=
 github.com/dimfeld/httptreemux/v5 v5.4.0 h1:IiHYEjh+A7pYbhWyjmGnj5HZK6gpOOvyBXCJ+BE8/Gs=
 github.com/dimfeld/httptreemux/v5 v5.4.0/go.mod h1:QeEylH57C0v3VO0tkKraVz9oD3Uu93CKPnTLbsidvSw=
-github.com/getkin/kin-openapi v0.98.0 h1:lIACvCG9cxmFsEywz+LCoVhcZHFLUy+Nv5QSkb43eAE=
-github.com/getkin/kin-openapi v0.98.0/go.mod h1:w4lRPHiyOdwGbOkLIyk+P0qCwlu7TXPCHD/64nSXzgE=
+github.com/getkin/kin-openapi v0.102.0 h1:qrzANKLqtUKlKUAofkmlV08pVNiVW2PgeWbKi/H2WVQ=
+github.com/getkin/kin-openapi v0.102.0/go.mod h1:w4lRPHiyOdwGbOkLIyk+P0qCwlu7TXPCHD/64nSXzgE=
 github.com/go-openapi/jsonpointer v0.19.5 h1:gZr+CIYByUqjcgeLXnQu2gHYQC9o73G2XUeOFYEICuY=
 github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg=
 github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk=
diff --git a/grpc/codegen/protobuf_transform.go b/grpc/codegen/protobuf_transform.go
index 9ee61c96..4445d23e 100644
--- a/grpc/codegen/protobuf_transform.go
+++ b/grpc/codegen/protobuf_transform.go
@@ -369,7 +369,7 @@ func transformObject(source, target *expr.AttributeExpr, sourceVar, targetVar st
 	return buffer.String(), nil
 }
 
-func isNonZero(v any) bool {
+func isNonZero(v interface{}) bool {
 	switch v := v.(type) {
 	case nil:
 		return false
diff --git a/http/codegen/openapi/v3/types.go b/http/codegen/openapi/v3/types.go
index f25cbb42..fc17566c 100644
--- a/http/codegen/openapi/v3/types.go
+++ b/http/codegen/openapi/v3/types.go
@@ -130,6 +130,20 @@ func buildBodyTypes(api *expr.APIExpr) (map[string]map[string]*EndpointBodies, m
 		}
 		bodies[s.Name()] = sbodies
 	}
+	for _, t := range expr.Root.Types {
+		_, ok := t.Attribute().Meta["type:generate:force"]
+		if !ok {
+			continue
+		}
+		sf.schemafy(&expr.AttributeExpr{Type: t})
+	}
+	for _, t := range expr.Root.ResultTypes {
+		_, ok := t.Attribute().Meta["type:generate:force"]
+		if !ok {
+			continue
+		}
+		sf.schemafy(&expr.AttributeExpr{Type: t})
+	}
 	return bodies, sf.schemas
 }
 
diff --git a/http/codegen/server.go b/http/codegen/server.go
index 96532a88..faacd810 100644
--- a/http/codegen/server.go
+++ b/http/codegen/server.go
@@ -304,7 +304,7 @@ func {{ .ServerInit }}(
 	decoder func(*http.Request) goahttp.Decoder,
 	encoder func(context.Context, http.ResponseWriter) goahttp.Encoder,
 	errhandler func(context.Context, http.ResponseWriter, error),
-	formatter func(err error) goahttp.Statuser,
+	formatter func(ctx context.Context, err error) goahttp.Statuser,
 	{{- if hasWebSocket . }}
 	upgrader goahttp.Upgrader,
 	configurer *ConnConfigurer,
@@ -432,7 +432,7 @@ func {{ .HandlerInit }}(
 	decoder func(*http.Request) goahttp.Decoder,
 	encoder func(context.Context, http.ResponseWriter) goahttp.Encoder,
 	errhandler func(context.Context, http.ResponseWriter, error),
-	formatter func(err error) goahttp.Statuser,
+	formatter func(ctx context.Context, err error) goahttp.Statuser,
 	{{- if isWebSocketEndpoint . }}
 	upgrader goahttp.Upgrader,
 	configurer goahttp.ConnConfigureFunc,
@@ -1200,7 +1200,7 @@ func {{ .ResponseEncoder }}(encoder func(context.Context, http.ResponseWriter) g
 
 // input: EndpointData
 const errorEncoderT = `{{ printf "%s returns an encoder for errors returned by the %s %s endpoint." .ErrorEncoder .Method.Name .ServiceName | comment }}
-func {{ .ErrorEncoder }}(encoder func(context.Context, http.ResponseWriter) goahttp.Encoder, formatter func(err error) goahttp.Statuser) func(context.Context, http.ResponseWriter, error) error {
+func {{ .ErrorEncoder }}(encoder func(context.Context, http.ResponseWriter) goahttp.Encoder, formatter func(ctx context.Context, err error) goahttp.Statuser) func(context.Context, http.ResponseWriter, error) error {
 	encodeError := goahttp.ErrorEncoder(encoder, formatter)
 	return func(ctx context.Context, w http.ResponseWriter, v error) error {
 		var en ErrorNamer
@@ -1253,7 +1253,7 @@ const responseT = `{{ define "response" -}}
 			{{- if .ErrorHeader }}
 	var body interface{}
 	if formatter != nil {
-		body = formatter({{ (index (index .ServerBody 0).Init.ServerArgs 0).Ref }})
+		body = formatter(ctx, {{ (index (index .ServerBody 0).Init.ServerArgs 0).Ref }})
 	} else {
 			{{- end }}
 	body {{ if not .ErrorHeader}}:{{ end }}= {{ (index .ServerBody 0).Init.Name }}({{ range (index .ServerBody 0).Init.ServerArgs }}{{ .Ref }}, {{ end }})
diff --git a/http/encoding.go b/http/encoding.go
index fa87dfa0..a576d143 100644
--- a/http/encoding.go
+++ b/http/encoding.go
@@ -51,10 +51,10 @@ type (
 // RequestDecoder returns a HTTP request body decoder suitable for the given
 // request. The decoder handles the following mime types:
 //
-//     * application/json using package encoding/json
-//     * application/xml using package encoding/xml
-//     * application/gob using package encoding/gob
-//     * text/html and text/plain for strings
+//   - application/json using package encoding/json
+//   - application/xml using package encoding/xml
+//   - application/gob using package encoding/gob
+//   - text/html and text/plain for strings
 //
 // RequestDecoder defaults to the JSON decoder if the request "Content-Type"
 // header does not match any of the supported mime type or is missing
@@ -88,10 +88,10 @@ func RequestDecoder(r *http.Request) Decoder {
 // set in the context under the AcceptTypeKey or the ContentTypeKey if any.
 // The encoder supports the following mime types:
 //
-//     * application/json using package encoding/json
-//     * application/xml using package encoding/xml
-//     * application/gob using package encoding/gob
-//     * text/html and text/plain for strings
+//   - application/json using package encoding/json
+//   - application/xml using package encoding/xml
+//   - application/gob using package encoding/gob
+//   - text/html and text/plain for strings
 //
 // ResponseEncoder defaults to the JSON encoder if the context AcceptTypeKey or
 // ContentTypeKey value does not match any of the supported mime types or is
@@ -181,11 +181,10 @@ func RequestEncoder(r *http.Request) Encoder {
 // ResponseDecoder returns a HTTP response decoder.
 // The decoder handles the following content types:
 //
-//   * application/json using package encoding/json (default)
-//   * application/xml using package encoding/xml
-//   * application/gob using package encoding/gob
-//   * text/html and text/plain for strings
-//
+//   - application/json using package encoding/json (default)
+//   - application/xml using package encoding/xml
+//   - application/gob using package encoding/gob
+//   - text/html and text/plain for strings
 func ResponseDecoder(resp *http.Response) Decoder {
 	ct := resp.Header.Get("Content-Type")
 	if ct == "" {
@@ -216,13 +215,13 @@ func ResponseDecoder(resp *http.Response) Decoder {
 // provided encoder. If the error is not a goa ServiceError struct then it is
 // encoded as a permanent internal server error. This behavior as well as the
 // shape of the response can be overridden by providing a non-nil formatter.
-func ErrorEncoder(encoder func(context.Context, http.ResponseWriter) Encoder, formatter func(err error) Statuser) func(context.Context, http.ResponseWriter, error) error {
+func ErrorEncoder(encoder func(context.Context, http.ResponseWriter) Encoder, formatter func(ctx context.Context, err error) Statuser) func(context.Context, http.ResponseWriter, error) error {
 	return func(ctx context.Context, w http.ResponseWriter, err error) error {
 		enc := encoder(ctx, w)
 		if formatter == nil {
 			formatter = NewErrorResponse
 		}
-		resp := formatter(err)
+		resp := formatter(ctx, err)
 		w.WriteHeader(resp.StatusCode())
 		return enc.Encode(resp)
 	}
diff --git a/http/error.go b/http/error.go
index f0dc0f75..94d42915 100644
--- a/http/error.go
+++ b/http/error.go
@@ -1,6 +1,7 @@
 package http
 
 import (
+	"context"
 	"net/http"
 
 	goa "goa.design/goa/v3/pkg"
@@ -35,7 +36,7 @@ type (
 )
 
 // NewErrorResponse creates a HTTP response from the given error.
-func NewErrorResponse(err error) Statuser {
+func NewErrorResponse(ctx context.Context, err error) Statuser {
 	if gerr, ok := err.(*goa.ServiceError); ok {
 		return &ErrorResponse{
 			Name:      gerr.Name,
@@ -46,7 +47,7 @@ func NewErrorResponse(err error) Statuser {
 			Fault:     gerr.Fault,
 		}
 	}
-	return NewErrorResponse(goa.Fault(err.Error()))
+	return NewErrorResponse(ctx, goa.Fault(err.Error()))
 }
 
 // StatusCode implements a heuristic that computes a HTTP response status code
diff --git a/http/middleware/requestid.go b/http/middleware/requestid.go
index e2a6efb6..07543644 100644
--- a/http/middleware/requestid.go
+++ b/http/middleware/requestid.go
@@ -13,19 +13,25 @@ import (
 // default behavior is to always generate a new ID.
 //
 // examples of use:
-//  service.Use(middleware.RequestID())
 //
-//  // enable options for using "X-Request-Id" header with length limit.
-//  service.Use(middleware.RequestID(
-//    middleware.UseXRequestIDHeaderOption(true),
-//    middleware.XRequestHeaderLimitOption(128)))
+//	service.Use(middleware.RequestID())
+//
+//	// enable options for using "X-Request-Id" header with length limit.
+//	service.Use(middleware.RequestID(
+//	  middleware.UseXRequestIDHeaderOption(true),
+//	  middleware.XRequestHeaderLimitOption(128)))
+//
+//	// enable options for using "Custom-Id" header.
+//	service.Use(middleware.RequestID(middleware.RequestIDHeaderOption("Custom-Id"))
 func RequestID(options ...middleware.RequestIDOption) func(http.Handler) http.Handler {
 	o := middleware.NewRequestIDOptions(options...)
+	useReqID := o.IsUseRequestID()
+	reqIDHeader := o.RequestIDHeader()
 	return func(h http.Handler) http.Handler {
 		return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
 			ctx := r.Context()
-			if o.IsUseRequestID() {
-				if id := r.Header.Get("X-Request-Id"); id != "" {
+			if useReqID {
+				if id := r.Header.Get(reqIDHeader); id != "" {
 					ctx = context.WithValue(ctx, middleware.RequestIDKey, id)
 				}
 			}
@@ -40,6 +46,12 @@ func UseXRequestIDHeaderOption(f bool) middleware.RequestIDOption {
 	return middleware.UseRequestIDOption(f)
 }
 
+// RequestIDHeaderOption sets the name of the header used to capture the incoming
+// request ID. This option also automatically enabled the use of that header.
+func RequestIDHeaderOption(name string) middleware.RequestIDOption {
+	return middleware.RequestIDHeaderOption(name)
+}
+
 // XRequestHeaderLimitOption sets the option for using "X-Request-Id" header.
 func XRequestHeaderLimitOption(limit int) middleware.RequestIDOption {
 	return middleware.RequestIDLimitOption(limit)
diff --git a/http/mux.go b/http/mux.go
index 29d06b85..1b5074fa 100644
--- a/http/mux.go
+++ b/http/mux.go
@@ -76,7 +76,7 @@ func NewMuxer() MiddlewareMuxer {
 		ctx := context.WithValue(req.Context(), AcceptTypeKey, req.Header.Get("Accept"))
 		enc := ResponseEncoder(ctx, w)
 		w.WriteHeader(http.StatusNotFound)
-		enc.Encode(NewErrorResponse(fmt.Errorf("404 page not found")))
+		enc.Encode(NewErrorResponse(ctx, fmt.Errorf("404 page not found")))
 	}
 	return &mux{r}
 }
diff --git a/middleware/ctxkeys.go b/middleware/ctxkeys.go
index 0274a6c5..873aac0c 100644
--- a/middleware/ctxkeys.go
+++ b/middleware/ctxkeys.go
@@ -1,24 +1,19 @@
 package middleware
 
-type (
-	// private type used to define context keys
-	ctxKey int
-)
-
-const (
+var (
 	// RequestIDKey is the request context key used to store the request ID
 	// created by the RequestID middleware.
-	RequestIDKey ctxKey = iota + 1
+	RequestIDKey = "goa-request-id"
 
 	// TraceIDKey is the request context key used to store the current Trace
 	// ID if any.
-	TraceIDKey
+	TraceIDKey = "goa-trace-id"
 
 	// TraceSpanIDKey is the request context key used to store the current
 	// trace span ID if any.
-	TraceSpanIDKey
+	TraceSpanIDKey = "goa-trace-span-id"
 
 	// TraceParentSpanIDKey is the request context key used to store the current
 	// trace parent span ID if any.
-	TraceParentSpanIDKey
+	TraceParentSpanIDKey = "goa-trace-parent-span-id"
 )
diff --git a/middleware/requestid.go b/middleware/requestid.go
index def38d57..557a9ba9 100644
--- a/middleware/requestid.go
+++ b/middleware/requestid.go
@@ -13,10 +13,13 @@ type (
 
 	// RequestIDOptions is the struct storing all the options.
 	RequestIDOptions struct {
-		// useRequestID if true indicates the middleware to use the RequestIDKey
-		// context key to infer the request ID instead of always generating
-		// unique IDs. Defaults to always-generate.
+		// useRequestID if true causes the middleware to look up the
+		// incoming request header to infer the request ID instead of
+		// always generating unique IDs. Defaults to always-generate.
 		useRequestID bool
+		// requestIDHeader is the name of the header used to capture the incoming
+		// request ID. Defaults to X-Request-Id.
+		requestIDHeader string
 		// requestIDLimit if positive truncates the request ID at the specified
 		// length. Defaults to no limit.
 		requestIDLimit int
@@ -57,11 +60,22 @@ func GenerateRequestID(ctx context.Context, o *RequestIDOptions) context.Context
 // the unique request ID.
 func UseRequestIDOption(f bool) RequestIDOption {
 	return func(o *RequestIDOptions) *RequestIDOptions {
+		o.requestIDHeader = "X-Request-Id"
 		o.useRequestID = f
 		return o
 	}
 }
 
+// RequestIDHeaderOption sets the name of the header used to capture the incoming
+// request ID. This option also automatically enabled the use of that header.
+func RequestIDHeaderOption(name string) RequestIDOption {
+	return func(o *RequestIDOptions) *RequestIDOptions {
+		o.requestIDHeader = name
+		o.useRequestID = true
+		return o
+	}
+}
+
 // RequestIDLimitOption sets the option for truncating the request ID stored
 // in the context at the specified length.
 func RequestIDLimitOption(limit int) RequestIDOption {
@@ -76,6 +90,11 @@ func (o *RequestIDOptions) IsUseRequestID() bool {
 	return o.useRequestID
 }
 
+// RequestIDHeader returns the request ID header option.
+func (o *RequestIDOptions) RequestIDHeader() string {
+	return o.requestIDHeader
+}
+
 // shortID produces a " unique" 6 bytes long string.
 // Do not use as a reliable way to get unique IDs, instead use for things like logging.
 func shortID() string {
diff --git a/pkg/version.go b/pkg/version.go
index 1c8dd5e7..0415e956 100644
--- a/pkg/version.go
+++ b/pkg/version.go
@@ -12,7 +12,7 @@ const (
 	// Minor version number
 	Minor = 8
 	// Build number
-	Build = 4
+	Build = 5
 	// Suffix - set to empty string in release tag commits.
 	Suffix = ""
 )
diff --git a/staticcheck.conf b/staticcheck.conf
new file mode 100644
index 00000000..2b2f3867
--- /dev/null
+++ b/staticcheck.conf
@@ -0,0 +1,1 @@
+checks = ["all", "-SA1029"]
\ No newline at end of file


Test patch:
diff --git a/codegen/service/service_test.go b/codegen/service/service_test.go
index 75bb2b35..1b107fd6 100644
--- a/codegen/service/service_test.go
+++ b/codegen/service/service_test.go
@@ -34,6 +34,7 @@ func TestService(t *testing.T) {
 		{"result-with-result-collection", testdata.ResultWithResultCollectionMethodDSL, testdata.ResultWithResultCollectionMethod},
 		{"result-with-dashed-mime-type", testdata.ResultWithDashedMimeTypeMethodDSL, testdata.ResultWithDashedMimeTypeMethod},
 		{"result-with-one-of-type", testdata.ResultWithOneOfTypeMethodDSL, testdata.ResultWithOneOfTypeMethod},
+		{"result-with-inline-validation", testdata.ResultWithInlineValidationDSL, testdata.ResultWithInlineValidation},
 		{"service-level-error", testdata.ServiceErrorDSL, testdata.ServiceError},
 		{"custom-errors", testdata.CustomErrorsDSL, testdata.CustomErrors},
 		{"custom-errors-custom-field", testdata.CustomErrorsCustomFieldDSL, testdata.CustomErrorsCustomField},
diff --git a/codegen/service/testdata/service_code.go b/codegen/service/testdata/service_code.go
index e0d9da73..e3ab82a6 100644
--- a/codegen/service/testdata/service_code.go
+++ b/codegen/service/testdata/service_code.go
@@ -1492,6 +1492,74 @@ func transformItemToResultwithoneoftypeviewsItemView(v *Item) *resultwithoneofty
 }
 `
 
+const ResultWithInlineValidation = `
+// Service is the ResultWithInlineValidation service interface.
+type Service interface {
+	// A implements A.
+	A(context.Context) (res *ResultInlineValidation, err error)
+	// B implements B.
+	B(context.Context) (res *ResultInlineValidationBResult, err error)
+}
+
+// ServiceName is the name of the service as defined in the design. This is the
+// same value that is set in the endpoint request contexts under the ServiceKey
+// key.
+const ServiceName = "ResultWithInlineValidation"
+
+// MethodNames lists the service method names as defined in the design. These
+// are the same values that are set in the endpoint request contexts under the
+// MethodKey key.
+var MethodNames = [2]string{"A", "B"}
+
+// ResultInlineValidation is the result type of the ResultWithInlineValidation
+// service A method.
+type ResultInlineValidation struct {
+	A *string
+	B *int
+}
+
+// ResultInlineValidationBResult is the result type of the
+// ResultWithInlineValidation service B method.
+type ResultInlineValidationBResult struct {
+	A string
+	B *int
+}
+
+// NewResultInlineValidation initializes result type ResultInlineValidation
+// from viewed result type ResultInlineValidation.
+func NewResultInlineValidation(vres *resultwithinlinevalidationviews.ResultInlineValidation) *ResultInlineValidation {
+	return newResultInlineValidation(vres.Projected)
+}
+
+// NewViewedResultInlineValidation initializes viewed result type
+// ResultInlineValidation from result type ResultInlineValidation using the
+// given view.
+func NewViewedResultInlineValidation(res *ResultInlineValidation, view string) *resultwithinlinevalidationviews.ResultInlineValidation {
+	p := newResultInlineValidationView(res)
+	return &resultwithinlinevalidationviews.ResultInlineValidation{Projected: p, View: "default"}
+}
+
+// newResultInlineValidation converts projected type ResultInlineValidation to
+// service type ResultInlineValidation.
+func newResultInlineValidation(vres *resultwithinlinevalidationviews.ResultInlineValidationView) *ResultInlineValidation {
+	res := &ResultInlineValidation{
+		A: vres.A,
+		B: vres.B,
+	}
+	return res
+}
+
+// newResultInlineValidationView projects result type ResultInlineValidation to
+// projected type ResultInlineValidationView using the "default" view.
+func newResultInlineValidationView(res *ResultInlineValidation) *resultwithinlinevalidationviews.ResultInlineValidationView {
+	vres := &resultwithinlinevalidationviews.ResultInlineValidationView{
+		A: res.A,
+		B: res.B,
+	}
+	return vres
+}
+`
+
 const ForceGenerateType = `
 // Service is the ForceGenerateType service interface.
 type Service interface {
diff --git a/codegen/service/testdata/service_dsls.go b/codegen/service/testdata/service_dsls.go
index 3488ab6c..5fd9b06b 100644
--- a/codegen/service/testdata/service_dsls.go
+++ b/codegen/service/testdata/service_dsls.go
@@ -454,6 +454,25 @@ var ResultWithOneOfTypeMethodDSL = func() {
 	})
 }
 
+var ResultWithInlineValidationDSL = func() {
+	var RT = ResultType("application/vnd.result.inline.validation", func() {
+		Attributes(func() {
+			Attribute("a", String)
+			Attribute("b", Int)
+		})
+	})
+	Service("ResultWithInlineValidation", func() {
+		Method("A", func() {
+			Result(RT)
+		})
+		Method("B", func() {
+			Result(RT, func() {
+				Required("a")
+			})
+		})
+	})
+}
+
 var ForceGenerateTypeDSL = func() {
 	var _ = Type("ForcedType", func() {
 		Attribute("a", String)
diff --git a/http/codegen/openapi/v3/testdata/dsls/types.go b/http/codegen/openapi/v3/testdata/dsls/types.go
index 54aa847e..a4e793fd 100644
--- a/http/codegen/openapi/v3/testdata/dsls/types.go
+++ b/http/codegen/openapi/v3/testdata/dsls/types.go
@@ -150,3 +150,37 @@ func ObjectErrorResponseBodyDSL(svcName, metName string) func() {
 		})
 	}
 }
+
+func ForcedTypeDSL(svcName, metName string) func() {
+	return func() {
+		var _ = Type("Forced", func() {
+			Attribute("foo")
+			Meta("type:generate:force")
+		})
+		var _ = Service(svcName, func() {
+			Method(metName, func() {
+				HTTP(func() {
+					POST("/")
+				})
+			})
+		})
+	}
+}
+
+func ForcedResultTypeDSL(svcName, metName string) func() {
+	return func() {
+		var _ = ResultType("Forced", func() {
+			Attributes(func() {
+				Attribute("foo")
+			})
+			Meta("type:generate:force")
+		})
+		var _ = Service(svcName, func() {
+			Method(metName, func() {
+				HTTP(func() {
+					POST("/")
+				})
+			})
+		})
+	}
+}
diff --git a/http/codegen/openapi/v3/types_test.go b/http/codegen/openapi/v3/types_test.go
index ace4e4a3..3607d533 100644
--- a/http/codegen/openapi/v3/types_test.go
+++ b/http/codegen/openapi/v3/types_test.go
@@ -70,6 +70,7 @@ func TestBuildBodyTypes(t *testing.T) {
 		ExpectedType          typ
 		ExpectedFormat        string
 		ExpectedResponseTypes rt
+		ExpectedExtraTypes    map[string]typ
 	}{{
 		Name: "string_body",
 		DSL:  dsls.StringBodyDSL(svcName, "string_body"),
@@ -142,6 +143,20 @@ func TestBuildBodyTypes(t *testing.T) {
 
 		ExpectedType:          tempty,
 		ExpectedResponseTypes: rt{204: tempty, 400: tobj("name", tstring, "age", tint)},
+	}, {
+		Name: "forced_type",
+		DSL:  dsls.ForcedTypeDSL(svcName, "forced_type"),
+
+		ExpectedType:          tempty,
+		ExpectedResponseTypes: rt{204: tempty},
+		ExpectedExtraTypes:    map[string]typ{"Forced": tobj("foo", tstring)},
+	}, {
+		Name: "forced_result_type",
+		DSL:  dsls.ForcedResultTypeDSL(svcName, "forced_result_type"),
+
+		ExpectedType:          tempty,
+		ExpectedResponseTypes: rt{204: tempty},
+		ExpectedExtraTypes:    map[string]typ{"Forced": tobj("foo", tstring)},
 	}}
 
 	for _, c := range cases {
@@ -180,6 +195,14 @@ func TestBuildBodyTypes(t *testing.T) {
 				}
 				matchesSchema(t, "response", met.ResponseBodies[s][0], types, r)
 			}
+			for name, forced := range c.ExpectedExtraTypes {
+				got, ok := types[name]
+				if !ok {
+					t.Errorf("missing forced type %q", name)
+					continue
+				}
+				matchesSchema(t, "extra type", got, types, forced)
+			}
 		})
 	}
 }
diff --git a/http/codegen/testdata/error_encoder_code.go b/http/codegen/testdata/error_encoder_code.go
index 8313d318..6a412385 100644
--- a/http/codegen/testdata/error_encoder_code.go
+++ b/http/codegen/testdata/error_encoder_code.go
@@ -3,7 +3,7 @@ package testdata
 var PrimitiveErrorResponseEncoderCode = `// EncodeMethodPrimitiveErrorResponseError returns an encoder for errors
 // returned by the MethodPrimitiveErrorResponse ServicePrimitiveErrorResponse
 // endpoint.
-func EncodeMethodPrimitiveErrorResponseError(encoder func(context.Context, http.ResponseWriter) goahttp.Encoder, formatter func(err error) goahttp.Statuser) func(context.Context, http.ResponseWriter, error) error {
+func EncodeMethodPrimitiveErrorResponseError(encoder func(context.Context, http.ResponseWriter) goahttp.Encoder, formatter func(ctx context.Context, err error) goahttp.Statuser) func(context.Context, http.ResponseWriter, error) error {
 	encodeError := goahttp.ErrorEncoder(encoder, formatter)
 	return func(ctx context.Context, w http.ResponseWriter, v error) error {
 		var en ErrorNamer
@@ -37,7 +37,7 @@ func EncodeMethodPrimitiveErrorResponseError(encoder func(context.Context, http.
 var PrimitiveErrorInResponseHeaderEncoderCode = `// EncodeMethodPrimitiveErrorInResponseHeaderError returns an encoder for
 // errors returned by the MethodPrimitiveErrorInResponseHeader
 // ServicePrimitiveErrorInResponseHeader endpoint.
-func EncodeMethodPrimitiveErrorInResponseHeaderError(encoder func(context.Context, http.ResponseWriter) goahttp.Encoder, formatter func(err error) goahttp.Statuser) func(context.Context, http.ResponseWriter, error) error {
+func EncodeMethodPrimitiveErrorInResponseHeaderError(encoder func(context.Context, http.ResponseWriter) goahttp.Encoder, formatter func(ctx context.Context, err error) goahttp.Statuser) func(context.Context, http.ResponseWriter, error) error {
 	encodeError := goahttp.ErrorEncoder(encoder, formatter)
 	return func(ctx context.Context, w http.ResponseWriter, v error) error {
 		var en ErrorNamer
@@ -77,7 +77,7 @@ func EncodeMethodPrimitiveErrorInResponseHeaderError(encoder func(context.Contex
 var APIPrimitiveErrorResponseEncoderCode = `// EncodeMethodAPIPrimitiveErrorResponseError returns an encoder for errors
 // returned by the MethodAPIPrimitiveErrorResponse
 // ServiceAPIPrimitiveErrorResponse endpoint.
-func EncodeMethodAPIPrimitiveErrorResponseError(encoder func(context.Context, http.ResponseWriter) goahttp.Encoder, formatter func(err error) goahttp.Statuser) func(context.Context, http.ResponseWriter, error) error {
+func EncodeMethodAPIPrimitiveErrorResponseError(encoder func(context.Context, http.ResponseWriter) goahttp.Encoder, formatter func(ctx context.Context, err error) goahttp.Statuser) func(context.Context, http.ResponseWriter, error) error {
 	encodeError := goahttp.ErrorEncoder(encoder, formatter)
 	return func(ctx context.Context, w http.ResponseWriter, v error) error {
 		var en ErrorNamer
@@ -91,7 +91,7 @@ func EncodeMethodAPIPrimitiveErrorResponseError(encoder func(context.Context, ht
 			enc := encoder(ctx, w)
 			var body interface{}
 			if formatter != nil {
-				body = formatter(res)
+				body = formatter(ctx, res)
 			} else {
 				body = NewMethodAPIPrimitiveErrorResponseInternalErrorResponseBody(res)
 			}
@@ -115,7 +115,7 @@ func EncodeMethodAPIPrimitiveErrorResponseError(encoder func(context.Context, ht
 
 var DefaultErrorResponseEncoderCode = `// EncodeMethodDefaultErrorResponseError returns an encoder for errors returned
 // by the MethodDefaultErrorResponse ServiceDefaultErrorResponse endpoint.
-func EncodeMethodDefaultErrorResponseError(encoder func(context.Context, http.ResponseWriter) goahttp.Encoder, formatter func(err error) goahttp.Statuser) func(context.Context, http.ResponseWriter, error) error {
+func EncodeMethodDefaultErrorResponseError(encoder func(context.Context, http.ResponseWriter) goahttp.Encoder, formatter func(ctx context.Context, err error) goahttp.Statuser) func(context.Context, http.ResponseWriter, error) error {
 	encodeError := goahttp.ErrorEncoder(encoder, formatter)
 	return func(ctx context.Context, w http.ResponseWriter, v error) error {
 		var en ErrorNamer
@@ -129,7 +129,7 @@ func EncodeMethodDefaultErrorResponseError(encoder func(context.Context, http.Re
 			enc := encoder(ctx, w)
 			var body interface{}
 			if formatter != nil {
-				body = formatter(res)
+				body = formatter(ctx, res)
 			} else {
 				body = NewMethodDefaultErrorResponseBadRequestResponseBody(res)
 			}
@@ -145,7 +145,7 @@ func EncodeMethodDefaultErrorResponseError(encoder func(context.Context, http.Re
 
 var DefaultErrorResponseWithContentTypeEncoderCode = `// EncodeMethodDefaultErrorResponseError returns an encoder for errors returned
 // by the MethodDefaultErrorResponse ServiceDefaultErrorResponse endpoint.
-func EncodeMethodDefaultErrorResponseError(encoder func(context.Context, http.ResponseWriter) goahttp.Encoder, formatter func(err error) goahttp.Statuser) func(context.Context, http.ResponseWriter, error) error {
+func EncodeMethodDefaultErrorResponseError(encoder func(context.Context, http.ResponseWriter) goahttp.Encoder, formatter func(ctx context.Context, err error) goahttp.Statuser) func(context.Context, http.ResponseWriter, error) error {
 	encodeError := goahttp.ErrorEncoder(encoder, formatter)
 	return func(ctx context.Context, w http.ResponseWriter, v error) error {
 		var en ErrorNamer
@@ -160,7 +160,7 @@ func EncodeMethodDefaultErrorResponseError(encoder func(context.Context, http.Re
 			enc := encoder(ctx, w)
 			var body interface{}
 			if formatter != nil {
-				body = formatter(res)
+				body = formatter(ctx, res)
 			} else {
 				body = NewMethodDefaultErrorResponseBadRequestResponseBody(res)
 			}
@@ -176,7 +176,7 @@ func EncodeMethodDefaultErrorResponseError(encoder func(context.Context, http.Re
 
 var ServiceErrorResponseEncoderCode = `// EncodeMethodServiceErrorResponseError returns an encoder for errors returned
 // by the MethodServiceErrorResponse ServiceServiceErrorResponse endpoint.
-func EncodeMethodServiceErrorResponseError(encoder func(context.Context, http.ResponseWriter) goahttp.Encoder, formatter func(err error) goahttp.Statuser) func(context.Context, http.ResponseWriter, error) error {
+func EncodeMethodServiceErrorResponseError(encoder func(context.Context, http.ResponseWriter) goahttp.Encoder, formatter func(ctx context.Context, err error) goahttp.Statuser) func(context.Context, http.ResponseWriter, error) error {
 	encodeError := goahttp.ErrorEncoder(encoder, formatter)
 	return func(ctx context.Context, w http.ResponseWriter, v error) error {
 		var en ErrorNamer
@@ -190,7 +190,7 @@ func EncodeMethodServiceErrorResponseError(encoder func(context.Context, http.Re
 			enc := encoder(ctx, w)
 			var body interface{}
 			if formatter != nil {
-				body = formatter(res)
+				body = formatter(ctx, res)
 			} else {
 				body = NewMethodServiceErrorResponseInternalErrorResponseBody(res)
 			}
@@ -203,7 +203,7 @@ func EncodeMethodServiceErrorResponseError(encoder func(context.Context, http.Re
 			enc := encoder(ctx, w)
 			var body interface{}
 			if formatter != nil {
-				body = formatter(res)
+				body = formatter(ctx, res)
 			} else {
 				body = NewMethodServiceErrorResponseBadRequestResponseBody(res)
 			}
@@ -219,7 +219,7 @@ func EncodeMethodServiceErrorResponseError(encoder func(context.Context, http.Re
 
 var ServiceErrorResponseWithContentTypeEncoderCode = `// EncodeMethodServiceErrorResponseError returns an encoder for errors returned
 // by the MethodServiceErrorResponse ServiceServiceErrorResponse endpoint.
-func EncodeMethodServiceErrorResponseError(encoder func(context.Context, http.ResponseWriter) goahttp.Encoder, formatter func(err error) goahttp.Statuser) func(context.Context, http.ResponseWriter, error) error {
+func EncodeMethodServiceErrorResponseError(encoder func(context.Context, http.ResponseWriter) goahttp.Encoder, formatter func(ctx context.Context, err error) goahttp.Statuser) func(context.Context, http.ResponseWriter, error) error {
 	encodeError := goahttp.ErrorEncoder(encoder, formatter)
 	return func(ctx context.Context, w http.ResponseWriter, v error) error {
 		var en ErrorNamer
@@ -233,7 +233,7 @@ func EncodeMethodServiceErrorResponseError(encoder func(context.Context, http.Re
 			enc := encoder(ctx, w)
 			var body interface{}
 			if formatter != nil {
-				body = formatter(res)
+				body = formatter(ctx, res)
 			} else {
 				body = NewMethodServiceErrorResponseInternalErrorResponseBody(res)
 			}
@@ -247,7 +247,7 @@ func EncodeMethodServiceErrorResponseError(encoder func(context.Context, http.Re
 			enc := encoder(ctx, w)
 			var body interface{}
 			if formatter != nil {
-				body = formatter(res)
+				body = formatter(ctx, res)
 			} else {
 				body = NewMethodServiceErrorResponseBadRequestResponseBody(res)
 			}
@@ -263,7 +263,7 @@ func EncodeMethodServiceErrorResponseError(encoder func(context.Context, http.Re
 
 var NoBodyErrorResponseEncoderCode = `// EncodeMethodServiceErrorResponseError returns an encoder for errors returned
 // by the MethodServiceErrorResponse ServiceNoBodyErrorResponse endpoint.
-func EncodeMethodServiceErrorResponseError(encoder func(context.Context, http.ResponseWriter) goahttp.Encoder, formatter func(err error) goahttp.Statuser) func(context.Context, http.ResponseWriter, error) error {
+func EncodeMethodServiceErrorResponseError(encoder func(context.Context, http.ResponseWriter) goahttp.Encoder, formatter func(ctx context.Context, err error) goahttp.Statuser) func(context.Context, http.ResponseWriter, error) error {
 	encodeError := goahttp.ErrorEncoder(encoder, formatter)
 	return func(ctx context.Context, w http.ResponseWriter, v error) error {
 		var en ErrorNamer
@@ -289,7 +289,7 @@ func EncodeMethodServiceErrorResponseError(encoder func(context.Context, http.Re
 
 var NoBodyErrorResponseWithContentTypeEncoderCode = `// EncodeMethodServiceErrorResponseError returns an encoder for errors returned
 // by the MethodServiceErrorResponse ServiceNoBodyErrorResponse endpoint.
-func EncodeMethodServiceErrorResponseError(encoder func(context.Context, http.ResponseWriter) goahttp.Encoder, formatter func(err error) goahttp.Statuser) func(context.Context, http.ResponseWriter, error) error {
+func EncodeMethodServiceErrorResponseError(encoder func(context.Context, http.ResponseWriter) goahttp.Encoder, formatter func(ctx context.Context, err error) goahttp.Statuser) func(context.Context, http.ResponseWriter, error) error {
 	encodeError := goahttp.ErrorEncoder(encoder, formatter)
 	return func(ctx context.Context, w http.ResponseWriter, v error) error {
 		var en ErrorNamer
@@ -317,7 +317,7 @@ func EncodeMethodServiceErrorResponseError(encoder func(context.Context, http.Re
 var EmptyErrorResponseBodyEncoderCode = `// EncodeMethodEmptyErrorResponseBodyError returns an encoder for errors
 // returned by the MethodEmptyErrorResponseBody ServiceEmptyErrorResponseBody
 // endpoint.
-func EncodeMethodEmptyErrorResponseBodyError(encoder func(context.Context, http.ResponseWriter) goahttp.Encoder, formatter func(err error) goahttp.Statuser) func(context.Context, http.ResponseWriter, error) error {
+func EncodeMethodEmptyErrorResponseBodyError(encoder func(context.Context, http.ResponseWriter) goahttp.Encoder, formatter func(ctx context.Context, err error) goahttp.Statuser) func(context.Context, http.ResponseWriter, error) error {
 	encodeError := goahttp.ErrorEncoder(encoder, formatter)
 	return func(ctx context.Context, w http.ResponseWriter, v error) error {
 		var en ErrorNamer
@@ -370,7 +370,7 @@ func EncodeMethodEmptyErrorResponseBodyError(encoder func(context.Context, http.
 var EmptyCustomErrorResponseBodyEncoderCode = `// EncodeMethodEmptyCustomErrorResponseBodyError returns an encoder for errors
 // returned by the MethodEmptyCustomErrorResponseBody
 // ServiceEmptyCustomErrorResponseBody endpoint.
-func EncodeMethodEmptyCustomErrorResponseBodyError(encoder func(context.Context, http.ResponseWriter) goahttp.Encoder, formatter func(err error) goahttp.Statuser) func(context.Context, http.ResponseWriter, error) error {
+func EncodeMethodEmptyCustomErrorResponseBodyError(encoder func(context.Context, http.ResponseWriter) goahttp.Encoder, formatter func(ctx context.Context, err error) goahttp.Statuser) func(context.Context, http.ResponseWriter, error) error {
 	encodeError := goahttp.ErrorEncoder(encoder, formatter)
 	return func(ctx context.Context, w http.ResponseWriter, v error) error {
 		var en ErrorNamer
diff --git a/http/codegen/testdata/handler_init_functions.go b/http/codegen/testdata/handler_init_functions.go
index 01027aa8..28bb26a3 100644
--- a/http/codegen/testdata/handler_init_functions.go
+++ b/http/codegen/testdata/handler_init_functions.go
@@ -9,7 +9,7 @@ func NewMethodNoPayloadNoResultHandler(
 	decoder func(*http.Request) goahttp.Decoder,
 	encoder func(context.Context, http.ResponseWriter) goahttp.Encoder,
 	errhandler func(context.Context, http.ResponseWriter, error),
-	formatter func(err error) goahttp.Statuser,
+	formatter func(ctx context.Context, err error) goahttp.Statuser,
 ) http.Handler {
 	var (
 		encodeResponse = EncodeMethodNoPayloadNoResultResponse(encoder)
@@ -43,7 +43,7 @@ func NewMethodNoPayloadNoResultHandler(
 	decoder func(*http.Request) goahttp.Decoder,
 	encoder func(context.Context, http.ResponseWriter) goahttp.Encoder,
 	errhandler func(context.Context, http.ResponseWriter, error),
-	formatter func(err error) goahttp.Statuser,
+	formatter func(ctx context.Context, err error) goahttp.Statuser,
 ) http.Handler {
 	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
 		ctx := context.WithValue(r.Context(), goahttp.AcceptTypeKey, r.Header.Get("Accept"))
@@ -63,7 +63,7 @@ func NewMethodPayloadNoResultHandler(
 	decoder func(*http.Request) goahttp.Decoder,
 	encoder func(context.Context, http.ResponseWriter) goahttp.Encoder,
 	errhandler func(context.Context, http.ResponseWriter, error),
-	formatter func(err error) goahttp.Statuser,
+	formatter func(ctx context.Context, err error) goahttp.Statuser,
 ) http.Handler {
 	var (
 		decodeRequest  = DecodeMethodPayloadNoResultRequest(mux, decoder)
@@ -104,7 +104,7 @@ func NewMethodPayloadNoResultHandler(
 	decoder func(*http.Request) goahttp.Decoder,
 	encoder func(context.Context, http.ResponseWriter) goahttp.Encoder,
 	errhandler func(context.Context, http.ResponseWriter, error),
-	formatter func(err error) goahttp.Statuser,
+	formatter func(ctx context.Context, err error) goahttp.Statuser,
 ) http.Handler {
 	var (
 		decodeRequest = DecodeMethodPayloadNoResultRequest(mux, decoder)
@@ -135,7 +135,7 @@ func NewMethodNoPayloadResultHandler(
 	decoder func(*http.Request) goahttp.Decoder,
 	encoder func(context.Context, http.ResponseWriter) goahttp.Encoder,
 	errhandler func(context.Context, http.ResponseWriter, error),
-	formatter func(err error) goahttp.Statuser,
+	formatter func(ctx context.Context, err error) goahttp.Statuser,
 ) http.Handler {
 	var (
 		encodeResponse = EncodeMethodNoPayloadResultResponse(encoder)
@@ -169,7 +169,7 @@ func NewMethodPayloadResultHandler(
 	decoder func(*http.Request) goahttp.Decoder,
 	encoder func(context.Context, http.ResponseWriter) goahttp.Encoder,
 	errhandler func(context.Context, http.ResponseWriter, error),
-	formatter func(err error) goahttp.Statuser,
+	formatter func(ctx context.Context, err error) goahttp.Statuser,
 ) http.Handler {
 	var (
 		decodeRequest  = DecodeMethodPayloadResultRequest(mux, decoder)
@@ -210,7 +210,7 @@ func NewMethodPayloadResultErrorHandler(
 	decoder func(*http.Request) goahttp.Decoder,
 	encoder func(context.Context, http.ResponseWriter) goahttp.Encoder,
 	errhandler func(context.Context, http.ResponseWriter, error),
-	formatter func(err error) goahttp.Statuser,
+	formatter func(ctx context.Context, err error) goahttp.Statuser,
 ) http.Handler {
 	var (
 		decodeRequest  = DecodeMethodPayloadResultErrorRequest(mux, decoder)
diff --git a/http/codegen/testdata/server_init_functions.go b/http/codegen/testdata/server_init_functions.go
index 532054e3..c2a66304 100644
--- a/http/codegen/testdata/server_init_functions.go
+++ b/http/codegen/testdata/server_init_functions.go
@@ -12,7 +12,7 @@ func New(
 	decoder func(*http.Request) goahttp.Decoder,
 	encoder func(context.Context, http.ResponseWriter) goahttp.Encoder,
 	errhandler func(context.Context, http.ResponseWriter, error),
-	formatter func(err error) goahttp.Statuser,
+	formatter func(ctx context.Context, err error) goahttp.Statuser,
 ) *Server {
 	return &Server{
 		Mounts: []*MountPoint{
@@ -37,7 +37,7 @@ func New(
 	decoder func(*http.Request) goahttp.Decoder,
 	encoder func(context.Context, http.ResponseWriter) goahttp.Encoder,
 	errhandler func(context.Context, http.ResponseWriter, error),
-	formatter func(err error) goahttp.Statuser,
+	formatter func(ctx context.Context, err error) goahttp.Statuser,
 ) *Server {
 	return &Server{
 		Mounts: []*MountPoint{
@@ -61,7 +61,7 @@ func New(
 	decoder func(*http.Request) goahttp.Decoder,
 	encoder func(context.Context, http.ResponseWriter) goahttp.Encoder,
 	errhandler func(context.Context, http.ResponseWriter, error),
-	formatter func(err error) goahttp.Statuser,
+	formatter func(ctx context.Context, err error) goahttp.Statuser,
 	fileSystemPathToFile1JSON http.FileSystem,
 	fileSystemPathToFile2JSON http.FileSystem,
 	fileSystemPathToFile3JSON http.FileSystem,
@@ -100,7 +100,7 @@ func New(
 	decoder func(*http.Request) goahttp.Decoder,
 	encoder func(context.Context, http.ResponseWriter) goahttp.Encoder,
 	errhandler func(context.Context, http.ResponseWriter, error),
-	formatter func(err error) goahttp.Statuser,
+	formatter func(ctx context.Context, err error) goahttp.Statuser,
 	fileSystemPathToFile1JSON http.FileSystem,
 	fileSystemPathToFile2JSON http.FileSystem,
 ) *Server {
@@ -137,7 +137,7 @@ func New(
 	decoder func(*http.Request) goahttp.Decoder,
 	encoder func(context.Context, http.ResponseWriter) goahttp.Encoder,
 	errhandler func(context.Context, http.ResponseWriter, error),
-	formatter func(err error) goahttp.Statuser,
+	formatter func(ctx context.Context, err error) goahttp.Statuser,
 	serviceMultipartMethodMultiBasesDecoderFn ServiceMultipartMethodMultiBasesDecoderFunc,
 ) *Server {
 	return &Server{
@@ -161,7 +161,7 @@ func New(
 	decoder func(*http.Request) goahttp.Decoder,
 	encoder func(context.Context, http.ResponseWriter) goahttp.Encoder,
 	errhandler func(context.Context, http.ResponseWriter, error),
-	formatter func(err error) goahttp.Statuser,
+	formatter func(ctx context.Context, err error) goahttp.Statuser,
 	upgrader goahttp.Upgrader,
 	configurer *ConnConfigurer,
 ) *Server {
diff --git a/http/codegen/testdata/streaming_code.go b/http/codegen/testdata/streaming_code.go
index 395f2047..786f00cd 100644
--- a/http/codegen/testdata/streaming_code.go
+++ b/http/codegen/testdata/streaming_code.go
@@ -25,7 +25,7 @@ func NewStreamingResultMethodHandler(
 	decoder func(*http.Request) goahttp.Decoder,
 	encoder func(context.Context, http.ResponseWriter) goahttp.Encoder,
 	errhandler func(context.Context, http.ResponseWriter, error),
-	formatter func(err error) goahttp.Statuser,
+	formatter func(ctx context.Context, err error) goahttp.Statuser,
 	upgrader goahttp.Upgrader,
 	configurer goahttp.ConnConfigureFunc,
 ) http.Handler {
@@ -170,7 +170,7 @@ func NewStreamingResultNoPayloadMethodHandler(
 	decoder func(*http.Request) goahttp.Decoder,
 	encoder func(context.Context, http.ResponseWriter) goahttp.Encoder,
 	errhandler func(context.Context, http.ResponseWriter, error),
-	formatter func(err error) goahttp.Statuser,
+	formatter func(ctx context.Context, err error) goahttp.Statuser,
 	upgrader goahttp.Upgrader,
 	configurer goahttp.ConnConfigureFunc,
 ) http.Handler {
@@ -910,7 +910,7 @@ func NewStreamingPayloadMethodHandler(
 	decoder func(*http.Request) goahttp.Decoder,
 	encoder func(context.Context, http.ResponseWriter) goahttp.Encoder,
 	errhandler func(context.Context, http.ResponseWriter, error),
-	formatter func(err error) goahttp.Statuser,
+	formatter func(ctx context.Context, err error) goahttp.Statuser,
 	upgrader goahttp.Upgrader,
 	configurer goahttp.ConnConfigureFunc,
 ) http.Handler {
@@ -1081,7 +1081,7 @@ func NewStreamingPayloadNoPayloadMethodHandler(
 	decoder func(*http.Request) goahttp.Decoder,
 	encoder func(context.Context, http.ResponseWriter) goahttp.Encoder,
 	errhandler func(context.Context, http.ResponseWriter, error),
-	formatter func(err error) goahttp.Statuser,
+	formatter func(ctx context.Context, err error) goahttp.Statuser,
 	upgrader goahttp.Upgrader,
 	configurer goahttp.ConnConfigureFunc,
 ) http.Handler {
@@ -2060,7 +2060,7 @@ func NewBidirectionalStreamingMethodHandler(
 	decoder func(*http.Request) goahttp.Decoder,
 	encoder func(context.Context, http.ResponseWriter) goahttp.Encoder,
 	errhandler func(context.Context, http.ResponseWriter, error),
-	formatter func(err error) goahttp.Statuser,
+	formatter func(ctx context.Context, err error) goahttp.Statuser,
 	upgrader goahttp.Upgrader,
 	configurer goahttp.ConnConfigureFunc,
 ) http.Handler {
@@ -2272,7 +2272,7 @@ func NewBidirectionalStreamingNoPayloadMethodHandler(
 	decoder func(*http.Request) goahttp.Decoder,
 	encoder func(context.Context, http.ResponseWriter) goahttp.Encoder,
 	errhandler func(context.Context, http.ResponseWriter, error),
-	formatter func(err error) goahttp.Statuser,
+	formatter func(ctx context.Context, err error) goahttp.Statuser,
 	upgrader goahttp.Upgrader,
 	configurer goahttp.ConnConfigureFunc,
 ) http.Handler {