Repository: goadesign/goa
Pull request #2851 (2021-05-18 21:04:36)

Fix code gen for empty error response bodies

raphael: This is great, thank you!

Patch:
diff --git a/codegen/cli/cli.go b/codegen/cli/cli.go
index 337ad9c2..af651058 100644
--- a/codegen/cli/cli.go
+++ b/codegen/cli/cli.go
@@ -564,7 +564,7 @@ func fieldCode(init *PayloadInitData) string {
 	}
 	// We can ignore the transform helpers as there won't be any generated
 	// because the args cannot be user types.
-	c, _, err := codegen.InitStructFields(init.Args, init.ReturnTypeName, varn, "", init.ReturnTypePkg, init.Code == "")
+	c, _, err := codegen.InitStructFields(init.Args, varn, "", init.ReturnTypePkg)
 	if err != nil {
 		panic(err) //bug
 	}
diff --git a/codegen/funcs.go b/codegen/funcs.go
index 107f5210..a252b5e5 100644
--- a/codegen/funcs.go
+++ b/codegen/funcs.go
@@ -258,7 +258,7 @@ func WrapText(text string, maxChars int) string {
 
 // InitStructFields produces Go code to initialize a struct and its fields from
 // the given init arguments.
-func InitStructFields(args []*InitArgData, name, targetVar, sourcePkg, targetPkg string, mustInit bool) (string, []*TransformFunctionData, error) {
+func InitStructFields(args []*InitArgData, targetVar, sourcePkg, targetPkg string) (string, []*TransformFunctionData, error) {
 	scope := NewNameScope()
 	scope.Unique(targetVar)
 
@@ -268,8 +268,8 @@ func InitStructFields(args []*InitArgData, name, targetVar, sourcePkg, targetPkg
 	)
 	for _, arg := range args {
 		switch {
-		case arg.FieldName == "":
-			// do nothing
+		case arg.FieldName == "" && arg.FieldType == nil:
+		// do nothing
 		case expr.Equal(arg.Type, arg.FieldType):
 			// arg type and struct field type are the same. No need to call transform
 			// to initialize the field
@@ -287,8 +287,10 @@ func InitStructFields(args []*InitArgData, name, targetVar, sourcePkg, targetPkg
 			}
 			if arg.FieldPointer {
 				code += fmt.Sprintf("tmp%s := %s\n%s.%s = &tmp%s\n", arg.Name, cast, targetVar, arg.FieldName, arg.Name)
-			} else {
+			} else if arg.FieldName != "" {
 				code += fmt.Sprintf("%s.%s = %s\n", targetVar, arg.FieldName, cast)
+			} else {
+				code += fmt.Sprintf("%s := %s\n", targetVar, cast)
 			}
 		default:
 			srcctx := NewAttributeContext(arg.Pointer, false, true, sourcePkg, scope)
diff --git a/expr/http_response.go b/expr/http_response.go
index dc3bb1d3..e8c7bd6b 100644
--- a/expr/http_response.go
+++ b/expr/http_response.go
@@ -256,7 +256,7 @@ func (r *HTTPResponseExpr) Validate(e *HTTPEndpointExpr) *eval.ValidationErrors
 func (r *HTTPResponseExpr) Finalize(a *HTTPEndpointExpr, svcAtt *AttributeExpr) {
 	r.Parent = a
 
-	if r.Body != nil {
+	if r.Body != nil && r.Body.Type != Empty {
 		bodyAtt := svcAtt
 		if o, ok := r.Body.Meta["origin:attribute"]; ok {
 			bodyAtt = svcAtt.Find(o[0])
diff --git a/http/codegen/client_types.go b/http/codegen/client_types.go
index 498b04d0..2db10fe3 100644
--- a/http/codegen/client_types.go
+++ b/http/codegen/client_types.go
@@ -243,8 +243,8 @@ func {{ .Name }}({{- range .ClientArgs }}{{ .VarName }} {{ .TypeRef }}, {{ end }
 	{{- if not .ClientCode }}
 	{{ if .ReturnTypeAttribute }}res{{ else }}v{{ end }} := &{{ .ReturnTypeName }}{}
 	{{- end }}
-	{{ fieldCode . "client" }}
 {{- end }}
+	{{ fieldCode . "client" }}
 	return {{ if .ReturnTypeAttribute }}res{{ else }}v{{ end }}
 }
 `
diff --git a/http/codegen/server.go b/http/codegen/server.go
index 19768497..c5d2a65d 100644
--- a/http/codegen/server.go
+++ b/http/codegen/server.go
@@ -1251,7 +1251,7 @@ const responseT = `{{ define "response" -}}
 	if res.{{ if $.ViewedResult }}Projected.{{ end }}{{ .FieldName }} != nil {
 		{{- end }}
 
-		{{- if eq .Type.Name "string" }}
+		{{- if and (eq .Type.Name "string") (not (isAliased .FieldType)) }}
 	w.Header().Set("{{ .CanonicalName }}", {{ if or .FieldPointer $.ViewedResult }}*{{ end }}res{{ if $.ViewedResult }}.Projected{{ end }}{{ if .FieldName }}.{{ .FieldName }}{{ end }})
 		{{- else }}
 			{{- if isAliased .FieldType }}
@@ -1324,7 +1324,7 @@ const responseT = `{{ define "response" -}}
 	{{- end }}
 
 	{{- if .ErrorHeader }}
-	w.Header().Set("goa-error", {{ printf "%q" .ErrorHeader }})
+	w.Header().Set("goa-error", res.ErrorName())
 	{{- end }}
 	w.WriteHeader({{ .StatusCode }})
 {{- end }}
diff --git a/http/codegen/server_types.go b/http/codegen/server_types.go
index 7724ddd2..054c7dc9 100644
--- a/http/codegen/server_types.go
+++ b/http/codegen/server_types.go
@@ -214,10 +214,6 @@ func fieldCode(init *InitData, typ string) string {
 	if typ == "client" {
 		args = init.ClientArgs
 	}
-	mustInit := false
-	if (typ == "server" && init.ServerCode == "") || (typ == "client" && init.ClientCode == "") {
-		mustInit = true
-	}
 	initArgs := make([]*codegen.InitArgData, len(args))
 	for i, arg := range args {
 		initArgs[i] = &codegen.InitArgData{
@@ -231,7 +227,7 @@ func fieldCode(init *InitData, typ string) string {
 	}
 	// We can ignore the transform helpers as there won't be any generated
 	// because the headers and params cannot be user types.
-	c, _, err := codegen.InitStructFields(initArgs, init.ReturnTypeName, varn, "", init.ReturnTypePkg, mustInit)
+	c, _, err := codegen.InitStructFields(initArgs, varn, "", init.ReturnTypePkg)
 	if err != nil {
 		panic(err) //bug
 	}


Test patch:
diff --git a/http/codegen/client_body_types_test.go b/http/codegen/client_body_types_test.go
index 0289c6f8..b516e3fb 100644
--- a/http/codegen/client_body_types_test.go
+++ b/http/codegen/client_body_types_test.go
@@ -77,6 +77,7 @@ func TestClientTypes(t *testing.T) {
 		{"payload-extend-validate", testdata.PayloadExtendedValidateDSL, PayloadExtendedValidateClientTypesFile},
 		{"result-type-validate", testdata.ResultTypeValidateDSL, ResultTypeValidateClientTypesFile},
 		{"with-result-collection", testdata.ResultWithResultCollectionDSL, WithResultCollectionClientTypesFile},
+		{"empty-error-response-body", testdata.EmptyErrorResponseBodyDSL, EmptyErrorResponseBodyClientTypesFile},
 	}
 	for _, c := range cases {
 		t.Run(c.Name, func(t *testing.T) {
@@ -705,3 +706,28 @@ func ValidateRtResponseBody(body *RtResponseBody) (err error) {
 	return
 }
 `
+
+const EmptyErrorResponseBodyClientTypesFile = `// NewMethodEmptyErrorResponseBodyInternalError builds a
+// ServiceEmptyErrorResponseBody service MethodEmptyErrorResponseBody endpoint
+// internal_error error.
+func NewMethodEmptyErrorResponseBodyInternalError(name string, id string, message string, fault bool, temporary bool, timeout bool) *goa.ServiceError {
+	v := &goa.ServiceError{}
+	v.Name = name
+	v.ID = id
+	v.Message = message
+	v.Fault = fault
+	v.Temporary = temporary
+	v.Timeout = timeout
+
+	return v
+}
+
+// NewMethodEmptyErrorResponseBodyNotFound builds a
+// ServiceEmptyErrorResponseBody service MethodEmptyErrorResponseBody endpoint
+// not_found error.
+func NewMethodEmptyErrorResponseBodyNotFound(inHeader string) serviceemptyerrorresponsebody.NotFound {
+	v := serviceemptyerrorresponsebody.NotFound(inHeader)
+
+	return v
+}
+`
diff --git a/http/codegen/client_decode_test.go b/http/codegen/client_decode_test.go
index 9c663045..af565db0 100644
--- a/http/codegen/client_decode_test.go
+++ b/http/codegen/client_decode_test.go
@@ -30,6 +30,7 @@ func TestClientDecode(t *testing.T) {
 		{"with-headers-dsl", testdata.WithHeadersBlockDSL, testdata.WithHeadersBlockResponseDecodeCode},
 		{"with-headers-dsl-viewed-result", testdata.WithHeadersBlockViewedResultDSL, testdata.WithHeadersBlockViewedResultResponseDecodeCode},
 		{"validate-error-response-type", testdata.ValidateErrorResponseTypeDSL, testdata.ValidateErrorResponseTypeDecodeCode},
+		{"empty-error-response-body", testdata.EmptyErrorResponseBodyDSL, testdata.EmptyErrorResponseBodyDecodeCode},
 	}
 	for _, c := range cases {
 		t.Run(c.Name, func(t *testing.T) {
diff --git a/http/codegen/server_error_encoder_test.go b/http/codegen/server_error_encoder_test.go
index bf070e09..413b5378 100644
--- a/http/codegen/server_error_encoder_test.go
+++ b/http/codegen/server_error_encoder_test.go
@@ -15,6 +15,7 @@ func TestEncodeError(t *testing.T) {
 		Code string
 	}{
 		{"primitive-error-response", testdata.PrimitiveErrorResponseDSL, testdata.PrimitiveErrorResponseEncoderCode},
+		{"primitive-error-response", testdata.PrimitiveErrorInResponseHeaderDSL, testdata.PrimitiveErrorInResponseHeaderEncoderCode},
 		{"default-error-response", testdata.DefaultErrorResponseDSL, testdata.DefaultErrorResponseEncoderCode},
 		{"default-error-response-with-content-type", testdata.DefaultErrorResponseWithContentTypeDSL, testdata.DefaultErrorResponseWithContentTypeEncoderCode},
 		{"service-error-response", testdata.ServiceErrorResponseDSL, testdata.ServiceErrorResponseEncoderCode},
@@ -24,6 +25,7 @@ func TestEncodeError(t *testing.T) {
 		{"no-body-error-response-with-content-type", testdata.NoBodyErrorResponseWithContentTypeDSL, testdata.NoBodyErrorResponseWithContentTypeEncoderCode},
 		{"api-no-body-error-response", testdata.APINoBodyErrorResponseDSL, testdata.NoBodyErrorResponseEncoderCode},
 		{"api-no-body-error-response-with-content-type", testdata.APINoBodyErrorResponseWithContentTypeDSL, testdata.NoBodyErrorResponseWithContentTypeEncoderCode},
+		{"empty-error-response-body", testdata.EmptyErrorResponseBodyDSL, testdata.EmptyErrorResponseBodyEncoderCode},
 	}
 	for _, c := range cases {
 		t.Run(c.Name, func(t *testing.T) {
diff --git a/http/codegen/server_types_test.go b/http/codegen/server_types_test.go
index c78fe7e9..3e56d225 100644
--- a/http/codegen/server_types_test.go
+++ b/http/codegen/server_types_test.go
@@ -22,6 +22,7 @@ func TestServerTypes(t *testing.T) {
 		{"result-type-validate", testdata.ResultTypeValidateDSL, ResultTypeValidateServerTypesFile},
 		{"with-result-collection", testdata.ResultWithResultCollectionDSL, ResultWithResultCollectionServerTypesFile},
 		{"with-result-view", testdata.ResultWithResultViewDSL, ResultWithResultViewServerTypesFile},
+		{"empty-error-response-body", testdata.EmptyErrorResponseBodyDSL, ""},
 	}
 	for _, c := range cases {
 		t.Run(c.Name, func(t *testing.T) {
diff --git a/http/codegen/testdata/error_encoder_code.go b/http/codegen/testdata/error_encoder_code.go
index d11d2484..1fcde8a6 100644
--- a/http/codegen/testdata/error_encoder_code.go
+++ b/http/codegen/testdata/error_encoder_code.go
@@ -20,7 +20,7 @@ func EncodeMethodPrimitiveErrorResponseError(encoder func(context.Context, http.
 			} else {
 				body = NewMethodPrimitiveErrorResponseBadRequestResponseBody(res)
 			}
-			w.Header().Set("goa-error", "bad_request")
+			w.Header().Set("goa-error", res.ErrorName())
 			w.WriteHeader(http.StatusBadRequest)
 			return enc.Encode(body)
 		case "internal_error":
@@ -32,7 +32,7 @@ func EncodeMethodPrimitiveErrorResponseError(encoder func(context.Context, http.
 			} else {
 				body = NewMethodPrimitiveErrorResponseInternalErrorResponseBody(res)
 			}
-			w.Header().Set("goa-error", "internal_error")
+			w.Header().Set("goa-error", res.ErrorName())
 			w.WriteHeader(http.StatusInternalServerError)
 			return enc.Encode(body)
 		default:
@@ -42,6 +42,40 @@ 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 {
+	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() {
+		case "bad_request":
+			res := v.(serviceprimitiveerrorinresponseheader.BadRequest)
+			val := string(res)
+			string_s := val
+			w.Header().Set("String", string_s)
+			w.Header().Set("goa-error", res.ErrorName())
+			w.WriteHeader(http.StatusBadRequest)
+			return nil
+		case "internal_error":
+			res := v.(serviceprimitiveerrorinresponseheader.InternalError)
+			val := int(res)
+			int_s := strconv.Itoa(val)
+			w.Header().Set("Int", int_s)
+			w.Header().Set("goa-error", res.ErrorName())
+			w.WriteHeader(http.StatusInternalServerError)
+			return nil
+		default:
+			return encodeError(ctx, w, v)
+		}
+	}
+}
+`
+
 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 {
@@ -61,7 +95,7 @@ func EncodeMethodDefaultErrorResponseError(encoder func(context.Context, http.Re
 			} else {
 				body = NewMethodDefaultErrorResponseBadRequestResponseBody(res)
 			}
-			w.Header().Set("goa-error", "bad_request")
+			w.Header().Set("goa-error", res.ErrorName())
 			w.WriteHeader(http.StatusBadRequest)
 			return enc.Encode(body)
 		default:
@@ -91,7 +125,7 @@ func EncodeMethodDefaultErrorResponseError(encoder func(context.Context, http.Re
 			} else {
 				body = NewMethodDefaultErrorResponseBadRequestResponseBody(res)
 			}
-			w.Header().Set("goa-error", "bad_request")
+			w.Header().Set("goa-error", res.ErrorName())
 			w.WriteHeader(http.StatusBadRequest)
 			return enc.Encode(body)
 		default:
@@ -120,7 +154,7 @@ func EncodeMethodServiceErrorResponseError(encoder func(context.Context, http.Re
 			} else {
 				body = NewMethodServiceErrorResponseInternalErrorResponseBody(res)
 			}
-			w.Header().Set("goa-error", "internal_error")
+			w.Header().Set("goa-error", res.ErrorName())
 			w.WriteHeader(http.StatusInternalServerError)
 			return enc.Encode(body)
 		case "bad_request":
@@ -132,7 +166,7 @@ func EncodeMethodServiceErrorResponseError(encoder func(context.Context, http.Re
 			} else {
 				body = NewMethodServiceErrorResponseBadRequestResponseBody(res)
 			}
-			w.Header().Set("goa-error", "bad_request")
+			w.Header().Set("goa-error", res.ErrorName())
 			w.WriteHeader(http.StatusBadRequest)
 			return enc.Encode(body)
 		default:
@@ -161,7 +195,7 @@ func EncodeMethodServiceErrorResponseError(encoder func(context.Context, http.Re
 			} else {
 				body = NewMethodServiceErrorResponseInternalErrorResponseBody(res)
 			}
-			w.Header().Set("goa-error", "internal_error")
+			w.Header().Set("goa-error", res.ErrorName())
 			w.WriteHeader(http.StatusInternalServerError)
 			return enc.Encode(body)
 		case "bad_request":
@@ -174,7 +208,7 @@ func EncodeMethodServiceErrorResponseError(encoder func(context.Context, http.Re
 			} else {
 				body = NewMethodServiceErrorResponseBadRequestResponseBody(res)
 			}
-			w.Header().Set("goa-error", "bad_request")
+			w.Header().Set("goa-error", res.ErrorName())
 			w.WriteHeader(http.StatusBadRequest)
 			return enc.Encode(body)
 		default:
@@ -199,7 +233,7 @@ func EncodeMethodServiceErrorResponseError(encoder func(context.Context, http.Re
 			if res.Header != nil {
 				w.Header().Set("Header", *res.Header)
 			}
-			w.Header().Set("goa-error", "bad_request")
+			w.Header().Set("goa-error", res.ErrorName())
 			w.WriteHeader(http.StatusBadRequest)
 			return nil
 		default:
@@ -225,7 +259,7 @@ func EncodeMethodServiceErrorResponseError(encoder func(context.Context, http.Re
 			if res.Header != nil {
 				w.Header().Set("Header", *res.Header)
 			}
-			w.Header().Set("goa-error", "bad_request")
+			w.Header().Set("goa-error", res.ErrorName())
 			w.WriteHeader(http.StatusBadRequest)
 			return nil
 		default:
@@ -234,3 +268,46 @@ 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 {
+	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() {
+		case "internal_error":
+			res := v.(*goa.ServiceError)
+			w.Header().Set("Error-Name", res.Name)
+			w.Header().Set("Error-Id", res.ID)
+			w.Header().Set("Error-Message", res.Message)
+			val := res.Fault
+			faults := strconv.FormatBool(val)
+			w.Header().Set("Error-Fault", faults)
+			val := res.Temporary
+			temporarys := strconv.FormatBool(val)
+			w.Header().Set("Error-Temporary", temporarys)
+			val := res.Timeout
+			timeouts := strconv.FormatBool(val)
+			w.Header().Set("Error-Timeout", timeouts)
+			w.Header().Set("goa-error", res.ErrorName())
+			w.WriteHeader(http.StatusInternalServerError)
+			return nil
+		case "not_found":
+			res := v.(serviceemptyerrorresponsebody.NotFound)
+			val := string(res)
+			inHeaders := val
+			w.Header().Set("In-Header", inHeaders)
+			w.Header().Set("goa-error", res.ErrorName())
+			w.WriteHeader(http.StatusNotFound)
+			return nil
+		default:
+			return encodeError(ctx, w, v)
+		}
+	}
+}
+`
diff --git a/http/codegen/testdata/error_response_dsls.go b/http/codegen/testdata/error_response_dsls.go
index 08842133..ae4d214e 100644
--- a/http/codegen/testdata/error_response_dsls.go
+++ b/http/codegen/testdata/error_response_dsls.go
@@ -44,6 +44,24 @@ var PrimitiveErrorResponseDSL = func() {
 	})
 }
 
+var PrimitiveErrorInResponseHeaderDSL = func() {
+	Service("ServicePrimitiveErrorInResponseHeader", func() {
+		Method("MethodPrimitiveErrorInResponseHeader", func() {
+			Error("bad_request", String)
+			Error("internal_error", Int)
+			HTTP(func() {
+				GET("/one/two")
+				Response("bad_request", StatusBadRequest, func() {
+					Header("string")
+				})
+				Response(StatusInternalServerError, "internal_error", func() {
+					Header("int")
+				})
+			})
+		})
+	})
+}
+
 var ServiceErrorResponseDSL = func() {
 	Service("ServiceServiceErrorResponse", func() {
 		Error("bad_request")
diff --git a/http/codegen/testdata/result_decode_functions.go b/http/codegen/testdata/result_decode_functions.go
index fecea34f..ae4034e8 100644
--- a/http/codegen/testdata/result_decode_functions.go
+++ b/http/codegen/testdata/result_decode_functions.go
@@ -825,3 +825,112 @@ func DecodeMethodAResponse(decoder func(*http.Response) goahttp.Decoder, restore
 	}
 }
 `
+
+var EmptyErrorResponseBodyDecodeCode = `// DecodeMethodEmptyErrorResponseBodyResponse returns a decoder for responses
+// returned by the ServiceEmptyErrorResponseBody MethodEmptyErrorResponseBody
+// endpoint. restoreBody controls whether the response body should be restored
+// after having been read.
+// DecodeMethodEmptyErrorResponseBodyResponse may return the following errors:
+//	- "internal_error" (type *goa.ServiceError): http.StatusInternalServerError
+//	- "not_found" (type serviceemptyerrorresponsebody.NotFound): http.StatusNotFound
+//	- error: internal error
+func DecodeMethodEmptyErrorResponseBodyResponse(decoder func(*http.Response) goahttp.Decoder, restoreBody bool) func(*http.Response) (interface{}, error) {
+	return func(resp *http.Response) (interface{}, error) {
+		if restoreBody {
+			b, err := ioutil.ReadAll(resp.Body)
+			if err != nil {
+				return nil, err
+			}
+			resp.Body = ioutil.NopCloser(bytes.NewBuffer(b))
+			defer func() {
+				resp.Body = ioutil.NopCloser(bytes.NewBuffer(b))
+			}()
+		} else {
+			defer resp.Body.Close()
+		}
+		switch resp.StatusCode {
+		case http.StatusOK:
+			return nil, nil
+		case http.StatusInternalServerError:
+			var (
+				name      string
+				id        string
+				message   string
+				fault     bool
+				temporary bool
+				timeout   bool
+				err       error
+			)
+			nameRaw := resp.Header.Get("Error-Name")
+			if nameRaw == "" {
+				err = goa.MergeErrors(err, goa.MissingFieldError("Error-Name", "header"))
+			}
+			name = nameRaw
+			idRaw := resp.Header.Get("Error-Id")
+			if idRaw == "" {
+				err = goa.MergeErrors(err, goa.MissingFieldError("Error-ID", "header"))
+			}
+			id = idRaw
+			messageRaw := resp.Header.Get("Error-Message")
+			if messageRaw == "" {
+				err = goa.MergeErrors(err, goa.MissingFieldError("Error-message", "header"))
+			}
+			message = messageRaw
+			{
+				faultRaw := resp.Header.Get("Error-Fault")
+				if faultRaw == "" {
+					return nil, goahttp.ErrValidationError("ServiceEmptyErrorResponseBody", "MethodEmptyErrorResponseBody", goa.MissingFieldError("Error-Fault", "header"))
+				}
+				v, err2 := strconv.ParseBool(faultRaw)
+				if err2 != nil {
+					err = goa.MergeErrors(err, goa.InvalidFieldTypeError("fault", faultRaw, "boolean"))
+				}
+				fault = v
+			}
+			{
+				temporaryRaw := resp.Header.Get("Error-Temporary")
+				if temporaryRaw == "" {
+					return nil, goahttp.ErrValidationError("ServiceEmptyErrorResponseBody", "MethodEmptyErrorResponseBody", goa.MissingFieldError("Error-Temporary", "header"))
+				}
+				v, err2 := strconv.ParseBool(temporaryRaw)
+				if err2 != nil {
+					err = goa.MergeErrors(err, goa.InvalidFieldTypeError("temporary", temporaryRaw, "boolean"))
+				}
+				temporary = v
+			}
+			{
+				timeoutRaw := resp.Header.Get("Error-Timeout")
+				if timeoutRaw == "" {
+					return nil, goahttp.ErrValidationError("ServiceEmptyErrorResponseBody", "MethodEmptyErrorResponseBody", goa.MissingFieldError("Error-Timeout", "header"))
+				}
+				v, err2 := strconv.ParseBool(timeoutRaw)
+				if err2 != nil {
+					err = goa.MergeErrors(err, goa.InvalidFieldTypeError("timeout", timeoutRaw, "boolean"))
+				}
+				timeout = v
+			}
+			if err != nil {
+				return nil, goahttp.ErrValidationError("ServiceEmptyErrorResponseBody", "MethodEmptyErrorResponseBody", err)
+			}
+			return nil, NewMethodEmptyErrorResponseBodyInternalError(name, id, message, fault, temporary, timeout)
+		case http.StatusNotFound:
+			var (
+				inHeader string
+				err      error
+			)
+			inHeaderRaw := resp.Header.Get("In-Header")
+			if inHeaderRaw == "" {
+				err = goa.MergeErrors(err, goa.MissingFieldError("in-header", "header"))
+			}
+			inHeader = inHeaderRaw
+			if err != nil {
+				return nil, goahttp.ErrValidationError("ServiceEmptyErrorResponseBody", "MethodEmptyErrorResponseBody", err)
+			}
+			return nil, NewMethodEmptyErrorResponseBodyNotFound(inHeader)
+		default:
+			body, _ := ioutil.ReadAll(resp.Body)
+			return nil, goahttp.ErrInvalidResponse("ServiceEmptyErrorResponseBody", "MethodEmptyErrorResponseBody", resp.StatusCode, string(body))
+		}
+	}
+}
+`
diff --git a/http/codegen/testdata/result_dsls.go b/http/codegen/testdata/result_dsls.go
index b31b2cae..36d4b264 100644
--- a/http/codegen/testdata/result_dsls.go
+++ b/http/codegen/testdata/result_dsls.go
@@ -744,6 +744,32 @@ var ResultWithResultCollectionDSL = func() {
 	})
 }
 
+var EmptyErrorResponseBodyDSL = func() {
+	Service("ServiceEmptyErrorResponseBody", func() {
+		Method("MethodEmptyErrorResponseBody", func() {
+			Error("internal_error")
+			Error("not_found", String)
+			HTTP(func() {
+				POST("/")
+				Response(StatusOK)
+				Response("internal_error", StatusInternalServerError, func() {
+					Body(Empty)
+					Header("name:Error-Name")
+					Header("id:Error-ID")
+					Header("message:Error-message")
+					Header("fault:Error-Fault")
+					Header("temporary:Error-Temporary")
+					Header("timeout:Error-Timeout")
+				})
+				Response("not_found", StatusNotFound, func() {
+					Body(Empty)
+					Header("in-header")
+				})
+			})
+		})
+	})
+}
+
 var ResultWithResultViewDSL = func() {
 	var RT = ResultType("RT", func() {
 		Attributes(func() {