Repository: deepmap/oapi-codegen
Pull request #557 (2022-04-23 02:12:10)

Add ErrorHandler for middleware
This adds roughly the same custom error handling logic across all three openapi validation middleware. For the particular API that I'm working with I want to standardize error messages across all API endpoints and responses originating from middleware. Since the current middleware make opinionated decisions around how errors coming from the validation middleware are sent, the thought is just to add an `ErrorHandler` option (similar to `ErrorHandlerFunc` in the generated server code) that allows you to do some custom error formatting.


Patch:
diff --git a/README.md b/README.md
index b7b9387..1656697 100644
--- a/README.md
+++ b/README.md
@@ -42,24 +42,34 @@ The `/components/schemas` section in OpenAPI defines reusable objects, so Go
 types are generated for these. The Pet Store example defines `Error`, `Pet`,
 `Pets` and `NewPet`, so we do the same in Go:
 ```go
-// Type definition for component schema "Error"
+// Error defines model for Error.
 type Error struct {
-    Code    int32  `json:"code"`
+    // Error code
+    Code int32 `json:"code"`
+
+    // Error message
     Message string `json:"message"`
 }
 
-// Type definition for component schema "NewPet"
+// NewPet defines model for NewPet.
 type NewPet struct {
-    Name string  `json:"name"`
-    Tag  *string `json:"tag,omitempty"`
+    // Name of the pet
+    Name string `json:"name"`
+
+    // Type of the pet
+    Tag *string `json:"tag,omitempty"`
 }
 
-// Type definition for component schema "Pet"
+// Pet defines model for Pet.
 type Pet struct {
-    // Embedded struct due to allOf(#/components/schemas/NewPet)
-    NewPet
-    // Embedded fields due to inline allOf schema
+    // Unique id of the pet
     Id int64 `json:"id"`
+
+    // Name of the pet
+    Name string `json:"name"`
+
+    // Type of the pet
+    Tag *string `json:"tag,omitempty"`
 }
 
 // Type definition for component schema "Pets"
diff --git a/cmd/oapi-codegen/oapi-codegen.go b/cmd/oapi-codegen/oapi-codegen.go
index f1a1ca2..88bacbf 100644
--- a/cmd/oapi-codegen/oapi-codegen.go
+++ b/cmd/oapi-codegen/oapi-codegen.go
@@ -46,6 +46,7 @@ var (
 	flagConfigFile     string
 	flagAliasTypes     bool
 	flagPrintVersion   bool
+	flagOlfAllOfOutput bool
 )
 
 type configuration struct {
@@ -57,6 +58,7 @@ type configuration struct {
 	TemplatesDir    string            `yaml:"templates"`
 	ImportMapping   map[string]string `yaml:"import-mapping"`
 	ExcludeSchemas  []string          `yaml:"exclude-schemas"`
+	OldAllOfOutput  bool              `yaml:"old-all-of-output"`
 }
 
 func main() {
@@ -73,6 +75,7 @@ func main() {
 	flag.StringVar(&flagConfigFile, "config", "", "a YAML config file that controls oapi-codegen behavior")
 	flag.BoolVar(&flagAliasTypes, "alias-types", false, "Alias type declarations of possible")
 	flag.BoolVar(&flagPrintVersion, "version", false, "when specified, print version and exit")
+	flag.BoolVar(&flagOlfAllOfOutput, "old-all-of-output", false, "when true, old and incorrect AllOf implementation is used")
 	flag.Parse()
 
 	if flagPrintVersion {
@@ -151,6 +154,7 @@ func main() {
 	opts.UserTemplates = templates
 
 	opts.ImportMapping = cfg.ImportMapping
+	opts.OldMergeSchemas = cfg.OldAllOfOutput
 
 	code, err := codegen.Generate(swagger, cfg.PackageName, opts)
 	if err != nil {
@@ -250,5 +254,10 @@ func configFromFlags() *configuration {
 	if cfg.OutputFile == "" {
 		cfg.OutputFile = flagOutputFile
 	}
+
+	if cfg.OldAllOfOutput == false {
+		cfg.OldAllOfOutput = flagOlfAllOfOutput
+	}
+
 	return &cfg
 }
diff --git a/examples/authenticated-api/echo/api/api.gen.go b/examples/authenticated-api/echo/api/api.gen.go
index 29d6ae4..5a5b006 100644
--- a/examples/authenticated-api/echo/api/api.gen.go
+++ b/examples/authenticated-api/echo/api/api.gen.go
@@ -41,10 +41,8 @@ type Thing struct {
 
 // ThingWithID defines model for ThingWithID.
 type ThingWithID struct {
-	// Embedded struct due to allOf(#/components/schemas/Thing)
-	Thing `yaml:",inline"`
-	// Embedded fields due to inline allOf schema
-	Id int64 `json:"id"`
+	Id   int64  `json:"id"`
+	Name string `json:"name"`
 }
 
 // AddThingJSONBody defines parameters for AddThing.
diff --git a/examples/authenticated-api/echo/server/server.go b/examples/authenticated-api/echo/server/server.go
index d4399b8..c57a897 100644
--- a/examples/authenticated-api/echo/server/server.go
+++ b/examples/authenticated-api/echo/server/server.go
@@ -6,10 +6,11 @@ import (
 	"sort"
 	"sync"
 
-	"github.com/deepmap/oapi-codegen/examples/authenticated-api/echo/api"
-	"github.com/deepmap/oapi-codegen/pkg/middleware"
 	"github.com/getkin/kin-openapi/openapi3filter"
 	"github.com/labstack/echo/v4"
+
+	"github.com/deepmap/oapi-codegen/examples/authenticated-api/echo/api"
+	"github.com/deepmap/oapi-codegen/pkg/middleware"
 )
 
 type server struct {
@@ -59,7 +60,10 @@ func (s *server) ListThings(ctx echo.Context) error {
 
 	for _, key := range thingKeys {
 		thing := s.things[key]
-		things = append(things, api.ThingWithID{Thing: thing, Id: key})
+		things = append(things, api.ThingWithID{
+			Id:   key,
+			Name: thing.Name,
+		})
 	}
 
 	s.RUnlock()
@@ -97,8 +101,8 @@ func (s *server) AddThing(ctx echo.Context) error {
 
 	s.things[s.lastID] = thing
 	thingWithId := api.ThingWithID{
-		Thing: thing,
-		Id:    s.lastID,
+		Name: thing.Name,
+		Id:   s.lastID,
 	}
 	s.lastID++
 
diff --git a/examples/petstore-expanded/chi/api/petstore.gen.go b/examples/petstore-expanded/chi/api/petstore.gen.go
index be839b0..01e759b 100644
--- a/examples/petstore-expanded/chi/api/petstore.gen.go
+++ b/examples/petstore-expanded/chi/api/petstore.gen.go
@@ -38,11 +38,14 @@ type NewPet struct {
 
 // Pet defines model for Pet.
 type Pet struct {
-	// Embedded struct due to allOf(#/components/schemas/NewPet)
-	NewPet `yaml:",inline"`
-	// Embedded fields due to inline allOf schema
 	// Unique id of the pet
 	Id int64 `json:"id"`
+
+	// Name of the pet
+	Name string `json:"name"`
+
+	// Type of the pet
+	Tag *string `json:"tag,omitempty"`
 }
 
 // FindPetsParams defines parameters for FindPets.
diff --git a/examples/petstore-expanded/echo/api/petstore-types.gen.go b/examples/petstore-expanded/echo/api/petstore-types.gen.go
index 24add64..8e7d9e2 100644
--- a/examples/petstore-expanded/echo/api/petstore-types.gen.go
+++ b/examples/petstore-expanded/echo/api/petstore-types.gen.go
@@ -23,11 +23,14 @@ type NewPet struct {
 
 // Pet defines model for Pet.
 type Pet struct {
-	// Embedded struct due to allOf(#/components/schemas/NewPet)
-	NewPet `yaml:",inline"`
-	// Embedded fields due to inline allOf schema
 	// Unique id of the pet
 	Id int64 `json:"id"`
+
+	// Name of the pet
+	Name string `json:"name"`
+
+	// Type of the pet
+	Tag *string `json:"tag,omitempty"`
 }
 
 // FindPetsParams defines parameters for FindPets.
diff --git a/examples/petstore-expanded/petstore-client.gen.go b/examples/petstore-expanded/petstore-client.gen.go
index 9c163ed..80eacf6 100644
--- a/examples/petstore-expanded/petstore-client.gen.go
+++ b/examples/petstore-expanded/petstore-client.gen.go
@@ -37,11 +37,14 @@ type NewPet struct {
 
 // Pet defines model for Pet.
 type Pet struct {
-	// Embedded struct due to allOf(#/components/schemas/NewPet)
-	NewPet `yaml:",inline"`
-	// Embedded fields due to inline allOf schema
 	// Unique id of the pet
 	Id int64 `json:"id"`
+
+	// Name of the pet
+	Name string `json:"name"`
+
+	// Type of the pet
+	Tag *string `json:"tag,omitempty"`
 }
 
 // FindPetsParams defines parameters for FindPets.
diff --git a/pkg/chi-middleware/oapi_validate.go b/pkg/chi-middleware/oapi_validate.go
index d9e8306..dfce720 100644
--- a/pkg/chi-middleware/oapi_validate.go
+++ b/pkg/chi-middleware/oapi_validate.go
@@ -15,9 +15,13 @@ import (
 	"github.com/getkin/kin-openapi/routers/gorillamux"
 )
 
+// ErrorHandler is called when there is an error in validation
+type ErrorHandler func(w http.ResponseWriter, message string, statusCode int)
+
 // Options to customize request validation, openapi3filter specified options will be passed through.
 type Options struct {
-	Options openapi3filter.Options
+	Options      openapi3filter.Options
+	ErrorHandler ErrorHandler
 }
 
 // OapiRequestValidator Creates middleware to validate request by swagger spec.
@@ -39,7 +43,11 @@ func OapiRequestValidatorWithOptions(swagger *openapi3.T, options *Options) func
 
 			// validate request
 			if statusCode, err := validateRequest(r, router, options); err != nil {
-				http.Error(w, err.Error(), statusCode)
+				if options != nil && options.ErrorHandler != nil {
+					options.ErrorHandler(w, err.Error(), statusCode)
+				} else {
+					http.Error(w, err.Error(), statusCode)
+				}
 				return
 			}
 
diff --git a/pkg/codegen/codegen.go b/pkg/codegen/codegen.go
index abe0072..36535b4 100644
--- a/pkg/codegen/codegen.go
+++ b/pkg/codegen/codegen.go
@@ -49,8 +49,13 @@ type Options struct {
 	UserTemplates      map[string]string // Override built-in templates from user-provided files
 	ImportMapping      map[string]string // ImportMapping specifies the golang package path for each external reference
 	ExcludeSchemas     []string          // Exclude from generation schemas with given names. Ignored when empty.
+	OldMergeSchemas    bool              // Schema merging for allOf was changed in a big way, when true, the old way is used
 }
 
+// We store options globally to simplify accessing them from all the codegen
+// functions.
+var options Options
+
 // goImport represents a go package to be imported in the generated code
 type goImport struct {
 	Name string // package name
@@ -79,7 +84,7 @@ func (im importMap) GoImports() []string {
 
 var importMapping importMap
 
-func constructImportMapping(input map[string]string) importMap {
+func constructImportMapping(importMapping map[string]string) importMap {
 	var (
 		pathToName = map[string]string{}
 		result     = importMap{}
@@ -87,7 +92,7 @@ func constructImportMapping(input map[string]string) importMap {
 
 	{
 		var packagePaths []string
-		for _, packageName := range input {
+		for _, packageName := range importMapping {
 			packagePaths = append(packagePaths, packageName)
 		}
 		sort.Strings(packagePaths)
@@ -98,16 +103,19 @@ func constructImportMapping(input map[string]string) importMap {
 			}
 		}
 	}
-	for specPath, packagePath := range input {
+	for specPath, packagePath := range importMapping {
 		result[specPath] = goImport{Name: pathToName[packagePath], Path: packagePath}
 	}
 	return result
 }
 
-// Uses the Go templating engine to generate all of our server wrappers from
+// Generate uses the Go templating engine to generate all of our server wrappers from
 // the descriptions we've built up above from the schema objects.
 // opts defines
 func Generate(swagger *openapi3.T, packageName string, opts Options) (string, error) {
+	// This is global state
+	options = opts
+
 	importMapping = constructImportMapping(opts.ImportMapping)
 
 	filterOperationsByTag(swagger, opts)
@@ -116,7 +124,7 @@ func Generate(swagger *openapi3.T, packageName string, opts Options) (string, er
 	}
 
 	// This creates the golang templates text package
-	TemplateFunctions["opts"] = func() Options { return opts }
+	TemplateFunctions["opts"] = func() Options { return options }
 	t := template.New("oapi-codegen").Funcs(TemplateFunctions)
 	// This parses all of our own template files into the template object
 	// above
diff --git a/pkg/codegen/merge_schemas.go b/pkg/codegen/merge_schemas.go
new file mode 100644
index 0000000..5bf378b
--- /dev/null
+++ b/pkg/codegen/merge_schemas.go
@@ -0,0 +1,189 @@
+package codegen
+
+import (
+	"errors"
+	"fmt"
+
+	"github.com/getkin/kin-openapi/openapi3"
+)
+
+// MergeSchemas merges all the fields in the schemas supplied into one giant schema.
+// The idea is that we merge all fields together into one schema.
+func MergeSchemas(allOf []*openapi3.SchemaRef, path []string) (Schema, error) {
+	// If someone asked for the old way, for backward compatibility, return the
+	// old style result.
+	if options.OldMergeSchemas {
+		return mergeSchemas_V1(allOf, path)
+	}
+	return mergeSchemas(allOf, path)
+}
+
+func mergeSchemas(allOf []*openapi3.SchemaRef, path []string) (Schema, error) {
+	var schema openapi3.Schema
+	for _, schemaRef := range allOf {
+		var err error
+		schema, err = mergeOpenapiSchemas(schema, *schemaRef.Value)
+		if err != nil {
+			return Schema{}, fmt.Errorf("error merging schemas for AllOf: %w", err)
+		}
+	}
+
+	return GenerateGoSchema(openapi3.NewSchemaRef("", &schema), path)
+}
+
+func mergeAllOf(allOf []*openapi3.SchemaRef) (openapi3.Schema, error) {
+	var schema openapi3.Schema
+	for _, schemaRef := range allOf {
+		var err error
+		schema, err = mergeOpenapiSchemas(schema, *schemaRef.Value)
+		if err != nil {
+			return openapi3.Schema{}, fmt.Errorf("error merging schemas for AllOf: %w", err)
+		}
+	}
+	return schema, nil
+}
+
+// mergeOpenapiSchemas merges two openAPI schemas and returns the schema
+// all of whose fields are composed.
+func mergeOpenapiSchemas(s1, s2 openapi3.Schema) (openapi3.Schema, error) {
+	var result openapi3.Schema
+	if s1.Extensions != nil || s2.Extensions != nil {
+		result.Extensions = make(map[string]interface{})
+		if s1.Extensions != nil {
+			for k, v := range s1.Extensions {
+				result.Extensions[k] = v
+			}
+		}
+		if s2.Extensions != nil {
+			for k, v := range s2.Extensions {
+				// TODO: Check for collisions
+				result.Extensions[k] = v
+			}
+		}
+	}
+
+	result.OneOf = append(s1.OneOf, s2.OneOf...)
+
+	// We are going to make AllOf transitive, so that merging an AllOf that
+	// contains AllOf's will result in a flat object.
+	var err error
+	if s1.AllOf != nil {
+		var merged openapi3.Schema
+		merged, err = mergeAllOf(s1.AllOf)
+		if err != nil {
+			return openapi3.Schema{}, fmt.Errorf("error transitive merging AllOf on schema 1")
+		}
+		s1 = merged
+	}
+	if s2.AllOf != nil {
+		var merged openapi3.Schema
+		merged, err = mergeAllOf(s2.AllOf)
+		if err != nil {
+			return openapi3.Schema{}, fmt.Errorf("error transitive merging AllOf on schema 2")
+		}
+		s2 = merged
+	}
+
+	result.AllOf = append(s1.AllOf, s2.AllOf...)
+
+	if s1.Type != "" && s2.Type != "" && s1.Type != s2.Type {
+		return openapi3.Schema{}, errors.New("can not merge incompatible types")
+	}
+	result.Type = s1.Type
+
+	if s1.Format != s2.Format {
+		return openapi3.Schema{}, errors.New("can not merge incompatible formats")
+	}
+	result.Format = s1.Format
+
+	// For Enums, do we union, or intersect? This is a bit vague. I choose
+	// to be more permissive and union.
+	result.Enum = append(s1.Enum, s2.Enum...)
+
+	// I don't know how to handle two different defaults.
+	if s1.Default != nil || s2.Default != nil {
+		return openapi3.Schema{}, errors.New("merging two sets of defaults is undefined")
+	}
+	if s1.Default != nil {
+		result.Default = s1.Default
+	}
+	if s2.Default != nil {
+		result.Default = s2.Default
+	}
+
+	// We skip Example
+	// We skip ExternalDocs
+
+	// If two schemas disagree on any of these flags, we error out.
+	if s1.UniqueItems != s2.UniqueItems {
+		return openapi3.Schema{}, errors.New("merging two schemas with different UniqueItems")
+
+	}
+	result.UniqueItems = s1.UniqueItems
+
+	if s1.ExclusiveMin != s2.ExclusiveMin {
+		return openapi3.Schema{}, errors.New("merging two schemas with different ExclusiveMin")
+
+	}
+	result.ExclusiveMin = s1.ExclusiveMin
+
+	if s1.ExclusiveMax != s2.ExclusiveMax {
+		return openapi3.Schema{}, errors.New("merging two schemas with different ExclusiveMax")
+
+	}
+	result.ExclusiveMax = s1.ExclusiveMax
+
+	if s1.Nullable != s2.Nullable {
+		return openapi3.Schema{}, errors.New("merging two schemas with different Nullable")
+
+	}
+	result.Nullable = s1.Nullable
+
+	if s1.ReadOnly != s2.ReadOnly {
+		return openapi3.Schema{}, errors.New("merging two schemas with different ReadOnly")
+
+	}
+	result.ReadOnly = s1.ReadOnly
+
+	if s1.WriteOnly != s2.WriteOnly {
+		return openapi3.Schema{}, errors.New("merging two schemas with different WriteOnly")
+
+	}
+	result.WriteOnly = s1.WriteOnly
+
+	if s1.AllowEmptyValue != s2.AllowEmptyValue {
+		return openapi3.Schema{}, errors.New("merging two schemas with different AllowEmptyValue")
+
+	}
+	result.AllowEmptyValue = s1.AllowEmptyValue
+
+	// Required. We merge these.
+	result.Required = append(s1.Required, s2.Required...)
+
+	// We merge all properties
+	result.Properties = make(map[string]*openapi3.SchemaRef)
+	for k, v := range s1.Properties {
+		result.Properties[k] = v
+	}
+	for k, v := range s2.Properties {
+		// TODO: detect conflicts
+		result.Properties[k] = v
+	}
+
+	if SchemaHasAdditionalProperties(&s1) && SchemaHasAdditionalProperties(&s2) {
+		return openapi3.Schema{}, errors.New("merging two schemas with additional properties, this is unhandled")
+	}
+	if s1.AdditionalProperties != nil {
+		result.AdditionalProperties = s1.AdditionalProperties
+	}
+	if s2.AdditionalProperties != nil {
+		result.AdditionalProperties = s2.AdditionalProperties
+	}
+
+	// Unhandled for now
+	if s1.Discriminator != nil || s2.Discriminator != nil {
+		return openapi3.Schema{}, errors.New("merging two schemas with discriminators is not supported")
+	}
+
+	return result, nil
+}
diff --git a/pkg/codegen/merge_schemas_v1.go b/pkg/codegen/merge_schemas_v1.go
new file mode 100644
index 0000000..ae2f125
--- /dev/null
+++ b/pkg/codegen/merge_schemas_v1.go
@@ -0,0 +1,111 @@
+package codegen
+
+import (
+	"errors"
+	"fmt"
+	"strings"
+
+	"github.com/getkin/kin-openapi/openapi3"
+)
+
+func mergeSchemas_V1(allOf []*openapi3.SchemaRef, path []string) (Schema, error) {
+	var outSchema Schema
+	for _, schemaOrRef := range allOf {
+		ref := schemaOrRef.Ref
+
+		var refType string
+		var err error
+		if IsGoTypeReference(ref) {
+			refType, err = RefPathToGoType(ref)
+			if err != nil {
+				return Schema{}, fmt.Errorf("error converting reference path to a go type: %w", err)
+			}
+		}
+
+		schema, err := GenerateGoSchema(schemaOrRef, path)
+		if err != nil {
+			return Schema{}, fmt.Errorf("error generating Go schema in allOf: %w", err)
+		}
+		schema.RefType = refType
+
+		for _, p := range schema.Properties {
+			err = outSchema.AddProperty(p)
+			if err != nil {
+				return Schema{}, fmt.Errorf("error merging properties: %w", err)
+			}
+		}
+
+		if schema.HasAdditionalProperties {
+			if outSchema.HasAdditionalProperties {
+				// Both this schema, and the aggregate schema have additional
+				// properties, they must match.
+				if schema.AdditionalPropertiesType.TypeDecl() != outSchema.AdditionalPropertiesType.TypeDecl() {
+					return Schema{}, errors.New("additional properties in allOf have incompatible types")
+				}
+			} else {
+				// We're switching from having no additional properties to having
+				// them
+				outSchema.HasAdditionalProperties = true
+				outSchema.AdditionalPropertiesType = schema.AdditionalPropertiesType
+			}
+		}
+	}
+
+	// Now, we generate the struct which merges together all the fields.
+	var err error
+	outSchema.GoType, err = GenStructFromAllOf(allOf, path)
+	if err != nil {
+		return Schema{}, fmt.Errorf("unable to generate aggregate type for AllOf: %w", err)
+	}
+	return outSchema, nil
+}
+
+// This function generates an object that is the union of the objects in the
+// input array. In the case of Ref objects, we use an embedded struct, otherwise,
+// we inline the fields.
+func GenStructFromAllOf(allOf []*openapi3.SchemaRef, path []string) (string, error) {
+	// Start out with struct {
+	objectParts := []string{"struct {"}
+	for _, schemaOrRef := range allOf {
+		ref := schemaOrRef.Ref
+		if IsGoTypeReference(ref) {
+			// We have a referenced type, we will generate an inlined struct
+			// member.
+			// struct {
+			//   InlinedMember
+			//   ...
+			// }
+			goType, err := RefPathToGoType(ref)
+			if err != nil {
+				return "", err
+			}
+			objectParts = append(objectParts,
+				fmt.Sprintf("   // Embedded struct due to allOf(%s)", ref))
+			objectParts = append(objectParts,
+				fmt.Sprintf("   %s `yaml:\",inline\"`", goType))
+		} else {
+			// Inline all the fields from the schema into the output struct,
+			// just like in the simple case of generating an object.
+			goSchema, err := GenerateGoSchema(schemaOrRef, path)
+			if err != nil {
+				return "", err
+			}
+			objectParts = append(objectParts, "   // Embedded fields due to inline allOf schema")
+			objectParts = append(objectParts, GenFieldsFromProperties(goSchema.Properties)...)
+
+			if goSchema.HasAdditionalProperties {
+				addPropsType := goSchema.AdditionalPropertiesType.GoType
+				if goSchema.AdditionalPropertiesType.RefType != "" {
+					addPropsType = goSchema.AdditionalPropertiesType.RefType
+				}
+
+				additionalPropertiesPart := fmt.Sprintf("AdditionalProperties map[string]%s `json:\"-\"`", addPropsType)
+				if !StringInArray(additionalPropertiesPart, objectParts) {
+					objectParts = append(objectParts, additionalPropertiesPart)
+				}
+			}
+		}
+	}
+	objectParts = append(objectParts, "}")
+	return strings.Join(objectParts, "\n"), nil
+}
diff --git a/pkg/codegen/schema.go b/pkg/codegen/schema.go
index ca3ed46..b8b86ca 100644
--- a/pkg/codegen/schema.go
+++ b/pkg/codegen/schema.go
@@ -41,7 +41,12 @@ func (s Schema) TypeDecl() string {
 	return s.GoType
 }
 
-func (s *Schema) MergeProperty(p Property) error {
+// AddProperty adds a new property to the current Schema, and returns an error
+// if it collides. Two identical fields will not collide, but two properties by
+// the same name, but different definition, will collide. It's safe to merge the
+// fields of two schemas with overalapping properties if those properties are
+// identical.
+func (s *Schema) AddProperty(p Property) error {
 	// Scan all existing properties for a conflict
 	for _, e := range s.Properties {
 		if e.JsonFieldName == p.JsonFieldName && !PropertiesEqual(e, p) {
@@ -504,109 +509,6 @@ func GenStructFromSchema(schema Schema) string {
 	return strings.Join(objectParts, "\n")
 }
 
-// Merge all the fields in the schemas supplied into one giant schema.
-func MergeSchemas(allOf []*openapi3.SchemaRef, path []string) (Schema, error) {
-	var outSchema Schema
-	for _, schemaOrRef := range allOf {
-		ref := schemaOrRef.Ref
-
-		var refType string
-		var err error
-		if IsGoTypeReference(ref) {
-			refType, err = RefPathToGoType(ref)
-			if err != nil {
-				return Schema{}, fmt.Errorf("error converting reference path to a go type: %w", err)
-			}
-		}
-
-		schema, err := GenerateGoSchema(schemaOrRef, path)
-		if err != nil {
-			return Schema{}, fmt.Errorf("error generating Go schema in allOf: %w", err)
-		}
-		schema.RefType = refType
-
-		for _, p := range schema.Properties {
-			err = outSchema.MergeProperty(p)
-			if err != nil {
-				return Schema{}, fmt.Errorf("error merging properties: %w", err)
-			}
-		}
-
-		if schema.HasAdditionalProperties {
-			if outSchema.HasAdditionalProperties {
-				// Both this schema, and the aggregate schema have additional
-				// properties, they must match.
-				if schema.AdditionalPropertiesType.TypeDecl() != outSchema.AdditionalPropertiesType.TypeDecl() {
-					return Schema{}, errors.New("additional properties in allOf have incompatible types")
-				}
-			} else {
-				// We're switching from having no additional properties to having
-				// them
-				outSchema.HasAdditionalProperties = true
-				outSchema.AdditionalPropertiesType = schema.AdditionalPropertiesType
-			}
-		}
-	}
-
-	// Now, we generate the struct which merges together all the fields.
-	var err error
-	outSchema.GoType, err = GenStructFromAllOf(allOf, path)
-	if err != nil {
-		return Schema{}, fmt.Errorf("unable to generate aggregate type for AllOf: %w", err)
-	}
-	return outSchema, nil
-}
-
-// This function generates an object that is the union of the objects in the
-// input array. In the case of Ref objects, we use an embedded struct, otherwise,
-// we inline the fields.
-func GenStructFromAllOf(allOf []*openapi3.SchemaRef, path []string) (string, error) {
-	// Start out with struct {
-	objectParts := []string{"struct {"}
-	for _, schemaOrRef := range allOf {
-		ref := schemaOrRef.Ref
-		if IsGoTypeReference(ref) {
-			// We have a referenced type, we will generate an inlined struct
-			// member.
-			// struct {
-			//   InlinedMember
-			//   ...
-			// }
-			goType, err := RefPathToGoType(ref)
-			if err != nil {
-				return "", err
-			}
-			objectParts = append(objectParts,
-				fmt.Sprintf("   // Embedded struct due to allOf(%s)", ref))
-			objectParts = append(objectParts,
-				fmt.Sprintf("   %s `yaml:\",inline\"`", goType))
-		} else {
-			// Inline all the fields from the schema into the output struct,
-			// just like in the simple case of generating an object.
-			goSchema, err := GenerateGoSchema(schemaOrRef, path)
-			if err != nil {
-				return "", err
-			}
-			objectParts = append(objectParts, "   // Embedded fields due to inline allOf schema")
-			objectParts = append(objectParts, GenFieldsFromProperties(goSchema.Properties)...)
-
-			if goSchema.HasAdditionalProperties {
-				addPropsType := goSchema.AdditionalPropertiesType.GoType
-				if goSchema.AdditionalPropertiesType.RefType != "" {
-					addPropsType = goSchema.AdditionalPropertiesType.RefType
-				}
-
-				additionalPropertiesPart := fmt.Sprintf("AdditionalProperties map[string]%s `json:\"-\"`", addPropsType)
-				if !StringInArray(additionalPropertiesPart, objectParts) {
-					objectParts = append(objectParts, additionalPropertiesPart)
-				}
-			}
-		}
-	}
-	objectParts = append(objectParts, "}")
-	return strings.Join(objectParts, "\n"), nil
-}
-
 // This constructs a Go type for a parameter, looking at either the schema or
 // the content, whichever is available
 func paramToGoType(param *openapi3.Parameter, path []string) (Schema, error) {
diff --git a/pkg/codegen/templates/imports.tmpl b/pkg/codegen/templates/imports.tmpl
index a55c15d..064e370 100644
--- a/pkg/codegen/templates/imports.tmpl
+++ b/pkg/codegen/templates/imports.tmpl
@@ -26,6 +26,7 @@ import (
 	"github.com/getkin/kin-openapi/openapi3"
 	"github.com/go-chi/chi/v5"
 	"github.com/labstack/echo/v4"
+	"github.com/gin-gonic/gin"
 	{{- range .ExternalImports}}
 	{{ . }}
 	{{- end}}
diff --git a/pkg/gin-middleware/oapi_validate.go b/pkg/gin-middleware/oapi_validate.go
index 82a885e..1f1687d 100644
--- a/pkg/gin-middleware/oapi_validate.go
+++ b/pkg/gin-middleware/oapi_validate.go
@@ -29,8 +29,10 @@ import (
 	"github.com/gin-gonic/gin"
 )
 
-const GinContextKey = "oapi-codegen/gin-context"
-const UserDataKey = "oapi-codegen/user-data"
+const (
+	GinContextKey = "oapi-codegen/gin-context"
+	UserDataKey   = "oapi-codegen/user-data"
+)
 
 // Create validator middleware from a YAML file path
 func OapiValidatorFromYamlFile(path string) (gin.HandlerFunc, error) {
@@ -54,9 +56,13 @@ func OapiRequestValidator(swagger *openapi3.T) gin.HandlerFunc {
 	return OapiRequestValidatorWithOptions(swagger, nil)
 }
 
+// ErrorHandler is called when there is an error in validation
+type ErrorHandler func(c *gin.Context, message string, statusCode int)
+
 // Options to customize request validation. These are passed through to
 // openapi3filter.
 type Options struct {
+	ErrorHandler ErrorHandler
 	Options      openapi3filter.Options
 	ParamDecoder openapi3filter.ContentParameterDecoder
 	UserData     interface{}
@@ -71,8 +77,14 @@ func OapiRequestValidatorWithOptions(swagger *openapi3.T, options *Options) gin.
 	return func(c *gin.Context) {
 		err := ValidateRequestFromContext(c, router, options)
 		if err != nil {
-			// note: i am not sure if this is the best way to handle this
-			c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": err.Error()})
+			if options != nil && options.ErrorHandler != nil {
+				options.ErrorHandler(c, err.Error(), http.StatusBadRequest)
+				// in case the handler didn't internally call Abort, stop the chain
+				c.Abort()
+			} else {
+				// note: i am not sure if this is the best way to handle this
+				c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": err.Error()})
+			}
 		}
 		c.Next()
 	}
diff --git a/pkg/middleware/oapi_validate.go b/pkg/middleware/oapi_validate.go
index 03b6f6a..b192818 100644
--- a/pkg/middleware/oapi_validate.go
+++ b/pkg/middleware/oapi_validate.go
@@ -29,8 +29,10 @@ import (
 	echomiddleware "github.com/labstack/echo/v4/middleware"
 )
 
-const EchoContextKey = "oapi-codegen/echo-context"
-const UserDataKey = "oapi-codegen/user-data"
+const (
+	EchoContextKey = "oapi-codegen/echo-context"
+	UserDataKey    = "oapi-codegen/user-data"
+)
 
 // This is an Echo middleware function which validates incoming HTTP requests
 // to make sure that they conform to the given OAPI 3.0 specification. When
@@ -56,9 +58,13 @@ func OapiRequestValidator(swagger *openapi3.T) echo.MiddlewareFunc {
 	return OapiRequestValidatorWithOptions(swagger, nil)
 }
 
+// ErrorHandler is called when there is an error in validation
+type ErrorHandler func(c echo.Context, err *echo.HTTPError) error
+
 // Options to customize request validation. These are passed through to
 // openapi3filter.
 type Options struct {
+	ErrorHandler ErrorHandler
 	Options      openapi3filter.Options
 	ParamDecoder openapi3filter.ContentParameterDecoder
 	UserData     interface{}
@@ -81,6 +87,9 @@ func OapiRequestValidatorWithOptions(swagger *openapi3.T, options *Options) echo
 
 			err := ValidateRequestFromContext(c, router, options)
 			if err != nil {
+				if options != nil && options.ErrorHandler != nil {
+					return options.ErrorHandler(c, err)
+				}
 				return err
 			}
 			return next(c)
@@ -90,7 +99,7 @@ func OapiRequestValidatorWithOptions(swagger *openapi3.T, options *Options) echo
 
 // ValidateRequestFromContext is called from the middleware above and actually does the work
 // of validating a request.
-func ValidateRequestFromContext(ctx echo.Context, router routers.Router, options *Options) error {
+func ValidateRequestFromContext(ctx echo.Context, router routers.Router, options *Options) *echo.HTTPError {
 	req := ctx.Request()
 	route, pathParams, err := router.FindRoute(req)
 
diff --git a/pkg/types/uuid.go b/pkg/types/uuid.go
index a4fb36d..1c11f38 100644
--- a/pkg/types/uuid.go
+++ b/pkg/types/uuid.go
@@ -1,29 +1,7 @@
 package types
 
 import (
-	"encoding/json"
-	"errors"
-
 	"github.com/google/uuid"
 )
 
-type UUID string
-
-func (u UUID) MarshalJSON() ([]byte, error) {
-	if _, err := uuid.Parse(string(u)); err != nil {
-		return nil, errors.New("uuid: failed to pass validation")
-	}
-	return json.Marshal(string(u))
-}
-
-func (u *UUID) UnmarshalJSON(data []byte) error {
-	var s string
-	if err := json.Unmarshal(data, &s); err != nil {
-		return err
-	}
-	if _, err := uuid.Parse(s); err != nil {
-		return errors.New("uuid: failed to pass validation")
-	}
-	*u = UUID(s)
-	return nil
-}
+type UUID = uuid.UUID


Test patch:
diff --git a/examples/petstore-expanded/chi/petstore_test.go b/examples/petstore-expanded/chi/petstore_test.go
index 71c9613..b12c4ce 100644
--- a/examples/petstore-expanded/chi/petstore_test.go
+++ b/examples/petstore-expanded/chi/petstore_test.go
@@ -103,12 +103,10 @@ func TestPetStore(t *testing.T) {
 		tag := "TagOfFido"
 
 		store.Pets = map[int64]api.Pet{
-			1: api.Pet{
-				NewPet: api.NewPet{
-					Tag: &tag,
-				},
+			1: {
+				Tag: &tag,
 			},
-			2: api.Pet{},
+			2: {},
 		}
 
 		// Filter pets by tag, we should have 1
diff --git a/internal/test/all_of/config1.yaml b/internal/test/all_of/config1.yaml
new file mode 100644
index 0000000..927b1da
--- /dev/null
+++ b/internal/test/all_of/config1.yaml
@@ -0,0 +1,8 @@
+output:
+  v1/openapi.gen.go
+package: v1
+generate:
+  - types
+  - skip-prune
+  - spec
+old-all-of-output: true
diff --git a/internal/test/all_of/config2.yaml b/internal/test/all_of/config2.yaml
new file mode 100644
index 0000000..04f5687
--- /dev/null
+++ b/internal/test/all_of/config2.yaml
@@ -0,0 +1,7 @@
+output:
+  v2/openapi.gen.go
+package: v2
+generate:
+  - types
+  - skip-prune
+  - spec
diff --git a/internal/test/all_of/doc.go b/internal/test/all_of/doc.go
new file mode 100644
index 0000000..0740268
--- /dev/null
+++ b/internal/test/all_of/doc.go
@@ -0,0 +1,4 @@
+package all_of
+
+//go:generate go run github.com/deepmap/oapi-codegen/cmd/oapi-codegen --config=config1.yaml openapi.yaml
+//go:generate go run github.com/deepmap/oapi-codegen/cmd/oapi-codegen --config=config2.yaml openapi.yaml
diff --git a/internal/test/all_of/openapi.yaml b/internal/test/all_of/openapi.yaml
new file mode 100644
index 0000000..e405c53
--- /dev/null
+++ b/internal/test/all_of/openapi.yaml
@@ -0,0 +1,53 @@
+openapi: "3.0.1"
+info:
+  version: 1.0.0
+  title: Tests AllOf composition
+paths:
+  /placeholder:
+    get:
+      operationId: placeholder
+      description: |
+        Validators want at least one path, so this makes them happy.
+      responses:
+        default:
+          description: placeholder
+          content:
+            application/json:
+              schema:
+                $ref: "#/components/schemas/PersonWithID"
+components:
+  schemas:
+    PersonProperties:
+      type: object
+      description: |
+        These are fields that specify a person. They are all optional, and
+        would be used by an `Edit` style API endpoint, where each is optional.
+      properties:
+        FirstName:
+          type: string
+        LastName:
+          type: string
+        GovernmentIDNumber:
+          type: integer
+          format: int64
+    Person:
+      type: object
+      description: |
+        This is a person, with mandatory first and last name, but optional ID
+        number. This would be returned by a `Get` style API. We merge the person
+        properties with another Schema which only provides required fields.
+      allOf:
+        - $ref: "#/components/schemas/PersonProperties"
+        - required: [FirstName, LastName]
+    PersonWithID:
+      type: object
+      description: |
+        This is a person record as returned from a Create endpoint. It contains
+        all the fields of a Person, with an additional resource UUID.
+      allOf:
+        - $ref: "#/components/schemas/Person"
+        - properties:
+            ID:
+              type: integer
+              format: int64
+          required: [ ID ]
diff --git a/internal/test/all_of/v1/openapi.gen.go b/internal/test/all_of/v1/openapi.gen.go
new file mode 100644
index 0000000..8092ab3
--- /dev/null
+++ b/internal/test/all_of/v1/openapi.gen.go
@@ -0,0 +1,127 @@
+// Package v1 provides primitives to interact with the openapi HTTP API.
+//
+// Code generated by github.com/deepmap/oapi-codegen version (devel) DO NOT EDIT.
+package v1
+
+import (
+	"bytes"
+	"compress/gzip"
+	"encoding/base64"
+	"fmt"
+	"net/url"
+	"path"
+	"strings"
+
+	"github.com/getkin/kin-openapi/openapi3"
+)
+
+// Person defines model for Person.
+type Person struct {
+	// Embedded struct due to allOf(#/components/schemas/PersonProperties)
+	PersonProperties `yaml:",inline"`
+	// Embedded fields due to inline allOf schema
+}
+
+// These are fields that specify a person. They are all optional, and
+// would be used by an `Edit` style API endpoint, where each is optional.
+type PersonProperties struct {
+	FirstName          *string `json:"FirstName,omitempty"`
+	GovernmentIDNumber *int64  `json:"GovernmentIDNumber,omitempty"`
+	LastName           *string `json:"LastName,omitempty"`
+}
+
+// PersonWithID defines model for PersonWithID.
+type PersonWithID struct {
+	// Embedded struct due to allOf(#/components/schemas/Person)
+	Person `yaml:",inline"`
+	// Embedded fields due to inline allOf schema
+	ID int64 `json:"ID"`
+}
+
+// Base64 encoded, gzipped, json marshaled Swagger object
+var swaggerSpec = []string{
+
+	"H4sIAAAAAAAC/5SUT2/bOBDFv8qAu0dBTrCLPegWrNtAQJEaaNIc4gAZSyOLKTVkyVEMwfB3L0jJ/+AW",
+	"aX0awOTjvDe/0VZVtnOWiSWoYqtC1VKHqVyQD5ZjhcZ8blTxtFV/e2pUof6aHW/Npiuz8fzCW0deNAW1",
+	"y7bK0/dee6pV8aQ+ah/kDjtSmfqEU/m8e85UTaHy2omO76n7VgfQARBcksxgo6WFDrlGsX6AJgoBcg0G",
+	"gwBjRxmsegGbJNBAOV8y992KfA5JbmN7U8OKwJP0nqmG1QAIL7ckLxBkMAQ3izKHR4KO/JpAWpqeX7I7",
+	"eBo7QbbSkocvyTlsWl21YNkM4Lx90zUF2PuGRpOpQ75klSkZHKlC2dUrVaJ2mbqIrNheZEGBAD1NQiAt",
+	"CgRHlW6GQ0LRJA3pGBpziCGLGS354L0Pk2+Glw+1PnUOxLWzmiWDTUuegLBq4xD2WqMDd9bqcaDFdm8u",
+	"iNe8juZu7Rt57oilnN+lWcRjjfUdiiqUZvnv32MomoXW5OPFAxuXqrtfhviopS3nf0prYvTc1Cjybpu7",
+	"7Iztcv47JIOnyvoaMBw5bLztAOF/Tyh0GEMOpUBlWVBzWHKcaiRygsA2gLA4XQ5kwLrWE/6egu19RfDw",
+	"UM5/yl7sX3NjU8ZaTPzvnoIEuInxQUosJD2VqTfyYXR0nV/lVzF164jRaVWof/Kr/DqygdKmBGfOYEWt",
+	"NfU48jXJJdhf0ei0zgE2yAIoYChus2WCKJVBsCAxwA6/UQSfOmjRuWE0FGeGUaysVaEWJ0/GyQRnOewX",
+	"qsHepBZioMSpROeMrpLA7HX6zo1sxOp9cibeUpDnzk7d79LvRwAAAP//lzc18GUFAAA=",
+}
+
+// GetSwagger returns the content of the embedded swagger specification file
+// or error if failed to decode
+func decodeSpec() ([]byte, error) {
+	zipped, err := base64.StdEncoding.DecodeString(strings.Join(swaggerSpec, ""))
+	if err != nil {
+		return nil, fmt.Errorf("error base64 decoding spec: %s", err)
+	}
+	zr, err := gzip.NewReader(bytes.NewReader(zipped))
+	if err != nil {
+		return nil, fmt.Errorf("error decompressing spec: %s", err)
+	}
+	var buf bytes.Buffer
+	_, err = buf.ReadFrom(zr)
+	if err != nil {
+		return nil, fmt.Errorf("error decompressing spec: %s", err)
+	}
+
+	return buf.Bytes(), nil
+}
+
+var rawSpec = decodeSpecCached()
+
+// a naive cached of a decoded swagger spec
+func decodeSpecCached() func() ([]byte, error) {
+	data, err := decodeSpec()
+	return func() ([]byte, error) {
+		return data, err
+	}
+}
+
+// Constructs a synthetic filesystem for resolving external references when loading openapi specifications.
+func PathToRawSpec(pathToFile string) map[string]func() ([]byte, error) {
+	var res = make(map[string]func() ([]byte, error))
+	if len(pathToFile) > 0 {
+		res[pathToFile] = rawSpec
+	}
+
+	return res
+}
+
+// GetSwagger returns the Swagger specification corresponding to the generated code
+// in this file. The external references of Swagger specification are resolved.
+// The logic of resolving external references is tightly connected to "import-mapping" feature.
+// Externally referenced files must be embedded in the corresponding golang packages.
+// Urls can be supported but this task was out of the scope.
+func GetSwagger() (swagger *openapi3.T, err error) {
+	var resolvePath = PathToRawSpec("")
+
+	loader := openapi3.NewLoader()
+	loader.IsExternalRefsAllowed = true
+	loader.ReadFromURIFunc = func(loader *openapi3.Loader, url *url.URL) ([]byte, error) {
+		var pathToFile = url.String()
+		pathToFile = path.Clean(pathToFile)
+		getSpec, ok := resolvePath[pathToFile]
+		if !ok {
+			err1 := fmt.Errorf("path not found: %s", pathToFile)
+			return nil, err1
+		}
+		return getSpec()
+	}
+	var specData []byte
+	specData, err = rawSpec()
+	if err != nil {
+		return
+	}
+	swagger, err = loader.LoadFromData(specData)
+	if err != nil {
+		return
+	}
+	return
+}
diff --git a/internal/test/all_of/v2/openapi.gen.go b/internal/test/all_of/v2/openapi.gen.go
new file mode 100644
index 0000000..f2bd6cb
--- /dev/null
+++ b/internal/test/all_of/v2/openapi.gen.go
@@ -0,0 +1,127 @@
+// Package v2 provides primitives to interact with the openapi HTTP API.
+//
+// Code generated by github.com/deepmap/oapi-codegen version (devel) DO NOT EDIT.
+package v2
+
+import (
+	"bytes"
+	"compress/gzip"
+	"encoding/base64"
+	"fmt"
+	"net/url"
+	"path"
+	"strings"
+
+	"github.com/getkin/kin-openapi/openapi3"
+)
+
+// Person defines model for Person.
+type Person struct {
+	FirstName          string `json:"FirstName"`
+	GovernmentIDNumber *int64 `json:"GovernmentIDNumber,omitempty"`
+	LastName           string `json:"LastName"`
+}
+
+// These are fields that specify a person. They are all optional, and
+// would be used by an `Edit` style API endpoint, where each is optional.
+type PersonProperties struct {
+	FirstName          *string `json:"FirstName,omitempty"`
+	GovernmentIDNumber *int64  `json:"GovernmentIDNumber,omitempty"`
+	LastName           *string `json:"LastName,omitempty"`
+}
+
+// PersonWithID defines model for PersonWithID.
+type PersonWithID struct {
+	FirstName          string `json:"FirstName"`
+	GovernmentIDNumber *int64 `json:"GovernmentIDNumber,omitempty"`
+	ID                 int64  `json:"ID"`
+	LastName           string `json:"LastName"`
+}
+
+// Base64 encoded, gzipped, json marshaled Swagger object
+var swaggerSpec = []string{
+
+	"H4sIAAAAAAAC/5SUT2/bOBDFv8qAu0dBTrCLPegWrNtAQJEaaNIc4gAZSyOLKTVkyVEMwfB3L0jJ/+AW",
+	"aX0awOTjvDe/0VZVtnOWiSWoYqtC1VKHqVyQD5ZjhcZ8blTxtFV/e2pUof6aHW/Npiuz8fzCW0deNAW1",
+	"y7bK0/dee6pV8aQ+ah/kDjtSmfqEU/m8e85UTaHy2omO76n7VgfQARBcksxgo6WFDrlGsX6AJgoBcg0G",
+	"gwBjRxmsegGbJNBAOV8y992KfA5JbmN7U8OKwJP0nqmG1QAIL7ckLxBkMAQ3izKHR4KO/JpAWpqeX7I7",
+	"eBo7QbbSkocvyTlsWl21YNkM4Lx90zUF2PuGRpOpQ75klSkZHKlC2dUrVaJ2mbqIrNheZEGBAD1NQiAt",
+	"CgRHlW6GQ0LRJA3pGBpziCGLGS354L0Pk2+Glw+1PnUOxLWzmiWDTUuegLBq4xD2WqMDd9bqcaDFdm8u",
+	"iNe8juZu7Rt57oilnN+lWcRjjfUdiiqUZvnv32MomoXW5OPFAxuXqrtfhviopS3nf0prYvTc1Cjybpu7",
+	"7Iztcv47JIOnyvoaMBw5bLztAOF/Tyh0GEMOpUBlWVBzWHKcaiRygsA2gLA4XQ5kwLrWE/6egu19RfDw",
+	"UM5/yl7sX3NjU8ZaTPzvnoIEuInxQUosJD2VqTfyYXR0nV/lVzF164jRaVWof/Kr/DqygdKmBGfOYEWt",
+	"NfU48jXJJdhf0ei0zgE2yAIoYChus2WCKJVBsCAxwA6/UQSfOmjRuWE0FGeGUaysVaEWJ0/GyQRnOewX",
+	"qsHepBZioMSpROeMrpLA7HX6zo1sxOp9cibeUpDnzk7d79LvRwAAAP//lzc18GUFAAA=",
+}
+
+// GetSwagger returns the content of the embedded swagger specification file
+// or error if failed to decode
+func decodeSpec() ([]byte, error) {
+	zipped, err := base64.StdEncoding.DecodeString(strings.Join(swaggerSpec, ""))
+	if err != nil {
+		return nil, fmt.Errorf("error base64 decoding spec: %s", err)
+	}
+	zr, err := gzip.NewReader(bytes.NewReader(zipped))
+	if err != nil {
+		return nil, fmt.Errorf("error decompressing spec: %s", err)
+	}
+	var buf bytes.Buffer
+	_, err = buf.ReadFrom(zr)
+	if err != nil {
+		return nil, fmt.Errorf("error decompressing spec: %s", err)
+	}
+
+	return buf.Bytes(), nil
+}
+
+var rawSpec = decodeSpecCached()
+
+// a naive cached of a decoded swagger spec
+func decodeSpecCached() func() ([]byte, error) {
+	data, err := decodeSpec()
+	return func() ([]byte, error) {
+		return data, err
+	}
+}
+
+// Constructs a synthetic filesystem for resolving external references when loading openapi specifications.
+func PathToRawSpec(pathToFile string) map[string]func() ([]byte, error) {
+	var res = make(map[string]func() ([]byte, error))
+	if len(pathToFile) > 0 {
+		res[pathToFile] = rawSpec
+	}
+
+	return res
+}
+
+// GetSwagger returns the Swagger specification corresponding to the generated code
+// in this file. The external references of Swagger specification are resolved.
+// The logic of resolving external references is tightly connected to "import-mapping" feature.
+// Externally referenced files must be embedded in the corresponding golang packages.
+// Urls can be supported but this task was out of the scope.
+func GetSwagger() (swagger *openapi3.T, err error) {
+	var resolvePath = PathToRawSpec("")
+
+	loader := openapi3.NewLoader()
+	loader.IsExternalRefsAllowed = true
+	loader.ReadFromURIFunc = func(loader *openapi3.Loader, url *url.URL) ([]byte, error) {
+		var pathToFile = url.String()
+		pathToFile = path.Clean(pathToFile)
+		getSpec, ok := resolvePath[pathToFile]
+		if !ok {
+			err1 := fmt.Errorf("path not found: %s", pathToFile)
+			return nil, err1
+		}
+		return getSpec()
+	}
+	var specData []byte
+	specData, err = rawSpec()
+	if err != nil {
+		return
+	}
+	swagger, err = loader.LoadFromData(specData)
+	if err != nil {
+		return
+	}
+	return
+}
diff --git a/pkg/chi-middleware/oapi_validate_test.go b/pkg/chi-middleware/oapi_validate_test.go
index b67d5f3..638ba86 100644
--- a/pkg/chi-middleware/oapi_validate_test.go
+++ b/pkg/chi-middleware/oapi_validate_test.go
@@ -134,6 +134,9 @@ func TestOapiRequestValidatorWithOptions(t *testing.T) {
 	// Set up an authenticator to check authenticated function. It will allow
 	// access to "someScope", but disallow others.
 	options := Options{
+		ErrorHandler: func(w http.ResponseWriter, message string, statusCode int) {
+			http.Error(w, "test: "+message, statusCode)
+		},
 		Options: openapi3filter.Options{
 			AuthenticationFunc: func(c context.Context, input *openapi3filter.AuthenticationInput) error {
 
@@ -188,6 +191,7 @@ func TestOapiRequestValidatorWithOptions(t *testing.T) {
 	{
 		rec := doGet(t, r, "http://deepmap.ai/protected_resource_401")
 		assert.Equal(t, http.StatusUnauthorized, rec.Code)
+		assert.Equal(t, "test: Security requirements failed\n", rec.Body.String())
 		assert.False(t, called, "Handler should not have been called")
 		called = false
 	}
diff --git a/pkg/gin-middleware/oapi_validate_test.go b/pkg/gin-middleware/oapi_validate_test.go
index de194ce..82fdad2 100644
--- a/pkg/gin-middleware/oapi_validate_test.go
+++ b/pkg/gin-middleware/oapi_validate_test.go
@@ -136,6 +136,9 @@ func TestOapiRequestValidator(t *testing.T) {
 	// Set up an authenticator to check authenticated function. It will allow
 	// access to "someScope", but disallow others.
 	options := Options{
+		ErrorHandler: func(c *gin.Context, message string, statusCode int) {
+			c.String(statusCode, "test: "+message)
+		},
 		Options: openapi3filter.Options{
 			AuthenticationFunc: func(c context.Context, input *openapi3filter.AuthenticationInput) error {
 				// The gin context should be propagated into here.
@@ -265,6 +268,7 @@ func TestOapiRequestValidator(t *testing.T) {
 	{
 		rec := doGet(t, g, "http://deepmap.ai/protected_resource_401")
 		assert.Equal(t, http.StatusBadRequest, rec.Code)
+		assert.Equal(t, "test: error in openapi3filter.SecurityRequirementsError: Security requirements failed", rec.Body.String())
 		assert.False(t, called, "Handler should not have been called")
 		called = false
 	}
diff --git a/pkg/middleware/oapi_validate_test.go b/pkg/middleware/oapi_validate_test.go
index 726f6f9..a4e17e0 100644
--- a/pkg/middleware/oapi_validate_test.go
+++ b/pkg/middleware/oapi_validate_test.go
@@ -137,6 +137,9 @@ func TestOapiRequestValidator(t *testing.T) {
 	// Set up an authenticator to check authenticated function. It will allow
 	// access to "someScope", but disallow others.
 	options := Options{
+		ErrorHandler: func(c echo.Context, err *echo.HTTPError) error {
+			return c.String(err.Code, "test: "+err.Error())
+		},
 		Options: openapi3filter.Options{
 			AuthenticationFunc: func(c context.Context, input *openapi3filter.AuthenticationInput) error {
 				// The echo context should be propagated into here.
@@ -268,6 +271,7 @@ func TestOapiRequestValidator(t *testing.T) {
 	{
 		rec := doGet(t, e, "http://deepmap.ai/protected_resource_401")
 		assert.Equal(t, http.StatusUnauthorized, rec.Code)
+		assert.Equal(t, "test: code=401, message=Unauthorized", rec.Body.String())
 		assert.False(t, called, "Handler should not have been called")
 		called = false
 	}
diff --git a/pkg/types/uuid_test.go b/pkg/types/uuid_test.go
index 7f19aaa..bb62040 100644
--- a/pkg/types/uuid_test.go
+++ b/pkg/types/uuid_test.go
@@ -4,26 +4,28 @@ import (
 	"encoding/json"
 	"testing"
 
+	"github.com/google/uuid"
 	"github.com/stretchr/testify/assert"
 )
 
-func TestUUID_MarshalJSON_Fail(t *testing.T) {
-	testUUID := "this-is-not-a-uuid"
+func TestUUID_MarshalJSON_Zero(t *testing.T) {
+	var testUUID UUID
 	b := struct {
 		UUIDField UUID `json:"uuid"`
 	}{
-		UUIDField: UUID(testUUID),
+		UUIDField: testUUID,
 	}
-	_, err := json.Marshal(b)
-	assert.Error(t, err)
+	marshaled, err := json.Marshal(b)
+	assert.NoError(t, err)
+	assert.JSONEq(t, `{"uuid":"00000000-0000-0000-0000-000000000000"}`, string(marshaled))
 }
 
 func TestUUID_MarshalJSON_Pass(t *testing.T) {
-	testUUID := "9cb14230-b640-11ec-b909-0242ac120002"
+	testUUID := uuid.MustParse("9cb14230-b640-11ec-b909-0242ac120002")
 	b := struct {
 		UUIDField UUID `json:"uuid"`
 	}{
-		UUIDField: UUID(testUUID),
+		UUIDField: testUUID,
 	}
 	jsonBytes, err := json.Marshal(b)
 	assert.NoError(t, err)
@@ -40,7 +42,7 @@ func TestUUID_UnmarshalJSON_Fail(t *testing.T) {
 }
 
 func TestUUID_UnmarshalJSON_Pass(t *testing.T) {
-	testUUID := UUID("9cb14230-b640-11ec-b909-0242ac120002")
+	testUUID := uuid.MustParse("9cb14230-b640-11ec-b909-0242ac120002")
 	jsonStr := `{"uuid":"9cb14230-b640-11ec-b909-0242ac120002"}`
 	b := struct {
 		UUIDField UUID `json:"uuid"`