Repository: goadesign/goa
Pull request #3741 (2025-07-18 19:57:58)

Codegen fixes
* Fixes user type imports which was broken after #3721 
* Adds Unwrap interface to unwrap interceptor objects which is then used in error handling
* Nil checks for `errhandler` func in HTTP since it is optional


Patch:
diff --git a/codegen/generator/service.go b/codegen/generator/service.go
index 3ecec131..35085c74 100644
--- a/codegen/generator/service.go
+++ b/codegen/generator/service.go
@@ -31,7 +31,9 @@ func Service(genpkg string, roots []eval.Root) ([]*codegen.File, error) {
 			}
 			for _, f := range files {
 				if len(f.SectionTemplates) > 0 {
-					service.AddServiceDataMetaTypeImports(f.SectionTemplates[0], s, services.Get(s.Name))
+					d := services.Get(s.Name)
+					service.AddServiceDataMetaTypeImports(f.SectionTemplates[0], s, d)
+					service.AddUserTypeImports(genpkg, f.SectionTemplates[0], d)
 				}
 			}
 			f, err := service.ConvertFile(r, s, services)
diff --git a/codegen/generator/transport.go b/codegen/generator/transport.go
index 20f10065..bb425dd4 100644
--- a/codegen/generator/transport.go
+++ b/codegen/generator/transport.go
@@ -44,7 +44,9 @@ func Transport(genpkg string, roots []eval.Root) ([]*codegen.File, error) {
 		for _, f := range files {
 			if len(f.SectionTemplates) > 0 {
 				for _, s := range r.Services {
-					service.AddServiceDataMetaTypeImports(f.SectionTemplates[0], s, services.Get(s.Name))
+					d := services.Get(s.Name)
+					service.AddServiceDataMetaTypeImports(f.SectionTemplates[0], s, d)
+					service.AddUserTypeImports(genpkg, f.SectionTemplates[0], d)
 				}
 			}
 		}
diff --git a/codegen/service/client.go b/codegen/service/client.go
index 3c2ee2e9..ee5feb38 100644
--- a/codegen/service/client.go
+++ b/codegen/service/client.go
@@ -26,7 +26,6 @@ func ClientFile(_ string, service *expr.ServiceExpr, services *ServicesData) *co
 			{Path: "io"},
 			codegen.GoaImport(""),
 		}
-		imports = append(imports, svc.UserTypeImports...)
 		header := codegen.Header(service.Name+" client", svc.PkgName, imports)
 		def := &codegen.SectionTemplate{
 			Name:   "client-struct",
diff --git a/codegen/service/endpoint.go b/codegen/service/endpoint.go
index 32864a1c..75476cce 100644
--- a/codegen/service/endpoint.go
+++ b/codegen/service/endpoint.go
@@ -80,7 +80,6 @@ func EndpointFile(genpkg string, service *expr.ServiceExpr, services *ServicesDa
 			codegen.GoaImport("security"),
 			{Path: genpkg + "/" + svcName + "/" + "views", Name: svc.ViewsPkg},
 		}
-		imports = append(imports, svc.UserTypeImports...)
 		header := codegen.Header(service.Name+" endpoints", svc.PkgName, imports)
 		def := &codegen.SectionTemplate{
 			Name:   "endpoints-struct",
diff --git a/codegen/service/interceptors.go b/codegen/service/interceptors.go
index 3602894d..e42ca128 100644
--- a/codegen/service/interceptors.go
+++ b/codegen/service/interceptors.go
@@ -78,7 +78,6 @@ func interceptorFile(svc *Data, server bool) *codegen.File {
 		},
 	}
 	if len(interceptors) > 0 {
-		codegen.AddImport(sections[0], svc.UserTypeImports...)
 		sections = append(sections, &codegen.SectionTemplate{
 			Name:   "interceptor-types",
 			Source: readTemplate("interceptors_types"),
diff --git a/codegen/service/service.go b/codegen/service/service.go
index 61114d67..af7f74f8 100644
--- a/codegen/service/service.go
+++ b/codegen/service/service.go
@@ -15,7 +15,6 @@ import (
 // type definitions.
 func Files(genpkg string, service *expr.ServiceExpr, services *ServicesData, userTypePkgs map[string][]string) []*codegen.File {
 	svc := services.Get(service.Name)
-	svc.initUserTypeImports(genpkg)
 	svcName := svc.PathName
 	svcPath := filepath.Join(codegen.Gendir, svcName, "service.go")
 	seen := make(map[string]struct{})
@@ -161,7 +160,6 @@ func Files(genpkg string, service *expr.ServiceExpr, services *ServicesData, use
 		codegen.GoaImport("security"),
 		codegen.NewImport(svc.ViewsPkg, genpkg+"/"+svcName+"/views"),
 	}
-	imports = append(imports, svc.UserTypeImports...)
 	header := codegen.Header(service.Name+" service", svc.PkgName, imports)
 	def := &codegen.SectionTemplate{
 		Name:    "service",
@@ -249,6 +247,38 @@ func AddServiceDataMetaTypeImports(header *codegen.SectionTemplate, svcExpr *exp
 	}
 }
 
+// AddUserTypeImports sets the import paths for the user types defined in the
+// service.  User types may be declared in multiple packages when defined with
+// the Meta key "struct:pkg:path".
+func AddUserTypeImports(genpkg string, header *codegen.SectionTemplate, d *Data) {
+	importsByPath := make(map[string]*codegen.ImportSpec)
+
+	initLoc := func(loc *codegen.Location) {
+		if loc == nil {
+			return
+		}
+		importsByPath[loc.FilePath] = &codegen.ImportSpec{Name: loc.PackageName(), Path: genpkg + "/" + loc.RelImportPath}
+	}
+
+	for _, m := range d.Methods {
+		initLoc(m.PayloadLoc)
+		initLoc(m.ResultLoc)
+		for _, l := range m.ErrorLocs {
+			initLoc(l)
+		}
+		for _, ut := range d.userTypes {
+			initLoc(ut.Loc)
+		}
+		for _, et := range d.errorTypes {
+			initLoc(et.Loc)
+		}
+	}
+
+	for _, imp := range importsByPath { // Order does not matter, imports are sorted during formatting.
+		codegen.AddImport(header, imp)
+	}
+}
+
 func errorName(et *UserTypeData) string {
 	obj := expr.AsObject(et.Type)
 	if obj != nil {
diff --git a/codegen/service/service_data.go b/codegen/service/service_data.go
index 11e76791..0213ab25 100644
--- a/codegen/service/service_data.go
+++ b/codegen/service/service_data.go
@@ -73,9 +73,6 @@ type (
 		Scope *codegen.NameScope
 		// ViewScope initialized with all the viewed types.
 		ViewScope *codegen.NameScope
-		// UserTypeImports lists the import specifications for the user
-		// types used by the service.
-		UserTypeImports []*codegen.ImportSpec
 		// ProtoImports lists the import specifications for the custom
 		// proto types used by the service.
 		ProtoImports []*codegen.ImportSpec
@@ -599,42 +596,6 @@ func (d *Data) Method(name string) *MethodData {
 	return nil
 }
 
-// initUserTypeImports sets the import paths for the user types defined in the
-// service.  User types may be declared in multiple packages when defined with
-// the Meta key "struct:pkg:path".
-func (d *Data) initUserTypeImports(genpkg string) {
-	importsByPath := make(map[string]*codegen.ImportSpec)
-
-	initLoc := func(loc *codegen.Location) {
-		if loc == nil {
-			return
-		}
-		importsByPath[loc.FilePath] = &codegen.ImportSpec{Name: loc.PackageName(), Path: genpkg + "/" + loc.RelImportPath}
-	}
-
-	for _, m := range d.Methods {
-		initLoc(m.PayloadLoc)
-		initLoc(m.ResultLoc)
-		for _, l := range m.ErrorLocs {
-			initLoc(l)
-		}
-		for _, ut := range d.userTypes {
-			initLoc(ut.Loc)
-		}
-		for _, et := range d.errorTypes {
-			initLoc(et.Loc)
-		}
-	}
-
-	imports := make([]*codegen.ImportSpec, len(importsByPath))
-	i := 0
-	for _, imp := range importsByPath { // Order does not matter, imports are sorted during formatting.
-		imports[i] = imp
-		i++
-	}
-	d.UserTypeImports = imports
-}
-
 // Scheme returns the scheme data with the given scheme name.
 func (r RequirementsData) Scheme(name string) *SchemeData {
 	for _, req := range r {
diff --git a/codegen/service/templates/client_interceptor_stream_wrappers.go.tpl b/codegen/service/templates/client_interceptor_stream_wrappers.go.tpl
index e1f3e2c5..74892ae7 100644
--- a/codegen/service/templates/client_interceptor_stream_wrappers.go.tpl
+++ b/codegen/service/templates/client_interceptor_stream_wrappers.go.tpl
@@ -2,6 +2,11 @@
 
 	{{- if ne .SendTypeRef "" }}
 
+{{ comment (print "Unwrap returns the underlying stream type.") }}
+func (w *wrapped{{ .Interface }}) Unwrap() interface{} {
+       return w.stream
+}
+
 {{ comment (printf "%s streams instances of \"%s\" after executing the applied interceptor." .SendName .Interface) }}
 func (w *wrapped{{ .Interface }}) {{ .SendName }}(v {{ .SendTypeRef }}) error {
 	return w.SendWithContext(w.ctx, v)
diff --git a/codegen/service/templates/server_interceptor_stream_wrapper_types.go.tpl b/codegen/service/templates/server_interceptor_stream_wrapper_types.go.tpl
index a33a45fa..f9196696 100644
--- a/codegen/service/templates/server_interceptor_stream_wrapper_types.go.tpl
+++ b/codegen/service/templates/server_interceptor_stream_wrapper_types.go.tpl
@@ -1,3 +1,9 @@
+{{- if .WrappedServerStreams }}
+type Unwrap interface {
+	Unwrap() interface{}
+}
+{{- end }}
+
 {{- range .WrappedServerStreams }}
 
 {{ comment (printf "wrapped%s is a server interceptor wrapper for the %s stream." .Interface .Interface) }}
diff --git a/codegen/service/templates/server_interceptor_stream_wrappers.go.tpl b/codegen/service/templates/server_interceptor_stream_wrappers.go.tpl
index f07af48c..2ff4ea7d 100644
--- a/codegen/service/templates/server_interceptor_stream_wrappers.go.tpl
+++ b/codegen/service/templates/server_interceptor_stream_wrappers.go.tpl
@@ -1,5 +1,10 @@
 {{- range .WrappedServerStreams }}
 
+{{ comment (print "Unwrap returns the underlying stream type.") }}
+func (w *wrapped{{ .Interface }}) Unwrap() interface{} {
+	return w.stream
+}
+
 	{{- if ne .SendTypeRef "" }}
 
 {{ comment (printf "%s streams instances of \"%s\" after executing the applied interceptor." .SendName .Interface) }}
diff --git a/grpc/codegen/client.go b/grpc/codegen/client.go
index a76fd289..4f7a8986 100644
--- a/grpc/codegen/client.go
+++ b/grpc/codegen/client.go
@@ -44,7 +44,6 @@ func clientFile(genpkg string, svc *expr.GRPCServiceExpr, services *ServicesData
 			{Path: path.Join(genpkg, svcName, "views"), Name: data.Service.ViewsPkg},
 			{Path: path.Join(genpkg, "grpc", svcName, pbPkgName), Name: data.PkgName},
 		}
-		imports = append(imports, data.Service.UserTypeImports...)
 		sections = []*codegen.SectionTemplate{
 			codegen.Header(svc.Name()+" gRPC client", "client", imports),
 		}
@@ -135,7 +134,6 @@ func clientEncodeDecode(genpkg string, svc *expr.GRPCServiceExpr, services *Serv
 			{Path: path.Join(genpkg, svcName, "views"), Name: data.Service.ViewsPkg},
 			{Path: path.Join(genpkg, "grpc", svcName, pbPkgName), Name: data.PkgName},
 		}
-		imports = append(imports, data.Service.UserTypeImports...)
 		sections = []*codegen.SectionTemplate{codegen.Header(svc.Name()+" gRPC client encoders and decoders", "client", imports)}
 		fm := transTmplFuncs(svc, services)
 		fm["metadataEncodeDecodeData"] = metadataEncodeDecodeData
diff --git a/grpc/codegen/client_cli.go b/grpc/codegen/client_cli.go
index 80ae5255..a8b47874 100644
--- a/grpc/codegen/client_cli.go
+++ b/grpc/codegen/client_cli.go
@@ -70,7 +70,6 @@ func endpointParser(genpkg string, services *ServicesData, svr *expr.ServerExpr,
 		specs = append(specs,
 			&codegen.ImportSpec{Path: path.Join(genpkg, "grpc", svcName, "client"), Name: sd.Service.PkgName + "c"},
 			&codegen.ImportSpec{Path: path.Join(genpkg, "grpc", svcName, pbPkgName), Name: svcName + pbPkgName})
-		specs = append(specs, sd.Service.UserTypeImports...)
 		// Add interceptors import if service has client interceptors
 		if len(sd.Service.ClientInterceptors) > 0 {
 			specs = append(specs, &codegen.ImportSpec{
@@ -118,7 +117,6 @@ func payloadBuilders(genpkg string, svc *expr.GRPCServiceExpr, data *cli.Command
 		{Path: path.Join(genpkg, svcName), Name: sd.Service.PkgName},
 		{Path: path.Join(genpkg, "grpc", svcName, pbPkgName), Name: sd.PkgName},
 	}
-	specs = append(specs, sd.Service.UserTypeImports...)
 	sections := []*codegen.SectionTemplate{
 		codegen.Header(title, "client", specs),
 	}
diff --git a/grpc/codegen/client_types.go b/grpc/codegen/client_types.go
index c7ed6ce4..2d46980b 100644
--- a/grpc/codegen/client_types.go
+++ b/grpc/codegen/client_types.go
@@ -75,7 +75,6 @@ func clientType(genpkg string, svc *expr.GRPCServiceExpr, services *ServicesData
 			{Path: path.Join(genpkg, svcName, "views"), Name: sd.Service.ViewsPkg},
 			{Path: path.Join(genpkg, "grpc", svcName, pbPkgName), Name: sd.PkgName},
 		}
-		imports = append(imports, sd.Service.UserTypeImports...)
 		imports = append(imports, sd.Service.ProtoImports...)
 		sections = []*codegen.SectionTemplate{codegen.Header(svc.Name()+" gRPC client types", "client", imports)}
 		for _, init := range initData {
diff --git a/grpc/codegen/server.go b/grpc/codegen/server.go
index 1bc9490b..7e72f595 100644
--- a/grpc/codegen/server.go
+++ b/grpc/codegen/server.go
@@ -46,7 +46,6 @@ func serverFile(genpkg string, svc *expr.GRPCServiceExpr, services *ServicesData
 			{Path: path.Join(genpkg, svcName, "views"), Name: data.Service.ViewsPkg},
 			{Path: path.Join(genpkg, "grpc", svcName, pbPkgName), Name: data.PkgName},
 		}
-		imports = append(imports, data.Service.UserTypeImports...)
 		sections = []*codegen.SectionTemplate{
 			codegen.Header(svc.Name()+" gRPC server", "server", imports),
 			{
@@ -142,7 +141,6 @@ func serverEncodeDecode(genpkg string, svc *expr.GRPCServiceExpr, services *Serv
 			{Path: path.Join(genpkg, svcName, "views"), Name: data.Service.ViewsPkg},
 			{Path: path.Join(genpkg, "grpc", svcName, pbPkgName), Name: data.PkgName},
 		}
-		imports = append(imports, data.Service.UserTypeImports...)
 		sections = []*codegen.SectionTemplate{codegen.Header(title, "server", imports)}
 
 		for _, e := range data.Endpoints {
diff --git a/grpc/codegen/server_types.go b/grpc/codegen/server_types.go
index 3f3b92a2..712fd94e 100644
--- a/grpc/codegen/server_types.go
+++ b/grpc/codegen/server_types.go
@@ -70,7 +70,6 @@ func serverType(genpkg string, svc *expr.GRPCServiceExpr, services *ServicesData
 			{Path: path.Join(genpkg, svcName, "views"), Name: sd.Service.ViewsPkg},
 			{Path: path.Join(genpkg, "grpc", svcName, pbPkgName), Name: sd.PkgName},
 		}
-		imports = append(imports, sd.Service.UserTypeImports...)
 		imports = append(imports, sd.Service.ProtoImports...)
 		sections = []*codegen.SectionTemplate{codegen.Header(svc.Name()+" gRPC server types", "server", imports)}
 		for _, init := range initData {
diff --git a/grpc/codegen/service_data.go b/grpc/codegen/service_data.go
index 9bc92b59..cbed58c7 100644
--- a/grpc/codegen/service_data.go
+++ b/grpc/codegen/service_data.go
@@ -370,7 +370,7 @@ type (
 // NewServicesData creates a new ServicesData instance for the given service data.
 func NewServicesData(services *service.ServicesData) *ServicesData {
 	return &ServicesData{
-		ServicesData:  services,
+		ServicesData: services,
 		GRPCServices: make(map[string]*ServiceData),
 	}
 }
diff --git a/http/codegen/client.go b/http/codegen/client.go
index e500429a..172026cd 100644
--- a/http/codegen/client.go
+++ b/http/codegen/client.go
@@ -124,7 +124,6 @@ func clientEncodeDecodeFile(genpkg string, svc *expr.HTTPServiceExpr, services *
 		{Path: genpkg + "/" + svcName, Name: data.Service.PkgName},
 		{Path: genpkg + "/" + svcName + "/" + "views", Name: data.Service.ViewsPkg},
 	}
-	imports = append(imports, data.Service.UserTypeImports...)
 	sections := []*codegen.SectionTemplate{codegen.Header(title, "client", imports)}
 
 	for _, e := range data.Endpoints {
diff --git a/http/codegen/client_cli.go b/http/codegen/client_cli.go
index 4203a4f6..30389efc 100644
--- a/http/codegen/client_cli.go
+++ b/http/codegen/client_cli.go
@@ -182,7 +182,6 @@ func payloadBuilders(genpkg string, svc *expr.HTTPServiceExpr, data *cli.Command
 		codegen.GoaNamedImport("http", "goahttp"),
 		{Path: genpkg + "/" + sd.Service.PathName, Name: sd.Service.PkgName},
 	}
-	specs = append(specs, sd.Service.UserTypeImports...)
 	sections := []*codegen.SectionTemplate{
 		codegen.Header(title, "client", specs),
 	}
diff --git a/http/codegen/client_types.go b/http/codegen/client_types.go
index 5bb30f0f..a7de570d 100644
--- a/http/codegen/client_types.go
+++ b/http/codegen/client_types.go
@@ -55,7 +55,6 @@ func clientType(genpkg string, svc *expr.HTTPServiceExpr, seen map[string]struct
 		{Path: genpkg + "/" + svcName + "/" + "views", Name: data.Service.ViewsPkg},
 		codegen.GoaImport(""),
 	}
-	imports = append(imports, data.Service.UserTypeImports...)
 	header := codegen.Header(svc.Name()+" HTTP client types", "client", imports)
 
 	var (
diff --git a/http/codegen/server.go b/http/codegen/server.go
index 524a346e..5f6a16d9 100644
--- a/http/codegen/server.go
+++ b/http/codegen/server.go
@@ -63,7 +63,6 @@ func serverFile(genpkg string, svc *expr.HTTPServiceExpr, services *ServicesData
 		{Path: genpkg + "/" + svcName, Name: data.Service.PkgName},
 		{Path: genpkg + "/" + svcName + "/" + "views", Name: data.Service.ViewsPkg},
 	}
-	imports = append(imports, data.Service.UserTypeImports...)
 	sections := []*codegen.SectionTemplate{
 		codegen.Header(title, "server", imports),
 	}
@@ -140,7 +139,6 @@ func serverEncodeDecodeFile(genpkg string, svc *expr.HTTPServiceExpr, services *
 		{Path: genpkg + "/" + svcName, Name: data.Service.PkgName},
 		{Path: genpkg + "/" + svcName + "/" + "views", Name: data.Service.ViewsPkg},
 	}
-	imports = append(imports, data.Service.UserTypeImports...)
 	sections := []*codegen.SectionTemplate{codegen.Header(title, "server", imports)}
 
 	for _, e := range data.Endpoints {
diff --git a/http/codegen/server_types.go b/http/codegen/server_types.go
index 351d2b1f..070311ff 100644
--- a/http/codegen/server_types.go
+++ b/http/codegen/server_types.go
@@ -56,7 +56,6 @@ func serverType(genpkg string, svc *expr.HTTPServiceExpr, _ map[string]struct{},
 		codegen.GoaImport(""),
 		{Path: genpkg + "/" + svcName + "/" + "views", Name: data.Service.ViewsPkg},
 	}
-	imports = append(imports, data.Service.UserTypeImports...)
 	header := codegen.Header(svc.Name()+" HTTP server types", "server", imports)
 
 	var (
diff --git a/http/codegen/service_data.go b/http/codegen/service_data.go
index c437ebc1..7d3d55dc 100644
--- a/http/codegen/service_data.go
+++ b/http/codegen/service_data.go
@@ -32,7 +32,6 @@ var (
 	)
 )
 
-
 type (
 	// ServicesData encapsulates the data computed from the design.
 	ServicesData struct {
diff --git a/http/codegen/templates/server_handler_init.go.tpl b/http/codegen/templates/server_handler_init.go.tpl
index cd2b4512..1ff583fa 100644
--- a/http/codegen/templates/server_handler_init.go.tpl
+++ b/http/codegen/templates/server_handler_init.go.tpl
@@ -34,7 +34,7 @@ func {{ .HandlerInit }}(
 	{{- if mustDecodeRequest . }}
 		{{ if .Redirect }}_{{ else }}payload{{ end }}, err := decodeRequest(r)
 		if err != nil {
-			if err := encodeError(ctx, w, err); err != nil {
+			if err := encodeError(ctx, w, err); err != nil && errhandler != nil {
 				errhandler(ctx, w, err)
 			}
 			return
@@ -93,13 +93,21 @@ func {{ .HandlerInit }}(
 	{{- if not .Redirect }}
 		if err != nil {
 			{{- if isWebSocketEndpoint . }}
-			if v.Stream.(*{{ .ServerWebSocket.VarName }}).conn != nil {
+			var stream *{{ .ServerWebSocket.VarName }}
+			if wrapper, ok := v.Stream.({{ .ServicePkgName }}.Unwrap); ok {
+				stream = wrapper.Unwrap().(*{{ .ServerWebSocket.VarName }})
+			} else {
+				stream = v.Stream.(*{{ .ServerWebSocket.VarName }})
+			}
+			if stream != nil && stream.conn != nil {
 				// Response writer has been hijacked, do not encode the error
-				errhandler(ctx, w, err)
+				if errhandler != nil {
+					errhandler(ctx, w, err)
+				}
 				return
 			}
 			{{- end }}
-			if err := encodeError(ctx, w, err); err != nil {
+			if err := encodeError(ctx, w, err); err != nil && errhandler != nil {
 				errhandler(ctx, w, err)
 			}
 			return
@@ -111,14 +119,16 @@ func {{ .HandlerInit }}(
 		if wt, ok := o.Body.(io.WriterTo); ok {
 			{{- if not (or .Redirect (isWebSocketEndpoint .)) }}
 			if err := encodeResponse(ctx, w, {{ if and .Method.SkipResponseBodyEncodeDecode .Result.Ref }}o.Result{{ else }}res{{ end }}); err != nil {
-				errhandler(ctx, w, err)
+				if errhandler != nil {
+					errhandler(ctx, w, err)
+				}
 				return
 			}
 			{{- end }}
 			n, err := wt.WriteTo(w)
 			if err != nil {
 				if n == 0 {
-					if err := encodeError(ctx, w, err); err != nil {
+					if err := encodeError(ctx, w, err); err != nil && errhandler != nil {
 						errhandler(ctx, w, err)
 					}
 				} else {
@@ -133,7 +143,7 @@ func {{ .HandlerInit }}(
 		// handle immediate read error like a returned error
 		buf := bufio.NewReader(o.Body)
 		if _, err := buf.Peek(1); err != nil && err != io.EOF {
-			if err := encodeError(ctx, w, err); err != nil {
+			if err := encodeError(ctx, w, err); err != nil && errhandler != nil {
 				errhandler(ctx, w, err)
 			}
 			return
@@ -141,7 +151,9 @@ func {{ .HandlerInit }}(
 	{{- end }}
 	{{- if not (or .Redirect (isWebSocketEndpoint .) (isSSEEndpoint .)) }}
 		if err := encodeResponse(ctx, w, {{ if and .Method.SkipResponseBodyEncodeDecode .Result.Ref }}o.Result{{ else }}res{{ end }}); err != nil {
-			errhandler(ctx, w, err)
+			if errhandler != nil {
+				errhandler(ctx, w, err)
+			}
 			{{- if .Method.SkipResponseBodyEncodeDecode }}
 			return
 			{{- end }}
diff --git a/http/codegen/websocket.go b/http/codegen/websocket.go
index 6a78e9db..aec7eb60 100644
--- a/http/codegen/websocket.go
+++ b/http/codegen/websocket.go
@@ -256,7 +256,6 @@ func websocketServerFile(genpkg string, svc *expr.HTTPServiceExpr, services *Ser
 		codegen.GoaNamedImport("http", "goahttp"),
 		{Path: genpkg + "/" + svcName, Name: data.Service.PkgName},
 	}
-	imports = append(imports, data.Service.UserTypeImports...)
 	sections := []*codegen.SectionTemplate{
 		codegen.Header(title, "server", imports),
 	}
@@ -290,7 +289,6 @@ func websocketClientFile(genpkg string, svc *expr.HTTPServiceExpr, services *Ser
 		{Path: genpkg + "/" + svcName + "/" + "views", Name: data.Service.ViewsPkg},
 		{Path: genpkg + "/" + svcName, Name: data.Service.PkgName},
 	}
-	imports = append(imports, data.Service.UserTypeImports...)
 	sections := []*codegen.SectionTemplate{
 		codegen.Header(title, "client", imports),
 	}


Test patch:
diff --git a/codegen/service/testdata/interceptors/streaming-interceptors-with-read-payload-and-read-streaming-payload_interceptor_wrappers.go.golden b/codegen/service/testdata/interceptors/streaming-interceptors-with-read-payload-and-read-streaming-payload_interceptor_wrappers.go.golden
index 01d2a8ba..37e5397e 100644
--- a/codegen/service/testdata/interceptors/streaming-interceptors-with-read-payload-and-read-streaming-payload_interceptor_wrappers.go.golden
+++ b/codegen/service/testdata/interceptors/streaming-interceptors-with-read-payload-and-read-streaming-payload_interceptor_wrappers.go.golden
@@ -1,4 +1,7 @@
 
+type Unwrap interface {
+	Unwrap() interface{}
+}
 
 // wrappedMethodServerStream is a server interceptor wrapper for the
 // MethodServerStream stream.
@@ -80,6 +83,11 @@ func wrapClientMethodLogging(endpoint goa.Endpoint, i ClientInterceptors) goa.En
 	}
 }
 
+// Unwrap returns the underlying stream type.
+func (w *wrappedMethodServerStream) Unwrap() interface{} {
+	return w.stream
+}
+
 // Recv reads instances of "MethodServerStream" from the stream after executing
 // the applied interceptor.
 func (w *wrappedMethodServerStream) Recv() (*MethodStreamingPayload, error) {
@@ -100,6 +108,11 @@ func (w *wrappedMethodServerStream) Close() error {
 	return w.stream.Close()
 }
 
+// Unwrap returns the underlying stream type.
+func (w *wrappedMethodClientStream) Unwrap() interface{} {
+	return w.stream
+}
+
 // Send streams instances of "MethodClientStream" after executing the applied
 // interceptor.
 func (w *wrappedMethodClientStream) Send(v *MethodStreamingPayload) error {
diff --git a/codegen/service/testdata/interceptors/streaming-interceptors-with-read-streaming-result_interceptor_wrappers.go.golden b/codegen/service/testdata/interceptors/streaming-interceptors-with-read-streaming-result_interceptor_wrappers.go.golden
index ac97f02b..ca9f499f 100644
--- a/codegen/service/testdata/interceptors/streaming-interceptors-with-read-streaming-result_interceptor_wrappers.go.golden
+++ b/codegen/service/testdata/interceptors/streaming-interceptors-with-read-streaming-result_interceptor_wrappers.go.golden
@@ -1,4 +1,7 @@
 
+type Unwrap interface {
+	Unwrap() interface{}
+}
 
 // wrappedMethodServerStream is a server interceptor wrapper for the
 // MethodServerStream stream.
@@ -68,6 +71,11 @@ func wrapClientMethodLogging(endpoint goa.Endpoint, i ClientInterceptors) goa.En
 	}
 }
 
+// Unwrap returns the underlying stream type.
+func (w *wrappedMethodServerStream) Unwrap() interface{} {
+	return w.stream
+}
+
 // Send streams instances of "MethodServerStream" after executing the applied
 // interceptor.
 func (w *wrappedMethodServerStream) Send(v *MethodResult) error {
diff --git a/codegen/service/testdata/interceptors/streaming-interceptors_interceptor_wrappers.go.golden b/codegen/service/testdata/interceptors/streaming-interceptors_interceptor_wrappers.go.golden
index 6134ce6b..517605f7 100644
--- a/codegen/service/testdata/interceptors/streaming-interceptors_interceptor_wrappers.go.golden
+++ b/codegen/service/testdata/interceptors/streaming-interceptors_interceptor_wrappers.go.golden
@@ -1,4 +1,7 @@
 
+type Unwrap interface {
+	Unwrap() interface{}
+}
 
 // wrappedMethodServerStream is a server interceptor wrapper for the
 // MethodServerStream stream.
@@ -95,6 +98,11 @@ func wrapClientMethodLogging(endpoint goa.Endpoint, i ClientInterceptors) goa.En
 	}
 }
 
+// Unwrap returns the underlying stream type.
+func (w *wrappedMethodServerStream) Unwrap() interface{} {
+	return w.stream
+}
+
 // Send streams instances of "MethodServerStream" after executing the applied
 // interceptor.
 func (w *wrappedMethodServerStream) Send(v *MethodResult) error {
@@ -130,6 +138,11 @@ func (w *wrappedMethodServerStream) Close() error {
 	return w.stream.Close()
 }
 
+// Unwrap returns the underlying stream type.
+func (w *wrappedMethodClientStream) Unwrap() interface{} {
+	return w.stream
+}
+
 // Send streams instances of "MethodClientStream" after executing the applied
 // interceptor.
 func (w *wrappedMethodClientStream) Send(v *MethodStreamingPayload) error {
diff --git a/http/codegen/testdata/handler_init_functions.go b/http/codegen/testdata/handler_init_functions.go
index b6532c39..05dd88a1 100644
--- a/http/codegen/testdata/handler_init_functions.go
+++ b/http/codegen/testdata/handler_init_functions.go
@@ -22,13 +22,15 @@ func NewMethodNoPayloadNoResultHandler(
 		var err error
 		res, err := endpoint(ctx, nil)
 		if err != nil {
-			if err := encodeError(ctx, w, err); err != nil {
+			if err := encodeError(ctx, w, err); err != nil && errhandler != nil {
 				errhandler(ctx, w, err)
 			}
 			return
 		}
 		if err := encodeResponse(ctx, w, res); err != nil {
-			errhandler(ctx, w, err)
+			if errhandler != nil {
+				errhandler(ctx, w, err)
+			}
 		}
 	})
 }
@@ -76,20 +78,22 @@ func NewMethodPayloadNoResultHandler(
 		ctx = context.WithValue(ctx, goa.ServiceKey, "ServicePayloadNoResult")
 		payload, err := decodeRequest(r)
 		if err != nil {
-			if err := encodeError(ctx, w, err); err != nil {
+			if err := encodeError(ctx, w, err); err != nil && errhandler != nil {
 				errhandler(ctx, w, err)
 			}
 			return
 		}
 		res, err := endpoint(ctx, payload)
 		if err != nil {
-			if err := encodeError(ctx, w, err); err != nil {
+			if err := encodeError(ctx, w, err); err != nil && errhandler != nil {
 				errhandler(ctx, w, err)
 			}
 			return
 		}
 		if err := encodeResponse(ctx, w, res); err != nil {
-			errhandler(ctx, w, err)
+			if errhandler != nil {
+				errhandler(ctx, w, err)
+			}
 		}
 	})
 }
@@ -116,7 +120,7 @@ func NewMethodPayloadNoResultHandler(
 		ctx = context.WithValue(ctx, goa.ServiceKey, "ServicePayloadNoResult")
 		_, err := decodeRequest(r)
 		if err != nil {
-			if err := encodeError(ctx, w, err); err != nil {
+			if err := encodeError(ctx, w, err); err != nil && errhandler != nil {
 				errhandler(ctx, w, err)
 			}
 			return
@@ -148,13 +152,15 @@ func NewMethodNoPayloadResultHandler(
 		var err error
 		res, err := endpoint(ctx, nil)
 		if err != nil {
-			if err := encodeError(ctx, w, err); err != nil {
+			if err := encodeError(ctx, w, err); err != nil && errhandler != nil {
 				errhandler(ctx, w, err)
 			}
 			return
 		}
 		if err := encodeResponse(ctx, w, res); err != nil {
-			errhandler(ctx, w, err)
+			if errhandler != nil {
+				errhandler(ctx, w, err)
+			}
 		}
 	})
 }
@@ -182,20 +188,22 @@ func NewMethodPayloadResultHandler(
 		ctx = context.WithValue(ctx, goa.ServiceKey, "ServicePayloadResult")
 		payload, err := decodeRequest(r)
 		if err != nil {
-			if err := encodeError(ctx, w, err); err != nil {
+			if err := encodeError(ctx, w, err); err != nil && errhandler != nil {
 				errhandler(ctx, w, err)
 			}
 			return
 		}
 		res, err := endpoint(ctx, payload)
 		if err != nil {
-			if err := encodeError(ctx, w, err); err != nil {
+			if err := encodeError(ctx, w, err); err != nil && errhandler != nil {
 				errhandler(ctx, w, err)
 			}
 			return
 		}
 		if err := encodeResponse(ctx, w, res); err != nil {
-			errhandler(ctx, w, err)
+			if errhandler != nil {
+				errhandler(ctx, w, err)
+			}
 		}
 	})
 }
@@ -223,20 +231,22 @@ func NewMethodPayloadResultErrorHandler(
 		ctx = context.WithValue(ctx, goa.ServiceKey, "ServicePayloadResultError")
 		payload, err := decodeRequest(r)
 		if err != nil {
-			if err := encodeError(ctx, w, err); err != nil {
+			if err := encodeError(ctx, w, err); err != nil && errhandler != nil {
 				errhandler(ctx, w, err)
 			}
 			return
 		}
 		res, err := endpoint(ctx, payload)
 		if err != nil {
-			if err := encodeError(ctx, w, err); err != nil {
+			if err := encodeError(ctx, w, err); err != nil && errhandler != nil {
 				errhandler(ctx, w, err)
 			}
 			return
 		}
 		if err := encodeResponse(ctx, w, res); err != nil {
-			errhandler(ctx, w, err)
+			if errhandler != nil {
+				errhandler(ctx, w, err)
+			}
 		}
 	})
 }
@@ -264,7 +274,7 @@ func NewMethodSkipResponseBodyEncodeDecodeHandler(
 		var err error
 		res, err := endpoint(ctx, nil)
 		if err != nil {
-			if err := encodeError(ctx, w, err); err != nil {
+			if err := encodeError(ctx, w, err); err != nil && errhandler != nil {
 				errhandler(ctx, w, err)
 			}
 			return
@@ -273,13 +283,15 @@ func NewMethodSkipResponseBodyEncodeDecodeHandler(
 		defer o.Body.Close()
 		if wt, ok := o.Body.(io.WriterTo); ok {
 			if err := encodeResponse(ctx, w, res); err != nil {
-				errhandler(ctx, w, err)
+				if errhandler != nil {
+					errhandler(ctx, w, err)
+				}
 				return
 			}
 			n, err := wt.WriteTo(w)
 			if err != nil {
 				if n == 0 {
-					if err := encodeError(ctx, w, err); err != nil {
+					if err := encodeError(ctx, w, err); err != nil && errhandler != nil {
 						errhandler(ctx, w, err)
 					}
 				} else {
@@ -294,13 +306,15 @@ func NewMethodSkipResponseBodyEncodeDecodeHandler(
 		// handle immediate read error like a returned error
 		buf := bufio.NewReader(o.Body)
 		if _, err := buf.Peek(1); err != nil && err != io.EOF {
-			if err := encodeError(ctx, w, err); err != nil {
+			if err := encodeError(ctx, w, err); err != nil && errhandler != nil {
 				errhandler(ctx, w, err)
 			}
 			return
 		}
 		if err := encodeResponse(ctx, w, res); err != nil {
-			errhandler(ctx, w, err)
+			if errhandler != nil {
+				errhandler(ctx, w, err)
+			}
 			return
 		}
 		if _, err := io.Copy(w, buf); err != nil {
diff --git a/http/codegen/testdata/streaming_code.go b/http/codegen/testdata/streaming_code.go
index 8feb4aba..99efee81 100644
--- a/http/codegen/testdata/streaming_code.go
+++ b/http/codegen/testdata/streaming_code.go
@@ -39,7 +39,7 @@ func NewStreamingResultMethodHandler(
 		ctx = context.WithValue(ctx, goa.ServiceKey, "StreamingResultService")
 		payload, err := decodeRequest(r)
 		if err != nil {
-			if err := encodeError(ctx, w, err); err != nil {
+			if err := encodeError(ctx, w, err); err != nil && errhandler != nil {
 				errhandler(ctx, w, err)
 			}
 			return
@@ -58,12 +58,20 @@ func NewStreamingResultMethodHandler(
 		}
 		_, err = endpoint(ctx, v)
 		if err != nil {
-			if v.Stream.(*StreamingResultMethodServerStream).conn != nil {
+			var stream *StreamingResultMethodServerStream
+			if wrapper, ok := v.Stream.(streamingresultservice.Unwrap); ok {
+				stream = wrapper.Unwrap().(*StreamingResultMethodServerStream)
+			} else {
+				stream = v.Stream.(*StreamingResultMethodServerStream)
+			}
+			if stream != nil && stream.conn != nil {
 				// Response writer has been hijacked, do not encode the error
-				errhandler(ctx, w, err)
+				if errhandler != nil {
+					errhandler(ctx, w, err)
+				}
 				return
 			}
-			if err := encodeError(ctx, w, err); err != nil {
+			if err := encodeError(ctx, w, err); err != nil && errhandler != nil {
 				errhandler(ctx, w, err)
 			}
 			return
@@ -208,12 +216,20 @@ func NewStreamingResultNoPayloadMethodHandler(
 		}
 		_, err = endpoint(ctx, v)
 		if err != nil {
-			if v.Stream.(*StreamingResultNoPayloadMethodServerStream).conn != nil {
+			var stream *StreamingResultNoPayloadMethodServerStream
+			if wrapper, ok := v.Stream.(streamingresultnopayloadservice.Unwrap); ok {
+				stream = wrapper.Unwrap().(*StreamingResultNoPayloadMethodServerStream)
+			} else {
+				stream = v.Stream.(*StreamingResultNoPayloadMethodServerStream)
+			}
+			if stream != nil && stream.conn != nil {
 				// Response writer has been hijacked, do not encode the error
-				errhandler(ctx, w, err)
+				if errhandler != nil {
+					errhandler(ctx, w, err)
+				}
 				return
 			}
-			if err := encodeError(ctx, w, err); err != nil {
+			if err := encodeError(ctx, w, err); err != nil && errhandler != nil {
 				errhandler(ctx, w, err)
 			}
 			return
@@ -1069,7 +1085,7 @@ func NewStreamingPayloadMethodHandler(
 		ctx = context.WithValue(ctx, goa.ServiceKey, "StreamingPayloadService")
 		payload, err := decodeRequest(r)
 		if err != nil {
-			if err := encodeError(ctx, w, err); err != nil {
+			if err := encodeError(ctx, w, err); err != nil && errhandler != nil {
 				errhandler(ctx, w, err)
 			}
 			return
@@ -1088,12 +1104,20 @@ func NewStreamingPayloadMethodHandler(
 		}
 		_, err = endpoint(ctx, v)
 		if err != nil {
-			if v.Stream.(*StreamingPayloadMethodServerStream).conn != nil {
+			var stream *StreamingPayloadMethodServerStream
+			if wrapper, ok := v.Stream.(streamingpayloadservice.Unwrap); ok {
+				stream = wrapper.Unwrap().(*StreamingPayloadMethodServerStream)
+			} else {
+				stream = v.Stream.(*StreamingPayloadMethodServerStream)
+			}
+			if stream != nil && stream.conn != nil {
 				// Response writer has been hijacked, do not encode the error
-				errhandler(ctx, w, err)
+				if errhandler != nil {
+					errhandler(ctx, w, err)
+				}
 				return
 			}
-			if err := encodeError(ctx, w, err); err != nil {
+			if err := encodeError(ctx, w, err); err != nil && errhandler != nil {
 				errhandler(ctx, w, err)
 			}
 			return
@@ -1275,12 +1299,20 @@ func NewStreamingPayloadNoPayloadMethodHandler(
 		}
 		_, err = endpoint(ctx, v)
 		if err != nil {
-			if v.Stream.(*StreamingPayloadNoPayloadMethodServerStream).conn != nil {
+			var stream *StreamingPayloadNoPayloadMethodServerStream
+			if wrapper, ok := v.Stream.(streamingpayloadnopayloadservice.Unwrap); ok {
+				stream = wrapper.Unwrap().(*StreamingPayloadNoPayloadMethodServerStream)
+			} else {
+				stream = v.Stream.(*StreamingPayloadNoPayloadMethodServerStream)
+			}
+			if stream != nil && stream.conn != nil {
 				// Response writer has been hijacked, do not encode the error
-				errhandler(ctx, w, err)
+				if errhandler != nil {
+					errhandler(ctx, w, err)
+				}
 				return
 			}
-			if err := encodeError(ctx, w, err); err != nil {
+			if err := encodeError(ctx, w, err); err != nil && errhandler != nil {
 				errhandler(ctx, w, err)
 			}
 			return
@@ -2533,7 +2565,7 @@ func NewBidirectionalStreamingMethodHandler(
 		ctx = context.WithValue(ctx, goa.ServiceKey, "BidirectionalStreamingService")
 		payload, err := decodeRequest(r)
 		if err != nil {
-			if err := encodeError(ctx, w, err); err != nil {
+			if err := encodeError(ctx, w, err); err != nil && errhandler != nil {
 				errhandler(ctx, w, err)
 			}
 			return
@@ -2552,12 +2584,20 @@ func NewBidirectionalStreamingMethodHandler(
 		}
 		_, err = endpoint(ctx, v)
 		if err != nil {
-			if v.Stream.(*BidirectionalStreamingMethodServerStream).conn != nil {
+			var stream *BidirectionalStreamingMethodServerStream
+			if wrapper, ok := v.Stream.(bidirectionalstreamingservice.Unwrap); ok {
+				stream = wrapper.Unwrap().(*BidirectionalStreamingMethodServerStream)
+			} else {
+				stream = v.Stream.(*BidirectionalStreamingMethodServerStream)
+			}
+			if stream != nil && stream.conn != nil {
 				// Response writer has been hijacked, do not encode the error
-				errhandler(ctx, w, err)
+				if errhandler != nil {
+					errhandler(ctx, w, err)
+				}
 				return
 			}
-			if err := encodeError(ctx, w, err); err != nil {
+			if err := encodeError(ctx, w, err); err != nil && errhandler != nil {
 				errhandler(ctx, w, err)
 			}
 			return
@@ -2782,12 +2822,20 @@ func NewBidirectionalStreamingNoPayloadMethodHandler(
 		}
 		_, err = endpoint(ctx, v)
 		if err != nil {
-			if v.Stream.(*BidirectionalStreamingNoPayloadMethodServerStream).conn != nil {
+			var stream *BidirectionalStreamingNoPayloadMethodServerStream
+			if wrapper, ok := v.Stream.(bidirectionalstreamingnopayloadservice.Unwrap); ok {
+				stream = wrapper.Unwrap().(*BidirectionalStreamingNoPayloadMethodServerStream)
+			} else {
+				stream = v.Stream.(*BidirectionalStreamingNoPayloadMethodServerStream)
+			}
+			if stream != nil && stream.conn != nil {
 				// Response writer has been hijacked, do not encode the error
-				errhandler(ctx, w, err)
+				if errhandler != nil {
+					errhandler(ctx, w, err)
+				}
 				return
 			}
-			if err := encodeError(ctx, w, err); err != nil {
+			if err := encodeError(ctx, w, err); err != nil && errhandler != nil {
 				errhandler(ctx, w, err)
 			}
 			return