Repository: goadesign/goa
Pull request #3081 (2022-06-13 21:26:42)

fix: example server code, exit on invalid host error
Closes https://github.com/goadesign/goa/issues/3077

Updated example http server code, when an invalid host is provided the process will exit with a non-zero exit code.

Used `logger.Fatalf` even thought `fmt.Fprintf` + `os.Exit(1)` is often used in `example_server_code.go`, the ending result is the same but I can change it.


raphael: Thank you for the PR! You're right that the use of `logger.Fatalf` feels a bit inconsistent. I would suggest changing the 3 other instances of `fmt.Fprintf`+`os.Exit(1)` to `logger.Fatalf` as well - one line is better than two :)
nya1: I see, I've now updated example_server.go and reflected the changes on the tests
raphael: Looks great, thank you!

Patch:
diff --git a/codegen/example/example_server.go b/codegen/example/example_server.go
index 24bfca90..bdea7ff6 100644
--- a/codegen/example/example_server.go
+++ b/codegen/example/example_server.go
@@ -258,16 +258,14 @@ func main() {
 						}
 					}
 					if !{{ .VarName }}Seen {
-						fmt.Fprintf(os.Stderr, "invalid value for URL '{{ .Name }}' variable: %q (valid values: {{ join .Values "," }})\n", *{{ .VarName }}F)
-						os.Exit(1)
+						logger.Fatalf("invalid value for URL '{{ .Name }}' variable: %q (valid values: {{ join .Values "," }})\n", *{{ .VarName }}F)
 					}
 				{{- end }}
 				addr = strings.Replace(addr, {{ printf "\"{%s}\"" .Name }}, *{{ .VarName }}F, -1)
 			{{- end }}
 			u, err := url.Parse(addr)
 			if err != nil {
-				fmt.Fprintf(os.Stderr, "invalid URL %#v: %s\n", addr, err)
-				os.Exit(1)
+				logger.Fatalf("invalid URL %#v: %s\n", addr, err)
 			}
 			if *secureF {
 				u.Scheme = "{{ $u.Transport.Type }}s"
@@ -278,8 +276,7 @@ func main() {
 			if *{{ $u.Transport.Type }}PortF != "" {
 				h, _, err := net.SplitHostPort(u.Host)
 				if err != nil {
-					fmt.Fprintf(os.Stderr, "invalid URL %#v: %s\n", u.Host, err)
-					os.Exit(1)
+					logger.Fatalf("invalid URL %#v: %s\n", u.Host, err)
 				}
 				u.Host = net.JoinHostPort(h, *{{ $u.Transport.Type }}PortF)
 			} else if u.Port() == "" {
@@ -291,7 +288,7 @@ func main() {
 	{{ end }}
 {{- end }}
 	default:
-		fmt.Fprintf(os.Stderr, "invalid host argument: %q (valid hosts: {{ join .Server.AvailableHosts "|" }})\n", *hostF)
+		logger.Fatalf("invalid host argument: %q (valid hosts: {{ join .Server.AvailableHosts "|" }})\n", *hostF)
 	}
 `
 
diff --git a/codegen/import.go b/codegen/import.go
index dc0e7bf8..3fe22cda 100644
--- a/codegen/import.go
+++ b/codegen/import.go
@@ -147,23 +147,25 @@ func safelyGetMetaTypeImports(att *expr.AttributeExpr, seen map[string]struct{})
 			}
 		}
 	case *expr.Array:
-		_, im := GetMetaType(t.ElemType)
-		if im != nil {
-			uniqueImports[*im] = struct{}{}
+		for _, im := range safelyGetMetaTypeImports(t.ElemType, seen) {
+			if im != nil {
+				uniqueImports[*im] = struct{}{}
+			}
 		}
 	case *expr.Map:
-		_, im := GetMetaType(t.ElemType)
-		if im != nil {
-			uniqueImports[*im] = struct{}{}
+		for _, im := range safelyGetMetaTypeImports(t.ElemType, seen) {
+			if im != nil {
+				uniqueImports[*im] = struct{}{}
+			}
 		}
-		_, im = GetMetaType(t.KeyType)
-		if im != nil {
-			uniqueImports[*im] = struct{}{}
+		for _, im := range safelyGetMetaTypeImports(t.KeyType, seen) {
+			if im != nil {
+				uniqueImports[*im] = struct{}{}
+			}
 		}
 	case *expr.Object:
-		for _, key := range *t {
-			if key != nil {
-				_, im := GetMetaType(key.Attribute)
+		for _, na := range *t {
+			for _, im := range safelyGetMetaTypeImports(na.Attribute, seen) {
 				if im != nil {
 					uniqueImports[*im] = struct{}{}
 				}
@@ -175,7 +177,7 @@ func safelyGetMetaTypeImports(att *expr.AttributeExpr, seen map[string]struct{})
 		uniqueImports[*im] = struct{}{}
 	}
 	for imp := range uniqueImports {
-		// Copy loop variable into body so next iteration doesnt overwrite its address https://stackoverflow.com/questions/27610039/golang-appending-leaves-only-last-element
+		// Copy loop variable into body so next iteration doesn't overwrite its address https://stackoverflow.com/questions/27610039/golang-appending-leaves-only-last-element
 		copy := imp
 		imports = append(imports, &copy)
 	}
diff --git a/expr/http_body_types.go b/expr/http_body_types.go
index 9a624df9..a981a458 100644
--- a/expr/http_body_types.go
+++ b/expr/http_body_types.go
@@ -85,7 +85,7 @@ func httpRequestBody(a *HTTPEndpointExpr) *AttributeExpr {
 		return unionToObject(payload, name, suffix, a.Service.Name())
 	}
 
-	// 2. If Payload is not an objectthen check whether there are
+	// 2. If Payload is not an object then check whether there are
 	// params, cookies or headers defined and if so return empty type
 	// (payload encoded in request params or headers) otherwise return
 	// payload type (payload encoded in request body).


Test patch:
diff --git a/codegen/example/testdata/example_server_code.go b/codegen/example/testdata/example_server_code.go
index d5e5056a..282d72c9 100644
--- a/codegen/example/testdata/example_server_code.go
+++ b/codegen/example/testdata/example_server_code.go
@@ -61,8 +61,7 @@ const (
 			addr := "http://localhost:80"
 			u, err := url.Parse(addr)
 			if err != nil {
-				fmt.Fprintf(os.Stderr, "invalid URL %#v: %s\n", addr, err)
-				os.Exit(1)
+				logger.Fatalf("invalid URL %#v: %s\n", addr, err)
 			}
 			if *secureF {
 				u.Scheme = "https"
@@ -73,8 +72,7 @@ const (
 			if *httpPortF != "" {
 				h, _, err := net.SplitHostPort(u.Host)
 				if err != nil {
-					fmt.Fprintf(os.Stderr, "invalid URL %#v: %s\n", u.Host, err)
-					os.Exit(1)
+					logger.Fatalf("invalid URL %#v: %s\n", u.Host, err)
 				}
 				u.Host = net.JoinHostPort(h, *httpPortF)
 			} else if u.Port() == "" {
@@ -87,8 +85,7 @@ const (
 			addr := "grpc://localhost:8080"
 			u, err := url.Parse(addr)
 			if err != nil {
-				fmt.Fprintf(os.Stderr, "invalid URL %#v: %s\n", addr, err)
-				os.Exit(1)
+				logger.Fatalf("invalid URL %#v: %s\n", addr, err)
 			}
 			if *secureF {
 				u.Scheme = "grpcs"
@@ -99,8 +96,7 @@ const (
 			if *grpcPortF != "" {
 				h, _, err := net.SplitHostPort(u.Host)
 				if err != nil {
-					fmt.Fprintf(os.Stderr, "invalid URL %#v: %s\n", u.Host, err)
-					os.Exit(1)
+					logger.Fatalf("invalid URL %#v: %s\n", u.Host, err)
 				}
 				u.Host = net.JoinHostPort(h, *grpcPortF)
 			} else if u.Port() == "" {
@@ -110,7 +106,7 @@ const (
 		}
 
 	default:
-		fmt.Fprintf(os.Stderr, "invalid host argument: %q (valid hosts: localhost)\n", *hostF)
+		logger.Fatalf("invalid host argument: %q (valid hosts: localhost)\n", *hostF)
 	}
 
 	// Wait for signal.
@@ -184,8 +180,7 @@ const (
 			addr := "http://localhost:80"
 			u, err := url.Parse(addr)
 			if err != nil {
-				fmt.Fprintf(os.Stderr, "invalid URL %#v: %s\n", addr, err)
-				os.Exit(1)
+				logger.Fatalf("invalid URL %#v: %s\n", addr, err)
 			}
 			if *secureF {
 				u.Scheme = "https"
@@ -196,8 +191,7 @@ const (
 			if *httpPortF != "" {
 				h, _, err := net.SplitHostPort(u.Host)
 				if err != nil {
-					fmt.Fprintf(os.Stderr, "invalid URL %#v: %s\n", u.Host, err)
-					os.Exit(1)
+					logger.Fatalf("invalid URL %#v: %s\n", u.Host, err)
 				}
 				u.Host = net.JoinHostPort(h, *httpPortF)
 			} else if u.Port() == "" {
@@ -210,8 +204,7 @@ const (
 			addr := "grpc://localhost:8080"
 			u, err := url.Parse(addr)
 			if err != nil {
-				fmt.Fprintf(os.Stderr, "invalid URL %#v: %s\n", addr, err)
-				os.Exit(1)
+				logger.Fatalf("invalid URL %#v: %s\n", addr, err)
 			}
 			if *secureF {
 				u.Scheme = "grpcs"
@@ -222,8 +215,7 @@ const (
 			if *grpcPortF != "" {
 				h, _, err := net.SplitHostPort(u.Host)
 				if err != nil {
-					fmt.Fprintf(os.Stderr, "invalid URL %#v: %s\n", u.Host, err)
-					os.Exit(1)
+					logger.Fatalf("invalid URL %#v: %s\n", u.Host, err)
 				}
 				u.Host = net.JoinHostPort(h, *grpcPortF)
 			} else if u.Port() == "" {
@@ -233,7 +225,7 @@ const (
 		}
 
 	default:
-		fmt.Fprintf(os.Stderr, "invalid host argument: %q (valid hosts: localhost)\n", *hostF)
+		logger.Fatalf("invalid host argument: %q (valid hosts: localhost)\n", *hostF)
 	}
 
 	// Wait for signal.
@@ -307,8 +299,7 @@ const (
 			addr := "http://example:8090"
 			u, err := url.Parse(addr)
 			if err != nil {
-				fmt.Fprintf(os.Stderr, "invalid URL %#v: %s\n", addr, err)
-				os.Exit(1)
+				logger.Fatalf("invalid URL %#v: %s\n", addr, err)
 			}
 			if *secureF {
 				u.Scheme = "https"
@@ -319,8 +310,7 @@ const (
 			if *httpPortF != "" {
 				h, _, err := net.SplitHostPort(u.Host)
 				if err != nil {
-					fmt.Fprintf(os.Stderr, "invalid URL %#v: %s\n", u.Host, err)
-					os.Exit(1)
+					logger.Fatalf("invalid URL %#v: %s\n", u.Host, err)
 				}
 				u.Host = net.JoinHostPort(h, *httpPortF)
 			} else if u.Port() == "" {
@@ -333,8 +323,7 @@ const (
 			addr := "https://example:80"
 			u, err := url.Parse(addr)
 			if err != nil {
-				fmt.Fprintf(os.Stderr, "invalid URL %#v: %s\n", addr, err)
-				os.Exit(1)
+				logger.Fatalf("invalid URL %#v: %s\n", addr, err)
 			}
 			if *secureF {
 				u.Scheme = "https"
@@ -345,8 +334,7 @@ const (
 			if *httpPortF != "" {
 				h, _, err := net.SplitHostPort(u.Host)
 				if err != nil {
-					fmt.Fprintf(os.Stderr, "invalid URL %#v: %s\n", u.Host, err)
-					os.Exit(1)
+					logger.Fatalf("invalid URL %#v: %s\n", u.Host, err)
 				}
 				u.Host = net.JoinHostPort(h, *httpPortF)
 			} else if u.Port() == "" {
@@ -359,8 +347,7 @@ const (
 			addr := "grpc://example:8080"
 			u, err := url.Parse(addr)
 			if err != nil {
-				fmt.Fprintf(os.Stderr, "invalid URL %#v: %s\n", addr, err)
-				os.Exit(1)
+				logger.Fatalf("invalid URL %#v: %s\n", addr, err)
 			}
 			if *secureF {
 				u.Scheme = "grpcs"
@@ -371,8 +358,7 @@ const (
 			if *grpcPortF != "" {
 				h, _, err := net.SplitHostPort(u.Host)
 				if err != nil {
-					fmt.Fprintf(os.Stderr, "invalid URL %#v: %s\n", u.Host, err)
-					os.Exit(1)
+					logger.Fatalf("invalid URL %#v: %s\n", u.Host, err)
 				}
 				u.Host = net.JoinHostPort(h, *grpcPortF)
 			} else if u.Port() == "" {
@@ -385,8 +371,7 @@ const (
 			addr := "http://[::1]:8080"
 			u, err := url.Parse(addr)
 			if err != nil {
-				fmt.Fprintf(os.Stderr, "invalid URL %#v: %s\n", addr, err)
-				os.Exit(1)
+				logger.Fatalf("invalid URL %#v: %s\n", addr, err)
 			}
 			if *secureF {
 				u.Scheme = "https"
@@ -397,8 +382,7 @@ const (
 			if *httpPortF != "" {
 				h, _, err := net.SplitHostPort(u.Host)
 				if err != nil {
-					fmt.Fprintf(os.Stderr, "invalid URL %#v: %s\n", u.Host, err)
-					os.Exit(1)
+					logger.Fatalf("invalid URL %#v: %s\n", u.Host, err)
 				}
 				u.Host = net.JoinHostPort(h, *httpPortF)
 			} else if u.Port() == "" {
@@ -408,7 +392,7 @@ const (
 		}
 
 	default:
-		fmt.Fprintf(os.Stderr, "invalid host argument: %q (valid hosts: dev)\n", *hostF)
+		logger.Fatalf("invalid host argument: %q (valid hosts: dev)\n", *hostF)
 	}
 
 	// Wait for signal.
@@ -499,8 +483,7 @@ const (
 			addr = strings.Replace(addr, "{bool}", *bool_F, -1)
 			u, err := url.Parse(addr)
 			if err != nil {
-				fmt.Fprintf(os.Stderr, "invalid URL %#v: %s\n", addr, err)
-				os.Exit(1)
+				logger.Fatalf("invalid URL %#v: %s\n", addr, err)
 			}
 			if *secureF {
 				u.Scheme = "https"
@@ -511,8 +494,7 @@ const (
 			if *httpPortF != "" {
 				h, _, err := net.SplitHostPort(u.Host)
 				if err != nil {
-					fmt.Fprintf(os.Stderr, "invalid URL %#v: %s\n", u.Host, err)
-					os.Exit(1)
+					logger.Fatalf("invalid URL %#v: %s\n", u.Host, err)
 				}
 				u.Host = net.JoinHostPort(h, *httpPortF)
 			} else if u.Port() == "" {
@@ -534,8 +516,7 @@ const (
 			addr = strings.Replace(addr, "{bool}", *bool_F, -1)
 			u, err := url.Parse(addr)
 			if err != nil {
-				fmt.Fprintf(os.Stderr, "invalid URL %#v: %s\n", addr, err)
-				os.Exit(1)
+				logger.Fatalf("invalid URL %#v: %s\n", addr, err)
 			}
 			if *secureF {
 				u.Scheme = "https"
@@ -546,8 +527,7 @@ const (
 			if *httpPortF != "" {
 				h, _, err := net.SplitHostPort(u.Host)
 				if err != nil {
-					fmt.Fprintf(os.Stderr, "invalid URL %#v: %s\n", u.Host, err)
-					os.Exit(1)
+					logger.Fatalf("invalid URL %#v: %s\n", u.Host, err)
 				}
 				u.Host = net.JoinHostPort(h, *httpPortF)
 			} else if u.Port() == "" {
@@ -557,7 +537,7 @@ const (
 		}
 
 	default:
-		fmt.Fprintf(os.Stderr, "invalid host argument: %q (valid hosts: dev)\n", *hostF)
+		logger.Fatalf("invalid host argument: %q (valid hosts: dev)\n", *hostF)
 	}
 
 	// Wait for signal.
@@ -613,8 +593,7 @@ const (
 			addr := "http://localhost:80"
 			u, err := url.Parse(addr)
 			if err != nil {
-				fmt.Fprintf(os.Stderr, "invalid URL %#v: %s\n", addr, err)
-				os.Exit(1)
+				logger.Fatalf("invalid URL %#v: %s\n", addr, err)
 			}
 			if *secureF {
 				u.Scheme = "https"
@@ -625,8 +604,7 @@ const (
 			if *httpPortF != "" {
 				h, _, err := net.SplitHostPort(u.Host)
 				if err != nil {
-					fmt.Fprintf(os.Stderr, "invalid URL %#v: %s\n", u.Host, err)
-					os.Exit(1)
+					logger.Fatalf("invalid URL %#v: %s\n", u.Host, err)
 				}
 				u.Host = net.JoinHostPort(h, *httpPortF)
 			} else if u.Port() == "" {
@@ -636,7 +614,7 @@ const (
 		}
 
 	default:
-		fmt.Fprintf(os.Stderr, "invalid host argument: %q (valid hosts: svc)\n", *hostF)
+		logger.Fatalf("invalid host argument: %q (valid hosts: svc)\n", *hostF)
 	}
 
 	// Wait for signal.
@@ -710,8 +688,7 @@ const (
 			addr := "http://example:8090"
 			u, err := url.Parse(addr)
 			if err != nil {
-				fmt.Fprintf(os.Stderr, "invalid URL %#v: %s\n", addr, err)
-				os.Exit(1)
+				logger.Fatalf("invalid URL %#v: %s\n", addr, err)
 			}
 			if *secureF {
 				u.Scheme = "https"
@@ -722,8 +699,7 @@ const (
 			if *httpPortF != "" {
 				h, _, err := net.SplitHostPort(u.Host)
 				if err != nil {
-					fmt.Fprintf(os.Stderr, "invalid URL %#v: %s\n", u.Host, err)
-					os.Exit(1)
+					logger.Fatalf("invalid URL %#v: %s\n", u.Host, err)
 				}
 				u.Host = net.JoinHostPort(h, *httpPortF)
 			} else if u.Port() == "" {
@@ -736,8 +712,7 @@ const (
 			addr := "grpc://localhost:8080"
 			u, err := url.Parse(addr)
 			if err != nil {
-				fmt.Fprintf(os.Stderr, "invalid URL %#v: %s\n", addr, err)
-				os.Exit(1)
+				logger.Fatalf("invalid URL %#v: %s\n", addr, err)
 			}
 			if *secureF {
 				u.Scheme = "grpcs"
@@ -748,8 +723,7 @@ const (
 			if *grpcPortF != "" {
 				h, _, err := net.SplitHostPort(u.Host)
 				if err != nil {
-					fmt.Fprintf(os.Stderr, "invalid URL %#v: %s\n", u.Host, err)
-					os.Exit(1)
+					logger.Fatalf("invalid URL %#v: %s\n", u.Host, err)
 				}
 				u.Host = net.JoinHostPort(h, *grpcPortF)
 			} else if u.Port() == "" {
@@ -759,7 +733,7 @@ const (
 		}
 
 	default:
-		fmt.Fprintf(os.Stderr, "invalid host argument: %q (valid hosts: dev)\n", *hostF)
+		logger.Fatalf("invalid host argument: %q (valid hosts: dev)\n", *hostF)
 	}
 
 	// Wait for signal.
@@ -837,8 +811,7 @@ const (
 			addr := "http://example:8090"
 			u, err := url.Parse(addr)
 			if err != nil {
-				fmt.Fprintf(os.Stderr, "invalid URL %#v: %s\n", addr, err)
-				os.Exit(1)
+				logger.Fatalf("invalid URL %#v: %s\n", addr, err)
 			}
 			if *secureF {
 				u.Scheme = "https"
@@ -849,8 +822,7 @@ const (
 			if *httpPortF != "" {
 				h, _, err := net.SplitHostPort(u.Host)
 				if err != nil {
-					fmt.Fprintf(os.Stderr, "invalid URL %#v: %s\n", u.Host, err)
-					os.Exit(1)
+					logger.Fatalf("invalid URL %#v: %s\n", u.Host, err)
 				}
 				u.Host = net.JoinHostPort(h, *httpPortF)
 			} else if u.Port() == "" {
@@ -863,8 +835,7 @@ const (
 			addr := "grpc://localhost:8080"
 			u, err := url.Parse(addr)
 			if err != nil {
-				fmt.Fprintf(os.Stderr, "invalid URL %#v: %s\n", addr, err)
-				os.Exit(1)
+				logger.Fatalf("invalid URL %#v: %s\n", addr, err)
 			}
 			if *secureF {
 				u.Scheme = "grpcs"
@@ -875,8 +846,7 @@ const (
 			if *grpcPortF != "" {
 				h, _, err := net.SplitHostPort(u.Host)
 				if err != nil {
-					fmt.Fprintf(os.Stderr, "invalid URL %#v: %s\n", u.Host, err)
-					os.Exit(1)
+					logger.Fatalf("invalid URL %#v: %s\n", u.Host, err)
 				}
 				u.Host = net.JoinHostPort(h, *grpcPortF)
 			} else if u.Port() == "" {
@@ -886,7 +856,7 @@ const (
 		}
 
 	default:
-		fmt.Fprintf(os.Stderr, "invalid host argument: %q (valid hosts: dev)\n", *hostF)
+		logger.Fatalf("invalid host argument: %q (valid hosts: dev)\n", *hostF)
 	}
 
 	// Wait for signal.
@@ -959,8 +929,7 @@ const (
 			addr := "http://example:8090"
 			u, err := url.Parse(addr)
 			if err != nil {
-				fmt.Fprintf(os.Stderr, "invalid URL %#v: %s\n", addr, err)
-				os.Exit(1)
+				logger.Fatalf("invalid URL %#v: %s\n", addr, err)
 			}
 			if *secureF {
 				u.Scheme = "https"
@@ -971,8 +940,7 @@ const (
 			if *httpPortF != "" {
 				h, _, err := net.SplitHostPort(u.Host)
 				if err != nil {
-					fmt.Fprintf(os.Stderr, "invalid URL %#v: %s\n", u.Host, err)
-					os.Exit(1)
+					logger.Fatalf("invalid URL %#v: %s\n", u.Host, err)
 				}
 				u.Host = net.JoinHostPort(h, *httpPortF)
 			} else if u.Port() == "" {
@@ -986,8 +954,7 @@ const (
 			addr := "https://example"
 			u, err := url.Parse(addr)
 			if err != nil {
-				fmt.Fprintf(os.Stderr, "invalid URL %#v: %s\n", addr, err)
-				os.Exit(1)
+				logger.Fatalf("invalid URL %#v: %s\n", addr, err)
 			}
 			if *secureF {
 				u.Scheme = "https"
@@ -998,8 +965,7 @@ const (
 			if *httpPortF != "" {
 				h, _, err := net.SplitHostPort(u.Host)
 				if err != nil {
-					fmt.Fprintf(os.Stderr, "invalid URL %#v: %s\n", u.Host, err)
-					os.Exit(1)
+					logger.Fatalf("invalid URL %#v: %s\n", u.Host, err)
 				}
 				u.Host = net.JoinHostPort(h, *httpPortF)
 			} else if u.Port() == "" {
@@ -1009,7 +975,7 @@ const (
 		}
 
 	default:
-		fmt.Fprintf(os.Stderr, "invalid host argument: %q (valid hosts: dev|stage)\n", *hostF)
+		logger.Fatalf("invalid host argument: %q (valid hosts: dev|stage)\n", *hostF)
 	}
 
 	// Wait for signal.
@@ -1093,14 +1059,12 @@ const (
 				}
 			}
 			if !versionSeen {
-				fmt.Fprintf(os.Stderr, "invalid value for URL 'version' variable: %q (valid values: v1,v2)\n", *versionF)
-				os.Exit(1)
+				logger.Fatalf("invalid value for URL 'version' variable: %q (valid values: v1,v2)\n", *versionF)
 			}
 			addr = strings.Replace(addr, "{version}", *versionF, -1)
 			u, err := url.Parse(addr)
 			if err != nil {
-				fmt.Fprintf(os.Stderr, "invalid URL %#v: %s\n", addr, err)
-				os.Exit(1)
+				logger.Fatalf("invalid URL %#v: %s\n", addr, err)
 			}
 			if *secureF {
 				u.Scheme = "https"
@@ -1111,8 +1075,7 @@ const (
 			if *httpPortF != "" {
 				h, _, err := net.SplitHostPort(u.Host)
 				if err != nil {
-					fmt.Fprintf(os.Stderr, "invalid URL %#v: %s\n", u.Host, err)
-					os.Exit(1)
+					logger.Fatalf("invalid URL %#v: %s\n", u.Host, err)
 				}
 				u.Host = net.JoinHostPort(h, *httpPortF)
 			} else if u.Port() == "" {
@@ -1128,8 +1091,7 @@ const (
 			addr = strings.Replace(addr, "{port}", *portF, -1)
 			u, err := url.Parse(addr)
 			if err != nil {
-				fmt.Fprintf(os.Stderr, "invalid URL %#v: %s\n", addr, err)
-				os.Exit(1)
+				logger.Fatalf("invalid URL %#v: %s\n", addr, err)
 			}
 			if *secureF {
 				u.Scheme = "https"
@@ -1140,8 +1102,7 @@ const (
 			if *httpPortF != "" {
 				h, _, err := net.SplitHostPort(u.Host)
 				if err != nil {
-					fmt.Fprintf(os.Stderr, "invalid URL %#v: %s\n", u.Host, err)
-					os.Exit(1)
+					logger.Fatalf("invalid URL %#v: %s\n", u.Host, err)
 				}
 				u.Host = net.JoinHostPort(h, *httpPortF)
 			} else if u.Port() == "" {
@@ -1151,7 +1112,7 @@ const (
 		}
 
 	default:
-		fmt.Fprintf(os.Stderr, "invalid host argument: %q (valid hosts: dev|stage)\n", *hostF)
+		logger.Fatalf("invalid host argument: %q (valid hosts: dev|stage)\n", *hostF)
 	}
 
 	// Wait for signal.
@@ -1224,8 +1185,7 @@ const (
 			addr := "http://localhost:80"
 			u, err := url.Parse(addr)
 			if err != nil {
-				fmt.Fprintf(os.Stderr, "invalid URL %#v: %s\n", addr, err)
-				os.Exit(1)
+				logger.Fatalf("invalid URL %#v: %s\n", addr, err)
 			}
 			if *secureF {
 				u.Scheme = "https"
@@ -1236,8 +1196,7 @@ const (
 			if *httpPortF != "" {
 				h, _, err := net.SplitHostPort(u.Host)
 				if err != nil {
-					fmt.Fprintf(os.Stderr, "invalid URL %#v: %s\n", u.Host, err)
-					os.Exit(1)
+					logger.Fatalf("invalid URL %#v: %s\n", u.Host, err)
 				}
 				u.Host = net.JoinHostPort(h, *httpPortF)
 			} else if u.Port() == "" {
@@ -1250,8 +1209,7 @@ const (
 			addr := "grpc://localhost:8080"
 			u, err := url.Parse(addr)
 			if err != nil {
-				fmt.Fprintf(os.Stderr, "invalid URL %#v: %s\n", addr, err)
-				os.Exit(1)
+				logger.Fatalf("invalid URL %#v: %s\n", addr, err)
 			}
 			if *secureF {
 				u.Scheme = "grpcs"
@@ -1262,8 +1220,7 @@ const (
 			if *grpcPortF != "" {
 				h, _, err := net.SplitHostPort(u.Host)
 				if err != nil {
-					fmt.Fprintf(os.Stderr, "invalid URL %#v: %s\n", u.Host, err)
-					os.Exit(1)
+					logger.Fatalf("invalid URL %#v: %s\n", u.Host, err)
 				}
 				u.Host = net.JoinHostPort(h, *grpcPortF)
 			} else if u.Port() == "" {
@@ -1273,7 +1230,7 @@ const (
 		}
 
 	default:
-		fmt.Fprintf(os.Stderr, "invalid host argument: %q (valid hosts: svc)\n", *hostF)
+		logger.Fatalf("invalid host argument: %q (valid hosts: svc)\n", *hostF)
 	}
 
 	// Wait for signal.
@@ -1346,8 +1303,7 @@ const (
 			addr := "http://localhost:80"
 			u, err := url.Parse(addr)
 			if err != nil {
-				fmt.Fprintf(os.Stderr, "invalid URL %#v: %s\n", addr, err)
-				os.Exit(1)
+				logger.Fatalf("invalid URL %#v: %s\n", addr, err)
 			}
 			if *secureF {
 				u.Scheme = "https"
@@ -1358,8 +1314,7 @@ const (
 			if *httpPortF != "" {
 				h, _, err := net.SplitHostPort(u.Host)
 				if err != nil {
-					fmt.Fprintf(os.Stderr, "invalid URL %#v: %s\n", u.Host, err)
-					os.Exit(1)
+					logger.Fatalf("invalid URL %#v: %s\n", u.Host, err)
 				}
 				u.Host = net.JoinHostPort(h, *httpPortF)
 			} else if u.Port() == "" {
@@ -1369,7 +1324,7 @@ const (
 		}
 
 	default:
-		fmt.Fprintf(os.Stderr, "invalid host argument: %q (valid hosts: localhost)\n", *hostF)
+		logger.Fatalf("invalid host argument: %q (valid hosts: localhost)\n", *hostF)
 	}
 
 	// Wait for signal.
@@ -1443,8 +1398,7 @@ const (
 			addr := "grpc://localhost:8080"
 			u, err := url.Parse(addr)
 			if err != nil {
-				fmt.Fprintf(os.Stderr, "invalid URL %#v: %s\n", addr, err)
-				os.Exit(1)
+				logger.Fatalf("invalid URL %#v: %s\n", addr, err)
 			}
 			if *secureF {
 				u.Scheme = "grpcs"
@@ -1455,8 +1409,7 @@ const (
 			if *grpcPortF != "" {
 				h, _, err := net.SplitHostPort(u.Host)
 				if err != nil {
-					fmt.Fprintf(os.Stderr, "invalid URL %#v: %s\n", u.Host, err)
-					os.Exit(1)
+					logger.Fatalf("invalid URL %#v: %s\n", u.Host, err)
 				}
 				u.Host = net.JoinHostPort(h, *grpcPortF)
 			} else if u.Port() == "" {
@@ -1466,7 +1419,7 @@ const (
 		}
 
 	default:
-		fmt.Fprintf(os.Stderr, "invalid host argument: %q (valid hosts: localhost)\n", *hostF)
+		logger.Fatalf("invalid host argument: %q (valid hosts: localhost)\n", *hostF)
 	}
 
 	// Wait for signal.
@@ -1544,8 +1497,7 @@ const (
 			addr := "http://localhost:80"
 			u, err := url.Parse(addr)
 			if err != nil {
-				fmt.Fprintf(os.Stderr, "invalid URL %#v: %s\n", addr, err)
-				os.Exit(1)
+				logger.Fatalf("invalid URL %#v: %s\n", addr, err)
 			}
 			if *secureF {
 				u.Scheme = "https"
@@ -1556,8 +1508,7 @@ const (
 			if *httpPortF != "" {
 				h, _, err := net.SplitHostPort(u.Host)
 				if err != nil {
-					fmt.Fprintf(os.Stderr, "invalid URL %#v: %s\n", u.Host, err)
-					os.Exit(1)
+					logger.Fatalf("invalid URL %#v: %s\n", u.Host, err)
 				}
 				u.Host = net.JoinHostPort(h, *httpPortF)
 			} else if u.Port() == "" {
@@ -1570,8 +1521,7 @@ const (
 			addr := "grpc://localhost:8080"
 			u, err := url.Parse(addr)
 			if err != nil {
-				fmt.Fprintf(os.Stderr, "invalid URL %#v: %s\n", addr, err)
-				os.Exit(1)
+				logger.Fatalf("invalid URL %#v: %s\n", addr, err)
 			}
 			if *secureF {
 				u.Scheme = "grpcs"
@@ -1582,8 +1532,7 @@ const (
 			if *grpcPortF != "" {
 				h, _, err := net.SplitHostPort(u.Host)
 				if err != nil {
-					fmt.Fprintf(os.Stderr, "invalid URL %#v: %s\n", u.Host, err)
-					os.Exit(1)
+					logger.Fatalf("invalid URL %#v: %s\n", u.Host, err)
 				}
 				u.Host = net.JoinHostPort(h, *grpcPortF)
 			} else if u.Port() == "" {
@@ -1593,7 +1542,7 @@ const (
 		}
 
 	default:
-		fmt.Fprintf(os.Stderr, "invalid host argument: %q (valid hosts: localhost)\n", *hostF)
+		logger.Fatalf("invalid host argument: %q (valid hosts: localhost)\n", *hostF)
 	}
 
 	// Wait for signal.
diff --git a/codegen/import_test.go b/codegen/import_test.go
new file mode 100644
index 00000000..92856fed
--- /dev/null
+++ b/codegen/import_test.go
@@ -0,0 +1,170 @@
+package codegen
+
+import (
+	"reflect"
+	"sort"
+	"testing"
+
+	"goa.design/goa/v3/dsl"
+	"goa.design/goa/v3/eval"
+	"goa.design/goa/v3/expr"
+)
+
+func TestGetMetaTypeImports(t *testing.T) {
+	testdata := []struct {
+		name string
+		dsl  func()
+		want []string
+	}{
+		{
+			name: "payload-primitive",
+			dsl: func() {
+				dsl.Method("m", func() {
+					dsl.Payload(func() {
+						dsl.Attribute("a", dsl.String, func() {
+							dsl.Meta("struct:field:type", "CustomTypeString", "package/string")
+						})
+						dsl.Attribute("b", dsl.Int, func() {
+							dsl.Meta("struct:field:type", "CustomTypeInt", "package/int")
+						})
+					})
+				})
+			},
+			want: []string{
+				"package/string",
+				"package/int",
+			},
+		},
+		{
+			name: "payload-map",
+			dsl: func() {
+				dsl.Method("m", func() {
+					dsl.Payload(func() {
+						dsl.Attribute("a", dsl.MapOf(dsl.String, dsl.String, func() {
+							dsl.Key(func() {
+								dsl.Meta("struct:field:type", "CustomTypeMapKey", "package/map-key")
+							})
+							dsl.Elem(func() {
+								dsl.Meta("struct:field:type", "CustomTypeMapElem", "package/map-elem")
+							})
+						}))
+					})
+				})
+			},
+			want: []string{
+				"package/map-elem",
+				"package/map-key",
+			},
+		},
+		{
+			name: "payload-map-map",
+			dsl: func() {
+				dsl.Method("m", func() {
+					dsl.Payload(func() {
+						dsl.Attribute("a", dsl.MapOf(dsl.String, dsl.MapOf(dsl.String, dsl.String, func() {
+							dsl.Key(func() {
+								dsl.Meta("struct:field:type", "CustomTypeMapKey", "package/map-map-key")
+							})
+							dsl.Elem(func() {
+								dsl.Meta("struct:field:type", "CustomTypeMapElem", "package/map-map-elem")
+							})
+						}), func() {
+							dsl.Key(func() {
+								dsl.Meta("struct:field:type", "CustomTypeMapKey", "package/map-key")
+							})
+							dsl.Elem(func() {
+								dsl.Meta("struct:field:type", "CustomTypeMapElem", "package/map-elem")
+							})
+						}))
+					})
+				})
+			},
+			want: []string{
+				"package/map-key",
+				"package/map-map-elem",
+				"package/map-map-key",
+				"package/map-elem",
+			},
+		},
+		{
+			name: "payload-array",
+			dsl: func() {
+				dsl.Method("m", func() {
+					dsl.Payload(func() {
+						dsl.Attribute("a", dsl.ArrayOf(dsl.String, func() {
+							dsl.Meta("struct:field:type", "SomeCustomTypeArrayElem", "package/array-elem")
+						}), func() {
+							dsl.Meta("struct:field:type", "SomeCustomTypeArray", "package/array")
+						})
+					})
+				})
+			},
+			want: []string{
+				"package/array-elem",
+				"package/array",
+			},
+		},
+		{
+			name: "result",
+			dsl: func() {
+				dsl.Method("m", func() {
+					dsl.Result(func() {
+						dsl.Attribute("a", dsl.String, func() {
+							dsl.Meta("struct:field:type", "CustomTypeString", "package/result-string")
+						})
+						dsl.Attribute("b", dsl.ArrayOf(dsl.String, func() {
+							dsl.Meta("struct:field:type", "SomeCustomTypeArrayElem", "package/result-array-elem")
+						}), func() {
+							dsl.Meta("struct:field:type", "SomeCustomTypeArray", "package/result-array")
+						})
+						dsl.Attribute("a", dsl.MapOf(dsl.String, dsl.String, func() {
+							dsl.Key(func() {
+								dsl.Meta("struct:field:type", "CustomTypeMapKey", "package/result-map-key")
+							})
+							dsl.Elem(func() {
+								dsl.Meta("struct:field:type", "CustomTypeMapElem", "package/result-map-elem")
+							})
+						}))
+					})
+				})
+			},
+			want: []string{
+				"package/result-string",
+				"package/result-array",
+				"package/result-array-elem",
+				"package/result-map-key",
+				"package/result-map-elem",
+			},
+		},
+	}
+	for _, tt := range testdata {
+		t.Run(tt.name, func(t *testing.T) {
+			eval.Context = &eval.DSLContext{}
+			serviceExpr := &expr.ServiceExpr{}
+			eval.Execute(tt.dsl, serviceExpr)
+			if eval.Context.Errors != nil {
+				t.Fatalf("%s: Service DSL failed unexpectedly with %s", tt.name, eval.Context.Errors)
+			}
+			for _, methodExpr := range serviceExpr.Methods {
+				eval.Execute(methodExpr.DSLFunc, methodExpr)
+				if eval.Context.Errors != nil {
+					t.Fatalf("%s: Method DSL failed unexpectedly with %s", tt.name, eval.Context.Errors)
+				}
+			}
+			for _, methodExpr := range serviceExpr.Methods {
+				var got []string
+				for _, v := range GetMetaTypeImports(methodExpr.Payload) {
+					got = append(got, v.Path)
+				}
+				for _, v := range GetMetaTypeImports(methodExpr.Result) {
+					got = append(got, v.Path)
+				}
+				sort.Strings(got)
+				sort.Strings(tt.want)
+				if !reflect.DeepEqual(tt.want, got) {
+					t.Errorf("want %+v, got %+v", tt.want, got)
+				}
+			}
+		})
+	}
+}