Repository: deepmap/oapi-codegen
Pull request #199 (2020-07-01 00:37:15)

fixed broken enum generation
Generated enum codes couldn't compile even its definition is valid for OpenAPI. E.g.; (related https://github.com/deepmap/oapi-codegen/commit/43d757f9dac52f93b116bd0c3f9a2390257cab03, https://github.com/deepmap/oapi-codegen/pull/181)

```yaml
openapi: 3.0.2

info:
  title: ...
  version: 0.0.0

paths:
  /foo:
    get:
      responses:
        200:
          description: ...
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/Bar'

components:
  schemas:
    Bar:
      type: string
      enum: 
        - Foo
        - Bar
        - Foo Bar
        - Foo-Bar
        - 1Foo
        - Bar
        - ' Foo'
        - ' Foo '
        - _Foo_
```


Patch:
diff --git a/README.md b/README.md
index 6f7d9ae..1d5d39a 100644
--- a/README.md
+++ b/README.md
@@ -440,6 +440,8 @@ you can specify any combination of those.
  the generated file in case the spec contains weird strings.
 - `skip-prune`: skip pruning unused components from the spec prior to generating
  the code.
+- `import-mapping`: specifies a map of references external OpenAPI specs to go
+ Go include paths. Please see below.
 
 So, for example, if you would like to produce only the server code, you could
 run `oapi-generate -generate types,server`. You could generate `types` and
@@ -453,6 +455,28 @@ To generate a server that only handles `admin` paths, use the argument
 `-include-tags="admin"`. When neither of these arguments is present, all paths
 are generated.
 
+### Import Mappings
+
+OpenAPI specifications may contain references to other OpenAPI specifications,
+and we need some additional information in order to be able to generate correct
+Go code.
+
+An external reference looks like this:
+
+    $ref: ./some_spec.yaml#/components/schemas/Type
+    
+We assume that you have already generated the boilerplate code for `./some_spec.yaml`
+using `oapi-codegen`, and you have a package which contains the generated code,
+let's call it `github.com/deepmap/some-package`. You need to tell `oapi-codegen` that
+`some_spec.yaml` corresponds to this package, and you would do it by specifying
+this command line argument:
+
+    -import-mapping=./some_spec.yaml:github.com/deepmap/some-package
+    
+This tells us that in order to resolve references generated from `some_spec.yaml` we
+need to import `github.com/deepmap/some-package`. You may specify multiple mappings
+by comma separating them in the form `key1:value1,key2:value2`.
+    
 ## What's missing or incomplete
 
 This code is still young, and not complete, since we're filling it in as we
diff --git a/cmd/oapi-codegen/oapi-codegen.go b/cmd/oapi-codegen/oapi-codegen.go
index b578e9f..6a6b523 100644
--- a/cmd/oapi-codegen/oapi-codegen.go
+++ b/cmd/oapi-codegen/oapi-codegen.go
@@ -33,12 +33,13 @@ func errExit(format string, args ...interface{}) {
 
 func main() {
 	var (
-		packageName  string
-		generate     string
-		outputFile   string
-		includeTags  string
-		excludeTags  string
-		templatesDir string
+		packageName   string
+		generate      string
+		outputFile    string
+		includeTags   string
+		excludeTags   string
+		templatesDir  string
+		importMapping string
 	)
 	flag.StringVar(&packageName, "package", "", "The package name for generated code")
 	flag.StringVar(&generate, "generate", "types,client,server,spec",
@@ -47,6 +48,7 @@ func main() {
 	flag.StringVar(&includeTags, "include-tags", "", "Only include operations with the given tags. Comma-separated list of tags.")
 	flag.StringVar(&excludeTags, "exclude-tags", "", "Exclude operations that are tagged with the given tags. Comma-separated list of tags.")
 	flag.StringVar(&templatesDir, "templates", "", "Path to directory containing user templates")
+	flag.StringVar(&importMapping, "import-mapping", "", "A dict from the external reference to golang package path")
 	flag.Parse()
 
 	if flag.NArg() < 1 {
@@ -106,6 +108,13 @@ func main() {
 	}
 	opts.UserTemplates = templates
 
+	if len(importMapping) > 0 {
+		opts.ImportMapping, err = util.ParseCommandlineMap(importMapping)
+		if err != nil {
+			errExit("error parsing import-mapping: %s\n", err)
+		}
+	}
+
 	code, err := codegen.Generate(swagger, packageName, opts)
 	if err != nil {
 		errExit("error generating code: %s\n", err)
@@ -159,3 +168,4 @@ func loadTemplateOverrides(templatesDir string) (map[string]string, error) {
 
 	return templates, nil
 }
+
diff --git a/go.sum b/go.sum
index d66a76f..1fb5ee4 100644
--- a/go.sum
+++ b/go.sum
@@ -6,8 +6,6 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c
 github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
 github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM=
 github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
-github.com/getkin/kin-openapi v0.3.0 h1:xsQ4mA20YJDMgIHdHqMKZ66QUe6/hi+x6yLsTTz8xyQ=
-github.com/getkin/kin-openapi v0.3.0/go.mod h1:W8dhxZgpE84ciM+VIItFqkmZ4eHtuomrdIHtASQIqi0=
 github.com/getkin/kin-openapi v0.13.0 h1:03fqBEEgivp4MVK2ElB140B56hjO9ZFvFTHBsvFsSro=
 github.com/getkin/kin-openapi v0.13.0/go.mod h1:WGRs2ZMM1Q8LR1QBEwUxC6RJEfaBcD0s+pcEVXFuAjw=
 github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk=
@@ -43,8 +41,6 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb
 github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
 github.com/stretchr/objx v0.1.0 h1:4G4v2dO3VZwixGIRoQ5Lfboy6nUhCyYzaqnIAPPhYs4=
 github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
-github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q=
-github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
 github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
 github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
 github.com/stretchr/testify v1.5.1 h1:nOGnQDM7FYENwehXlg/kFVnos3rEvtKTjRvOWSzb6H4=
@@ -89,7 +85,5 @@ gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33
 gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
 gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
 gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
-gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10=
-gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
 gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU=
 gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
diff --git a/pkg/codegen/codegen.go b/pkg/codegen/codegen.go
index 9f12de5..a4b979e 100644
--- a/pkg/codegen/codegen.go
+++ b/pkg/codegen/codegen.go
@@ -42,6 +42,7 @@ type Options struct {
 	IncludeTags        []string          // Only include operations that have one of these tags. Ignored when empty.
 	ExcludeTags        []string          // Exclude operations that have one of these tags. Ignored when empty.
 	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
 }
 
 type goImport struct {
@@ -84,12 +85,41 @@ var (
 		{lookFor: "xml\\.", packageName: "encoding/xml"},
 		{lookFor: "yaml\\.", packageName: "gopkg.in/yaml.v2"},
 	}
+
+	importMapping = map[string]goImport{}
 )
 
+func constructImportMapping(input map[string]string) map[string]goImport {
+	var (
+		nameToAlias = map[string]string{}
+		result      = map[string]goImport{}
+	)
+
+	{
+		var packagePaths []string
+		for _, packageName := range input {
+			packagePaths = append(packagePaths, packageName)
+		}
+		sort.Strings(packagePaths)
+
+		for _, packageName := range packagePaths {
+			if _, ok := nameToAlias[packageName]; !ok {
+				nameToAlias[packageName] = fmt.Sprintf("externalRef%d", len(nameToAlias))
+			}
+		}
+	}
+	for urlOrPath, packageName := range input {
+		result[urlOrPath] = goImport{alias: nameToAlias[packageName], packageName: packageName}
+	}
+	return result
+}
+
 // 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.Swagger, packageName string, opts Options) (string, error) {
+	importMapping = constructImportMapping(opts.ImportMapping)
+
 	filterOperationsByTag(swagger, opts)
 	if !opts.SkipPrune {
 		pruneUnusedComponents(swagger)
@@ -169,6 +199,9 @@ func Generate(swagger *openapi3.Swagger, packageName string, opts Options) (stri
 
 	// Imports needed for the generated code to compile
 	var imports []string
+	for _, importGo := range importMapping {
+		imports = append(imports, importGo.String())
+	}
 
 	var buf bytes.Buffer
 	w := bufio.NewWriter(&buf)
diff --git a/pkg/codegen/schema.go b/pkg/codegen/schema.go
index c3983d6..68ddffb 100644
--- a/pkg/codegen/schema.go
+++ b/pkg/codegen/schema.go
@@ -13,7 +13,7 @@ type Schema struct {
 	GoType  string // The Go type needed to represent the schema
 	RefType string // If the type has a type name, this is set
 
-	EnumValues []string // Enum values
+	EnumValues map[string]string // Enum values
 
 	Properties               []Property       // For an object, the fields with names
 	HasAdditionalProperties  bool             // Whether we support additional properties
@@ -253,13 +253,19 @@ func GenerateGoSchema(sref *openapi3.SchemaRef, path []string) (Schema, error) {
 			}
 			outSchema.GoType = "bool"
 		case "string":
-			for _, enumValue := range schema.Enum {
-				outSchema.EnumValues = append(outSchema.EnumValues, enumValue.(string))
+			enumValues := make([]string, len(schema.Enum))
+			for i, enumValue := range schema.Enum {
+				enumValues[i] = enumValue.(string)
 			}
+
+			outSchema.EnumValues = SanitizeEnumNames(enumValues)
+
 			// Special case string formats here.
 			switch f {
 			case "byte":
 				outSchema.GoType = "[]byte"
+			case "email":
+				outSchema.GoType = "openapi_types.Email"
 			case "date":
 				outSchema.GoType = "openapi_types.Date"
 			case "date-time":
diff --git a/pkg/codegen/templates/templates.gen.go b/pkg/codegen/templates/templates.gen.go
index 491eda3..02ba038 100644
--- a/pkg/codegen/templates/templates.gen.go
+++ b/pkg/codegen/templates/templates.gen.go
@@ -758,8 +758,8 @@ type {{.TypeName}} {{.Schema.TypeDecl}}
 // List of {{ .TypeName }}
 const (
 	{{- $typeName := .TypeName }}
-    {{- range .Schema.EnumValues }}
-    {{ $typeName }}_{{ . }} {{ $typeName }} = "{{ . }}"
+    {{- range $key, $value := .Schema.EnumValues }}
+    {{ $typeName }}_{{ $key }} {{ $typeName }} = "{{ $value }}"
     {{- end }}
 )
 {{- end }}
diff --git a/pkg/codegen/templates/typedef.tmpl b/pkg/codegen/templates/typedef.tmpl
index 4f20b07..170984f 100644
--- a/pkg/codegen/templates/typedef.tmpl
+++ b/pkg/codegen/templates/typedef.tmpl
@@ -5,8 +5,8 @@ type {{.TypeName}} {{.Schema.TypeDecl}}
 // List of {{ .TypeName }}
 const (
 	{{- $typeName := .TypeName }}
-    {{- range .Schema.EnumValues }}
-    {{ $typeName }}_{{ . }} {{ $typeName }} = "{{ . }}"
+    {{- range $key, $value := .Schema.EnumValues }}
+    {{ $typeName }}_{{ $key }} {{ $typeName }} = "{{ $value }}"
     {{- end }}
 )
 {{- end }}
diff --git a/pkg/codegen/utils.go b/pkg/codegen/utils.go
index 2a35b03..6c577d9 100644
--- a/pkg/codegen/utils.go
+++ b/pkg/codegen/utils.go
@@ -17,6 +17,7 @@ import (
 	"fmt"
 	"regexp"
 	"sort"
+	"strconv"
 	"strings"
 	"unicode"
 
@@ -65,7 +66,7 @@ func ToCamelCase(str string) string {
 		if unicode.IsUpper(v) {
 			n += string(v)
 		}
-			if unicode.IsDigit(v) {
+		if unicode.IsDigit(v) {
 			n += string(v)
 		}
 		if unicode.IsLower(v) {
@@ -76,7 +77,7 @@ func ToCamelCase(str string) string {
 			}
 		}
 
-		 if strings.ContainsRune(separators, v) {
+		if strings.ContainsRune(separators, v) {
 			capNext = true
 		} else {
 			capNext = false
@@ -197,20 +198,31 @@ func StringInArray(str string, array []string) bool {
 // #/components/schemas/Foo -> Foo
 // #/components/parameters/Bar -> Bar
 // #/components/responses/Baz -> Baz
-// Remote components (document.json#/Foo) are not yet supported
-// URL components (http://deepmap.com/schemas/document.json#Foo) are not yet
-// supported
-// We only support flat components for now, so no components in a schema under
-// components.
+// Remote components (document.json#/Foo) are supported if they present in --import-mapping
+// URL components (http://deepmap.com/schemas/document.json#Foo) are supported if they present in --import-mapping
+//
 func RefPathToGoType(refPath string) (string, error) {
-	pathParts := strings.Split(refPath, "/")
-	if pathParts[0] != "#" {
-		return "", errors.New("Only local document components are supported")
+	if refPath[0] == '#' {
+		pathParts := strings.Split(refPath, "/")
+		if len(pathParts) != 4 {
+			return "", errors.New("Parameter nesting is deeper than supported")
+		}
+		return SchemaNameToTypeName(pathParts[3]), nil
 	}
-	if len(pathParts) != 4 {
-		return "", errors.New("Parameter nesting is deeper than supported")
+	pathParts := strings.Split(refPath, "#")
+	if len(pathParts) != 2 {
+		return "", fmt.Errorf("unsupported reference: %s", refPath)
+	}
+	remoteComponent, flatComponent := pathParts[0], pathParts[1]
+	if goImport, ok := importMapping[remoteComponent]; !ok {
+		return "", fmt.Errorf("unrecognized external reference '%s'; please provide the known import for this reference using option --import-mapping", remoteComponent)
+	} else {
+		goType, err := RefPathToGoType("#" + flatComponent)
+		if err != nil {
+			return "", err
+		}
+		return fmt.Sprintf("%s.%s", goImport.alias, goType), nil
 	}
-	return SchemaNameToTypeName(pathParts[3]), nil
 }
 
 // This function converts a swagger style path URI with parameters to a
@@ -317,6 +329,155 @@ func IsGoKeyword(str string) bool {
 	return false
 }
 
+// IsPredeclaredGoIdentifier returns whether the given string
+// is a predefined go indentifier.
+//
+// See https://golang.org/ref/spec#Predeclared_identifiers
+func IsPredeclaredGoIdentifier(str string) bool {
+	predeclaredIdentifiers := []string{
+		// Types
+		"bool",
+		"byte",
+		"complex64",
+		"complex128",
+		"error",
+		"float32",
+		"float64",
+		"int",
+		"int8",
+		"int16",
+		"int32",
+		"int64",
+		"rune",
+		"string",
+		"uint",
+		"uint8",
+		"uint16",
+		"uint32",
+		"uint64",
+		"uintptr",
+		// Constants
+		"true",
+		"false",
+		"iota",
+		// Zero value
+		"nil",
+		// Functions
+		"append",
+		"cap",
+		"close",
+		"complex",
+		"copy",
+		"delete",
+		"imag",
+		"len",
+		"make",
+		"new",
+		"panic",
+		"print",
+		"println",
+		"real",
+		"recover",
+	}
+
+	for _, k := range predeclaredIdentifiers {
+		if k == str {
+			return true
+		}
+	}
+
+	return false
+}
+
+// IsGoIdentity checks if the given string can be used as an identity
+// in the generated code like a type name or constant name.
+//
+// See https://golang.org/ref/spec#Identifiers
+func IsGoIdentity(str string) bool {
+	for i, c := range str {
+		if !isValidRuneForGoID(i, c) {
+			return false
+		}
+	}
+
+	return IsGoKeyword(str)
+}
+
+func isValidRuneForGoID(index int, char rune) bool {
+	if index == 0 && unicode.IsNumber(char) {
+		return false
+	}
+
+	return unicode.IsLetter(char) || char == '_' || unicode.IsNumber(char)
+}
+
+// IsValidGoIdentity checks if the given string can be used as a
+// name of variable, constant, or type.
+func IsValidGoIdentity(str string) bool {
+	if IsGoIdentity(str) {
+		return false
+	}
+
+	return !IsPredeclaredGoIdentifier(str)
+}
+
+// SanitizeGoIdentity deletes and replaces the illegal runes in the given
+// string to use the string as a valid identity.
+func SanitizeGoIdentity(str string) string {
+	sanitized := []rune(str)
+
+	for i, c := range sanitized {
+		if !isValidRuneForGoID(i, c) {
+			sanitized[i] = '_'
+		} else {
+			sanitized[i] = c
+		}
+	}
+
+	str = string(sanitized)
+
+	if IsGoKeyword(str) || IsPredeclaredGoIdentifier(str) {
+		str = "_" + str
+	}
+
+	if !IsValidGoIdentity(str) {
+		panic("here is a bug")
+	}
+
+	return str
+}
+
+// SanitizeEnumNames fixes illegal chars in the enum names
+// and removes duplicates
+func SanitizeEnumNames(enumNames []string) map[string]string {
+	dupCheck := make(map[string]int, len(enumNames))
+	deDup := make([]string, 0, len(enumNames))
+
+	for _, n := range enumNames {
+		if _, dup := dupCheck[n]; !dup {
+			deDup = append(deDup, n)
+		}
+
+		dupCheck[n] = 0
+	}
+
+	dupCheck = make(map[string]int, len(deDup))
+	sanitizedDeDup := make(map[string]string, len(deDup))
+
+	for _, n := range deDup {
+		sanitized := SanitizeGoIdentity(n)
+
+		if _, dup := dupCheck[sanitized]; !dup {
+			sanitizedDeDup[sanitized] = n
+			dupCheck[sanitized]++
+		} else {
+			sanitizedDeDup[sanitized+strconv.Itoa(dupCheck[sanitized])] = n
+		}
+	}
+
+	return sanitizedDeDup
+}
+
 // Converts a Schema name to a valid Go type name. It converts to camel case, and makes sure the name is
 // valid in Go
 func SchemaNameToTypeName(name string) string {
diff --git a/pkg/types/email.go b/pkg/types/email.go
new file mode 100644
index 0000000..00a4cf6
--- /dev/null
+++ b/pkg/types/email.go
@@ -0,0 +1,27 @@
+package types
+
+import (
+	"encoding/json"
+	"errors"
+)
+
+type Email string
+
+func (e Email) MarshalJSON() ([]byte, error) {
+	if !emailRegex.MatchString(string(e)) {
+		return nil, errors.New("email: failed to pass regex validation")
+	}
+	return json.Marshal(string(e))
+}
+
+func (e *Email) UnmarshalJSON(data []byte) error {
+	var s string
+	if err := json.Unmarshal(data, &s); err != nil {
+		return err
+	}
+	if !emailRegex.MatchString(s) {
+		return errors.New("email: failed to pass regex validation")
+	}
+	*e = Email(s)
+	return nil
+}
diff --git a/pkg/types/regexes.go b/pkg/types/regexes.go
new file mode 100644
index 0000000..94f17df
--- /dev/null
+++ b/pkg/types/regexes.go
@@ -0,0 +1,11 @@
+package types
+
+import "regexp"
+
+const (
+	emailRegexString = "^(?:(?:(?:(?:[a-zA-Z]|\\d|[!#\\$%&'\\*\\+\\-\\/=\\?\\^_`{\\|}~]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])+(?:\\.([a-zA-Z]|\\d|[!#\\$%&'\\*\\+\\-\\/=\\?\\^_`{\\|}~]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])+)*)|(?:(?:\\x22)(?:(?:(?:(?:\\x20|\\x09)*(?:\\x0d\\x0a))?(?:\\x20|\\x09)+)?(?:(?:[\\x01-\\x08\\x0b\\x0c\\x0e-\\x1f\\x7f]|\\x21|[\\x23-\\x5b]|[\\x5d-\\x7e]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])|(?:(?:[\\x01-\\x09\\x0b\\x0c\\x0d-\\x7f]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}]))))*(?:(?:(?:\\x20|\\x09)*(?:\\x0d\\x0a))?(\\x20|\\x09)+)?(?:\\x22))))@(?:(?:(?:[a-zA-Z]|\\d|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])|(?:(?:[a-zA-Z]|\\d|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])(?:[a-zA-Z]|\\d|-|\\.|~|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])*(?:[a-zA-Z]|\\d|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])))\\.)+(?:(?:[a-zA-Z]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])|(?:(?:[a-zA-Z]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])(?:[a-zA-Z]|\\d|-|\\.|~|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])*(?:[a-zA-Z]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])))\\.?$"
+)
+
+var (
+	emailRegex = regexp.MustCompile(emailRegexString)
+)
diff --git a/pkg/util/inputmapping.go b/pkg/util/inputmapping.go
new file mode 100644
index 0000000..3557ce5
--- /dev/null
+++ b/pkg/util/inputmapping.go
@@ -0,0 +1,60 @@
+package util
+
+import (
+	"fmt"
+	"strings"
+)
+
+// The input mapping is experessed on the command line as `key1:value1,key2:value2,...`
+// We parse it here, but need to keep in mind that keys or values may contain
+// commas and colons. We will allow escaping those using double quotes, so
+// when passing in "key1":"value1", we will not look inside the quoted sections.
+func ParseCommandlineMap(src string) (map[string]string, error) {
+	result := make(map[string]string)
+	tuples := splitString(src, ',')
+	for _, t := range tuples {
+		kv := splitString(t, ':')
+		if len(kv) != 2 {
+			return nil, fmt.Errorf("expected key:value, got :%s", t)
+		}
+		key := strings.TrimLeft(kv[0], `"`)
+		key = strings.TrimRight(key, `"`)
+
+		value := strings.TrimLeft(kv[1], `"`)
+		value = strings.TrimRight(value, `"`)
+
+		result[key] = value
+	}
+	return result, nil
+}
+
+// This function splits a string along the specifed separator, but it
+// ignores anything between double quotes for splitting. We do simple
+// inside/outside quote counting. Quotes are not stripped from output.
+func splitString(s string, sep rune) ([]string) {
+	const escapeChar rune = '"'
+
+	var parts []string
+	var part string
+	inQuotes := false
+
+	for _, c := range s {
+		if c == escapeChar{
+			if inQuotes {
+				inQuotes = false
+			} else {
+				inQuotes = true
+			}
+		}
+
+		// If we've gotten the separator rune, consider the previous part
+		// complete, but only if we're outside of quoted sections
+		if c == sep && !inQuotes {
+			parts = append(parts, part)
+			part = ""
+			continue
+		}
+		part = part + string(c)
+	}
+	return append(parts, part)
+}
diff --git a/pkg/util/loader.go b/pkg/util/loader.go
index 6538e24..56b0297 100644
--- a/pkg/util/loader.go
+++ b/pkg/util/loader.go
@@ -1,32 +1,13 @@
 package util
 
 import (
-	"fmt"
-	"io/ioutil"
-	"path/filepath"
-	"strings"
-
 	"github.com/getkin/kin-openapi/openapi3"
 )
 
-func LoadSwagger(filePath string) (*openapi3.Swagger, error) {
-	data, err := ioutil.ReadFile(filePath)
-	if err != nil {
-		return nil, err
-	}
-
-	var swagger *openapi3.Swagger
-	ext := filepath.Ext(filePath)
-	ext = strings.ToLower(ext)
-	switch ext {
-	// The YAML handler can parse both YAML and JSON
-	case ".yaml", ".yml", ".json":
-		swagger, err = openapi3.NewSwaggerLoader().LoadSwaggerFromData(data)
-	default:
-		return nil, fmt.Errorf("%s is not a supported extension, use .yaml, .yml or .json", ext)
-	}
-	if err != nil {
-		return nil, err
-	}
-	return swagger, nil
+func LoadSwagger(filePath string) (swagger *openapi3.Swagger, err error) {
+	loader := openapi3.NewSwaggerLoader()
+	loader.IsExternalRefsAllowed = true
+	swagger, err = loader.LoadSwaggerFromFile(filePath)
+	return
 }
+


Test patch:
diff --git a/internal/test/externalref/doc.go b/internal/test/externalref/doc.go
new file mode 100644
index 0000000..e239ead
--- /dev/null
+++ b/internal/test/externalref/doc.go
@@ -0,0 +1,3 @@
+package externalref
+
+//go:generate go run github.com/deepmap/oapi-codegen/cmd/oapi-codegen -generate types,skip-prune --package=externalref -o externalref.gen.go --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 spec.yaml
diff --git a/internal/test/externalref/externalref.gen.go b/internal/test/externalref/externalref.gen.go
new file mode 100644
index 0000000..5fc09bd
--- /dev/null
+++ b/internal/test/externalref/externalref.gen.go
@@ -0,0 +1,15 @@
+// Package externalref provides primitives to interact the openapi HTTP API.
+//
+// Code generated by github.com/deepmap/oapi-codegen DO NOT EDIT.
+package externalref
+
+import (
+	externalRef0 "github.com/deepmap/oapi-codegen/internal/test/externalref/packageA"
+	externalRef1 "github.com/deepmap/oapi-codegen/internal/test/externalref/packageB"
+)
+
+// Container defines model for Container.
+type Container struct {
+	ObjectA *externalRef0.ObjectA `json:"object_a,omitempty"`
+	ObjectB *externalRef1.ObjectB `json:"object_b,omitempty"`
+}
diff --git a/internal/test/externalref/imports_test.go b/internal/test/externalref/imports_test.go
new file mode 100644
index 0000000..284964a
--- /dev/null
+++ b/internal/test/externalref/imports_test.go
@@ -0,0 +1,16 @@
+package externalref
+
+import (
+	"testing"
+
+	"github.com/deepmap/oapi-codegen/internal/test/externalref/packageA"
+	"github.com/deepmap/oapi-codegen/internal/test/externalref/packageB"
+)
+
+func TestParameters(t *testing.T) {
+	b := &packageB.ObjectB{}
+	_ = Container{
+		ObjectA: &packageA.ObjectA{ObjectB: b},
+		ObjectB: b,
+	}
+}
diff --git a/internal/test/externalref/packageA/doc.go b/internal/test/externalref/packageA/doc.go
new file mode 100644
index 0000000..410765f
--- /dev/null
+++ b/internal/test/externalref/packageA/doc.go
@@ -0,0 +1,3 @@
+package packageA
+
+//go:generate go run github.com/deepmap/oapi-codegen/cmd/oapi-codegen -generate types,skip-prune --package=packageA -o externalref.gen.go --import-mapping=../packageB/spec.yaml:github.com/deepmap/oapi-codegen/internal/test/externalref/packageB spec.yaml
diff --git a/internal/test/externalref/packageA/externalref.gen.go b/internal/test/externalref/packageA/externalref.gen.go
new file mode 100644
index 0000000..066f2b7
--- /dev/null
+++ b/internal/test/externalref/packageA/externalref.gen.go
@@ -0,0 +1,14 @@
+// Package packageA provides primitives to interact the openapi HTTP API.
+//
+// Code generated by github.com/deepmap/oapi-codegen DO NOT EDIT.
+package packageA
+
+import (
+	externalRef0 "github.com/deepmap/oapi-codegen/internal/test/externalref/packageB"
+)
+
+// ObjectA defines model for ObjectA.
+type ObjectA struct {
+	Name    *string               `json:"name,omitempty"`
+	ObjectB *externalRef0.ObjectB `json:"object_b,omitempty"`
+}
diff --git a/internal/test/externalref/packageA/spec.yaml b/internal/test/externalref/packageA/spec.yaml
new file mode 100644
index 0000000..b2386a0
--- /dev/null
+++ b/internal/test/externalref/packageA/spec.yaml
@@ -0,0 +1,8 @@
+components:
+  schemas:
+    ObjectA:
+      properties:
+        name:
+          type: string
+        object_b:
+          $ref: ../packageB/spec.yaml#/components/schemas/ObjectB
\ No newline at end of file
diff --git a/internal/test/externalref/packageB/doc.go b/internal/test/externalref/packageB/doc.go
new file mode 100644
index 0000000..4d28c8c
--- /dev/null
+++ b/internal/test/externalref/packageB/doc.go
@@ -0,0 +1,3 @@
+package packageB
+
+//go:generate go run github.com/deepmap/oapi-codegen/cmd/oapi-codegen -generate types,skip-prune --package=packageB -o externalref.gen.go spec.yaml
diff --git a/internal/test/externalref/packageB/externalref.gen.go b/internal/test/externalref/packageB/externalref.gen.go
new file mode 100644
index 0000000..831b7e8
--- /dev/null
+++ b/internal/test/externalref/packageB/externalref.gen.go
@@ -0,0 +1,9 @@
+// Package packageB provides primitives to interact the openapi HTTP API.
+//
+// Code generated by github.com/deepmap/oapi-codegen DO NOT EDIT.
+package packageB
+
+// ObjectB defines model for ObjectB.
+type ObjectB struct {
+	Name *string `json:"name,omitempty"`
+}
diff --git a/internal/test/externalref/packageB/spec.yaml b/internal/test/externalref/packageB/spec.yaml
new file mode 100644
index 0000000..6f90634
--- /dev/null
+++ b/internal/test/externalref/packageB/spec.yaml
@@ -0,0 +1,6 @@
+components:
+  schemas:
+    ObjectB:
+      properties:
+        name:
+          type: string
diff --git a/internal/test/externalref/spec.yaml b/internal/test/externalref/spec.yaml
new file mode 100644
index 0000000..455ee66
--- /dev/null
+++ b/internal/test/externalref/spec.yaml
@@ -0,0 +1,11 @@
+openapi: "3.0.0"
+info: {}
+paths: {}
+components:
+  schemas:
+    Container:
+      properties:
+        object_a:
+          $ref: ./packageA/spec.yaml#/components/schemas/ObjectA
+        object_b:
+          $ref: ./packageB/spec.yaml#/components/schemas/ObjectB
diff --git a/internal/test/issues/issue-illegal_enum_names/doc.go b/internal/test/issues/issue-illegal_enum_names/doc.go
new file mode 100644
index 0000000..b348a97
--- /dev/null
+++ b/internal/test/issues/issue-illegal_enum_names/doc.go
@@ -0,0 +1,3 @@
+package illegal_enum_names
+
+//go:generate go run github.com/deepmap/oapi-codegen/cmd/oapi-codegen  --package=illegal_enum_names -o issue.gen.go spec.yaml
diff --git a/internal/test/issues/issue-illegal_enum_names/issue.gen.go b/internal/test/issues/issue-illegal_enum_names/issue.gen.go
new file mode 100644
index 0000000..05ea488
--- /dev/null
+++ b/internal/test/issues/issue-illegal_enum_names/issue.gen.go
@@ -0,0 +1,322 @@
+// Package illegal_enum_names provides primitives to interact the openapi HTTP API.
+//
+// Code generated by github.com/deepmap/oapi-codegen DO NOT EDIT.
+package illegal_enum_names
+
+import (
+	"bytes"
+	"compress/gzip"
+	"context"
+	"encoding/base64"
+	"encoding/json"
+	"fmt"
+	"github.com/getkin/kin-openapi/openapi3"
+	"github.com/labstack/echo/v4"
+	"io/ioutil"
+	"net/http"
+	"net/url"
+	"strings"
+)
+
+// Bar defines model for Bar.
+type Bar string
+
+// List of Bar
+const (
+	Bar_Bar      Bar = "Bar"
+	Bar_Foo      Bar = "Foo"
+	Bar_Foo_Bar  Bar = "Foo Bar"
+	Bar_Foo_Bar1 Bar = "Foo-Bar"
+	Bar__Foo     Bar = "1Foo"
+	Bar__Foo1    Bar = " Foo"
+	Bar__Foo_    Bar = " Foo "
+	Bar__Foo_1   Bar = "_Foo_"
+)
+
+// RequestEditorFn  is the function signature for the RequestEditor callback function
+type RequestEditorFn func(ctx context.Context, req *http.Request) error
+
+// Doer performs HTTP requests.
+//
+// The standard http.Client implements this interface.
+type HttpRequestDoer interface {
+	Do(req *http.Request) (*http.Response, error)
+}
+
+// Client which conforms to the OpenAPI3 specification for this service.
+type Client struct {
+	// The endpoint of the server conforming to this interface, with scheme,
+	// https://api.deepmap.com for example.
+	Server string
+
+	// Doer for performing requests, typically a *http.Client with any
+	// customized settings, such as certificate chains.
+	Client HttpRequestDoer
+
+	// A callback for modifying requests which are generated before sending over
+	// the network.
+	RequestEditor RequestEditorFn
+}
+
+// ClientOption allows setting custom parameters during construction
+type ClientOption func(*Client) error
+
+// Creates a new Client, with reasonable defaults
+func NewClient(server string, opts ...ClientOption) (*Client, error) {
+	// create a client with sane default values
+	client := Client{
+		Server: server,
+	}
+	// mutate client and add all optional params
+	for _, o := range opts {
+		if err := o(&client); err != nil {
+			return nil, err
+		}
+	}
+	// ensure the server URL always has a trailing slash
+	if !strings.HasSuffix(client.Server, "/") {
+		client.Server += "/"
+	}
+	// create httpClient, if not already present
+	if client.Client == nil {
+		client.Client = http.DefaultClient
+	}
+	return &client, nil
+}
+
+// WithHTTPClient allows overriding the default Doer, which is
+// automatically created using http.Client. This is useful for tests.
+func WithHTTPClient(doer HttpRequestDoer) ClientOption {
+	return func(c *Client) error {
+		c.Client = doer
+		return nil
+	}
+}
+
+// WithRequestEditorFn allows setting up a callback function, which will be
+// called right before sending the request. This can be used to mutate the request.
+func WithRequestEditorFn(fn RequestEditorFn) ClientOption {
+	return func(c *Client) error {
+		c.RequestEditor = fn
+		return nil
+	}
+}
+
+// The interface specification for the client above.
+type ClientInterface interface {
+	// GetFoo request
+	GetFoo(ctx context.Context) (*http.Response, error)
+}
+
+func (c *Client) GetFoo(ctx context.Context) (*http.Response, error) {
+	req, err := NewGetFooRequest(c.Server)
+	if err != nil {
+		return nil, err
+	}
+	req = req.WithContext(ctx)
+	if c.RequestEditor != nil {
+		err = c.RequestEditor(ctx, req)
+		if err != nil {
+			return nil, err
+		}
+	}
+	return c.Client.Do(req)
+}
+
+// NewGetFooRequest generates requests for GetFoo
+func NewGetFooRequest(server string) (*http.Request, error) {
+	var err error
+
+	queryUrl, err := url.Parse(server)
+	if err != nil {
+		return nil, err
+	}
+
+	basePath := fmt.Sprintf("/foo")
+	if basePath[0] == '/' {
+		basePath = basePath[1:]
+	}
+
+	queryUrl, err = queryUrl.Parse(basePath)
+	if err != nil {
+		return nil, err
+	}
+
+	req, err := http.NewRequest("GET", queryUrl.String(), nil)
+	if err != nil {
+		return nil, err
+	}
+
+	return req, nil
+}
+
+// ClientWithResponses builds on ClientInterface to offer response payloads
+type ClientWithResponses struct {
+	ClientInterface
+}
+
+// NewClientWithResponses creates a new ClientWithResponses, which wraps
+// Client with return type handling
+func NewClientWithResponses(server string, opts ...ClientOption) (*ClientWithResponses, error) {
+	client, err := NewClient(server, opts...)
+	if err != nil {
+		return nil, err
+	}
+	return &ClientWithResponses{client}, nil
+}
+
+// WithBaseURL overrides the baseURL.
+func WithBaseURL(baseURL string) ClientOption {
+	return func(c *Client) error {
+		newBaseURL, err := url.Parse(baseURL)
+		if err != nil {
+			return err
+		}
+		c.Server = newBaseURL.String()
+		return nil
+	}
+}
+
+// ClientWithResponsesInterface is the interface specification for the client with responses above.
+type ClientWithResponsesInterface interface {
+	// GetFoo request
+	GetFooWithResponse(ctx context.Context) (*GetFooResponse, error)
+}
+
+type GetFooResponse struct {
+	Body         []byte
+	HTTPResponse *http.Response
+	JSON200      *[]Bar
+}
+
+// Status returns HTTPResponse.Status
+func (r GetFooResponse) Status() string {
+	if r.HTTPResponse != nil {
+		return r.HTTPResponse.Status
+	}
+	return http.StatusText(0)
+}
+
+// StatusCode returns HTTPResponse.StatusCode
+func (r GetFooResponse) StatusCode() int {
+	if r.HTTPResponse != nil {
+		return r.HTTPResponse.StatusCode
+	}
+	return 0
+}
+
+// GetFooWithResponse request returning *GetFooResponse
+func (c *ClientWithResponses) GetFooWithResponse(ctx context.Context) (*GetFooResponse, error) {
+	rsp, err := c.GetFoo(ctx)
+	if err != nil {
+		return nil, err
+	}
+	return ParseGetFooResponse(rsp)
+}
+
+// ParseGetFooResponse parses an HTTP response from a GetFooWithResponse call
+func ParseGetFooResponse(rsp *http.Response) (*GetFooResponse, error) {
+	bodyBytes, err := ioutil.ReadAll(rsp.Body)
+	defer rsp.Body.Close()
+	if err != nil {
+		return nil, err
+	}
+
+	response := &GetFooResponse{
+		Body:         bodyBytes,
+		HTTPResponse: rsp,
+	}
+
+	switch {
+	case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200:
+		var dest []Bar
+		if err := json.Unmarshal(bodyBytes, &dest); err != nil {
+			return nil, err
+		}
+		response.JSON200 = &dest
+
+	}
+
+	return response, nil
+}
+
+// ServerInterface represents all server handlers.
+type ServerInterface interface {
+
+	// (GET /foo)
+	GetFoo(ctx echo.Context) error
+}
+
+// ServerInterfaceWrapper converts echo contexts to parameters.
+type ServerInterfaceWrapper struct {
+	Handler ServerInterface
+}
+
+// GetFoo converts echo context to params.
+func (w *ServerInterfaceWrapper) GetFoo(ctx echo.Context) error {
+	var err error
+
+	// Invoke the callback with all the unmarshalled arguments
+	err = w.Handler.GetFoo(ctx)
+	return err
+}
+
+// This is a simple interface which specifies echo.Route addition functions which
+// are present on both echo.Echo and echo.Group, since we want to allow using
+// either of them for path registration
+type EchoRouter interface {
+	CONNECT(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) *echo.Route
+	DELETE(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) *echo.Route
+	GET(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) *echo.Route
+	HEAD(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) *echo.Route
+	OPTIONS(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) *echo.Route
+	PATCH(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) *echo.Route
+	POST(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) *echo.Route
+	PUT(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) *echo.Route
+	TRACE(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) *echo.Route
+}
+
+// RegisterHandlers adds each server route to the EchoRouter.
+func RegisterHandlers(router EchoRouter, si ServerInterface) {
+
+	wrapper := ServerInterfaceWrapper{
+		Handler: si,
+	}
+
+	router.GET("/foo", wrapper.GetFoo)
+
+}
+
+// Base64 encoded, gzipped, json marshaled Swagger object
+var swaggerSpec = []string{
+
+	"H4sIAAAAAAAC/0yQzU4DMQyEX6UaOIbsUm45cijiGRCqoq23DeraUWKQqirvjpyFQi4z+ZnYn6+YZMnC",
+	"xFoRrqjTiZbY7XMsJsSfC8IbdiJw/dCZ39zcw+oe/z/YrBuTDRz2O5E93h30kgkBVUviI1prDolnsTqa",
+	"9Gx33ns4fFGpSRgBox/9iOYgmTjmhIAnP/otHHLUU291mKX/cSQ1kUwlahJ+PSDghXTtplDNwpV6ZDuO",
+	"JpOwEvdUzPmcpp4bPqrV/h2HuaS09OB9oRkBd8Pf4IafqQ0G326UsZR4WSEPVKeSsq5Ihtj6+g4AAP//",
+	"Jkt64H8BAAA=",
+}
+
+// GetSwagger returns the Swagger specification corresponding to the generated code
+// in this file.
+func GetSwagger() (*openapi3.Swagger, 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)
+	}
+
+	swagger, err := openapi3.NewSwaggerLoader().LoadSwaggerFromData(buf.Bytes())
+	if err != nil {
+		return nil, fmt.Errorf("error loading Swagger: %s", err)
+	}
+	return swagger, nil
+}
diff --git a/internal/test/issues/issue-illegal_enum_names/issue_test.go b/internal/test/issues/issue-illegal_enum_names/issue_test.go
new file mode 100644
index 0000000..9fbd124
--- /dev/null
+++ b/internal/test/issues/issue-illegal_enum_names/issue_test.go
@@ -0,0 +1,54 @@
+package illegal_enum_names
+
+import (
+	"go/ast"
+	"go/parser"
+	"go/token"
+	"testing"
+
+	"github.com/deepmap/oapi-codegen/pkg/codegen"
+	"github.com/getkin/kin-openapi/openapi3"
+	"github.com/stretchr/testify/require"
+)
+
+func TestIllegalEnumNames(t *testing.T) {
+	swagger, err := openapi3.NewSwaggerLoader().LoadSwaggerFromFile("spec.yaml")
+	require.NoError(t, err)
+
+	opts := codegen.Options{
+		GenerateClient:     true,
+		GenerateEchoServer: true,
+		GenerateTypes:      true,
+		EmbedSpec:          true,
+	}
+
+	code, err := codegen.Generate(swagger, "illegal_enum_names", opts)
+	require.NoError(t, err)
+
+	f, err := parser.ParseFile(token.NewFileSet(), "", code, parser.AllErrors)
+	require.NoError(t, err)
+
+	constDefs := make(map[string]string)
+	for _, d := range f.Decls {
+		switch decl := d.(type) {
+		case *ast.GenDecl:
+			if token.CONST == decl.Tok {
+				for _, s := range decl.Specs {
+					switch spec := s.(type) {
+					case *ast.ValueSpec:
+						constDefs[spec.Names[0].Name] = spec.Names[0].Obj.Decl.(*ast.ValueSpec).Values[0].(*ast.BasicLit).Value
+					}
+				}
+			}
+		}
+	}
+
+	require.Equal(t, `"Bar"`, constDefs["Bar_Bar"])
+	require.Equal(t, `"Foo"`, constDefs["Bar_Foo"])
+	require.Equal(t, `"Foo Bar"`, constDefs["Bar_Foo_Bar"])
+	require.Equal(t, `"Foo-Bar"`, constDefs["Bar_Foo_Bar1"])
+	require.Equal(t, `"1Foo"`, constDefs["Bar__Foo"])
+	require.Equal(t, `" Foo"`, constDefs["Bar__Foo1"])
+	require.Equal(t, `" Foo "`, constDefs["Bar__Foo_"])
+	require.Equal(t, `"_Foo_"`, constDefs["Bar__Foo_1"])
+}
diff --git a/internal/test/issues/issue-illegal_enum_names/spec.yaml b/internal/test/issues/issue-illegal_enum_names/spec.yaml
new file mode 100644
index 0000000..de6cbe8
--- /dev/null
+++ b/internal/test/issues/issue-illegal_enum_names/spec.yaml
@@ -0,0 +1,33 @@
+openapi: 3.0.2
+
+info:
+  title: ...
+  version: 0.0.0
+
+paths:
+  /foo:
+    get:
+      responses:
+        200:
+          description: ...
+          content:
+            application/json:
+              schema:
+                type: array
+                items:
+                  $ref: '#/components/schemas/Bar'
+
+components:
+  schemas:
+    Bar:
+      type: string
+      enum: 
+        - Foo
+        - Bar
+        - Foo Bar
+        - Foo-Bar
+        - 1Foo
+        - Bar
+        - ' Foo'
+        - ' Foo '
+        - _Foo_
\ No newline at end of file
diff --git a/internal/test/server/server.gen.go b/internal/test/server/server.gen.go
index b4f50e6..9b68f51 100644
--- a/internal/test/server/server.gen.go
+++ b/internal/test/server/server.gen.go
@@ -37,14 +37,15 @@ type EveryTypeOptional struct {
 
 // EveryTypeRequired defines model for EveryTypeRequired.
 type EveryTypeRequired struct {
-	ArrayInlineField     []int              `json:"array_inline_field"`
-	ArrayReferencedField []SomeObject       `json:"array_referenced_field"`
-	BoolField            bool               `json:"bool_field"`
-	ByteField            []byte             `json:"byte_field"`
-	DateField            openapi_types.Date `json:"date_field"`
-	DateTimeField        time.Time          `json:"date_time_field"`
-	DoubleField          float64            `json:"double_field"`
-	FloatField           float32            `json:"float_field"`
+	ArrayInlineField     []int                `json:"array_inline_field"`
+	ArrayReferencedField []SomeObject         `json:"array_referenced_field"`
+	BoolField            bool                 `json:"bool_field"`
+	ByteField            []byte               `json:"byte_field"`
+	DateField            openapi_types.Date   `json:"date_field"`
+	DateTimeField        time.Time            `json:"date_time_field"`
+	DoubleField          float64              `json:"double_field"`
+	EmailField           *openapi_types.Email `json:"email_field,omitempty"`
+	FloatField           float32              `json:"float_field"`
 	InlineObjectField    struct {
 		Name   string `json:"name"`
 		Number int    `json:"number"`
diff --git a/internal/test/test-schema.yaml b/internal/test/test-schema.yaml
index a2fbf31..cc34a9f 100644
--- a/internal/test/test-schema.yaml
+++ b/internal/test/test-schema.yaml
@@ -248,6 +248,9 @@ components:
           type: boolean
         string_field:
           type: string
+        email_field:
+          type: string
+          format: email
         date_field:
           type: string
           format: date
diff --git a/pkg/types/email_test.go b/pkg/types/email_test.go
new file mode 100644
index 0000000..69fb975
--- /dev/null
+++ b/pkg/types/email_test.go
@@ -0,0 +1,31 @@
+package types
+
+import (
+	"encoding/json"
+	"testing"
+
+	"github.com/stretchr/testify/assert"
+)
+
+func TestEmail_MarshalJSON(t *testing.T) {
+	testEmail := "gaben@valvesoftware.com"
+	b := struct {
+		EmailField Email `json:"email"`
+	}{
+		EmailField: Email(testEmail),
+	}
+	jsonBytes, err := json.Marshal(b)
+	assert.NoError(t, err)
+	assert.JSONEq(t, `{"email":"gaben@valvesoftware.com"}`, string(jsonBytes))
+}
+
+func TestEmail_UnmarshalJSON(t *testing.T) {
+	testEmail := Email("gaben@valvesoftware.com")
+	jsonStr := `{"email":"gaben@valvesoftware.com"}`
+	b := struct {
+		EmailField Email `json:"email"`
+	}{}
+	err := json.Unmarshal([]byte(jsonStr), &b)
+	assert.NoError(t, err)
+	assert.Equal(t, testEmail, b.EmailField)
+}
diff --git a/pkg/util/inputmapping_test.go b/pkg/util/inputmapping_test.go
new file mode 100644
index 0000000..4310463
--- /dev/null
+++ b/pkg/util/inputmapping_test.go
@@ -0,0 +1,62 @@
+package util
+
+import (
+	"testing"
+
+	"github.com/stretchr/testify/assert"
+	"github.com/stretchr/testify/require"
+)
+
+func TestParseInputMapping(t *testing.T) {
+	var src string
+	var expected, parsed map[string]string
+	var err error
+
+	src = "key1:value1,key2:value2"
+	expected = map[string]string{"key1": "value1", "key2":"value2"}
+	parsed, err = ParseCommandlineMap(src)
+	require.NoError(t, err)
+	assert.Equal(t, expected, parsed)
+
+	src = `key1:"value1,value2",key2:value3`
+	expected = map[string]string{"key1": "value1,value2", "key2":"value3"}
+	parsed, err = ParseCommandlineMap(src)
+	require.NoError(t, err)
+	assert.Equal(t, expected, parsed)
+
+	src = `key1:"value1,value2,key2:value3"`
+	expected = map[string]string{"key1": "value1,value2,key2:value3"}
+	parsed, err = ParseCommandlineMap(src)
+	require.NoError(t, err)
+	assert.Equal(t, expected, parsed)
+
+	src = `"key1,key2":value1`
+	expected = map[string]string{"key1,key2": "value1"}
+	parsed, err = ParseCommandlineMap(src)
+	require.NoError(t, err)
+	assert.Equal(t, expected, parsed)
+}
+
+func TestSplitString(t *testing.T) {
+	var src string
+	var expected, result []string
+	var err error
+
+	src = "1,2,3"
+	expected = []string{"1", "2", "3"}
+	result = splitString(src, ',')
+	require.NoError(t, err)
+	assert.Equal(t, expected, result)
+
+	src = `"1,2",3`
+	expected = []string{`"1,2"`,"3"}
+	result = splitString(src, ',')
+	require.NoError(t, err)
+	assert.Equal(t, expected, result)
+
+	src = `1,"2,3",`
+	expected = []string{"1",`"2,3"`, ""}
+	result = splitString(src, ',')
+	require.NoError(t, err)
+	assert.Equal(t, expected, result)
+}