Repository: goadesign/goa (go)

Issue:
Goa should find its own errors if wrapper when encoding errors
I have a layer of libraries and I might use the MakeXXX error helpers to return errors that then get 'wrapped' using fmt.Errorf(".... :%w", err) to salt the message

The generated EncodeError functions don't support wrapping though.
ie:
```go
func EncodeTestError(encoder func(context.Context, http.ResponseWriter) goahttp.Encoder, formatter func(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 {
		en, ok := v.(ErrorNamer)
		if !ok {
			return encodeError(ctx, w, v)
		}
		switch en.ErrorName() {
```

Instead of [which would find the nested goa error] (and I believe this is correct but haven't patched/tested it)
```go
var en *ErrorNamer
if !errors.As(err, &en) {
	return encodeError(ctx, w, v)
}
switch en.ErrorName()
```

Pull request:
Unwrap errors when encoding responses.
This commit makes it possible for service methods to return
wrapped errors that implement the ErrorNamer interface (such
as the errors generated by Goa). The generated code now
unwraps the errors returned by the service methods to
render the response defined in the design.

Fix #3000 

Patch:
diff --git a/grpc/codegen/server.go b/grpc/codegen/server.go
index d651a1f0..9e82d514 100644
--- a/grpc/codegen/server.go
+++ b/grpc/codegen/server.go
@@ -40,6 +40,7 @@ func serverFile(genpkg string, svc *expr.GRPCServiceExpr) *codegen.File {
 		sections = []*codegen.SectionTemplate{
 			codegen.Header(svc.Name()+" gRPC server", "server", []*codegen.ImportSpec{
 				{Path: "context"},
+				{Path: "errors"},
 				codegen.GoaImport(""),
 				codegen.GoaNamedImport("grpc", "goagrpc"),
 				{Path: "google.golang.org/grpc/codes"},
@@ -263,7 +264,8 @@ func (s *{{ .ServerStruct }}) {{ .Method.VarName }}(
 {{- define "handle_error" }}
 	if err != nil {
 	{{- if .Errors }}
-		if en, ok := err.(ErrorNamer); ok {
+		var en ErrorNamer
+		if errors.As(err, &en) {
 			switch en.ErrorName() {
 		{{- range .Errors }}
 			case {{ printf "%q" .Name }}:
diff --git a/http/codegen/server.go b/http/codegen/server.go
index f6a6d17d..7977a3d6 100644
--- a/http/codegen/server.go
+++ b/http/codegen/server.go
@@ -99,6 +99,7 @@ func serverEncodeDecodeFile(genpkg string, svc *expr.HTTPServiceExpr) *codegen.F
 	sections := []*codegen.SectionTemplate{
 		codegen.Header(title, "server", []*codegen.ImportSpec{
 			{Path: "context"},
+			{Path: "errors"},
 			{Path: "fmt"},
 			{Path: "io"},
 			{Path: "net/http"},
@@ -1202,8 +1203,8 @@ const errorEncoderT = `{{ printf "%s returns an encoder for errors returned by t
 func {{ .ErrorEncoder }}(encoder func(context.Context, http.ResponseWriter) goahttp.Encoder, formatter func(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 {
-		en, ok := v.(ErrorNamer)
-		if !ok {
+		var en ErrorNamer
+		if !errors.As(v, &en) {
 			return encodeError(ctx, w, v)
 		}
 		switch en.ErrorName() {


Test patch:
diff --git a/grpc/codegen/testdata/server_interface_code.go b/grpc/codegen/testdata/server_interface_code.go
index 566f8595..ead53a70 100644
--- a/grpc/codegen/testdata/server_interface_code.go
+++ b/grpc/codegen/testdata/server_interface_code.go
@@ -58,7 +58,8 @@ func (s *Server) MethodUnaryRPCWithErrors(ctx context.Context, message *service_
 	ctx = context.WithValue(ctx, goa.ServiceKey, "ServiceUnaryRPCWithErrors")
 	resp, err := s.MethodUnaryRPCWithErrorsH.Handle(ctx, message)
 	if err != nil {
-		if en, ok := err.(ErrorNamer); ok {
+		var en ErrorNamer
+		if errors.As(err, &en) {
 			switch en.ErrorName() {
 			case "timeout":
 				return nil, goagrpc.NewStatusError(codes.Canceled, err, goagrpc.NewErrorResponse(err))
@@ -88,7 +89,8 @@ func (s *Server) MethodUnaryRPCWithOverridingErrors(ctx context.Context, message
 	ctx = context.WithValue(ctx, goa.ServiceKey, "ServiceUnaryRPCWithOverridingErrors")
 	resp, err := s.MethodUnaryRPCWithOverridingErrorsH.Handle(ctx, message)
 	if err != nil {
-		if en, ok := err.(ErrorNamer); ok {
+		var en ErrorNamer
+		if errors.As(err, &en) {
 			switch en.ErrorName() {
 			case "overridden":
 				return nil, goagrpc.NewStatusError(codes.Unknown, err, goagrpc.NewErrorResponse(err))
@@ -226,7 +228,8 @@ func (s *Server) MethodBidirectionalStreamingRPCWithErrors(stream service_bidire
 	ctx = context.WithValue(ctx, goa.ServiceKey, "ServiceBidirectionalStreamingRPCWithErrors")
 	_, err := s.MethodBidirectionalStreamingRPCWithErrorsH.Decode(ctx, nil)
 	if err != nil {
-		if en, ok := err.(ErrorNamer); ok {
+		var en ErrorNamer
+		if errors.As(err, &en) {
 			switch en.ErrorName() {
 			case "timeout":
 				return goagrpc.NewStatusError(codes.Canceled, err, goagrpc.NewErrorResponse(err))
@@ -243,7 +246,8 @@ func (s *Server) MethodBidirectionalStreamingRPCWithErrors(stream service_bidire
 	}
 	err = s.MethodBidirectionalStreamingRPCWithErrorsH.Handle(ctx, ep)
 	if err != nil {
-		if en, ok := err.(ErrorNamer); ok {
+		var en ErrorNamer
+		if errors.As(err, &en) {
 			switch en.ErrorName() {
 			case "timeout":
 				return goagrpc.NewStatusError(codes.Canceled, err, goagrpc.NewErrorResponse(err))
diff --git a/http/codegen/testdata/error_encoder_code.go b/http/codegen/testdata/error_encoder_code.go
index 22901630..7ae42525 100644
--- a/http/codegen/testdata/error_encoder_code.go
+++ b/http/codegen/testdata/error_encoder_code.go
@@ -6,8 +6,8 @@ var PrimitiveErrorResponseEncoderCode = `// EncodeMethodPrimitiveErrorResponseEr
 func EncodeMethodPrimitiveErrorResponseError(encoder func(context.Context, http.ResponseWriter) goahttp.Encoder, formatter func(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 {
-		en, ok := v.(ErrorNamer)
-		if !ok {
+		var en ErrorNamer
+		if !errors.As(v, &en) {
 			return encodeError(ctx, w, v)
 		}
 		switch en.ErrorName() {
@@ -48,8 +48,8 @@ var PrimitiveErrorInResponseHeaderEncoderCode = `// EncodeMethodPrimitiveErrorIn
 func EncodeMethodPrimitiveErrorInResponseHeaderError(encoder func(context.Context, http.ResponseWriter) goahttp.Encoder, formatter func(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 {
-		en, ok := v.(ErrorNamer)
-		if !ok {
+		var en ErrorNamer
+		if !errors.As(v, &en) {
 			return encodeError(ctx, w, v)
 		}
 		switch en.ErrorName() {
@@ -86,8 +86,8 @@ var APIPrimitiveErrorResponseEncoderCode = `// EncodeMethodAPIPrimitiveErrorResp
 func EncodeMethodAPIPrimitiveErrorResponseError(encoder func(context.Context, http.ResponseWriter) goahttp.Encoder, formatter func(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 {
-		en, ok := v.(ErrorNamer)
-		if !ok {
+		var en ErrorNamer
+		if !errors.As(v, &en) {
 			return encodeError(ctx, w, v)
 		}
 		switch en.ErrorName() {
@@ -127,8 +127,8 @@ var DefaultErrorResponseEncoderCode = `// EncodeMethodDefaultErrorResponseError
 func EncodeMethodDefaultErrorResponseError(encoder func(context.Context, http.ResponseWriter) goahttp.Encoder, formatter func(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 {
-		en, ok := v.(ErrorNamer)
-		if !ok {
+		var en ErrorNamer
+		if !errors.As(v, &en) {
 			return encodeError(ctx, w, v)
 		}
 		switch en.ErrorName() {
@@ -156,8 +156,8 @@ var DefaultErrorResponseWithContentTypeEncoderCode = `// EncodeMethodDefaultErro
 func EncodeMethodDefaultErrorResponseError(encoder func(context.Context, http.ResponseWriter) goahttp.Encoder, formatter func(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 {
-		en, ok := v.(ErrorNamer)
-		if !ok {
+		var en ErrorNamer
+		if !errors.As(v, &en) {
 			return encodeError(ctx, w, v)
 		}
 		switch en.ErrorName() {
@@ -186,8 +186,8 @@ var ServiceErrorResponseEncoderCode = `// EncodeMethodServiceErrorResponseError
 func EncodeMethodServiceErrorResponseError(encoder func(context.Context, http.ResponseWriter) goahttp.Encoder, formatter func(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 {
-		en, ok := v.(ErrorNamer)
-		if !ok {
+		var en ErrorNamer
+		if !errors.As(v, &en) {
 			return encodeError(ctx, w, v)
 		}
 		switch en.ErrorName() {
@@ -227,8 +227,8 @@ var ServiceErrorResponseWithContentTypeEncoderCode = `// EncodeMethodServiceErro
 func EncodeMethodServiceErrorResponseError(encoder func(context.Context, http.ResponseWriter) goahttp.Encoder, formatter func(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 {
-		en, ok := v.(ErrorNamer)
-		if !ok {
+		var en ErrorNamer
+		if !errors.As(v, &en) {
 			return encodeError(ctx, w, v)
 		}
 		switch en.ErrorName() {
@@ -269,8 +269,8 @@ var NoBodyErrorResponseEncoderCode = `// EncodeMethodServiceErrorResponseError r
 func EncodeMethodServiceErrorResponseError(encoder func(context.Context, http.ResponseWriter) goahttp.Encoder, formatter func(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 {
-		en, ok := v.(ErrorNamer)
-		if !ok {
+		var en ErrorNamer
+		if !errors.As(v, &en) {
 			return encodeError(ctx, w, v)
 		}
 		switch en.ErrorName() {
@@ -294,8 +294,8 @@ var NoBodyErrorResponseWithContentTypeEncoderCode = `// EncodeMethodServiceError
 func EncodeMethodServiceErrorResponseError(encoder func(context.Context, http.ResponseWriter) goahttp.Encoder, formatter func(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 {
-		en, ok := v.(ErrorNamer)
-		if !ok {
+		var en ErrorNamer
+		if !errors.As(v, &en) {
 			return encodeError(ctx, w, v)
 		}
 		switch en.ErrorName() {
@@ -321,8 +321,8 @@ var EmptyErrorResponseBodyEncoderCode = `// EncodeMethodEmptyErrorResponseBodyEr
 func EncodeMethodEmptyErrorResponseBodyError(encoder func(context.Context, http.ResponseWriter) goahttp.Encoder, formatter func(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 {
-		en, ok := v.(ErrorNamer)
-		if !ok {
+		var en ErrorNamer
+		if !errors.As(v, &en) {
 			return encodeError(ctx, w, v)
 		}
 		switch en.ErrorName() {
@@ -372,8 +372,8 @@ var EmptyCustomErrorResponseBodyEncoderCode = `// EncodeMethodEmptyCustomErrorRe
 func EncodeMethodEmptyCustomErrorResponseBodyError(encoder func(context.Context, http.ResponseWriter) goahttp.Encoder, formatter func(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 {
-		en, ok := v.(ErrorNamer)
-		if !ok {
+		var en ErrorNamer
+		if !errors.As(v, &en) {
 			return encodeError(ctx, w, v)
 		}
 		switch en.ErrorName() {