Repository: deepmap/oapi-codegen
Pull request #787 (2022-10-14 05:45:49)

Chain Chi middlewares in reverse order so they execute first-to-last
Resolves reversed-ordering of Chi middleware chaining.

After this change, Chi middlewares will be applied in first-to-last ordering i.e. earlier wired/invoked middlewares will run before later wired middlewares.

This addresses https://github.com/deepmap/oapi-codegen/issues/687 and https://github.com/deepmap/oapi-codegen/issues/786

Not sure how I'd go about testing this since the existing unit tests don't seem to have complex middlewares, could use some advice if we want to include a unit test for this.

Note that this will change behavior for Chi users who wired their middlewares in reverse order to compensate, I think most people at this point have adopted version pinning for code generation since we've had some breaking changes in the past few versions but it's worth noting in the release notes.


Patch:
diff --git a/README.md b/README.md
index 8f1cd41..ba45d6b 100644
--- a/README.md
+++ b/README.md
@@ -289,7 +289,7 @@ func (*PetStoreImpl) GetPets(ctx context.Context, request GetPetsRequestObject)
 ```
 For a complete example see `/examples/petstore-expanded/strict`.
 
-Code is generation with a configuration flag `generate: strict-server: true` along with any other server (echo, chi, gin and gorilla are supported).
+Code is generated with a configuration flag `generate: strict-server: true` along with any other server (echo, chi, gin and gorilla are supported).
 The generated strict wrapper can then be used as an implementation for `ServerInterface`. Setup example:
 ```go
 func SetupHandler() {
diff --git a/cmd/oapi-codegen/oapi-codegen.go b/cmd/oapi-codegen/oapi-codegen.go
index a3833e7..ebca364 100644
--- a/cmd/oapi-codegen/oapi-codegen.go
+++ b/cmd/oapi-codegen/oapi-codegen.go
@@ -39,6 +39,7 @@ var (
 	flagOutputConfig   bool
 	flagPrintVersion   bool
 	flagPackageName    string
+	flagPrintUsage     bool
 
 	// The options below are deprecated, and they will be removed in a future
 	// release. Please use the new config file format.
@@ -81,6 +82,8 @@ func main() {
 	flag.StringVar(&flagConfigFile, "config", "", "a YAML config file that controls oapi-codegen behavior")
 	flag.BoolVar(&flagPrintVersion, "version", false, "when specified, print version and exit")
 	flag.StringVar(&flagPackageName, "package", "", "The package name for generated code")
+	flag.BoolVar(&flagPrintUsage, "help", false, "show this help and exit")
+	flag.BoolVar(&flagPrintUsage, "h", false, "same as -help")
 
 	// All flags below are deprecated, and will be removed in a future release. Please do not
 	// update their behavior.
@@ -96,6 +99,11 @@ func main() {
 
 	flag.Parse()
 
+	if flagPrintUsage {
+		flag.Usage()
+		os.Exit(0)
+	}
+
 	if flagPrintVersion {
 		bi, ok := debug.ReadBuildInfo()
 		if !ok {
@@ -108,8 +116,9 @@ func main() {
 	}
 
 	if flag.NArg() < 1 {
-		fmt.Println("Please specify a path to a OpenAPI 3.0 spec file")
-		os.Exit(1)
+		errExit("Please specify a path to a OpenAPI 3.0 spec file\n")
+	} else if flag.NArg() > 1 {
+		errExit("Only one OpenAPI 3.0 spec file is accepted and it must be the last CLI argument\n")
 	}
 
 	// We will try to infer whether the user has an old-style config, or a new
diff --git a/examples/petstore-expanded/chi/api/cfg.yaml b/examples/petstore-expanded/chi/api/cfg.yaml
index 6d2fa08..d1a29f3 100644
--- a/examples/petstore-expanded/chi/api/cfg.yaml
+++ b/examples/petstore-expanded/chi/api/cfg.yaml
@@ -3,4 +3,6 @@ generate:
   chi-server: true
   models: true
   embedded-spec: true
+compatibility:
+  apply-chi-middleware-first-to-last: true
 output: petstore.gen.go
diff --git a/examples/petstore-expanded/chi/api/petstore.gen.go b/examples/petstore-expanded/chi/api/petstore.gen.go
index 4234451..06bba0c 100644
--- a/examples/petstore-expanded/chi/api/petstore.gen.go
+++ b/examples/petstore-expanded/chi/api/petstore.gen.go
@@ -114,8 +114,8 @@ func (siw *ServerInterfaceWrapper) FindPets(w http.ResponseWriter, r *http.Reque
 		siw.Handler.FindPets(w, r, params)
 	})
 
-	for _, middleware := range siw.HandlerMiddlewares {
-		handler = middleware(handler)
+	for i := len(siw.HandlerMiddlewares) - 1; i >= 0; i-- {
+		handler = siw.HandlerMiddlewares[i](handler)
 	}
 
 	handler.ServeHTTP(w, r.WithContext(ctx))
@@ -129,8 +129,8 @@ func (siw *ServerInterfaceWrapper) AddPet(w http.ResponseWriter, r *http.Request
 		siw.Handler.AddPet(w, r)
 	})
 
-	for _, middleware := range siw.HandlerMiddlewares {
-		handler = middleware(handler)
+	for i := len(siw.HandlerMiddlewares) - 1; i >= 0; i-- {
+		handler = siw.HandlerMiddlewares[i](handler)
 	}
 
 	handler.ServeHTTP(w, r.WithContext(ctx))
@@ -155,8 +155,8 @@ func (siw *ServerInterfaceWrapper) DeletePet(w http.ResponseWriter, r *http.Requ
 		siw.Handler.DeletePet(w, r, id)
 	})
 
-	for _, middleware := range siw.HandlerMiddlewares {
-		handler = middleware(handler)
+	for i := len(siw.HandlerMiddlewares) - 1; i >= 0; i-- {
+		handler = siw.HandlerMiddlewares[i](handler)
 	}
 
 	handler.ServeHTTP(w, r.WithContext(ctx))
@@ -181,8 +181,8 @@ func (siw *ServerInterfaceWrapper) FindPetByID(w http.ResponseWriter, r *http.Re
 		siw.Handler.FindPetByID(w, r, id)
 	})
 
-	for _, middleware := range siw.HandlerMiddlewares {
-		handler = middleware(handler)
+	for i := len(siw.HandlerMiddlewares) - 1; i >= 0; i-- {
+		handler = siw.HandlerMiddlewares[i](handler)
 	}
 
 	handler.ServeHTTP(w, r.WithContext(ctx))
diff --git a/pkg/codegen/codegen.go b/pkg/codegen/codegen.go
index 7d03752..308d1cd 100644
--- a/pkg/codegen/codegen.go
+++ b/pkg/codegen/codegen.go
@@ -138,8 +138,12 @@ func Generate(spec *openapi3.T, opts Configuration) (string, error) {
 		return "", fmt.Errorf("error creating operation definitions: %w", err)
 	}
 
+	xGoTypeImports, err := OperationImports(ops)
+	if err != nil {
+		return "", fmt.Errorf("error getting operation imports: %w", err)
+	}
+
 	var typeDefinitions, constantDefinitions string
-	var xGoTypeImports map[string]goImport
 	if opts.Generate.Models {
 		typeDefinitions, err = GenerateTypeDefinitions(t, spec, ops, opts.OutputOptions.ExcludeSchemas)
 		if err != nil {
@@ -151,10 +155,11 @@ func Generate(spec *openapi3.T, opts Configuration) (string, error) {
 			return "", fmt.Errorf("error generating constants: %w", err)
 		}
 
-		xGoTypeImports, err = GetTypeDefinitionsImports(spec, opts.OutputOptions.ExcludeSchemas)
+		imprts, err := GetTypeDefinitionsImports(spec, opts.OutputOptions.ExcludeSchemas)
 		if err != nil {
 			return "", fmt.Errorf("error getting type definition imports: %w", err)
 		}
+		MergeImports(xGoTypeImports, imprts)
 	}
 
 	var echoServerOut string
@@ -794,6 +799,60 @@ func LoadTemplates(src embed.FS, t *template.Template) error {
 	})
 }
 
+func OperationSchemaImports(s *Schema) (map[string]goImport, error) {
+	res := map[string]goImport{}
+
+	for _, p := range s.Properties {
+		imprts, err := GoSchemaImports(&openapi3.SchemaRef{Value: p.Schema.OAPISchema})
+		if err != nil {
+			return nil, err
+		}
+		MergeImports(res, imprts)
+	}
+
+	imprts, err := GoSchemaImports(&openapi3.SchemaRef{Value: s.OAPISchema})
+	if err != nil {
+		return nil, err
+	}
+	MergeImports(res, imprts)
+	return res, nil
+}
+
+func OperationImports(ops []OperationDefinition) (map[string]goImport, error) {
+	res := map[string]goImport{}
+	for _, op := range ops {
+		for _, pd := range [][]ParameterDefinition{op.PathParams, op.QueryParams} {
+			for _, p := range pd {
+				imprts, err := OperationSchemaImports(&p.Schema)
+				if err != nil {
+					return nil, err
+				}
+				MergeImports(res, imprts)
+			}
+		}
+
+		for _, b := range op.Bodies {
+			imprts, err := OperationSchemaImports(&b.Schema)
+			if err != nil {
+				return nil, err
+			}
+			MergeImports(res, imprts)
+		}
+
+		for _, b := range op.Responses {
+			for _, c := range b.Contents {
+				imprts, err := OperationSchemaImports(&c.Schema)
+				if err != nil {
+					return nil, err
+				}
+				MergeImports(res, imprts)
+			}
+		}
+
+	}
+	return res, nil
+}
+
 func GetTypeDefinitionsImports(swagger *openapi3.T, excludeSchemas []string) (map[string]goImport, error) {
 	res := map[string]goImport{}
 	schemaImports, err := GetSchemaImports(swagger.Components.Schemas, excludeSchemas)
@@ -811,9 +870,48 @@ func GetTypeDefinitionsImports(swagger *openapi3.T, excludeSchemas []string) (ma
 		return nil, err
 	}
 
-	for _, imprts := range []map[string]goImport{schemaImports, reqBodiesImports, responsesImports} {
-		for k, v := range imprts {
-			res[k] = v
+	parametersImports, err := GetParametersImports(swagger.Components.Parameters)
+	if err != nil {
+		return nil, err
+	}
+
+	for _, imprts := range []map[string]goImport{schemaImports, reqBodiesImports, responsesImports, parametersImports} {
+		MergeImports(res, imprts)
+	}
+	return res, nil
+}
+
+func GoSchemaImports(schemas ...*openapi3.SchemaRef) (map[string]goImport, error) {
+	res := map[string]goImport{}
+	for _, sref := range schemas {
+		if sref == nil || sref.Value == nil || IsGoTypeReference(sref.Ref) {
+			return nil, nil
+		}
+		if gi, err := ParseGoImportExtension(sref); err != nil {
+			return nil, err
+		} else {
+			if gi != nil {
+				res[gi.String()] = *gi
+			}
+		}
+		schemaVal := sref.Value
+
+		t := schemaVal.Type
+		switch t {
+		case "", "object":
+			for _, v := range schemaVal.Properties {
+				imprts, err := GoSchemaImports(v)
+				if err != nil {
+					return nil, err
+				}
+				MergeImports(res, imprts)
+			}
+		case "array":
+			imprts, err := GoSchemaImports(schemaVal.Items)
+			if err != nil {
+				return nil, err
+			}
+			MergeImports(res, imprts)
 		}
 	}
 	return res, nil
@@ -825,48 +923,31 @@ func GetSchemaImports(schemas map[string]*openapi3.SchemaRef, excludeSchemas []s
 	for _, schema := range excludeSchemas {
 		excludeSchemasMap[schema] = true
 	}
-	for _, schemaName := range SortedSchemaKeys(schemas) {
+	for schemaName, schema := range schemas {
 		if _, ok := excludeSchemasMap[schemaName]; ok {
 			continue
 		}
-		schema := schemas[schemaName].Value
-
-		if schema == nil || schema.Properties == nil {
-			continue
-		}
 
-		imprts, err := GetImports(schema.Properties)
+		imprts, err := GoSchemaImports(schema)
 		if err != nil {
 			return nil, err
 		}
-
-		for s, gi := range imprts {
-			res[s] = gi
-		}
+		MergeImports(res, imprts)
 	}
 	return res, nil
 }
 
 func GetRequestBodiesImports(bodies map[string]*openapi3.RequestBodyRef) (map[string]goImport, error) {
 	res := map[string]goImport{}
-	for _, requestBodyName := range SortedRequestBodyKeys(bodies) {
-		requestBodyRef := bodies[requestBodyName]
-		response := requestBodyRef.Value
+	for _, r := range bodies {
+		response := r.Value
 		jsonBody, found := response.Content["application/json"]
 		if found {
-			schema := jsonBody.Schema
-			if schema == nil || schema.Value == nil || schema.Value.Properties == nil {
-				continue
-			}
-
-			imprts, err := GetImports(schema.Value.Properties)
+			imprts, err := GoSchemaImports(jsonBody.Schema)
 			if err != nil {
 				return nil, err
 			}
-
-			for s, gi := range imprts {
-				res[s] = gi
-			}
+			MergeImports(res, imprts)
 		}
 	}
 	return res, nil
@@ -874,25 +955,31 @@ func GetRequestBodiesImports(bodies map[string]*openapi3.RequestBodyRef) (map[st
 
 func GetResponsesImports(responses map[string]*openapi3.ResponseRef) (map[string]goImport, error) {
 	res := map[string]goImport{}
-	for _, responseName := range SortedResponsesKeys(responses) {
-		responseOrRef := responses[responseName]
-		response := responseOrRef.Value
+	for _, r := range responses {
+		response := r.Value
 		jsonResponse, found := response.Content["application/json"]
 		if found {
-			schema := jsonResponse.Schema
-			if schema == nil || schema.Value == nil || schema.Value.Properties == nil {
-				continue
-			}
-
-			imprts, err := GetImports(schema.Value.Properties)
+			imprts, err := GoSchemaImports(jsonResponse.Schema)
 			if err != nil {
 				return nil, err
 			}
+			MergeImports(res, imprts)
+		}
+	}
+	return res, nil
+}
 
-			for s, gi := range imprts {
-				res[s] = gi
-			}
+func GetParametersImports(params map[string]*openapi3.ParameterRef) (map[string]goImport, error) {
+	res := map[string]goImport{}
+	for _, param := range params {
+		if param.Value == nil {
+			continue
+		}
+		imprts, err := GoSchemaImports(param.Value.Schema)
+		if err != nil {
+			return nil, err
 		}
+		MergeImports(res, imprts)
 	}
 	return res, nil
 }
diff --git a/pkg/codegen/configuration.go b/pkg/codegen/configuration.go
index 290c139..df8fa73 100644
--- a/pkg/codegen/configuration.go
+++ b/pkg/codegen/configuration.go
@@ -64,6 +64,11 @@ type CompatibilityOptions struct {
 	// 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"`
+	// Our generated code for Chi has historically inverted the order in which Chi middleware is
+	// applied such that the last invoked middleware ends up executing first in the Chi chain
+	// This resolves the behavior such that middlewares are chained in the order they are invoked.
+	// Please see https://github.com/deepmap/oapi-codegen/issues/786
+	ApplyChiMiddlewareFirstToLast bool `yaml:"apply-chi-middleware-first-to-last,omitempty"`
 }
 
 // OutputOptions are used to modify the output code in some way.
diff --git a/pkg/codegen/extension.go b/pkg/codegen/extension.go
index a9ee43d..5d01c78 100644
--- a/pkg/codegen/extension.go
+++ b/pkg/codegen/extension.go
@@ -6,6 +6,7 @@ import (
 )
 
 const (
+	extPropGoImport     = "x-go-type-import"
 	extPropGoType       = "x-go-type"
 	extGoName           = "x-go-name"
 	extPropGoJsonIgnore = "x-go-json-ignore"
diff --git a/pkg/codegen/schema.go b/pkg/codegen/schema.go
index 9d83bd5..607e5d0 100644
--- a/pkg/codegen/schema.go
+++ b/pkg/codegen/schema.go
@@ -27,7 +27,7 @@ type Schema struct {
 
 	Description string // The description of the element
 
-	UnionElements []string       // Possible elements of oneOf/anyOf union
+	UnionElements []UnionElement // Possible elements of oneOf/anyOf union
 	Discriminator *Discriminator // Describes which value is stored in a union
 
 	// If this is set, the schema will declare a type via alias, eg,
@@ -195,6 +195,23 @@ func (d *Discriminator) PropertyName() string {
 	return SchemaNameToTypeName(d.Property)
 }
 
+// UnionElement describe union element, based on prefix externalRef\d+ and real ref name from external schema.
+type UnionElement string
+
+// String returns externalRef\d+ and real ref name from external schema, like externalRef0.SomeType.
+func (u UnionElement) String() string {
+	return string(u)
+}
+
+// Method generate union method name for template functions `As/From/Merge`.
+func (u UnionElement) Method() string {
+	var method string
+	for _, part := range strings.Split(string(u), `.`) {
+		method += UppercaseFirstCharacter(part)
+	}
+	return method
+}
+
 func PropertiesEqual(a, b Property) bool {
 	return a.JsonFieldName == b.JsonFieldName && a.Schema.TypeDecl() == b.Schema.TypeDecl() && a.Required == b.Required
 }
@@ -728,7 +745,7 @@ func generateUnion(outSchema *Schema, elements openapi3.SchemaRefs, discriminato
 				}
 			}
 		}
-		outSchema.UnionElements = append(outSchema.UnionElements, elementSchema.GoType)
+		outSchema.UnionElements = append(outSchema.UnionElements, UnionElement(elementSchema.GoType))
 	}
 
 	return nil
diff --git a/pkg/codegen/template_helpers.go b/pkg/codegen/template_helpers.go
index a677404..3a7a15f 100644
--- a/pkg/codegen/template_helpers.go
+++ b/pkg/codegen/template_helpers.go
@@ -29,9 +29,9 @@ const (
 )
 
 var (
-	contentTypesJSON = []string{echo.MIMEApplicationJSON, "text/x-json"}
+	contentTypesJSON = []string{echo.MIMEApplicationJSON, "text/x-json", "application/problem+json"}
 	contentTypesYAML = []string{"application/yaml", "application/x-yaml", "text/yaml", "text/x-yaml"}
-	contentTypesXML  = []string{echo.MIMEApplicationXML, echo.MIMETextXML}
+	contentTypesXML  = []string{echo.MIMEApplicationXML, echo.MIMETextXML, "application/problems+xml"}
 
 	responseTypeSuffix = "Response"
 )
diff --git a/pkg/codegen/templates/chi/chi-middleware.tmpl b/pkg/codegen/templates/chi/chi-middleware.tmpl
index 7311ec4..77a5c2a 100644
--- a/pkg/codegen/templates/chi/chi-middleware.tmpl
+++ b/pkg/codegen/templates/chi/chi-middleware.tmpl
@@ -177,9 +177,15 @@ func (siw *ServerInterfaceWrapper) {{$opid}}(w http.ResponseWriter, r *http.Requ
     siw.Handler.{{.OperationId}}(w, r{{genParamNames .PathParams}}{{if .RequiresParamObject}}, params{{end}})
   })
 
+  {{if opts.Compatibility.ApplyChiMiddlewareFirstToLast}}
+  for i := len(siw.HandlerMiddlewares) -1; i >= 0; i-- {
+    handler = siw.HandlerMiddlewares[i](handler)
+  }
+  {{else}}
   for _, middleware := range siw.HandlerMiddlewares {
     handler = middleware(handler)
   }
+  {{end}}
 
   handler.ServeHTTP(w, r.WithContext(ctx))
 }
diff --git a/pkg/codegen/templates/imports.tmpl b/pkg/codegen/templates/imports.tmpl
index 376e1f9..ef43ef3 100644
--- a/pkg/codegen/templates/imports.tmpl
+++ b/pkg/codegen/templates/imports.tmpl
@@ -27,6 +27,7 @@ import (
 	"github.com/go-chi/chi/v5"
 	"github.com/labstack/echo/v4"
 	"github.com/gin-gonic/gin"
+	"github.com/gorilla/mux"
 	{{- range .ExternalImports}}
 	{{ . }}
 	{{- end}}
diff --git a/pkg/codegen/templates/union.tmpl b/pkg/codegen/templates/union.tmpl
index d233e13..eb015be 100644
--- a/pkg/codegen/templates/union.tmpl
+++ b/pkg/codegen/templates/union.tmpl
@@ -4,15 +4,15 @@
     {{$properties := .Schema.Properties -}}
     {{range .Schema.UnionElements}}
         {{$element := . -}}
-        // As{{.}} returns the union data inside the {{$typeName}} as a {{.}}
-        func (t {{$typeName}}) As{{.}}() ({{.}}, error) {
+        // As{{ .Method }} returns the union data inside the {{$typeName}} as a {{.}}
+        func (t {{$typeName}}) As{{ .Method }}() ({{.}}, error) {
             var body {{.}}
             err := json.Unmarshal(t.union, &body)
             return body, err
         }
 
-        // From{{.}} overwrites any union data inside the {{$typeName}} as the provided {{.}}
-        func (t *{{$typeName}}) From{{.}} (v {{.}}) error {
+        // From{{ .Method }} overwrites any union data inside the {{$typeName}} as the provided {{.}}
+        func (t *{{$typeName}}) From{{ .Method }} (v {{.}}) error {
             {{if $discriminator -}}
                 {{range $value, $type := $discriminator.Mapping -}}
                     {{if eq $type $element -}}
@@ -32,8 +32,8 @@
             return err
         }
 
-        // Merge{{.}} performs a merge with any union data inside the {{$typeName}}, using the provided {{.}}
-        func (t *{{$typeName}}) Merge{{.}} (v {{.}}) error {
+        // Merge{{ .Method }} performs a merge with any union data inside the {{$typeName}}, using the provided {{.}}
+        func (t *{{$typeName}}) Merge{{ .Method }} (v {{.}}) error {
             {{if $discriminator -}}
                 {{range $value, $type := $discriminator.Mapping -}}
                     {{if eq $type $element -}}
diff --git a/pkg/codegen/utils.go b/pkg/codegen/utils.go
index 0796b33..a31b553 100644
--- a/pkg/codegen/utils.go
+++ b/pkg/codegen/utils.go
@@ -791,27 +791,26 @@ func findSchemaNameByRefPath(refPath string, spec *openapi3.T) (string, error) {
 	return "", nil
 }
 
-func GetImports(dict map[string]*openapi3.SchemaRef) (map[string]goImport, error) {
-	res := map[string]goImport{}
-	for _, v := range dict {
-		if v == nil || v.Value == nil {
-			continue
-		}
+func ParseGoImportExtension(v *openapi3.SchemaRef) (*goImport, error) {
+	if v.Value.Extensions[extPropGoImport] == nil || v.Value.Extensions[extPropGoType] == nil {
+		return nil, nil
+	}
 
-		if v.Value.Extensions["x-go-type-import"] == nil || v.Value.Extensions["x-go-type"] == nil {
-			continue
-		}
-		goTypeImportExt := v.Value.Extensions["x-go-type-import"]
+	goTypeImportExt := v.Value.Extensions[extPropGoImport]
 
-		if raw, ok := goTypeImportExt.(json.RawMessage); ok {
-			gi := goImport{}
-			if err := json.Unmarshal(raw, &gi); err != nil {
-				return nil, err
-			}
-			res[gi.String()] = gi
-		} else {
-			continue
+	if raw, ok := goTypeImportExt.(json.RawMessage); ok {
+		gi := goImport{}
+		if err := json.Unmarshal(raw, &gi); err != nil {
+			return nil, err
 		}
+		return &gi, nil
+	}
+
+	return nil, nil
+}
+
+func MergeImports(dst, src map[string]goImport) {
+	for k, v := range src {
+		dst[k] = v
 	}
-	return res, nil
 }


Test patch:
diff --git a/pkg/codegen/codegen_test.go b/pkg/codegen/codegen_test.go
index 7b6ee26..9994e07 100644
--- a/pkg/codegen/codegen_test.go
+++ b/pkg/codegen/codegen_test.go
@@ -20,9 +20,15 @@ import (
 	examplePetstore "github.com/deepmap/oapi-codegen/examples/petstore-expanded/echo/api"
 )
 
+const (
+	remoteRefFile = `https://raw.githubusercontent.com/deepmap/oapi-codegen/master/examples/petstore-expanded` +
+		`/petstore-expanded.yaml`
+	remoteRefImport = `github.com/deepmap/oapi-codegen/examples/petstore-expanded`
+)
+
 func checkLint(t *testing.T, filename string, code []byte) {
 	linter := new(lint.Linter)
-	problems, err := linter.Lint("test.gen.go", code)
+	problems, err := linter.Lint(filename, code)
 	assert.NoError(t, err)
 	assert.Len(t, problems, 0)
 }
@@ -190,12 +196,14 @@ type GetTestByNameResponse struct {
 	checkLint(t, "test.gen.go", []byte(code))
 }
 
-func TestXGoTypeImport(t *testing.T) {
+func TestGoTypeImport(t *testing.T) {
 	packageName := "api"
 	opts := Configuration{
 		PackageName: packageName,
 		Generate: GenerateOptions{
-			Models: true,
+			EchoServer:   true,
+			Models:       true,
+			EmbeddedSpec: true,
 		},
 	}
 	spec := "test_specs/x-go-type-import-pet.yaml"
@@ -211,23 +219,85 @@ func TestXGoTypeImport(t *testing.T) {
 	_, err = format.Source([]byte(code))
 	assert.NoError(t, err)
 
+	imports := []string{
+		`github.com/CavernaTechnologies/pgext`, // schemas - direct object
+		`myuuid "github.com/google/uuid"`,      // schemas - object
+		`github.com/lib/pq`,                    // schemas - array
+		`github.com/spf13/viper`,               // responses - direct object
+		`golang.org/x/text`,                    // responses - complex object
+		`golang.org/x/email`,                   // requestBodies - in components
+		`github.com/fatih/color`,               // parameters - query
+		`github.com/go-openapi/swag`,           // parameters - path
+		`github.com/jackc/pgtype`,              // direct parameters - path
+		`github.com/mailru/easyjson`,           // direct parameters - query
+		`github.com/subosito/gotenv`,           // direct request body
+	}
+
+	// Check import
+	for _, imp := range imports {
+		assert.Contains(t, code, imp)
+	}
+
+	// Make sure the generated code is valid:
+	checkLint(t, "test.gen.go", []byte(code))
+
+}
+
+func TestRemoteExternalReference(t *testing.T) {
+	packageName := "api"
+	opts := Configuration{
+		PackageName: packageName,
+		Generate: GenerateOptions{
+			Models: true,
+		},
+		ImportMapping: map[string]string{
+			remoteRefFile: remoteRefImport,
+		},
+	}
+	spec := "test_specs/remote-external-reference.yaml"
+	swagger, err := util.LoadSwagger(spec)
+	require.NoError(t, err)
+
+	// Run our code generation:
+	code, err := Generate(swagger, opts)
+	assert.NoError(t, err)
+	assert.NotEmpty(t, code)
+
+	// Check that we have valid (formattable) code:
+	_, err = format.Source([]byte(code))
+	assert.NoError(t, err)
+
 	// Check that we have a package:
 	assert.Contains(t, code, "package api")
 
 	// Check import
-	assert.Contains(t, code, `myuuid "github.com/google/uuid"`)
+	assert.Contains(t, code, `externalRef0 "github.com/deepmap/oapi-codegen/examples/petstore-expanded"`)
 
-	// Check generated struct
-	assert.Contains(t, code, "type Pet struct {\n\tAge myuuid.UUID `json:\"age\"`\n}")
+	// Check generated oneOf structure:
+	assert.Contains(t, code, `
+// ExampleSchema_Item defines model for ExampleSchema.Item.
+type ExampleSchema_Item struct {
+	union json.RawMessage
+}
+`)
 
-	// Check import
-	assert.Contains(t, code, `github.com/CavernaTechnologies/pgext`)
+	// Check generated oneOf structure As method:
+	assert.Contains(t, code, `
+// AsExternalRef0NewPet returns the union data inside the ExampleSchema_Item as a externalRef0.NewPet
+func (t ExampleSchema_Item) AsExternalRef0NewPet() (externalRef0.NewPet, error) {
+`)
 
-	// Check generated struct
-	assert.Contains(t, code, "type Person struct {\n\tAge pgext.Puint `json:\"age\"`\n}")
+	// Check generated oneOf structure From method:
+	assert.Contains(t, code, `
+// FromExternalRef0NewPet overwrites any union data inside the ExampleSchema_Item as the provided externalRef0.NewPet
+func (t *ExampleSchema_Item) FromExternalRef0NewPet(v externalRef0.NewPet) error {
+`)
 
-	// Check generated struct
-	assert.Contains(t, code, "type Car struct {\n\tAge int `json:\"age\"`\n}")
+	// Check generated oneOf structure Merge method:
+	assert.Contains(t, code, `
+// FromExternalRef0NewPet overwrites any union data inside the ExampleSchema_Item as the provided externalRef0.NewPet
+func (t *ExampleSchema_Item) FromExternalRef0NewPet(v externalRef0.NewPet) error {
+`)
 
 	// Make sure the generated code is valid:
 	checkLint(t, "test.gen.go", []byte(code))
diff --git a/pkg/codegen/test_specs/remote-external-reference.yaml b/pkg/codegen/test_specs/remote-external-reference.yaml
new file mode 100644
index 0000000..b966df1
--- /dev/null
+++ b/pkg/codegen/test_specs/remote-external-reference.yaml
@@ -0,0 +1,26 @@
+openapi: "3.0.0"
+info:
+  version: 1.0.0
+  title: Example with oneOf and external remote ref
+paths:
+  /check:
+    get:
+      summary: Return broken example
+      responses:
+        '200':
+          description: Ok
+          content:
+            application/json:
+              schema:
+                items:
+                  $ref: '#/components/schemas/exampleSchema'
+components:
+  schemas:
+    exampleSchema:
+      properties:
+        item:
+          type: object
+          oneOf:
+            - $ref: 'https://raw.githubusercontent.com/deepmap/oapi-codegen/master/examples/petstore-expanded/petstore-expanded.yaml#/components/schemas/NewPet'
+      required:
+        - item
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 9df66b5..b236564 100644
--- a/pkg/codegen/test_specs/x-go-type-import-pet.yaml
+++ b/pkg/codegen/test_specs/x-go-type-import-pet.yaml
@@ -14,70 +14,111 @@ info:
 servers:
   - url: http://petstore.swagger.io/api
 paths:
-  /pets/{id}:
+  /schemas/pet-object:
     get:
-      summary: Returns a pet by ID
-      description: Returns a pet based on a single ID
-      operationId: findPetByID
-      parameters:
-        - name: id
-          in: path
-          description: ID of pet to fetch
-          required: true
-          schema:
-            type: integer
-            format: int64
+      description: return pet object
       responses:
         "200":
-          description: pet response
+          description: person response
           content:
             application/json:
               schema:
-                $ref: "#/components/schemas/Pet"
-  /person/{id}:
+                $ref: "#/components/schemas/PetObject"
+  /schemas/age-simple:
     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
+      description: return pet object
       responses:
         "200":
           description: person response
           content:
             application/json:
               schema:
-                $ref: "#/components/schemas/Person"
-  /car/{id}:
+                $ref: "#/components/schemas/AgeSimple"
+
+  /schemas/schema-with-array:
     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
+      description: return schema object with array
       responses:
         "200":
-          description: car response
+          description: person response
           content:
             application/json:
               schema:
-                $ref: "#/components/schemas/Car"
+                $ref: "#/components/schemas/PetArray"
+
+  /responses/type:
+    get:
+      responses:
+        '200':
+          $ref: '#/components/responses/StringResponse'
+
+  /responses/complex-response:
+    get:
+      responses:
+        '200':
+          $ref: '#/components/responses/ComplexResponse'
+
+  /requests/:
+    post:
+      requestBody:
+        $ref: "#/components/requestBodies/RegisterBody"
+      responses:
+        '200':
+          $ref: '#/components/responses/ComplexResponse'
+
+  /parameters/query:
+    post:
+      parameters:
+        - $ref: "#/components/parameters/Version"
+      requestBody:
+        $ref: "#/components/requestBodies/RegisterBody"
+      responses:
+        '200':
+          $ref: '#/components/responses/ComplexResponse'
+
+  /parameters/{versionPath}:
+    post:
+      parameters:
+        - $ref: "#/components/parameters/VersionPath"
+      requestBody:
+        $ref: "#/components/requestBodies/RegisterBody"
+      responses:
+        '200':
+          $ref: '#/components/responses/ComplexResponse'
+
+  /requests/{path}:
+    post:
+      parameters:
+        - name: version
+          in: query
+          schema:
+            x-go-type: easyjson.Json
+            x-go-type-import:
+              path: github.com/mailru/easyjson
+        - name: path
+          in: path
+          schema:
+            x-go-type: pgtype.Color
+            x-go-type-import:
+              path: github.com/jackc/pgtype
+      requestBody:
+        required: true
+        content:
+          application/json:
+            schema:
+              type: object
+              properties:
+                email:
+                  x-go-type: gotenv.Env
+                  x-go-type-import:
+                    path: github.com/subosito/gotenv
+      responses:
+        '200':
+            $ref: '#/components/responses/ComplexResponse'
 
 components:
   schemas:
-    Pet:
+    PetObject:
       properties:
         age:
           x-go-type: myuuid.UUID
@@ -86,17 +127,67 @@ 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
+    AgeSimple:
+      x-go-type: pgext.Puint
+      x-go-type-import:
+        path: github.com/CavernaTechnologies/pgext
+    PetArray:
+      type: array
+      items:
+        x-go-type: pq.Int
+        x-go-type-import:
+          path: github.com/lib/pq
+
+  responses:
+    StringResponse:
+      description: An image.
+      content:
+        application/json:
+          schema:
+            type: string
+            x-go-type: viper.ID
+            x-go-type-import:
+              path: github.com/spf13/viper
+    ComplexResponse:
+      description: Complex response
+      content:
+        application/json:
+          schema:
+            type: object
+            properties:
+              keys:
+                type: array
+                items:
+                  x-go-type: text.Text
+                  x-go-type-import:
+                    path: golang.org/x/text
+
+  requestBodies:
+    RegisterBody:
+      required: true
+      content:
+        application/json:
+          schema:
+            type: object
+            properties:
+              email:
+                x-go-type: email.Email
+                x-go-type-import:
+                  path: golang.org/x/email
+
+  parameters:
+    Version:
+      name: version
+      in: query
+      schema:
+        x-go-type: color.Color
+        x-go-type-import:
+          path: github.com/fatih/color
+
+    VersionPath:
+      name: versionPath
+      in: path
+      schema:
+        x-go-type: swag.Swag
+        x-go-type-import:
+          path: github.com/go-openapi/swag
\ No newline at end of file
diff --git a/pkg/codegen/utils_test.go b/pkg/codegen/utils_test.go
index 7bbfaff..518e287 100644
--- a/pkg/codegen/utils_test.go
+++ b/pkg/codegen/utils_test.go
@@ -14,8 +14,6 @@
 package codegen
 
 import (
-	"encoding/json"
-	"fmt"
 	"testing"
 
 	"github.com/getkin/kin-openapi/openapi3"
@@ -434,44 +432,3 @@ func TestSchemaNameToTypeName(t *testing.T) {
 		assert.Equal(t, want, SchemaNameToTypeName(in))
 	}
 }
-
-func TestGetImports(t *testing.T) {
-	schemas := map[string]*openapi3.SchemaRef{
-		"age": {
-			Value: &openapi3.Schema{
-				ExtensionProps: openapi3.ExtensionProps{
-					Extensions: map[string]interface{}{
-						"x-go-type-import": json.RawMessage(
-							`{"name": "hello", "path": "github.com/google/uuid"}`,
-						),
-						"x-go-type": json.RawMessage(
-							"hello.UUID",
-						),
-					},
-				},
-			},
-		},
-		"name": {
-			Value: &openapi3.Schema{
-				ExtensionProps: openapi3.ExtensionProps{
-					Extensions: map[string]interface{}{"other-tag": json.RawMessage(
-						`bla`,
-					)},
-				},
-			},
-		},
-		"value": nil,
-	}
-
-	expected := map[string]goImport{
-		fmt.Sprintf("%s %q", "hello", "github.com/google/uuid"): {
-			Name: "hello",
-			Path: "github.com/google/uuid",
-		},
-	}
-
-	res, err := GetImports(schemas)
-
-	assert.NoError(t, err)
-	assert.Equal(t, expected, res)
-}