Repository: deepmap/oapi-codegen
Pull request #749 (2022-09-12 09:08:51)

Generate names with initialisms
Adding support of two naming styles:
* new - with initialisms like `MyHTTPHandlerForAPI`
* old - with simple camel case like `MyHttpHandlerForApi`

By the steps of https://github.com/deepmap/oapi-codegen/pull/715.


Patch:
diff --git a/pkg/codegen/codegen.go b/pkg/codegen/codegen.go
index 7d03752..4460bd1 100644
--- a/pkg/codegen/codegen.go
+++ b/pkg/codegen/codegen.go
@@ -113,6 +113,17 @@ func Generate(spec *openapi3.T, opts Configuration) (string, error) {
 		responseTypeSuffix = opts.OutputOptions.ResponseTypeSuffix
 	}
 
+	// if we provided an override for name normalizer update it
+	// default normalizer is ToCamelCase
+	nameNormalizer = ToCamelCase
+	if opts.OutputOptions.NameNormalizer != "" {
+		nameNormalizer = nameNormalizerMap[opts.OutputOptions.NameNormalizer]
+		if nameNormalizer == nil {
+			return "", fmt.Errorf(`error cannot find name-normalizer "%s" among options %q`,
+				opts.OutputOptions.NameNormalizer, nameNormalizerMap.Options())
+		}
+	}
+
 	// This creates the golang templates text package
 	TemplateFunctions["opts"] = func() Configuration { return globalState.options }
 	t := template.New("oapi-codegen").Funcs(TemplateFunctions)
diff --git a/pkg/codegen/configuration.go b/pkg/codegen/configuration.go
index 290c139..576de90 100644
--- a/pkg/codegen/configuration.go
+++ b/pkg/codegen/configuration.go
@@ -76,6 +76,7 @@ type OutputOptions struct {
 
 	ExcludeSchemas     []string `yaml:"exclude-schemas,omitempty"`      // Exclude from generation schemas with given names. Ignored when empty.
 	ResponseTypeSuffix string   `yaml:"response-type-suffix,omitempty"` // The suffix used for responses types
+	NameNormalizer     string   `yaml:"name-normalizer,omitempty"`      // Method used to normalize golang names and types. See the options in pkg/codegen/utils.go:nameNormalizerMap
 }
 
 // UpdateDefaults sets reasonable default values for unset fields in Configuration
diff --git a/pkg/codegen/operations.go b/pkg/codegen/operations.go
index 3aa64ac..21ca3d5 100644
--- a/pkg/codegen/operations.go
+++ b/pkg/codegen/operations.go
@@ -114,7 +114,7 @@ func (pd *ParameterDefinition) Explode() bool {
 }
 
 func (pd ParameterDefinition) GoVariableName() string {
-	name := LowercaseFirstCharacter(pd.GoName())
+	name := LowercaseFirstCharacters(pd.GoName())
 	if IsGoKeyword(name) {
 		name = "p" + UppercaseFirstCharacter(name)
 	}
@@ -294,13 +294,13 @@ func (o *OperationDefinition) GetResponseTypeDefinitions() ([]ResponseTypeDefini
 					var typeName string
 					switch {
 					case StringInArray(contentTypeName, contentTypesJSON):
-						typeName = fmt.Sprintf("JSON%s", ToCamelCase(responseName))
+						typeName = fmt.Sprintf("JSON%s", nameNormalizer(responseName))
 					// YAML:
 					case StringInArray(contentTypeName, contentTypesYAML):
-						typeName = fmt.Sprintf("YAML%s", ToCamelCase(responseName))
+						typeName = fmt.Sprintf("YAML%s", nameNormalizer(responseName))
 					// XML:
 					case StringInArray(contentTypeName, contentTypesXML):
-						typeName = fmt.Sprintf("XML%s", ToCamelCase(responseName))
+						typeName = fmt.Sprintf("XML%s", nameNormalizer(responseName))
 					default:
 						continue
 					}
@@ -512,7 +512,7 @@ func OperationDefinitions(swagger *openapi3.T) ([]OperationDefinition, error) {
 				}
 				op.OperationID = op.OperationID
 			} else {
-				op.OperationID = ToCamelCase(op.OperationID)
+				op.OperationID = nameNormalizer(op.OperationID)
 			}
 
 			// These are parameters defined for the specific path method that
@@ -550,7 +550,7 @@ func OperationDefinitions(swagger *openapi3.T) ([]OperationDefinition, error) {
 				HeaderParams: FilterParameterDefinitionByType(allParams, "header"),
 				QueryParams:  FilterParameterDefinitionByType(allParams, "query"),
 				CookieParams: FilterParameterDefinitionByType(allParams, "cookie"),
-				OperationId:  ToCamelCase(op.OperationID),
+				OperationId:  nameNormalizer(op.OperationID),
 				// Replace newlines in summary.
 				Summary:         op.Summary,
 				Method:          opName,
@@ -605,7 +605,7 @@ func generateDefaultOperationID(opName string, requestPath string) (string, erro
 		}
 	}
 
-	return ToCamelCase(operationId), nil
+	return nameNormalizer(operationId), nil
 }
 
 // This function turns the Swagger body definitions into a list of our body
diff --git a/pkg/codegen/utils.go b/pkg/codegen/utils.go
index fe25353..bc88299 100644
--- a/pkg/codegen/utils.go
+++ b/pkg/codegen/utils.go
@@ -14,6 +14,7 @@
 package codegen
 
 import (
+	"bytes"
 	"encoding/json"
 	"fmt"
 	"net/url"
@@ -26,7 +27,32 @@ import (
 	"github.com/getkin/kin-openapi/openapi3"
 )
 
-var pathParamRE *regexp.Regexp
+var (
+	pathParamRE    *regexp.Regexp
+	nameNormalizer NameNormalizer = ToCamelCase
+)
+
+type NameNormalizer func(string) string
+
+type NameNormalizerMap map[string]NameNormalizer
+
+func (m NameNormalizerMap) Options() []string {
+	options := make([]string, 0, len(m))
+
+	for key := range nameNormalizerMap {
+		options = append(options, key)
+	}
+
+	sort.Strings(options)
+
+	return options
+}
+
+var nameNormalizerMap = NameNormalizerMap{
+	"":                           ToCamelCase,
+	"ToCamelCase":                ToCamelCase,
+	"ToCamelCaseWithInitialisms": ToCamelCaseWithInitialisms,
+}
 
 func init() {
 	pathParamRE = regexp.MustCompile("{[.;?]?([^{}*]+)\\*?}")
@@ -53,38 +79,87 @@ func LowercaseFirstCharacter(str string) string {
 	return string(runes)
 }
 
-// This function will convert query-arg style strings to CamelCase. We will
+// Lowercase the first upper characters in a string for case of abbreviation.
+// This assumes UTF-8, so we have to be careful with unicode, don't treat it as a byte array.
+func LowercaseFirstCharacters(str string) string {
+	if str == "" {
+		return ""
+	}
+
+	runes := []rune(str)
+
+	for i := 0; i < len(runes); i++ {
+		next := i + 1
+		if i != 0 && next < len(runes) && unicode.IsLower(runes[next]) {
+			break
+		}
+
+		runes[i] = unicode.ToLower(runes[i])
+	}
+
+	return string(runes)
+}
+
+// ToCamelCase function will convert query-arg style strings to CamelCase. We will
 // use `., -, +, :, ;, _, ~, ' ', (, ), {, }, [, ]` as valid delimiters for words.
 // So, "word.word-word+word:word;word_word~word word(word)word{word}[word]"
 // would be converted to WordWordWordWordWordWordWordWordWordWordWordWordWord
-func ToCamelCase(str string) string {
-	separators := "-#@!$&=.+:;_~ (){}[]"
-	s := strings.Trim(str, " ")
-
-	n := ""
+func ToCamelCase(s string) string {
+	res := bytes.NewBuffer(nil)
 	capNext := true
 	for _, v := range s {
 		if unicode.IsUpper(v) {
-			n += string(v)
+			res.WriteRune(v)
+			capNext = false
+			continue
 		}
 		if unicode.IsDigit(v) {
-			n += string(v)
+			res.WriteRune(v)
+			capNext = true
+			continue
 		}
 		if unicode.IsLower(v) {
 			if capNext {
-				n += strings.ToUpper(string(v))
+				res.WriteRune(unicode.ToUpper(v))
 			} else {
-				n += string(v)
+				res.WriteRune(v)
 			}
+			capNext = false
+			continue
 		}
+		capNext = true
+	}
+	return res.String()
+}
 
-		if strings.ContainsRune(separators, v) {
-			capNext = true
-		} else {
-			capNext = false
+// ToCamelCaseWithInitialisms function will convert query-arg style strings to CamelCase with initialisms in uppercase.
+// So, httpOperationId would be converted to HTTPOperationID
+func ToCamelCaseWithInitialisms(s string) string {
+	parts := camelCaseMatchParts.FindAllString(ToCamelCase(s), -1)
+	for i := range parts {
+		if v, ok := initialismsMap[strings.ToLower(parts[i])]; ok {
+			parts[i] = v
 		}
 	}
-	return n
+	return strings.Join(parts, "")
+}
+
+var camelCaseMatchParts = regexp.MustCompile(`[\p{Lu}\d]+([\p{Ll}\d]+|$)`)
+
+// initialismsMap stores initialisms as "lower(initialism) -> initialism" map.
+// List of initialisms was taken from https://staticcheck.io/docs/configuration/options/#initialisms.
+var initialismsMap = makeInitialismsMap([]string{
+	"ACL", "API", "ASCII", "CPU", "CSS", "DNS", "EOF", "GUID", "HTML", "HTTP", "HTTPS", "ID", "IP", "JSON",
+	"QPS", "RAM", "RPC", "SLA", "SMTP", "SQL", "SSH", "TCP", "TLS", "TTL", "UDP", "UI", "GID", "UID", "UUID",
+	"URI", "URL", "UTF8", "VM", "XML", "XMPP", "XSRF", "XSS", "SIP", "RTP", "AMQP", "DB", "TS",
+})
+
+func makeInitialismsMap(l []string) map[string]string {
+	m := make(map[string]string, len(l))
+	for i := range l {
+		m[strings.ToLower(l[i])] = l[i]
+	}
+	return m
 }
 
 // This function returns the keys of the given SchemaRef dictionary in sorted
@@ -623,7 +698,7 @@ func typeNamePrefix(name string) (prefix string) {
 // SchemaNameToTypeName 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 {
-	return typeNamePrefix(name) + ToCamelCase(name)
+	return typeNamePrefix(name) + nameNormalizer(name)
 }
 
 // According to the spec, additionalProperties may be true, false, or a
@@ -647,7 +722,7 @@ func SchemaHasAdditionalProperties(schema *openapi3.Schema) bool {
 // type name.
 func PathToTypeName(path []string) string {
 	for i, p := range path {
-		path[i] = ToCamelCase(p)
+		path[i] = nameNormalizer(p)
 	}
 	return strings.Join(path, "_")
 }


Test patch:
diff --git a/internal/test/name-normalizer/config.yaml b/internal/test/name-normalizer/config.yaml
new file mode 100644
index 0000000..0939acb
--- /dev/null
+++ b/internal/test/name-normalizer/config.yaml
@@ -0,0 +1,9 @@
+package: name_normalizer
+generate:
+  echo-server: true
+  client: true
+  models: true
+  embedded-spec: true
+output-options:
+  name-normalizer: ToCamelCaseWithInitialisms
+output: name_normalizer.gen.go
diff --git a/internal/test/name-normalizer/doc.go b/internal/test/name-normalizer/doc.go
new file mode 100644
index 0000000..a9e5748
--- /dev/null
+++ b/internal/test/name-normalizer/doc.go
@@ -0,0 +1,3 @@
+package name_normalizer
+
+//go:generate go run github.com/deepmap/oapi-codegen/cmd/oapi-codegen --config=config.yaml spec.yaml
diff --git a/internal/test/name-normalizer/name_normalizer.gen.go b/internal/test/name-normalizer/name_normalizer.gen.go
new file mode 100644
index 0000000..74b36a9
--- /dev/null
+++ b/internal/test/name-normalizer/name_normalizer.gen.go
@@ -0,0 +1,402 @@
+// Package name_normalizer provides primitives to interact with the openapi HTTP API.
+//
+// Code generated by github.com/deepmap/oapi-codegen version (devel) DO NOT EDIT.
+package name_normalizer
+
+import (
+	"bytes"
+	"compress/gzip"
+	"context"
+	"encoding/base64"
+	"encoding/json"
+	"fmt"
+	"io"
+	"net/http"
+	"net/url"
+	"path"
+	"strings"
+
+	"github.com/deepmap/oapi-codegen/pkg/runtime"
+	"github.com/getkin/kin-openapi/openapi3"
+	"github.com/labstack/echo/v4"
+)
+
+// Pet defines model for Pet.
+type Pet struct {
+	// Name The name of the pet.
+	Name string `json:"name"`
+
+	// UUID The pet uuid.
+	UUID string `json:"uuid"`
+}
+
+// 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. This can contain a path relative
+	// to the server, such as https://api.deepmap.com/dev-test, and all the
+	// paths in the swagger spec will be appended to the server.
+	Server string
+
+	// Doer for performing requests, typically a *http.Client with any
+	// customized settings, such as certificate chains.
+	Client HttpRequestDoer
+
+	// A list of callbacks for modifying requests which are generated before sending over
+	// the network.
+	RequestEditors []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.Client{}
+	}
+	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.RequestEditors = append(c.RequestEditors, fn)
+		return nil
+	}
+}
+
+// The interface specification for the client above.
+type ClientInterface interface {
+	// GetHTTPPet request
+	GetHTTPPet(ctx context.Context, petID string, reqEditors ...RequestEditorFn) (*http.Response, error)
+}
+
+func (c *Client) GetHTTPPet(ctx context.Context, petID string, reqEditors ...RequestEditorFn) (*http.Response, error) {
+	req, err := NewGetHTTPPetRequest(c.Server, petID)
+	if err != nil {
+		return nil, err
+	}
+	req = req.WithContext(ctx)
+	if err := c.applyEditors(ctx, req, reqEditors); err != nil {
+		return nil, err
+	}
+	return c.Client.Do(req)
+}
+
+// NewGetHTTPPetRequest generates requests for GetHTTPPet
+func NewGetHTTPPetRequest(server string, petID string) (*http.Request, error) {
+	var err error
+
+	var pathParam0 string
+
+	pathParam0, err = runtime.StyleParamWithLocation("simple", false, "petId", runtime.ParamLocationPath, petID)
+	if err != nil {
+		return nil, err
+	}
+
+	serverURL, err := url.Parse(server)
+	if err != nil {
+		return nil, err
+	}
+
+	operationPath := fmt.Sprintf("/api/pets/%s", pathParam0)
+	if operationPath[0] == '/' {
+		operationPath = "." + operationPath
+	}
+
+	queryURL, err := serverURL.Parse(operationPath)
+	if err != nil {
+		return nil, err
+	}
+
+	req, err := http.NewRequest("GET", queryURL.String(), nil)
+	if err != nil {
+		return nil, err
+	}
+
+	return req, nil
+}
+
+func (c *Client) applyEditors(ctx context.Context, req *http.Request, additionalEditors []RequestEditorFn) error {
+	for _, r := range c.RequestEditors {
+		if err := r(ctx, req); err != nil {
+			return err
+		}
+	}
+	for _, r := range additionalEditors {
+		if err := r(ctx, req); err != nil {
+			return err
+		}
+	}
+	return 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 {
+	// GetHTTPPet request
+	GetHTTPPetWithResponse(ctx context.Context, petID string, reqEditors ...RequestEditorFn) (*GetHTTPPetResponse, error)
+}
+
+type GetHTTPPetResponse struct {
+	Body         []byte
+	HTTPResponse *http.Response
+	JSON200      *Pet
+}
+
+// Status returns HTTPResponse.Status
+func (r GetHTTPPetResponse) Status() string {
+	if r.HTTPResponse != nil {
+		return r.HTTPResponse.Status
+	}
+	return http.StatusText(0)
+}
+
+// StatusCode returns HTTPResponse.StatusCode
+func (r GetHTTPPetResponse) StatusCode() int {
+	if r.HTTPResponse != nil {
+		return r.HTTPResponse.StatusCode
+	}
+	return 0
+}
+
+// GetHTTPPetWithResponse request returning *GetHTTPPetResponse
+func (c *ClientWithResponses) GetHTTPPetWithResponse(ctx context.Context, petID string, reqEditors ...RequestEditorFn) (*GetHTTPPetResponse, error) {
+	rsp, err := c.GetHTTPPet(ctx, petID, reqEditors...)
+	if err != nil {
+		return nil, err
+	}
+	return ParseGetHTTPPetResponse(rsp)
+}
+
+// ParseGetHTTPPetResponse parses an HTTP response from a GetHTTPPetWithResponse call
+func ParseGetHTTPPetResponse(rsp *http.Response) (*GetHTTPPetResponse, error) {
+	bodyBytes, err := io.ReadAll(rsp.Body)
+	defer func() { _ = rsp.Body.Close() }()
+	if err != nil {
+		return nil, err
+	}
+
+	response := &GetHTTPPetResponse{
+		Body:         bodyBytes,
+		HTTPResponse: rsp,
+	}
+
+	switch {
+	case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200:
+		var dest Pet
+		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 pet given identifier.
+	// (GET /api/pets/{petId})
+	GetHTTPPet(ctx echo.Context, petID string) error
+}
+
+// ServerInterfaceWrapper converts echo contexts to parameters.
+type ServerInterfaceWrapper struct {
+	Handler ServerInterface
+}
+
+// GetHTTPPet converts echo context to params.
+func (w *ServerInterfaceWrapper) GetHTTPPet(ctx echo.Context) error {
+	var err error
+	// ------------- Path parameter "petId" -------------
+	var petID string
+
+	err = runtime.BindStyledParameterWithLocation("simple", false, "petId", runtime.ParamLocationPath, ctx.Param("petId"), &petID)
+	if err != nil {
+		return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter petId: %s", err))
+	}
+
+	// Invoke the callback with all the unmarshalled arguments
+	err = w.Handler.GetHTTPPet(ctx, petID)
+	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) {
+	RegisterHandlersWithBaseURL(router, si, "")
+}
+
+// Registers handlers, and prepends BaseURL to the paths, so that the paths
+// can be served under a prefix.
+func RegisterHandlersWithBaseURL(router EchoRouter, si ServerInterface, baseURL string) {
+
+	wrapper := ServerInterfaceWrapper{
+		Handler: si,
+	}
+
+	router.GET(baseURL+"/api/pets/:petId", wrapper.GetHTTPPet)
+
+}
+
+// Base64 encoded, gzipped, json marshaled Swagger object
+var swaggerSpec = []string{
+
+	"H4sIAAAAAAAC/2xSPXPbMAz9Kzy0o85yko1rh9ZbBm+5DCz1bCG1SJSEfJfT6b/3QKVp2nqCDgIe3gcX",
+	"inmSnJC0kl+oxhFTaJ+PUCtSsqAoozVTmGB1QI2FRTkn8nQc4eyPyyenI5xAd9SRvgrIU9XC6UxrR/PM",
+	"w+1tgTr7e2Nt7ajg58wFA/mnDaPbiDy/D+fvL4hKq01zOuX/r3wZEX9Ut8lxqDEIp7MxllDCBEWpdpz1",
+	"YoCHWme4h7t7p6hKHV1R6oZ0t9vv9iYnC1IQJk8PrdWRBB2bT30Q7gVa+0Wgh2G15nlz1AgEY3UYyNNX",
+	"6Lfj8dHctv13Kv5pIbZzhvlbsKeGRh8t0TKjewvO4P+179mGq+RUtwjv93srMSdFaoSCyIVjo9S/VNO4",
+	"fMD7XHAiT5/6Py+lf3smvbFunv/t9TVceLBMW3p1nqZQXjetLekzX5EcD0jKJ0bZGcj6KwAA//+jSqqR",
+	"jAIAAA==",
+}
+
+// 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/name-normalizer/name_normalizer_test.go b/internal/test/name-normalizer/name_normalizer_test.go
new file mode 100644
index 0000000..10b2413
--- /dev/null
+++ b/internal/test/name-normalizer/name_normalizer_test.go
@@ -0,0 +1,23 @@
+package name_normalizer
+
+import (
+	"testing"
+
+	"github.com/stretchr/testify/assert"
+)
+
+const hostname = "http://host"
+
+func TestGenCodeHasCorrectNamesWithInitialisms(t *testing.T) {
+	pet := &Pet{}
+	assert.Equal(t, "", pet.Name)
+	assert.Equal(t, "", pet.UUID)
+
+	uri := "https://my-api.com/some-base-url/v1/"
+	client, err := NewClient(uri)
+	assert.Nil(t, err)
+	assert.NotNil(t, client.GetHTTPPet)
+
+	server := &ServerInterfaceWrapper{}
+	assert.NotNil(t, server.GetHTTPPet)
+}
diff --git a/internal/test/name-normalizer/spec.yaml b/internal/test/name-normalizer/spec.yaml
new file mode 100644
index 0000000..4775b2e
--- /dev/null
+++ b/internal/test/name-normalizer/spec.yaml
@@ -0,0 +1,49 @@
+openapi: "3.0.0"
+info:
+  version: 1.0.0
+  title: Issue 312 test
+  description: Checks proper escaping of parameters
+paths:
+  /api/pets/{petId}:
+    get:
+      summary: Get pet given identifier.
+      operationId: getHttpPet
+      parameters:
+      - name: petId
+        in: path
+        required: true
+        schema:
+          type: string
+      responses:
+        '200':
+          description: valid pet
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/Pet'
+components:
+  schemas:
+    Pet:
+      type: object
+      required:
+        - uuid
+        - name
+      properties:
+        uuid:
+          type: string
+          description: The pet uuid.
+        name:
+          type: string
+          description: The name of the pet.
+    Error:
+      required:
+        - code
+        - message
+      properties:
+        code:
+          type: integer
+          format: int32
+          description: Error code
+        message:
+          type: string
+          description: Error message
diff --git a/internal/test/parameters/parameters.gen.go b/internal/test/parameters/parameters.gen.go
index 9d0fc0e..e75e902 100644
--- a/internal/test/parameters/parameters.gen.go
+++ b/internal/test/parameters/parameters.gen.go
@@ -63,8 +63,8 @@ type GetCookieParams struct {
 	// Co complex object
 	Co *ComplexObject `form:"co,omitempty" json:"co,omitempty"`
 
-	// N1s name starting with number
-	N1s *string `form:"1s,omitempty" json:"1s,omitempty"`
+	// N1S name starting with number
+	N1S *string `form:"1s,omitempty" json:"1s,omitempty"`
 }
 
 // EnumParamsParams defines parameters for EnumParams.
@@ -135,8 +135,8 @@ type GetQueryFormParams struct {
 	// Co complex object
 	Co *ComplexObject `form:"co,omitempty" json:"co,omitempty"`
 
-	// N1s name starting with number
-	N1s *string `form:"1s,omitempty" json:"1s,omitempty"`
+	// N1S name starting with number
+	N1S *string `form:"1s,omitempty" json:"1s,omitempty"`
 }
 
 // RequestEditorFn  is the function signature for the RequestEditor callback function
@@ -273,7 +273,7 @@ type ClientInterface interface {
 	GetSimplePrimitive(ctx context.Context, param int32, reqEditors ...RequestEditorFn) (*http.Response, error)
 
 	// GetStartingWithNumber request
-	GetStartingWithNumber(ctx context.Context, n1param string, reqEditors ...RequestEditorFn) (*http.Response, error)
+	GetStartingWithNumber(ctx context.Context, n1Param string, reqEditors ...RequestEditorFn) (*http.Response, error)
 }
 
 func (c *Client) GetContentObject(ctx context.Context, param ComplexObject, reqEditors ...RequestEditorFn) (*http.Response, error) {
@@ -516,8 +516,8 @@ func (c *Client) GetSimplePrimitive(ctx context.Context, param int32, reqEditors
 	return c.Client.Do(req)
 }
 
-func (c *Client) GetStartingWithNumber(ctx context.Context, n1param string, reqEditors ...RequestEditorFn) (*http.Response, error) {
-	req, err := NewGetStartingWithNumberRequest(c.Server, n1param)
+func (c *Client) GetStartingWithNumber(ctx context.Context, n1Param string, reqEditors ...RequestEditorFn) (*http.Response, error) {
+	req, err := NewGetStartingWithNumberRequest(c.Server, n1Param)
 	if err != nil {
 		return nil, err
 	}
@@ -695,10 +695,10 @@ func NewGetCookieRequest(server string, params *GetCookieParams) (*http.Request,
 		req.AddCookie(cookie6)
 	}
 
-	if params.N1s != nil {
+	if params.N1S != nil {
 		var cookieParam7 string
 
-		cookieParam7, err = runtime.StyleParamWithLocation("simple", true, "1s", runtime.ParamLocationCookie, *params.N1s)
+		cookieParam7, err = runtime.StyleParamWithLocation("simple", true, "1s", runtime.ParamLocationCookie, *params.N1S)
 		if err != nil {
 			return nil, err
 		}
@@ -1366,9 +1366,9 @@ func NewGetQueryFormRequest(server string, params *GetQueryFormParams) (*http.Re
 
 	}
 
-	if params.N1s != nil {
+	if params.N1S != nil {
 
-		if queryFrag, err := runtime.StyleParamWithLocation("form", true, "1s", runtime.ParamLocationQuery, *params.N1s); err != nil {
+		if queryFrag, err := runtime.StyleParamWithLocation("form", true, "1s", runtime.ParamLocationQuery, *params.N1S); err != nil {
 			return nil, err
 		} else if parsed, err := url.ParseQuery(queryFrag); err != nil {
 			return nil, err
@@ -1563,12 +1563,12 @@ func NewGetSimplePrimitiveRequest(server string, param int32) (*http.Request, er
 }
 
 // NewGetStartingWithNumberRequest generates requests for GetStartingWithNumber
-func NewGetStartingWithNumberRequest(server string, n1param string) (*http.Request, error) {
+func NewGetStartingWithNumberRequest(server string, n1Param string) (*http.Request, error) {
 	var err error
 
 	var pathParam0 string
 
-	pathParam0 = n1param
+	pathParam0 = n1Param
 
 	serverURL, err := url.Parse(server)
 	if err != nil {
@@ -1697,7 +1697,7 @@ type ClientWithResponsesInterface interface {
 	GetSimplePrimitiveWithResponse(ctx context.Context, param int32, reqEditors ...RequestEditorFn) (*GetSimplePrimitiveResponse, error)
 
 	// GetStartingWithNumber request
-	GetStartingWithNumberWithResponse(ctx context.Context, n1param string, reqEditors ...RequestEditorFn) (*GetStartingWithNumberResponse, error)
+	GetStartingWithNumberWithResponse(ctx context.Context, n1Param string, reqEditors ...RequestEditorFn) (*GetStartingWithNumberResponse, error)
 }
 
 type GetContentObjectResponse struct {
@@ -2322,8 +2322,8 @@ func (c *ClientWithResponses) GetSimplePrimitiveWithResponse(ctx context.Context
 }
 
 // GetStartingWithNumberWithResponse request returning *GetStartingWithNumberResponse
-func (c *ClientWithResponses) GetStartingWithNumberWithResponse(ctx context.Context, n1param string, reqEditors ...RequestEditorFn) (*GetStartingWithNumberResponse, error) {
-	rsp, err := c.GetStartingWithNumber(ctx, n1param, reqEditors...)
+func (c *ClientWithResponses) GetStartingWithNumberWithResponse(ctx context.Context, n1Param string, reqEditors ...RequestEditorFn) (*GetStartingWithNumberResponse, error) {
+	rsp, err := c.GetStartingWithNumber(ctx, n1Param, reqEditors...)
 	if err != nil {
 		return nil, err
 	}
@@ -2730,7 +2730,7 @@ type ServerInterface interface {
 	GetSimplePrimitive(ctx echo.Context, param int32) error
 
 	// (GET /startingWithNumber/{1param})
-	GetStartingWithNumber(ctx echo.Context, n1param string) error
+	GetStartingWithNumber(ctx echo.Context, n1Param string) error
 }
 
 // ServerInterfaceWrapper converts echo contexts to parameters.
@@ -2850,7 +2850,7 @@ func (w *ServerInterfaceWrapper) GetCookie(ctx echo.Context) error {
 		if err != nil {
 			return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter 1s: %s", err))
 		}
-		params.N1s = &value
+		params.N1S = &value
 
 	}
 
@@ -3240,7 +3240,7 @@ func (w *ServerInterfaceWrapper) GetQueryForm(ctx echo.Context) error {
 
 	// ------------- Optional query parameter "1s" -------------
 
-	err = runtime.BindQueryParameter("form", true, false, "1s", ctx.QueryParams(), &params.N1s)
+	err = runtime.BindQueryParameter("form", true, false, "1s", ctx.QueryParams(), &params.N1S)
 	if err != nil {
 		return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter 1s: %s", err))
 	}
@@ -3334,12 +3334,12 @@ func (w *ServerInterfaceWrapper) GetSimplePrimitive(ctx echo.Context) error {
 func (w *ServerInterfaceWrapper) GetStartingWithNumber(ctx echo.Context) error {
 	var err error
 	// ------------- Path parameter "1param" -------------
-	var n1param string
+	var n1Param string
 
-	n1param = ctx.Param("1param")
+	n1Param = ctx.Param("1param")
 
 	// Invoke the callback with all the unmarshalled arguments
-	err = w.Handler.GetStartingWithNumber(ctx, n1param)
+	err = w.Handler.GetStartingWithNumber(ctx, n1Param)
 	return err
 }
 
diff --git a/internal/test/parameters/parameters_test.go b/internal/test/parameters/parameters_test.go
index b0fe6b1..18836dd 100644
--- a/internal/test/parameters/parameters_test.go
+++ b/internal/test/parameters/parameters_test.go
@@ -170,8 +170,8 @@ func (t *testServer) GetQueryForm(ctx echo.Context, params GetQueryFormParams) e
 	if params.Co != nil {
 		t.complexObject = params.Co
 	}
-	if params.N1s != nil {
-		t.n1param = params.N1s
+	if params.N1S != nil {
+		t.n1param = params.N1S
 	}
 	return nil
 }
@@ -230,8 +230,8 @@ func (t *testServer) GetCookie(ctx echo.Context, params GetCookieParams) error {
 	if params.Co != nil {
 		t.complexObject = params.Co
 	}
-	if params.N1s != nil {
-		t.n1param = params.N1s
+	if params.N1S != nil {
+		t.n1param = params.N1S
 	}
 	return nil
 }
@@ -690,7 +690,7 @@ func TestClientQueryParams(t *testing.T) {
 		P:   &expectedPrimitive2,
 		Ps:  &expectedPrimitiveString,
 		Co:  &expectedComplexObject,
-		N1s: &expectedStartingWithNumber,
+		N1S: &expectedStartingWithNumber,
 	}
 
 	req, err := NewGetQueryFormRequest(server, &qParams)
@@ -709,7 +709,7 @@ func TestClientQueryParams(t *testing.T) {
 		Ep:  &expectedPrimitive1,
 		P:   &expectedPrimitive2,
 		Co:  &expectedComplexObject,
-		N1s: &expectedStartingWithNumber,
+		N1S: &expectedStartingWithNumber,
 	}
 	req, err = NewGetCookieRequest(server, &cParams)
 	assert.NoError(t, err)
diff --git a/internal/test/schemas/schemas.gen.go b/internal/test/schemas/schemas.gen.go
index b6a9f20..053ee7b 100644
--- a/internal/test/schemas/schemas.gen.go
+++ b/internal/test/schemas/schemas.gen.go
@@ -178,7 +178,7 @@ type ClientInterface interface {
 	GetIssues375(ctx context.Context, reqEditors ...RequestEditorFn) (*http.Response, error)
 
 	// Issue41 request
-	Issue41(ctx context.Context, n1param N5StartsWithNumber, reqEditors ...RequestEditorFn) (*http.Response, error)
+	Issue41(ctx context.Context, n1Param N5StartsWithNumber, reqEditors ...RequestEditorFn) (*http.Response, error)
 
 	// Issue9 request with any body
 	Issue9WithBody(ctx context.Context, params *Issue9Params, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error)
@@ -270,8 +270,8 @@ func (c *Client) GetIssues375(ctx context.Context, reqEditors ...RequestEditorFn
 	return c.Client.Do(req)
 }
 
-func (c *Client) Issue41(ctx context.Context, n1param N5StartsWithNumber, reqEditors ...RequestEditorFn) (*http.Response, error) {
-	req, err := NewIssue41Request(c.Server, n1param)
+func (c *Client) Issue41(ctx context.Context, n1Param N5StartsWithNumber, reqEditors ...RequestEditorFn) (*http.Response, error) {
+	req, err := NewIssue41Request(c.Server, n1Param)
 	if err != nil {
 		return nil, err
 	}
@@ -496,12 +496,12 @@ func NewGetIssues375Request(server string) (*http.Request, error) {
 }
 
 // NewIssue41Request generates requests for Issue41
-func NewIssue41Request(server string, n1param N5StartsWithNumber) (*http.Request, error) {
+func NewIssue41Request(server string, n1Param N5StartsWithNumber) (*http.Request, error) {
 	var err error
 
 	var pathParam0 string
 
-	pathParam0, err = runtime.StyleParamWithLocation("simple", false, "1param", runtime.ParamLocationPath, n1param)
+	pathParam0, err = runtime.StyleParamWithLocation("simple", false, "1param", runtime.ParamLocationPath, n1Param)
 	if err != nil {
 		return nil, err
 	}
@@ -649,7 +649,7 @@ type ClientWithResponsesInterface interface {
 	GetIssues375WithResponse(ctx context.Context, reqEditors ...RequestEditorFn) (*GetIssues375Response, error)
 
 	// Issue41 request
-	Issue41WithResponse(ctx context.Context, n1param N5StartsWithNumber, reqEditors ...RequestEditorFn) (*Issue41Response, error)
+	Issue41WithResponse(ctx context.Context, n1Param N5StartsWithNumber, reqEditors ...RequestEditorFn) (*Issue41Response, error)
 
 	// Issue9 request with any body
 	Issue9WithBodyWithResponse(ctx context.Context, params *Issue9Params, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*Issue9Response, error)
@@ -902,8 +902,8 @@ func (c *ClientWithResponses) GetIssues375WithResponse(ctx context.Context, reqE
 }
 
 // Issue41WithResponse request returning *Issue41Response
-func (c *ClientWithResponses) Issue41WithResponse(ctx context.Context, n1param N5StartsWithNumber, reqEditors ...RequestEditorFn) (*Issue41Response, error) {
-	rsp, err := c.Issue41(ctx, n1param, reqEditors...)
+func (c *ClientWithResponses) Issue41WithResponse(ctx context.Context, n1Param N5StartsWithNumber, reqEditors ...RequestEditorFn) (*Issue41Response, error) {
+	rsp, err := c.Issue41(ctx, n1Param, reqEditors...)
 	if err != nil {
 		return nil, err
 	}
@@ -1142,7 +1142,7 @@ type ServerInterface interface {
 	GetIssues375(ctx echo.Context) error
 
 	// (GET /issues/41/{1param})
-	Issue41(ctx echo.Context, n1param N5StartsWithNumber) error
+	Issue41(ctx echo.Context, n1Param N5StartsWithNumber) error
 
 	// (GET /issues/9)
 	Issue9(ctx echo.Context, params Issue9Params) error
@@ -1237,9 +1237,9 @@ func (w *ServerInterfaceWrapper) GetIssues375(ctx echo.Context) error {
 func (w *ServerInterfaceWrapper) Issue41(ctx echo.Context) error {
 	var err error
 	// ------------- Path parameter "1param" -------------
-	var n1param N5StartsWithNumber
+	var n1Param N5StartsWithNumber
 
-	err = runtime.BindStyledParameterWithLocation("simple", false, "1param", runtime.ParamLocationPath, ctx.Param("1param"), &n1param)
+	err = runtime.BindStyledParameterWithLocation("simple", false, "1param", runtime.ParamLocationPath, ctx.Param("1param"), &n1Param)
 	if err != nil {
 		return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter 1param: %s", err))
 	}
@@ -1247,7 +1247,7 @@ func (w *ServerInterfaceWrapper) Issue41(ctx echo.Context) error {
 	ctx.Set(Access_tokenScopes, []string{""})
 
 	// Invoke the callback with all the unmarshalled arguments
-	err = w.Handler.Issue41(ctx, n1param)
+	err = w.Handler.Issue41(ctx, n1Param)
 	return err
 }
 
diff --git a/internal/test/strict-server/chi/server.gen.go b/internal/test/strict-server/chi/server.gen.go
index 06dbc6c..ca61f2c 100644
--- a/internal/test/strict-server/chi/server.gen.go
+++ b/internal/test/strict-server/chi/server.gen.go
@@ -506,12 +506,12 @@ func (response MultipleRequestAndResponseTypes200FormdataResponse) VisitMultiple
 	}
 }
 
-type MultipleRequestAndResponseTypes200ImagepngResponse struct {
+type MultipleRequestAndResponseTypes200ImagePngResponse struct {
 	Body          io.Reader
 	ContentLength int64
 }
 
-func (response MultipleRequestAndResponseTypes200ImagepngResponse) VisitMultipleRequestAndResponseTypesResponse(w http.ResponseWriter) error {
+func (response MultipleRequestAndResponseTypes200ImagePngResponse) VisitMultipleRequestAndResponseTypesResponse(w http.ResponseWriter) error {
 	w.Header().Set("Content-Type", "image/png")
 	if response.ContentLength != 0 {
 		w.Header().Set("Content-Length", fmt.Sprint(response.ContentLength))
@@ -630,12 +630,12 @@ type UnknownExampleResponseObject interface {
 	VisitUnknownExampleResponse(w http.ResponseWriter) error
 }
 
-type UnknownExample200Videomp4Response struct {
+type UnknownExample200VideoMp4Response struct {
 	Body          io.Reader
 	ContentLength int64
 }
 
-func (response UnknownExample200Videomp4Response) VisitUnknownExampleResponse(w http.ResponseWriter) error {
+func (response UnknownExample200VideoMp4Response) VisitUnknownExampleResponse(w http.ResponseWriter) error {
 	w.Header().Set("Content-Type", "video/mp4")
 	if response.ContentLength != 0 {
 		w.Header().Set("Content-Length", fmt.Sprint(response.ContentLength))
diff --git a/internal/test/strict-server/chi/server.go b/internal/test/strict-server/chi/server.go
index 748fc7d..3b64606 100644
--- a/internal/test/strict-server/chi/server.go
+++ b/internal/test/strict-server/chi/server.go
@@ -43,7 +43,7 @@ func (s StrictServer) MultipartExample(ctx context.Context, request MultipartExa
 func (s StrictServer) MultipleRequestAndResponseTypes(ctx context.Context, request MultipleRequestAndResponseTypesRequestObject) (MultipleRequestAndResponseTypesResponseObject, error) {
 	switch {
 	case request.Body != nil:
-		return MultipleRequestAndResponseTypes200ImagepngResponse{Body: request.Body}, nil
+		return MultipleRequestAndResponseTypes200ImagePngResponse{Body: request.Body}, nil
 	case request.JSONBody != nil:
 		return MultipleRequestAndResponseTypes200JSONResponse(*request.JSONBody), nil
 	case request.FormdataBody != nil:
@@ -82,7 +82,7 @@ func (s StrictServer) TextExample(ctx context.Context, request TextExampleReques
 }
 
 func (s StrictServer) UnknownExample(ctx context.Context, request UnknownExampleRequestObject) (UnknownExampleResponseObject, error) {
-	return UnknownExample200Videomp4Response{Body: request.Body}, nil
+	return UnknownExample200VideoMp4Response{Body: request.Body}, nil
 }
 
 func (s StrictServer) UnspecifiedContentType(ctx context.Context, request UnspecifiedContentTypeRequestObject) (UnspecifiedContentTypeResponseObject, error) {
diff --git a/internal/test/strict-server/echo/server.gen.go b/internal/test/strict-server/echo/server.gen.go
index 3002b2a..30303fb 100644
--- a/internal/test/strict-server/echo/server.gen.go
+++ b/internal/test/strict-server/echo/server.gen.go
@@ -332,12 +332,12 @@ func (response MultipleRequestAndResponseTypes200FormdataResponse) VisitMultiple
 	}
 }
 
-type MultipleRequestAndResponseTypes200ImagepngResponse struct {
+type MultipleRequestAndResponseTypes200ImagePngResponse struct {
 	Body          io.Reader
 	ContentLength int64
 }
 
-func (response MultipleRequestAndResponseTypes200ImagepngResponse) VisitMultipleRequestAndResponseTypesResponse(w http.ResponseWriter) error {
+func (response MultipleRequestAndResponseTypes200ImagePngResponse) VisitMultipleRequestAndResponseTypesResponse(w http.ResponseWriter) error {
 	w.Header().Set("Content-Type", "image/png")
 	if response.ContentLength != 0 {
 		w.Header().Set("Content-Length", fmt.Sprint(response.ContentLength))
@@ -456,12 +456,12 @@ type UnknownExampleResponseObject interface {
 	VisitUnknownExampleResponse(w http.ResponseWriter) error
 }
 
-type UnknownExample200Videomp4Response struct {
+type UnknownExample200VideoMp4Response struct {
 	Body          io.Reader
 	ContentLength int64
 }
 
-func (response UnknownExample200Videomp4Response) VisitUnknownExampleResponse(w http.ResponseWriter) error {
+func (response UnknownExample200VideoMp4Response) VisitUnknownExampleResponse(w http.ResponseWriter) error {
 	w.Header().Set("Content-Type", "video/mp4")
 	if response.ContentLength != 0 {
 		w.Header().Set("Content-Length", fmt.Sprint(response.ContentLength))
diff --git a/internal/test/strict-server/echo/server.go b/internal/test/strict-server/echo/server.go
index 748fc7d..3b64606 100644
--- a/internal/test/strict-server/echo/server.go
+++ b/internal/test/strict-server/echo/server.go
@@ -43,7 +43,7 @@ func (s StrictServer) MultipartExample(ctx context.Context, request MultipartExa
 func (s StrictServer) MultipleRequestAndResponseTypes(ctx context.Context, request MultipleRequestAndResponseTypesRequestObject) (MultipleRequestAndResponseTypesResponseObject, error) {
 	switch {
 	case request.Body != nil:
-		return MultipleRequestAndResponseTypes200ImagepngResponse{Body: request.Body}, nil
+		return MultipleRequestAndResponseTypes200ImagePngResponse{Body: request.Body}, nil
 	case request.JSONBody != nil:
 		return MultipleRequestAndResponseTypes200JSONResponse(*request.JSONBody), nil
 	case request.FormdataBody != nil:
@@ -82,7 +82,7 @@ func (s StrictServer) TextExample(ctx context.Context, request TextExampleReques
 }
 
 func (s StrictServer) UnknownExample(ctx context.Context, request UnknownExampleRequestObject) (UnknownExampleResponseObject, error) {
-	return UnknownExample200Videomp4Response{Body: request.Body}, nil
+	return UnknownExample200VideoMp4Response{Body: request.Body}, nil
 }
 
 func (s StrictServer) UnspecifiedContentType(ctx context.Context, request UnspecifiedContentTypeRequestObject) (UnspecifiedContentTypeResponseObject, error) {
diff --git a/internal/test/strict-server/gin/server.gen.go b/internal/test/strict-server/gin/server.gen.go
index 5192a0d..f41d3b1 100644
--- a/internal/test/strict-server/gin/server.gen.go
+++ b/internal/test/strict-server/gin/server.gen.go
@@ -354,12 +354,12 @@ func (response MultipleRequestAndResponseTypes200FormdataResponse) VisitMultiple
 	}
 }
 
-type MultipleRequestAndResponseTypes200ImagepngResponse struct {
+type MultipleRequestAndResponseTypes200ImagePngResponse struct {
 	Body          io.Reader
 	ContentLength int64
 }
 
-func (response MultipleRequestAndResponseTypes200ImagepngResponse) VisitMultipleRequestAndResponseTypesResponse(w http.ResponseWriter) error {
+func (response MultipleRequestAndResponseTypes200ImagePngResponse) VisitMultipleRequestAndResponseTypesResponse(w http.ResponseWriter) error {
 	w.Header().Set("Content-Type", "image/png")
 	if response.ContentLength != 0 {
 		w.Header().Set("Content-Length", fmt.Sprint(response.ContentLength))
@@ -478,12 +478,12 @@ type UnknownExampleResponseObject interface {
 	VisitUnknownExampleResponse(w http.ResponseWriter) error
 }
 
-type UnknownExample200Videomp4Response struct {
+type UnknownExample200VideoMp4Response struct {
 	Body          io.Reader
 	ContentLength int64
 }
 
-func (response UnknownExample200Videomp4Response) VisitUnknownExampleResponse(w http.ResponseWriter) error {
+func (response UnknownExample200VideoMp4Response) VisitUnknownExampleResponse(w http.ResponseWriter) error {
 	w.Header().Set("Content-Type", "video/mp4")
 	if response.ContentLength != 0 {
 		w.Header().Set("Content-Length", fmt.Sprint(response.ContentLength))
diff --git a/internal/test/strict-server/gin/server.go b/internal/test/strict-server/gin/server.go
index 748fc7d..3b64606 100644
--- a/internal/test/strict-server/gin/server.go
+++ b/internal/test/strict-server/gin/server.go
@@ -43,7 +43,7 @@ func (s StrictServer) MultipartExample(ctx context.Context, request MultipartExa
 func (s StrictServer) MultipleRequestAndResponseTypes(ctx context.Context, request MultipleRequestAndResponseTypesRequestObject) (MultipleRequestAndResponseTypesResponseObject, error) {
 	switch {
 	case request.Body != nil:
-		return MultipleRequestAndResponseTypes200ImagepngResponse{Body: request.Body}, nil
+		return MultipleRequestAndResponseTypes200ImagePngResponse{Body: request.Body}, nil
 	case request.JSONBody != nil:
 		return MultipleRequestAndResponseTypes200JSONResponse(*request.JSONBody), nil
 	case request.FormdataBody != nil:
@@ -82,7 +82,7 @@ func (s StrictServer) TextExample(ctx context.Context, request TextExampleReques
 }
 
 func (s StrictServer) UnknownExample(ctx context.Context, request UnknownExampleRequestObject) (UnknownExampleResponseObject, error) {
-	return UnknownExample200Videomp4Response{Body: request.Body}, nil
+	return UnknownExample200VideoMp4Response{Body: request.Body}, nil
 }
 
 func (s StrictServer) UnspecifiedContentType(ctx context.Context, request UnspecifiedContentTypeRequestObject) (UnspecifiedContentTypeResponseObject, error) {
diff --git a/pkg/codegen/utils_test.go b/pkg/codegen/utils_test.go
index 7bbfaff..fa14c90 100644
--- a/pkg/codegen/utils_test.go
+++ b/pkg/codegen/utils_test.go
@@ -20,14 +20,102 @@ import (
 
 	"github.com/getkin/kin-openapi/openapi3"
 	"github.com/stretchr/testify/assert"
+	"github.com/stretchr/testify/require"
 )
 
-func TestStringOps(t *testing.T) {
-	// Test that each substitution works
-	assert.Equal(t, "WordWordWORDWordWordWordWordWordWordWordWordWordWord", ToCamelCase("word.word-WORD+Word_word~word(Word)Word{Word}Word[Word]Word:Word;"), "Camel case conversion failed")
+func TestToCamelCase(t *testing.T) {
+	tests := []struct {
+		str  string
+		want string
+	}{{
+		str:  "",
+		want: "",
+	}, {
+		str:  " foo_bar ",
+		want: "FooBar",
+	}, {
+		str:  "hi hello-hey-hallo",
+		want: "HiHelloHeyHallo",
+	}, {
+		str:  "foo#bar",
+		want: "FooBar",
+	}, {
+		str:  "foo2bar",
+		want: "Foo2Bar",
+	}, {
+		// Test that each substitution works
+		str:  "word.word-WORD+Word_word~word(Word)Word{Word}Word[Word]Word:Word;",
+		want: "WordWordWORDWordWordWordWordWordWordWordWordWordWord",
+	}, {
+		// Make sure numbers don't interact in a funny way.
+		str:  "number-1234",
+		want: "Number1234",
+	},
+	}
+	for i := range tests {
+		tt := tests[i]
+		t.Run(tt.str, func(t *testing.T) {
+			require.Equal(t, tt.want, ToCamelCase(tt.str))
+		})
+	}
+}
 
-	// Make sure numbers don't interact in a funny way.
-	assert.Equal(t, "Number1234", ToCamelCase("number-1234"), "Number Camelcasing not working.")
+func TestToCamelCaseWithInitialisms(t *testing.T) {
+	tests := []struct {
+		str  string
+		want string
+	}{{
+		str:  "",
+		want: "",
+	}, {
+		str:  "hello",
+		want: "Hello",
+	}, {
+		str:  "DBError",
+		want: "DBError",
+	}, {
+		str:  "httpOperationId",
+		want: "HTTPOperationID",
+	}, {
+		str:  "OperationId",
+		want: "OperationID",
+	}, {
+		str:  "peer2peer",
+		want: "Peer2Peer",
+	}, {
+		str:  "makeUtf8",
+		want: "MakeUTF8",
+	}, {
+		str:  "utf8Hello",
+		want: "UTF8Hello",
+	}, {
+		str:  "myDBError",
+		want: "MyDBError",
+	}, {
+		str:  " DbLayer ",
+		want: "DBLayer",
+	}, {
+		str:  "FindPetById",
+		want: "FindPetByID",
+	}, {
+		str:  "MyHttpUrl",
+		want: "MyHTTPURL",
+	}, {
+		str:  "find_user_by_uuid",
+		want: "FindUserByUUID",
+	}, {
+		str:  "HelloПриветWorldМир42",
+		want: "HelloПриветWorldМир42",
+	}, {
+		str:  "пир2пир",
+		want: "Пир2Пир",
+	}}
+	for i := range tests {
+		tt := tests[i]
+		t.Run(tt.str, func(t *testing.T) {
+			require.Equal(t, tt.want, ToCamelCaseWithInitialisms(tt.str))
+		})
+	}
 }
 
 func TestSortedSchemaKeys(t *testing.T) {
@@ -475,3 +563,34 @@ func TestGetImports(t *testing.T) {
 	assert.NoError(t, err)
 	assert.Equal(t, expected, res)
 }
+
+func TestLowercaseFirstCharacters(t *testing.T) {
+	tests := []struct {
+		name     string
+		in       string
+		expected string
+	}{
+		{
+			name:     "id",
+			expected: "id",
+		},
+		{
+			name:     "CamelCase",
+			expected: "camelCase",
+		},
+		{
+			name:     "ID",
+			expected: "id",
+		},
+		{
+			name:     "DBTree",
+			expected: "dbTree",
+		},
+	}
+
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			assert.Equal(t, tt.expected, LowercaseFirstCharacters(tt.name))
+		})
+	}
+}