Repository: deepmap/oapi-codegen
Pull request #649 (2022-07-01 02:38:11)

Allow providing custom error funcs in strict chi server
The new strict server option is great, but it doesn't provide an option to supply custom error functions like the standard server offers. This change adds options to optionally supply custom functions for both request and response errors via separate funcs. It defaults to functions that should preserve existing behavior.

One use case for this is when you don't want to send the detailed error back to the client, thus revealing details of the server, and instead want to log it and return a generic error in its place.



Patch:
diff --git a/README.md b/README.md
index 58fe1c9..6bfea4a 100644
--- a/README.md
+++ b/README.md
@@ -682,8 +682,9 @@ output:
   externalref.gen.go
 package: externalref
 generate:
-  - types
-  - skip-prune
+  models: true
+output-options:
+  skip-prune: true
 import-mapping:
   ./packageA/spec.yaml: github.com/deepmap/oapi-codegen/internal/test/externalref/packageA
   ./packageB/spec.yaml: github.com/deepmap/oapi-codegen/internal/test/externalref/packageB
diff --git a/examples/petstore-expanded/chi/api/petstore.gen.go b/examples/petstore-expanded/chi/api/petstore.gen.go
index 4a514dc..feb20aa 100644
--- a/examples/petstore-expanded/chi/api/petstore.gen.go
+++ b/examples/petstore-expanded/chi/api/petstore.gen.go
@@ -95,9 +95,6 @@ func (siw *ServerInterfaceWrapper) FindPets(w http.ResponseWriter, r *http.Reque
 	var params FindPetsParams
 
 	// ------------- Optional query parameter "tags" -------------
-	if paramValue := r.URL.Query().Get("tags"); paramValue != "" {
-
-	}
 
 	err = runtime.BindQueryParameter("form", true, false, "tags", r.URL.Query(), &params.Tags)
 	if err != nil {
@@ -106,9 +103,6 @@ func (siw *ServerInterfaceWrapper) FindPets(w http.ResponseWriter, r *http.Reque
 	}
 
 	// ------------- Optional query parameter "limit" -------------
-	if paramValue := r.URL.Query().Get("limit"); paramValue != "" {
-
-	}
 
 	err = runtime.BindQueryParameter("form", true, false, "limit", r.URL.Query(), &params.Limit)
 	if err != nil {
diff --git a/examples/petstore-expanded/gin/api/petstore-server.gen.go b/examples/petstore-expanded/gin/api/petstore-server.gen.go
index eeba01a..09ec102 100644
--- a/examples/petstore-expanded/gin/api/petstore-server.gen.go
+++ b/examples/petstore-expanded/gin/api/petstore-server.gen.go
@@ -51,9 +51,6 @@ func (siw *ServerInterfaceWrapper) FindPets(c *gin.Context) {
 	var params FindPetsParams
 
 	// ------------- Optional query parameter "tags" -------------
-	if paramValue := c.Query("tags"); paramValue != "" {
-
-	}
 
 	err = runtime.BindQueryParameter("form", true, false, "tags", c.Request.URL.Query(), &params.Tags)
 	if err != nil {
@@ -62,9 +59,6 @@ func (siw *ServerInterfaceWrapper) FindPets(c *gin.Context) {
 	}
 
 	// ------------- Optional query parameter "limit" -------------
-	if paramValue := c.Query("limit"); paramValue != "" {
-
-	}
 
 	err = runtime.BindQueryParameter("form", true, false, "limit", c.Request.URL.Query(), &params.Limit)
 	if err != nil {
diff --git a/examples/petstore-expanded/gorilla/api/petstore.gen.go b/examples/petstore-expanded/gorilla/api/petstore.gen.go
index 5a3e901..0531f7f 100644
--- a/examples/petstore-expanded/gorilla/api/petstore.gen.go
+++ b/examples/petstore-expanded/gorilla/api/petstore.gen.go
@@ -95,9 +95,6 @@ func (siw *ServerInterfaceWrapper) FindPets(w http.ResponseWriter, r *http.Reque
 	var params FindPetsParams
 
 	// ------------- Optional query parameter "tags" -------------
-	if paramValue := r.URL.Query().Get("tags"); paramValue != "" {
-
-	}
 
 	err = runtime.BindQueryParameter("form", true, false, "tags", r.URL.Query(), &params.Tags)
 	if err != nil {
@@ -106,9 +103,6 @@ func (siw *ServerInterfaceWrapper) FindPets(w http.ResponseWriter, r *http.Reque
 	}
 
 	// ------------- Optional query parameter "limit" -------------
-	if paramValue := r.URL.Query().Get("limit"); paramValue != "" {
-
-	}
 
 	err = runtime.BindQueryParameter("form", true, false, "limit", r.URL.Query(), &params.Limit)
 	if err != nil {
diff --git a/examples/petstore-expanded/strict/api/petstore-server.gen.go b/examples/petstore-expanded/strict/api/petstore-server.gen.go
index 7c10ee2..b229efd 100644
--- a/examples/petstore-expanded/strict/api/petstore-server.gen.go
+++ b/examples/petstore-expanded/strict/api/petstore-server.gen.go
@@ -55,9 +55,6 @@ func (siw *ServerInterfaceWrapper) FindPets(w http.ResponseWriter, r *http.Reque
 	var params FindPetsParams
 
 	// ------------- Optional query parameter "tags" -------------
-	if paramValue := r.URL.Query().Get("tags"); paramValue != "" {
-
-	}
 
 	err = runtime.BindQueryParameter("form", true, false, "tags", r.URL.Query(), &params.Tags)
 	if err != nil {
@@ -66,9 +63,6 @@ func (siw *ServerInterfaceWrapper) FindPets(w http.ResponseWriter, r *http.Reque
 	}
 
 	// ------------- Optional query parameter "limit" -------------
-	if paramValue := r.URL.Query().Get("limit"); paramValue != "" {
-
-	}
 
 	err = runtime.BindQueryParameter("form", true, false, "limit", r.URL.Query(), &params.Limit)
 	if err != nil {
@@ -376,13 +370,30 @@ type StrictHandlerFunc func(ctx context.Context, w http.ResponseWriter, r *http.
 
 type StrictMiddlewareFunc func(f StrictHandlerFunc, operationID string) StrictHandlerFunc
 
+type StrictChiServerOptions struct {
+	RequestErrorHandlerFunc  func(w http.ResponseWriter, r *http.Request, err error)
+	ResponseErrorHandlerFunc func(w http.ResponseWriter, r *http.Request, err error)
+}
+
 func NewStrictHandler(ssi StrictServerInterface, middlewares []StrictMiddlewareFunc) ServerInterface {
-	return &strictHandler{ssi: ssi, middlewares: middlewares}
+	return &strictHandler{ssi: ssi, middlewares: middlewares, options: StrictChiServerOptions{
+		RequestErrorHandlerFunc: func(w http.ResponseWriter, r *http.Request, err error) {
+			http.Error(w, err.Error(), http.StatusBadRequest)
+		},
+		ResponseErrorHandlerFunc: func(w http.ResponseWriter, r *http.Request, err error) {
+			http.Error(w, err.Error(), http.StatusInternalServerError)
+		},
+	}}
+}
+
+func NewStrictHandlerWithOptions(ssi StrictServerInterface, middlewares []StrictMiddlewareFunc, options StrictChiServerOptions) ServerInterface {
+	return &strictHandler{ssi: ssi, middlewares: middlewares, options: options}
 }
 
 type strictHandler struct {
 	ssi         StrictServerInterface
 	middlewares []StrictMiddlewareFunc
+	options     StrictChiServerOptions
 }
 
 // FindPets operation middleware
@@ -410,10 +421,10 @@ func (sh *strictHandler) FindPets(w http.ResponseWriter, r *http.Request, params
 		w.WriteHeader(v.StatusCode)
 		writeJSON(w, v)
 	case error:
-		http.Error(w, v.Error(), http.StatusInternalServerError)
+		sh.options.ResponseErrorHandlerFunc(w, r, v)
 	case nil:
 	default:
-		http.Error(w, fmt.Sprintf("Unexpected response type: %T", v), http.StatusInternalServerError)
+		sh.options.ResponseErrorHandlerFunc(w, r, fmt.Errorf("Unexpected response type: %T", v))
 	}
 }
 
@@ -423,7 +434,7 @@ func (sh *strictHandler) AddPet(w http.ResponseWriter, r *http.Request) {
 
 	var body AddPetJSONRequestBody
 	if err := json.NewDecoder(r.Body).Decode(&body); err != nil {
-		http.Error(w, "can't decode JSON body: "+err.Error(), http.StatusBadRequest)
+		sh.options.RequestErrorHandlerFunc(w, r, fmt.Errorf("can't decode JSON body: %w", err))
 		return
 	}
 	request.Body = &body
@@ -447,10 +458,10 @@ func (sh *strictHandler) AddPet(w http.ResponseWriter, r *http.Request) {
 		w.WriteHeader(v.StatusCode)
 		writeJSON(w, v)
 	case error:
-		http.Error(w, v.Error(), http.StatusInternalServerError)
+		sh.options.ResponseErrorHandlerFunc(w, r, v)
 	case nil:
 	default:
-		http.Error(w, fmt.Sprintf("Unexpected response type: %T", v), http.StatusInternalServerError)
+		sh.options.ResponseErrorHandlerFunc(w, r, fmt.Errorf("Unexpected response type: %T", v))
 	}
 }
 
@@ -477,10 +488,10 @@ func (sh *strictHandler) DeletePet(w http.ResponseWriter, r *http.Request, id in
 		w.WriteHeader(v.StatusCode)
 		writeJSON(w, v)
 	case error:
-		http.Error(w, v.Error(), http.StatusInternalServerError)
+		sh.options.ResponseErrorHandlerFunc(w, r, v)
 	case nil:
 	default:
-		http.Error(w, fmt.Sprintf("Unexpected response type: %T", v), http.StatusInternalServerError)
+		sh.options.ResponseErrorHandlerFunc(w, r, fmt.Errorf("Unexpected response type: %T", v))
 	}
 }
 
@@ -509,10 +520,10 @@ func (sh *strictHandler) FindPetByID(w http.ResponseWriter, r *http.Request, id
 		w.WriteHeader(v.StatusCode)
 		writeJSON(w, v)
 	case error:
-		http.Error(w, v.Error(), http.StatusInternalServerError)
+		sh.options.ResponseErrorHandlerFunc(w, r, v)
 	case nil:
 	default:
-		http.Error(w, fmt.Sprintf("Unexpected response type: %T", v), http.StatusInternalServerError)
+		sh.options.ResponseErrorHandlerFunc(w, r, fmt.Errorf("Unexpected response type: %T", v))
 	}
 }
 
diff --git a/pkg/codegen/codegen.go b/pkg/codegen/codegen.go
index 6d9f345..7d03752 100644
--- a/pkg/codegen/codegen.go
+++ b/pkg/codegen/codegen.go
@@ -621,9 +621,10 @@ func GenerateEnums(t *template.Template, types []TypeDefinition) (string, error)
 				wrapper = `"`
 			}
 			enums = append(enums, EnumDefinition{
-				Schema:       tp.Schema,
-				TypeName:     tp.TypeName,
-				ValueWrapper: wrapper,
+				Schema:         tp.Schema,
+				TypeName:       tp.TypeName,
+				ValueWrapper:   wrapper,
+				PrefixTypeName: globalState.options.Compatibility.AlwaysPrefixEnumValues,
 			})
 		}
 	}
@@ -640,8 +641,8 @@ func GenerateEnums(t *template.Template, types []TypeDefinition) (string, error)
 			for e1key := range e1.GetValues() {
 				_, found := e2.GetValues()[e1key]
 				if found {
-					e1.Conflicts = true
-					e2.Conflicts = true
+					e1.PrefixTypeName = true
+					e2.PrefixTypeName = true
 					enums[i] = e1
 					enums[j] = e2
 					break
@@ -657,7 +658,7 @@ func GenerateEnums(t *template.Template, types []TypeDefinition) (string, error)
 			}
 			_, found := e1.Schema.EnumValues[tp.TypeName]
 			if found {
-				e1.Conflicts = true
+				e1.PrefixTypeName = true
 				enums[i] = e1
 			}
 		}
@@ -666,7 +667,7 @@ func GenerateEnums(t *template.Template, types []TypeDefinition) (string, error)
 		// type name.
 		_, found := e1.GetValues()[e1.TypeName]
 		if found {
-			e1.Conflicts = true
+			e1.PrefixTypeName = true
 			enums[i] = e1
 		}
 	}
@@ -819,7 +820,6 @@ func GetTypeDefinitionsImports(swagger *openapi3.T, excludeSchemas []string) (ma
 }
 
 func GetSchemaImports(schemas map[string]*openapi3.SchemaRef, excludeSchemas []string) (map[string]goImport, error) {
-	var err error
 	res := map[string]goImport{}
 	excludeSchemasMap := make(map[string]bool)
 	for _, schema := range excludeSchemas {
@@ -835,17 +835,20 @@ func GetSchemaImports(schemas map[string]*openapi3.SchemaRef, excludeSchemas []s
 			continue
 		}
 
-		res, err = GetImports(schema.Properties)
+		imprts, err := GetImports(schema.Properties)
 		if err != nil {
 			return nil, err
 		}
+
+		for s, gi := range imprts {
+			res[s] = gi
+		}
 	}
 	return res, nil
 }
 
 func GetRequestBodiesImports(bodies map[string]*openapi3.RequestBodyRef) (map[string]goImport, error) {
-	var res map[string]goImport
-	var err error
+	res := map[string]goImport{}
 	for _, requestBodyName := range SortedRequestBodyKeys(bodies) {
 		requestBodyRef := bodies[requestBodyName]
 		response := requestBodyRef.Value
@@ -856,18 +859,21 @@ func GetRequestBodiesImports(bodies map[string]*openapi3.RequestBodyRef) (map[st
 				continue
 			}
 
-			res, err = GetImports(schema.Value.Properties)
+			imprts, err := GetImports(schema.Value.Properties)
 			if err != nil {
 				return nil, err
 			}
+
+			for s, gi := range imprts {
+				res[s] = gi
+			}
 		}
 	}
 	return res, nil
 }
 
 func GetResponsesImports(responses map[string]*openapi3.ResponseRef) (map[string]goImport, error) {
-	var res map[string]goImport
-	var err error
+	res := map[string]goImport{}
 	for _, responseName := range SortedResponsesKeys(responses) {
 		responseOrRef := responses[responseName]
 		response := responseOrRef.Value
@@ -878,10 +884,14 @@ func GetResponsesImports(responses map[string]*openapi3.ResponseRef) (map[string
 				continue
 			}
 
-			res, err = GetImports(schema.Value.Properties)
+			imprts, err := GetImports(schema.Value.Properties)
 			if err != nil {
 				return nil, err
 			}
+
+			for s, gi := range imprts {
+				res[s] = gi
+			}
 		}
 	}
 	return res, nil
diff --git a/pkg/codegen/configuration.go b/pkg/codegen/configuration.go
index c8530c8..727d813 100644
--- a/pkg/codegen/configuration.go
+++ b/pkg/codegen/configuration.go
@@ -26,7 +26,7 @@ type GenerateOptions struct {
 	EchoServer    bool `yaml:"echo-server,omitempty"`    // EchoServer specifies whether to generate echo server boilerplate
 	GinServer     bool `yaml:"gin-server,omitempty"`     // GinServer specifies whether to generate gin server boilerplate
 	GorillaServer bool `yaml:"gorilla-server,omitempty"` // GorillaServer specifies whether to generate Gorilla server boilerplate
-	Strict        bool `yaml:"strict-server,omitempty"` // Strict specifies whether to generate strict server wrapper
+	Strict        bool `yaml:"strict-server,omitempty"`  // Strict specifies whether to generate strict server wrapper
 	Client        bool `yaml:"client,omitempty"`         // Client specifies whether to generate client boilerplate
 	Models        bool `yaml:"models,omitempty"`         // Models specifies whether to generate type definitions
 	EmbeddedSpec  bool `yaml:"embedded-spec,omitempty"`  // Whether to embed the swagger spec in the generated code
@@ -57,6 +57,9 @@ type CompatibilityOptions struct {
 	// When an object contains no members, and only an additionalProperties specification,
 	// it is flattened to a map. Set
 	DisableFlattenAdditionalProperties bool `yaml:"disable-flatten-additional-properties,omitempty"`
+	// When set to true, always prefix enum values with their type name instead of only
+	// when typenames would be conflicting.
+	AlwaysPrefixEnumValues bool `yaml:"always-prefix-enum-values,omitempty"`
 }
 
 // OutputOptions are used to modify the output code in some way.
diff --git a/pkg/codegen/operations.go b/pkg/codegen/operations.go
index 32c50c0..3d63f85 100644
--- a/pkg/codegen/operations.go
+++ b/pkg/codegen/operations.go
@@ -615,7 +615,8 @@ func GenerateBodyDefinitions(operationID string, bodyOrRef *openapi3.RequestBody
 	var bodyDefinitions []RequestBodyDefinition
 	var typeDefinitions []TypeDefinition
 
-	for contentType, content := range body.Content {
+	for _, contentType := range SortedContentKeys(body.Content) {
+		content := body.Content[contentType]
 		var tag string
 		var defaultBody bool
 
diff --git a/pkg/codegen/schema.go b/pkg/codegen/schema.go
index f8da94f..ce5f586 100644
--- a/pkg/codegen/schema.go
+++ b/pkg/codegen/schema.go
@@ -111,15 +111,17 @@ type EnumDefinition struct {
 	// ValueWrapper wraps the value. It's used to conditionally apply quotes
 	// around strings.
 	ValueWrapper string
-	// Conflicts is set to true when this enum conflicts with another in
-	// terms of TypeNames
-	Conflicts bool
+	// PrefixTypeName determines if the enum value is prefixed with its TypeName.
+	// This is set to true when this enum conflicts with another in terms of
+	// TypeNames or when explicitly requested via the
+	// `compatibility.always-prefix-enum-values` option.
+	PrefixTypeName bool
 }
 
 // GetValues generates enum names in a way to minimize global conflicts
 func (e *EnumDefinition) GetValues() map[string]string {
 	// in case there are no conflicts, it's safe to use the values as-is
-	if !e.Conflicts {
+	if !e.PrefixTypeName {
 		return e.Schema.EnumValues
 	}
 	// If we do have conflicts, we will prefix the enum's typename to the values.
diff --git a/pkg/codegen/templates/chi/chi-middleware.tmpl b/pkg/codegen/templates/chi/chi-middleware.tmpl
index 30270cc..7311ec4 100644
--- a/pkg/codegen/templates/chi/chi-middleware.tmpl
+++ b/pkg/codegen/templates/chi/chi-middleware.tmpl
@@ -47,27 +47,32 @@ func (siw *ServerInterfaceWrapper) {{$opid}}(w http.ResponseWriter, r *http.Requ
     // Parameter object where we will unmarshal all parameters from the context
     var params {{.OperationId}}Params
 
-    {{range $paramIdx, $param := .QueryParams}}// ------------- {{if .Required}}Required{{else}}Optional{{end}} query parameter "{{.ParamName}}" -------------
-      if paramValue := r.URL.Query().Get("{{.ParamName}}"); paramValue != "" {
+    {{range $paramIdx, $param := .QueryParams}}
+      {{- if (or (or .Required .IsPassThrough) (or .IsJson .IsStyled)) -}}
+        // ------------- {{if .Required}}Required{{else}}Optional{{end}} query parameter "{{.ParamName}}" -------------
+      {{ end }}
+      {{ if (or (or .Required .IsPassThrough) .IsJson) }}
+        if paramValue := r.URL.Query().Get("{{.ParamName}}"); paramValue != "" {
 
-      {{if .IsPassThrough}}
-        params.{{.GoName}} = {{if not .Required}}&{{end}}paramValue
-      {{end}}
+        {{if .IsPassThrough}}
+          params.{{.GoName}} = {{if not .Required}}&{{end}}paramValue
+        {{end}}
 
-      {{if .IsJson}}
-        var value {{.TypeDef}}
-        err = json.Unmarshal([]byte(paramValue), &value)
-        if err != nil {
-          siw.ErrorHandlerFunc(w, r, &UnmarshalingParamError{ParamName: "{{.ParamName}}", Err: err})
-          return
-        }
+        {{if .IsJson}}
+          var value {{.TypeDef}}
+          err = json.Unmarshal([]byte(paramValue), &value)
+          if err != nil {
+            siw.ErrorHandlerFunc(w, r, &UnmarshalingParamError{ParamName: "{{.ParamName}}", Err: err})
+            return
+          }
 
-        params.{{.GoName}} = {{if not .Required}}&{{end}}value
+          params.{{.GoName}} = {{if not .Required}}&{{end}}value
+        {{end}}
+        }{{if .Required}} else {
+            siw.ErrorHandlerFunc(w, r, &RequiredParamError{ParamName: "{{.ParamName}}"})
+            return
+        }{{end}}
       {{end}}
-      }{{if .Required}} else {
-          siw.ErrorHandlerFunc(w, r, &RequiredParamError{ParamName: "{{.ParamName}}"})
-          return
-      }{{end}}
       {{if .IsStyled}}
       err = runtime.BindQueryParameter("{{.Style}}", {{.Explode}}, {{.Required}}, "{{.ParamName}}", r.URL.Query(), &params.{{.GoName}})
       if err != nil {
diff --git a/pkg/codegen/templates/echo/echo-wrappers.tmpl b/pkg/codegen/templates/echo/echo-wrappers.tmpl
index 537751a..3402583 100644
--- a/pkg/codegen/templates/echo/echo-wrappers.tmpl
+++ b/pkg/codegen/templates/echo/echo-wrappers.tmpl
@@ -32,7 +32,10 @@ func (w *ServerInterfaceWrapper) {{.OperationId}} (ctx echo.Context) error {
 {{if .RequiresParamObject}}
     // Parameter object where we will unmarshal all parameters from the context
     var params {{.OperationId}}Params
-{{range $paramIdx, $param := .QueryParams}}// ------------- {{if .Required}}Required{{else}}Optional{{end}} query parameter "{{.ParamName}}" -------------
+{{range $paramIdx, $param := .QueryParams}}
+    {{- if (or (or .Required .IsPassThrough) (or .IsJson .IsStyled)) -}}
+      // ------------- {{if .Required}}Required{{else}}Optional{{end}} query parameter "{{.ParamName}}" -------------
+    {{ end }}
     {{if .IsStyled}}
     err = runtime.BindQueryParameter("{{.Style}}", {{.Explode}}, {{.Required}}, "{{.ParamName}}", ctx.QueryParams(), &params.{{.GoName}})
     if err != nil {
diff --git a/pkg/codegen/templates/gin/gin-wrappers.tmpl b/pkg/codegen/templates/gin/gin-wrappers.tmpl
index 27bf19c..59d03f5 100644
--- a/pkg/codegen/templates/gin/gin-wrappers.tmpl
+++ b/pkg/codegen/templates/gin/gin-wrappers.tmpl
@@ -46,27 +46,32 @@ func (siw *ServerInterfaceWrapper) {{$opid}}(c *gin.Context) {
     // Parameter object where we will unmarshal all parameters from the context
     var params {{.OperationId}}Params
 
-    {{range $paramIdx, $param := .QueryParams}}// ------------- {{if .Required}}Required{{else}}Optional{{end}} query parameter "{{.ParamName}}" -------------
-      if paramValue := c.Query("{{.ParamName}}"); paramValue != "" {
+    {{range $paramIdx, $param := .QueryParams}}
+      {{- if (or (or .Required .IsPassThrough) (or .IsJson .IsStyled)) -}}
+        // ------------- {{if .Required}}Required{{else}}Optional{{end}} query parameter "{{.ParamName}}" -------------
+      {{ end }}
+      {{ if (or (or .Required .IsPassThrough) .IsJson) }}
+        if paramValue := c.Query("{{.ParamName}}"); paramValue != "" {
 
-      {{if .IsPassThrough}}
-        params.{{.GoName}} = {{if not .Required}}&{{end}}paramValue
-      {{end}}
+        {{if .IsPassThrough}}
+          params.{{.GoName}} = {{if not .Required}}&{{end}}paramValue
+        {{end}}
 
-      {{if .IsJson}}
-        var value {{.TypeDef}}
-        err = json.Unmarshal([]byte(paramValue), &value)
-        if err != nil {
-          c.JSON(http.StatusBadRequest, gin.H{"msg": "Error unmarshaling parameter '{{.ParamName}}' as JSON"})
-          return
-        }
+        {{if .IsJson}}
+          var value {{.TypeDef}}
+          err = json.Unmarshal([]byte(paramValue), &value)
+          if err != nil {
+            c.JSON(http.StatusBadRequest, gin.H{"msg": "Error unmarshaling parameter '{{.ParamName}}' as JSON"})
+            return
+          }
 
-        params.{{.GoName}} = {{if not .Required}}&{{end}}value
+          params.{{.GoName}} = {{if not .Required}}&{{end}}value
+        {{end}}
+        }{{if .Required}} else {
+            c.JSON(http.StatusBadRequest, gin.H{"msg": "Query argument {{.ParamName}} is required, but not found"})
+            return
+        }{{end}}
       {{end}}
-      }{{if .Required}} else {
-          c.JSON(http.StatusBadRequest, gin.H{"msg": "Query argument {{.ParamName}} is required, but not found"})
-          return
-      }{{end}}
       {{if .IsStyled}}
       err = runtime.BindQueryParameter("{{.Style}}", {{.Explode}}, {{.Required}}, "{{.ParamName}}", c.Request.URL.Query(), &params.{{.GoName}})
       if err != nil {
diff --git a/pkg/codegen/templates/gorilla/gorilla-middleware.tmpl b/pkg/codegen/templates/gorilla/gorilla-middleware.tmpl
index a08bc5e..34b866a 100644
--- a/pkg/codegen/templates/gorilla/gorilla-middleware.tmpl
+++ b/pkg/codegen/templates/gorilla/gorilla-middleware.tmpl
@@ -47,27 +47,32 @@ func (siw *ServerInterfaceWrapper) {{$opid}}(w http.ResponseWriter, r *http.Requ
     // Parameter object where we will unmarshal all parameters from the context
     var params {{.OperationId}}Params
 
-    {{range $paramIdx, $param := .QueryParams}}// ------------- {{if .Required}}Required{{else}}Optional{{end}} query parameter "{{.ParamName}}" -------------
-      if paramValue := r.URL.Query().Get("{{.ParamName}}"); paramValue != "" {
+    {{range $paramIdx, $param := .QueryParams}}
+      {{- if (or (or .Required .IsPassThrough) (or .IsJson .IsStyled)) -}}
+        // ------------- {{if .Required}}Required{{else}}Optional{{end}} query parameter "{{.ParamName}}" -------------
+      {{ end }}
+      {{ if (or (or .Required .IsPassThrough) .IsJson) }}
+        if paramValue := r.URL.Query().Get("{{.ParamName}}"); paramValue != "" {
 
-      {{if .IsPassThrough}}
-        params.{{.GoName}} = {{if not .Required}}&{{end}}paramValue
-      {{end}}
+        {{if .IsPassThrough}}
+          params.{{.GoName}} = {{if not .Required}}&{{end}}paramValue
+        {{end}}
 
-      {{if .IsJson}}
-        var value {{.TypeDef}}
-        err = json.Unmarshal([]byte(paramValue), &value)
-        if err != nil {
-          siw.ErrorHandlerFunc(w, r, &UnmarshalingParamError{ParamName: "{{.ParamName}}", Err: err})
-          return
-        }
+        {{if .IsJson}}
+          var value {{.TypeDef}}
+          err = json.Unmarshal([]byte(paramValue), &value)
+          if err != nil {
+            siw.ErrorHandlerFunc(w, r, &UnmarshalingParamError{ParamName: "{{.ParamName}}", Err: err})
+            return
+          }
 
-        params.{{.GoName}} = {{if not .Required}}&{{end}}value
+          params.{{.GoName}} = {{if not .Required}}&{{end}}value
+        {{end}}
+        }{{if .Required}} else {
+            siw.ErrorHandlerFunc(w, r, &RequiredParamError{ParamName: "{{.ParamName}}"})
+            return
+        }{{end}}
       {{end}}
-      }{{if .Required}} else {
-          siw.ErrorHandlerFunc(w, r, &RequiredParamError{ParamName: "{{.ParamName}}"})
-          return
-      }{{end}}
       {{if .IsStyled}}
       err = runtime.BindQueryParameter("{{.Style}}", {{.Explode}}, {{.Required}}, "{{.ParamName}}", r.URL.Query(), &params.{{.GoName}})
       if err != nil {
diff --git a/pkg/codegen/templates/strict/strict-chi.tmpl b/pkg/codegen/templates/strict/strict-chi.tmpl
index ba2da83..c8465db 100644
--- a/pkg/codegen/templates/strict/strict-chi.tmpl
+++ b/pkg/codegen/templates/strict/strict-chi.tmpl
@@ -2,13 +2,30 @@ type StrictHandlerFunc func(ctx context.Context, w http.ResponseWriter, r *http.
 
 type StrictMiddlewareFunc func(f StrictHandlerFunc, operationID string) StrictHandlerFunc
 
+type StrictChiServerOptions struct {
+    RequestErrorHandlerFunc  func(w http.ResponseWriter, r *http.Request, err error)
+    ResponseErrorHandlerFunc func(w http.ResponseWriter, r *http.Request, err error)
+}
+
 func NewStrictHandler(ssi StrictServerInterface, middlewares []StrictMiddlewareFunc) ServerInterface {
-    return &strictHandler{ssi: ssi, middlewares: middlewares}
+    return &strictHandler{ssi: ssi, middlewares: middlewares, options: StrictChiServerOptions {
+        RequestErrorHandlerFunc: func(w http.ResponseWriter, r *http.Request, err error) {
+            http.Error(w, err.Error(), http.StatusBadRequest)
+        },
+        ResponseErrorHandlerFunc: func(w http.ResponseWriter, r *http.Request, err error) {
+            http.Error(w, err.Error(), http.StatusInternalServerError)
+        },
+    }}
+}
+
+func NewStrictHandlerWithOptions(ssi StrictServerInterface, middlewares []StrictMiddlewareFunc, options StrictChiServerOptions) ServerInterface {
+    return &strictHandler{ssi: ssi, middlewares: middlewares, options: options}
 }
 
 type strictHandler struct {
     ssi StrictServerInterface
     middlewares []StrictMiddlewareFunc
+    options StrictChiServerOptions
 }
 
 {{range .}}
@@ -36,24 +53,24 @@ type strictHandler struct {
                 {{if eq .NameTag "JSON" -}}
                     var body {{$opid}}{{.NameTag}}RequestBody
                     if err := json.NewDecoder(r.Body).Decode(&body); err != nil {
-                        http.Error(w, "can't decode JSON body: " + err.Error(), http.StatusBadRequest)
+                        sh.options.RequestErrorHandlerFunc(w, r, fmt.Errorf("can't decode JSON body: %w", err))
                         return
                     }
                     request.{{if $multipleBodies}}{{.NameTag}}{{end}}Body = &body
                 {{else if eq .NameTag "Formdata" -}}
                     if err := r.ParseForm(); err != nil {
-                        http.Error(w, "can't decode formdata: " + err.Error(), http.StatusBadRequest)
+                        sh.options.RequestErrorHandlerFunc(w, r, fmt.Errorf("can't decode formdata: %w", err))
                         return
                     }
                     var body {{$opid}}{{.NameTag}}RequestBody
                     if err := runtime.BindForm(&body, r.Form, nil, nil); err != nil {
-                        http.Error(w, "can't bind formdata: " + err.Error(), http.StatusBadRequest)
+                        sh.options.RequestErrorHandlerFunc(w, r, fmt.Errorf("can't bind formdata: %w", err))
                         return
                     }
                     request.{{if $multipleBodies}}{{.NameTag}}{{end}}Body = &body
                 {{else if eq .NameTag "Multipart" -}}
                     if reader, err := r.MultipartReader(); err != nil {
-                        http.Error(w, "can't decode multipart body: " + err.Error(), http.StatusBadRequest)
+                        sh.options.RequestErrorHandlerFunc(w, r, fmt.Errorf("can't decode multipart body: %w", err))
                         return
                     } else {
                         request.{{if $multipleBodies}}{{.NameTag}}{{end}}Body = reader
@@ -61,7 +78,7 @@ type strictHandler struct {
                 {{else if eq .NameTag "Text" -}}
                     data, err := ioutil.ReadAll(r.Body)
                     if err != nil {
-                        http.Error(w, "can't read body: " + err.Error(), http.StatusBadRequest)
+                        sh.options.RequestErrorHandlerFunc(w, r, fmt.Errorf("can't read body: %w", err))
                         return
                     }
                     body := {{$opid}}{{.NameTag}}RequestBody(data)
@@ -114,7 +131,7 @@ type strictHandler struct {
                     {{else if eq .NameTag "Multipart" -}}
                         defer writer.Close()
                         if err := v(writer); err != nil {
-                            http.Error(w, err.Error(), http.StatusInternalServerError)
+                            sh.options.ResponseErrorHandlerFunc(w, r, err)
                         }
                     {{else -}}
                         if closer, ok := v.Body.(io.ReadCloser); ok {
@@ -132,10 +149,10 @@ type strictHandler struct {
                 {{end}}{{/* if eq 0 (len .Contents) */ -}}
             {{end}}{{/* range .Responses */ -}}
             case error:
-                http.Error(w, v.Error(), http.StatusInternalServerError)
+                sh.options.ResponseErrorHandlerFunc(w, r, v)
             case nil:
             default:
-                http.Error(w, fmt.Sprintf("Unexpected response type: %T", v), http.StatusInternalServerError)
+                sh.options.ResponseErrorHandlerFunc(w, r, fmt.Errorf("Unexpected response type: %T", v))
         }
     }
 {{end}}
diff --git a/pkg/codegen/templates/union.tmpl b/pkg/codegen/templates/union.tmpl
index a29e2d0..ac7657d 100644
--- a/pkg/codegen/templates/union.tmpl
+++ b/pkg/codegen/templates/union.tmpl
@@ -65,9 +65,11 @@
                 return nil, err
             }
             object := make(map[string]json.RawMessage)
-            err = json.Unmarshal(b, &object)
-            if err != nil {
+            if t.union != nil {
+              err = json.Unmarshal(b, &object)
+              if err != nil {
                 return nil, err
+              }
             }
             {{range .Schema.Properties}}
                 object["{{.JsonFieldName}}"], err = json.Marshal(t.{{.GoFieldName}})
diff --git a/pkg/codegen/utils.go b/pkg/codegen/utils.go
index afa2059..e5f79fe 100644
--- a/pkg/codegen/utils.go
+++ b/pkg/codegen/utils.go
@@ -604,6 +604,8 @@ func typeNamePrefix(name string) (prefix string) {
 			prefix += "Asterisk"
 		case '^':
 			prefix += "Caret"
+		case '%':
+			prefix += "Percent"
 		default:
 			// Prepend "N" to schemas starting with a number
 			if prefix == "" && unicode.IsDigit(r) {


Test patch:
diff --git a/internal/test/components/components.gen.go b/internal/test/components/components.gen.go
index 37b0cef..76aa43f 100644
--- a/internal/test/components/components.gen.go
+++ b/internal/test/components/components.gen.go
@@ -66,6 +66,7 @@ const (
 	FunnyValuesAsterisk FunnyValues = "*"
 	FunnyValuesEmpty    FunnyValues = ""
 	FunnyValuesN5       FunnyValues = "5"
+	FunnyValuesPercent  FunnyValues = "%"
 )
 
 // Defines values for EnumParam1.
@@ -349,14 +350,14 @@ type RenamedRequestBody struct {
 	Field SchemaObject `json:"Field"`
 }
 
+// EnsureEverythingIsReferencedTextBody defines parameters for EnsureEverythingIsReferenced.
+type EnsureEverythingIsReferencedTextBody = string
+
 // EnsureEverythingIsReferencedJSONBody defines parameters for EnsureEverythingIsReferenced.
 type EnsureEverythingIsReferencedJSONBody struct {
 	Field SchemaObject `json:"Field"`
 }
 
-// EnsureEverythingIsReferencedTextBody defines parameters for EnsureEverythingIsReferenced.
-type EnsureEverythingIsReferencedTextBody = string
-
 // ParamsWithAddPropsParams defines parameters for ParamsWithAddProps.
 type ParamsWithAddPropsParams struct {
 	// This parameter has additional properties
@@ -879,9 +880,11 @@ func (t OneOfObject10) MarshalJSON() ([]byte, error) {
 		return nil, err
 	}
 	object := make(map[string]json.RawMessage)
-	err = json.Unmarshal(b, &object)
-	if err != nil {
-		return nil, err
+	if t.union != nil {
+		err = json.Unmarshal(b, &object)
+		if err != nil {
+			return nil, err
+		}
 	}
 
 	object["one"], err = json.Marshal(t.One)
@@ -1175,9 +1178,11 @@ func (t OneOfObject4) MarshalJSON() ([]byte, error) {
 		return nil, err
 	}
 	object := make(map[string]json.RawMessage)
-	err = json.Unmarshal(b, &object)
-	if err != nil {
-		return nil, err
+	if t.union != nil {
+		err = json.Unmarshal(b, &object)
+		if err != nil {
+			return nil, err
+		}
 	}
 
 	object["fixedProperty"], err = json.Marshal(t.FixedProperty)
@@ -1374,9 +1379,11 @@ func (t OneOfObject8) MarshalJSON() ([]byte, error) {
 		return nil, err
 	}
 	object := make(map[string]json.RawMessage)
-	err = json.Unmarshal(b, &object)
-	if err != nil {
-		return nil, err
+	if t.union != nil {
+		err = json.Unmarshal(b, &object)
+		if err != nil {
+			return nil, err
+		}
 	}
 
 	object["fixed"], err = json.Marshal(t.Fixed)
@@ -1465,9 +1472,11 @@ func (t OneOfObject9) MarshalJSON() ([]byte, error) {
 		return nil, err
 	}
 	object := make(map[string]json.RawMessage)
-	err = json.Unmarshal(b, &object)
-	if err != nil {
-		return nil, err
+	if t.union != nil {
+		err = json.Unmarshal(b, &object)
+		if err != nil {
+			return nil, err
+		}
 	}
 
 	object["type"], err = json.Marshal(t.Type)
diff --git a/internal/test/components/components.yaml b/internal/test/components/components.yaml
index f5d6441..f49173a 100644
--- a/internal/test/components/components.yaml
+++ b/internal/test/components/components.yaml
@@ -429,6 +429,7 @@ components:
         - '*'
         - '5'
         - '&'
+        - '%'
         - ''
     RenameMe:
       description: This schema should be renamed via x-go-name when generating
diff --git a/internal/test/components/components_test.go b/internal/test/components/components_test.go
index b4f8933..bcb34d7 100644
--- a/internal/test/components/components_test.go
+++ b/internal/test/components/components_test.go
@@ -202,3 +202,13 @@ func TestAnyOf(t *testing.T) {
 	assert.NoError(t, err)
 	assert.Equal(t, OneOfVariant5{Discriminator: "all", Id: 456}, v5)
 }
+
+func TestMarshalWhenNoUnionValueSet(t *testing.T) {
+	const expected = `{"one":null,"three":null,"two":null}`
+
+	var dst OneOfObject10
+
+	bytes, err := dst.MarshalJSON()
+	assert.Nil(t, err)
+	assert.Equal(t, expected, string(bytes))
+}
diff --git a/internal/test/server/server.gen.go b/internal/test/server/server.gen.go
index bd747ca..ed4a92f 100644
--- a/internal/test/server/server.gen.go
+++ b/internal/test/server/server.gen.go
@@ -211,9 +211,6 @@ func (siw *ServerInterfaceWrapper) GetWithArgs(w http.ResponseWriter, r *http.Re
 	var params GetWithArgsParams
 
 	// ------------- Optional query parameter "optional_argument" -------------
-	if paramValue := r.URL.Query().Get("optional_argument"); paramValue != "" {
-
-	}
 
 	err = runtime.BindQueryParameter("form", true, false, "optional_argument", r.URL.Query(), &params.OptionalArgument)
 	if err != nil {
@@ -222,6 +219,7 @@ func (siw *ServerInterfaceWrapper) GetWithArgs(w http.ResponseWriter, r *http.Re
 	}
 
 	// ------------- Required query parameter "required_argument" -------------
+
 	if paramValue := r.URL.Query().Get("required_argument"); paramValue != "" {
 
 	} else {
@@ -388,9 +386,6 @@ func (siw *ServerInterfaceWrapper) CreateResource2(w http.ResponseWriter, r *htt
 	var params CreateResource2Params
 
 	// ------------- Optional query parameter "inline_query_argument" -------------
-	if paramValue := r.URL.Query().Get("inline_query_argument"); paramValue != "" {
-
-	}
 
 	err = runtime.BindQueryParameter("form", true, false, "inline_query_argument", r.URL.Query(), &params.InlineQueryArgument)
 	if err != nil {
diff --git a/internal/test/strict-server/chi/server.gen.go b/internal/test/strict-server/chi/server.gen.go
index 32f2692..a40b603 100644
--- a/internal/test/strict-server/chi/server.gen.go
+++ b/internal/test/strict-server/chi/server.gen.go
@@ -597,13 +597,30 @@ type StrictHandlerFunc func(ctx context.Context, w http.ResponseWriter, r *http.
 
 type StrictMiddlewareFunc func(f StrictHandlerFunc, operationID string) StrictHandlerFunc
 
+type StrictChiServerOptions struct {
+	RequestErrorHandlerFunc  func(w http.ResponseWriter, r *http.Request, err error)
+	ResponseErrorHandlerFunc func(w http.ResponseWriter, r *http.Request, err error)
+}
+
 func NewStrictHandler(ssi StrictServerInterface, middlewares []StrictMiddlewareFunc) ServerInterface {
-	return &strictHandler{ssi: ssi, middlewares: middlewares}
+	return &strictHandler{ssi: ssi, middlewares: middlewares, options: StrictChiServerOptions{
+		RequestErrorHandlerFunc: func(w http.ResponseWriter, r *http.Request, err error) {
+			http.Error(w, err.Error(), http.StatusBadRequest)
+		},
+		ResponseErrorHandlerFunc: func(w http.ResponseWriter, r *http.Request, err error) {
+			http.Error(w, err.Error(), http.StatusInternalServerError)
+		},
+	}}
+}
+
+func NewStrictHandlerWithOptions(ssi StrictServerInterface, middlewares []StrictMiddlewareFunc, options StrictChiServerOptions) ServerInterface {
+	return &strictHandler{ssi: ssi, middlewares: middlewares, options: options}
 }
 
 type strictHandler struct {
 	ssi         StrictServerInterface
 	middlewares []StrictMiddlewareFunc
+	options     StrictChiServerOptions
 }
 
 // JSONExample operation middleware
@@ -612,7 +629,7 @@ func (sh *strictHandler) JSONExample(w http.ResponseWriter, r *http.Request) {
 
 	var body JSONExampleJSONRequestBody
 	if err := json.NewDecoder(r.Body).Decode(&body); err != nil {
-		http.Error(w, "can't decode JSON body: "+err.Error(), http.StatusBadRequest)
+		sh.options.RequestErrorHandlerFunc(w, r, fmt.Errorf("can't decode JSON body: %w", err))
 		return
 	}
 	request.Body = &body
@@ -636,10 +653,10 @@ func (sh *strictHandler) JSONExample(w http.ResponseWriter, r *http.Request) {
 	case JSONExampledefaultResponse:
 		w.WriteHeader(v.StatusCode)
 	case error:
-		http.Error(w, v.Error(), http.StatusInternalServerError)
+		sh.options.ResponseErrorHandlerFunc(w, r, v)
 	case nil:
 	default:
-		http.Error(w, fmt.Sprintf("Unexpected response type: %T", v), http.StatusInternalServerError)
+		sh.options.ResponseErrorHandlerFunc(w, r, fmt.Errorf("Unexpected response type: %T", v))
 	}
 }
 
@@ -648,7 +665,7 @@ func (sh *strictHandler) MultipartExample(w http.ResponseWriter, r *http.Request
 	var request MultipartExampleRequestObject
 
 	if reader, err := r.MultipartReader(); err != nil {
-		http.Error(w, "can't decode multipart body: "+err.Error(), http.StatusBadRequest)
+		sh.options.RequestErrorHandlerFunc(w, r, fmt.Errorf("can't decode multipart body: %w", err))
 		return
 	} else {
 		request.Body = reader
@@ -670,17 +687,17 @@ func (sh *strictHandler) MultipartExample(w http.ResponseWriter, r *http.Request
 		w.WriteHeader(200)
 		defer writer.Close()
 		if err := v(writer); err != nil {
-			http.Error(w, err.Error(), http.StatusInternalServerError)
+			sh.options.ResponseErrorHandlerFunc(w, r, err)
 		}
 	case MultipartExample400Response:
 		w.WriteHeader(400)
 	case MultipartExampledefaultResponse:
 		w.WriteHeader(v.StatusCode)
 	case error:
-		http.Error(w, v.Error(), http.StatusInternalServerError)
+		sh.options.ResponseErrorHandlerFunc(w, r, v)
 	case nil:
 	default:
-		http.Error(w, fmt.Sprintf("Unexpected response type: %T", v), http.StatusInternalServerError)
+		sh.options.ResponseErrorHandlerFunc(w, r, fmt.Errorf("Unexpected response type: %T", v))
 	}
 }
 
@@ -691,19 +708,19 @@ func (sh *strictHandler) MultipleRequestAndResponseTypes(w http.ResponseWriter,
 	if strings.HasPrefix(r.Header.Get("Content-Type"), "application/json") {
 		var body MultipleRequestAndResponseTypesJSONRequestBody
 		if err := json.NewDecoder(r.Body).Decode(&body); err != nil {
-			http.Error(w, "can't decode JSON body: "+err.Error(), http.StatusBadRequest)
+			sh.options.RequestErrorHandlerFunc(w, r, fmt.Errorf("can't decode JSON body: %w", err))
 			return
 		}
 		request.JSONBody = &body
 	}
 	if strings.HasPrefix(r.Header.Get("Content-Type"), "application/x-www-form-urlencoded") {
 		if err := r.ParseForm(); err != nil {
-			http.Error(w, "can't decode formdata: "+err.Error(), http.StatusBadRequest)
+			sh.options.RequestErrorHandlerFunc(w, r, fmt.Errorf("can't decode formdata: %w", err))
 			return
 		}
 		var body MultipleRequestAndResponseTypesFormdataRequestBody
 		if err := runtime.BindForm(&body, r.Form, nil, nil); err != nil {
-			http.Error(w, "can't bind formdata: "+err.Error(), http.StatusBadRequest)
+			sh.options.RequestErrorHandlerFunc(w, r, fmt.Errorf("can't bind formdata: %w", err))
 			return
 		}
 		request.FormdataBody = &body
@@ -713,7 +730,7 @@ func (sh *strictHandler) MultipleRequestAndResponseTypes(w http.ResponseWriter,
 	}
 	if strings.HasPrefix(r.Header.Get("Content-Type"), "multipart/form-data") {
 		if reader, err := r.MultipartReader(); err != nil {
-			http.Error(w, "can't decode multipart body: "+err.Error(), http.StatusBadRequest)
+			sh.options.RequestErrorHandlerFunc(w, r, fmt.Errorf("can't decode multipart body: %w", err))
 			return
 		} else {
 			request.MultipartBody = reader
@@ -722,7 +739,7 @@ func (sh *strictHandler) MultipleRequestAndResponseTypes(w http.ResponseWriter,
 	if strings.HasPrefix(r.Header.Get("Content-Type"), "text/plain") {
 		data, err := ioutil.ReadAll(r.Body)
 		if err != nil {
-			http.Error(w, "can't read body: "+err.Error(), http.StatusBadRequest)
+			sh.options.RequestErrorHandlerFunc(w, r, fmt.Errorf("can't read body: %w", err))
 			return
 		}
 		body := MultipleRequestAndResponseTypesTextRequestBody(data)
@@ -767,7 +784,7 @@ func (sh *strictHandler) MultipleRequestAndResponseTypes(w http.ResponseWriter,
 		w.WriteHeader(200)
 		defer writer.Close()
 		if err := v(writer); err != nil {
-			http.Error(w, err.Error(), http.StatusInternalServerError)
+			sh.options.ResponseErrorHandlerFunc(w, r, err)
 		}
 	case MultipleRequestAndResponseTypes200TextResponse:
 		w.Header().Set("Content-Type", "text/plain")
@@ -776,10 +793,10 @@ func (sh *strictHandler) MultipleRequestAndResponseTypes(w http.ResponseWriter,
 	case MultipleRequestAndResponseTypes400Response:
 		w.WriteHeader(400)
 	case error:
-		http.Error(w, v.Error(), http.StatusInternalServerError)
+		sh.options.ResponseErrorHandlerFunc(w, r, v)
 	case nil:
 	default:
-		http.Error(w, fmt.Sprintf("Unexpected response type: %T", v), http.StatusInternalServerError)
+		sh.options.ResponseErrorHandlerFunc(w, r, fmt.Errorf("Unexpected response type: %T", v))
 	}
 }
 
@@ -789,7 +806,7 @@ func (sh *strictHandler) ReusableResponses(w http.ResponseWriter, r *http.Reques
 
 	var body ReusableResponsesJSONRequestBody
 	if err := json.NewDecoder(r.Body).Decode(&body); err != nil {
-		http.Error(w, "can't decode JSON body: "+err.Error(), http.StatusBadRequest)
+		sh.options.RequestErrorHandlerFunc(w, r, fmt.Errorf("can't decode JSON body: %w", err))
 		return
 	}
 	request.Body = &body
@@ -815,10 +832,10 @@ func (sh *strictHandler) ReusableResponses(w http.ResponseWriter, r *http.Reques
 	case ReusableResponsesdefaultResponse:
 		w.WriteHeader(v.StatusCode)
 	case error:
-		http.Error(w, v.Error(), http.StatusInternalServerError)
+		sh.options.ResponseErrorHandlerFunc(w, r, v)
 	case nil:
 	default:
-		http.Error(w, fmt.Sprintf("Unexpected response type: %T", v), http.StatusInternalServerError)
+		sh.options.ResponseErrorHandlerFunc(w, r, fmt.Errorf("Unexpected response type: %T", v))
 	}
 }
 
@@ -828,7 +845,7 @@ func (sh *strictHandler) TextExample(w http.ResponseWriter, r *http.Request) {
 
 	data, err := ioutil.ReadAll(r.Body)
 	if err != nil {
-		http.Error(w, "can't read body: "+err.Error(), http.StatusBadRequest)
+		sh.options.RequestErrorHandlerFunc(w, r, fmt.Errorf("can't read body: %w", err))
 		return
 	}
 	body := TextExampleTextRequestBody(data)
@@ -853,10 +870,10 @@ func (sh *strictHandler) TextExample(w http.ResponseWriter, r *http.Request) {
 	case TextExampledefaultResponse:
 		w.WriteHeader(v.StatusCode)
 	case error:
-		http.Error(w, v.Error(), http.StatusInternalServerError)
+		sh.options.ResponseErrorHandlerFunc(w, r, v)
 	case nil:
 	default:
-		http.Error(w, fmt.Sprintf("Unexpected response type: %T", v), http.StatusInternalServerError)
+		sh.options.ResponseErrorHandlerFunc(w, r, fmt.Errorf("Unexpected response type: %T", v))
 	}
 }
 
@@ -891,10 +908,10 @@ func (sh *strictHandler) UnknownExample(w http.ResponseWriter, r *http.Request)
 	case UnknownExampledefaultResponse:
 		w.WriteHeader(v.StatusCode)
 	case error:
-		http.Error(w, v.Error(), http.StatusInternalServerError)
+		sh.options.ResponseErrorHandlerFunc(w, r, v)
 	case nil:
 	default:
-		http.Error(w, fmt.Sprintf("Unexpected response type: %T", v), http.StatusInternalServerError)
+		sh.options.ResponseErrorHandlerFunc(w, r, fmt.Errorf("Unexpected response type: %T", v))
 	}
 }
 
@@ -935,10 +952,10 @@ func (sh *strictHandler) UnspecifiedContentType(w http.ResponseWriter, r *http.R
 	case UnspecifiedContentTypedefaultResponse:
 		w.WriteHeader(v.StatusCode)
 	case error:
-		http.Error(w, v.Error(), http.StatusInternalServerError)
+		sh.options.ResponseErrorHandlerFunc(w, r, v)
 	case nil:
 	default:
-		http.Error(w, fmt.Sprintf("Unexpected response type: %T", v), http.StatusInternalServerError)
+		sh.options.ResponseErrorHandlerFunc(w, r, fmt.Errorf("Unexpected response type: %T", v))
 	}
 }
 
@@ -947,12 +964,12 @@ func (sh *strictHandler) URLEncodedExample(w http.ResponseWriter, r *http.Reques
 	var request URLEncodedExampleRequestObject
 
 	if err := r.ParseForm(); err != nil {
-		http.Error(w, "can't decode formdata: "+err.Error(), http.StatusBadRequest)
+		sh.options.RequestErrorHandlerFunc(w, r, fmt.Errorf("can't decode formdata: %w", err))
 		return
 	}
 	var body URLEncodedExampleFormdataRequestBody
 	if err := runtime.BindForm(&body, r.Form, nil, nil); err != nil {
-		http.Error(w, "can't bind formdata: "+err.Error(), http.StatusBadRequest)
+		sh.options.RequestErrorHandlerFunc(w, r, fmt.Errorf("can't bind formdata: %w", err))
 		return
 	}
 	request.Body = &body
@@ -980,10 +997,10 @@ func (sh *strictHandler) URLEncodedExample(w http.ResponseWriter, r *http.Reques
 	case URLEncodedExampledefaultResponse:
 		w.WriteHeader(v.StatusCode)
 	case error:
-		http.Error(w, v.Error(), http.StatusInternalServerError)
+		sh.options.ResponseErrorHandlerFunc(w, r, v)
 	case nil:
 	default:
-		http.Error(w, fmt.Sprintf("Unexpected response type: %T", v), http.StatusInternalServerError)
+		sh.options.ResponseErrorHandlerFunc(w, r, fmt.Errorf("Unexpected response type: %T", v))
 	}
 }
 
@@ -995,7 +1012,7 @@ func (sh *strictHandler) HeadersExample(w http.ResponseWriter, r *http.Request,
 
 	var body HeadersExampleJSONRequestBody
 	if err := json.NewDecoder(r.Body).Decode(&body); err != nil {
-		http.Error(w, "can't decode JSON body: "+err.Error(), http.StatusBadRequest)
+		sh.options.RequestErrorHandlerFunc(w, r, fmt.Errorf("can't decode JSON body: %w", err))
 		return
 	}
 	request.Body = &body
@@ -1021,10 +1038,10 @@ func (sh *strictHandler) HeadersExample(w http.ResponseWriter, r *http.Request,
 	case HeadersExampledefaultResponse:
 		w.WriteHeader(v.StatusCode)
 	case error:
-		http.Error(w, v.Error(), http.StatusInternalServerError)
+		sh.options.ResponseErrorHandlerFunc(w, r, v)
 	case nil:
 	default:
-		http.Error(w, fmt.Sprintf("Unexpected response type: %T", v), http.StatusInternalServerError)
+		sh.options.ResponseErrorHandlerFunc(w, r, fmt.Errorf("Unexpected response type: %T", v))
 	}
 }
 
diff --git a/pkg/codegen/codegen_test.go b/pkg/codegen/codegen_test.go
index d5840b3..ba72ceb 100644
--- a/pkg/codegen/codegen_test.go
+++ b/pkg/codegen/codegen_test.go
@@ -220,6 +220,15 @@ func TestXGoTypeImport(t *testing.T) {
 	// Check generated struct
 	assert.Contains(t, code, "type Pet struct {\n\tAge myuuid.UUID `json:\"age\"`\n}")
 
+	// Check import
+	assert.Contains(t, code, `github.com/CavernaTechnologies/pgext`)
+
+	// Check generated struct
+	assert.Contains(t, code, "type Person struct {\n\tAge pgext.Puint `json:\"age\"`\n}")
+
+	// Check generated struct
+	assert.Contains(t, code, "type Car struct {\n\tAge int `json:\"age\"`\n}")
+
 	// Make sure the generated code is valid:
 	checkLint(t, "test.gen.go", []byte(code))
 
diff --git a/pkg/codegen/test_specs/x-go-type-import-pet.yaml b/pkg/codegen/test_specs/x-go-type-import-pet.yaml
index 4fea6ff..9df66b5 100644
--- a/pkg/codegen/test_specs/x-go-type-import-pet.yaml
+++ b/pkg/codegen/test_specs/x-go-type-import-pet.yaml
@@ -28,12 +28,53 @@ paths:
             type: integer
             format: int64
       responses:
-        '200':
+        "200":
           description: pet response
           content:
             application/json:
               schema:
-                $ref: '#/components/schemas/Pet'
+                $ref: "#/components/schemas/Pet"
+  /person/{id}:
+    get:
+      summary: Returns a person by ID
+      description: Returns a person based on a single ID
+      operationId: findPersonByID
+      parameters:
+        - name: id
+          in: path
+          description: ID of person to fetch
+          required: true
+          schema:
+            type: integer
+            format: int64
+      responses:
+        "200":
+          description: person response
+          content:
+            application/json:
+              schema:
+                $ref: "#/components/schemas/Person"
+  /car/{id}:
+    get:
+      summary: Returns a car by ID
+      description: Returns a car based on a single ID
+      operationId: findCarByID
+      parameters:
+        - name: id
+          in: path
+          description: ID of car to fetch
+          required: true
+          schema:
+            type: integer
+            format: int64
+      responses:
+        "200":
+          description: car response
+          content:
+            application/json:
+              schema:
+                $ref: "#/components/schemas/Car"
+
 components:
   schemas:
     Pet:
@@ -45,3 +86,17 @@ components:
             name: myuuid
       required:
         - age
+    Person:
+      properties:
+        age:
+          x-go-type: pgext.Puint
+          x-go-type-import:
+            path: github.com/CavernaTechnologies/pgext
+      required:
+        - age
+    Car:
+      properties:
+        age:
+          type: integer
+      required:
+        - age