Repository: ogen-go/ogen
Pull request #190 (2022-02-15 11:30:11)

refactor(gen): return `ErrNotImplemented`, if `nameGen` unable to generate name


Patch:
diff --git a/gen/_template/param_dec.tmpl b/gen/_template/param_dec.tmpl
index 5631175c..248456b4 100644
--- a/gen/_template/param_dec.tmpl
+++ b/gen/_template/param_dec.tmpl
@@ -34,7 +34,7 @@ func decode{{ $op.Name }}Params(args [{{ $op.PathParamsCount }}]string, r *http.
 		if ok {
 			d := uri.NewQueryDecoder(uri.QueryDecoderConfig{
 				Values: values,
-				Style: uri.QueryStyle{{ pascalSpecial $p.Spec.Style.String }},
+				Style: uri.QueryStyle{{ capitalize $p.Spec.Style.String }},
 				Explode: {{ if $p.Spec.Explode }}true{{ else }}false{{ end }},
 			})
 
@@ -62,7 +62,7 @@ func decode{{ $op.Name }}Params(args [{{ $op.PathParamsCount }}]string, r *http.
 			d := uri.NewPathDecoder(uri.PathDecoderConfig{
 				Param: {{ quote $p.Spec.Name}},
 				Value: param,
-				Style: uri.PathStyle{{ pascalSpecial $p.Spec.Style.String }},
+				Style: uri.PathStyle{{ capitalize $p.Spec.Style.String }},
 				Explode: {{ if $p.Spec.Explode }}true{{ else }}false{{ end }},
 			})
 
diff --git a/gen/_template/param_enc.tmpl b/gen/_template/param_enc.tmpl
index 294f2685..57c3b5c0 100644
--- a/gen/_template/param_enc.tmpl
+++ b/gen/_template/param_enc.tmpl
@@ -10,7 +10,7 @@ u.Path += {{ quote $part.Raw }}
 	// Encode {{ quote $param.Spec.Name }} parameter.
 	e := uri.NewPathEncoder(uri.PathEncoderConfig{
 		Param: {{ quote $param.Spec.Name }},
-		Style: uri.PathStyle{{ pascalSpecial $param.Spec.Style.String }},
+		Style: uri.PathStyle{{ capitalize $param.Spec.Style.String }},
 		Explode: {{ if $param.Spec.Explode }}true{{ else }}false{{ end }},
 	})
 	if err := func() error {
@@ -32,7 +32,7 @@ q := u.Query()
 {
 	// Encode {{ quote $param.Spec.Name }} parameter.
 	e := uri.NewQueryEncoder(uri.QueryEncoderConfig{
-		Style: uri.QueryStyle{{ pascalSpecial $param.Spec.Style.String }},
+		Style: uri.QueryStyle{{ capitalize $param.Spec.Style.String }},
 		Explode: {{ if $param.Spec.Explode }}true{{ else }}false{{ end }},
 	})
 	if err := func() error {
diff --git a/gen/fix.go b/gen/fix.go
index 8fae371b..13dca118 100644
--- a/gen/fix.go
+++ b/gen/fix.go
@@ -5,6 +5,8 @@ import (
 	"reflect"
 	"sort"
 
+	"github.com/go-faster/errors"
+
 	"github.com/ogen-go/ogen/internal/ir"
 )
 
@@ -37,13 +39,13 @@ import (
 // func(*FooResponseOK) FooResponse() {}
 //
 // type FooResponseAccepted Foo
-// func(*FooResponseOK) FooResponse() {}
+// func(*FooResponseAccepted) FooResponse() {}
 //
 // Referring to the same schema in different content types
 // also can cause a collision and it will be fixed in the same way.
-func fixEqualResponses(ctx *genctx, op *ir.Operation) {
+func fixEqualResponses(ctx *genctx, op *ir.Operation) error {
 	if !op.Response.Type.Is(ir.KindInterface) {
-		return
+		return nil
 	}
 
 	// We can modify contents of operation response.
@@ -74,14 +76,23 @@ func fixEqualResponses(ctx *genctx, op *ir.Operation) {
 			lresp, rresp := op.Response.StatusCode[lcode], op.Response.StatusCode[rcode]
 			if (lresp.NoContent != nil && rresp.NoContent != nil) && lcode != rcode {
 				if reflect.DeepEqual(lresp.NoContent, rresp.NoContent) {
+					lname, err := pascal(op.Name, http.StatusText(lcode))
+					if err != nil {
+						return errors.Wrap(err, "lname")
+					}
+					rname, err := pascal(op.Name, http.StatusText(rcode))
+					if err != nil {
+						return errors.Wrap(err, "rname")
+					}
+
 					candidates = append(candidates, candidate{
-						renameTo:   pascal(op.Name, http.StatusText(lcode)),
+						renameTo:   lname,
 						typ:        lresp.NoContent,
 						replaceNoc: true,
 						response:   lresp,
 					})
 					candidates = append(candidates, candidate{
-						renameTo:   pascal(op.Name, http.StatusText(rcode)),
+						renameTo:   rname,
 						typ:        rresp.NoContent,
 						replaceNoc: true,
 						response:   rresp,
@@ -109,14 +120,23 @@ func fixEqualResponses(ctx *genctx, op *ir.Operation) {
 					}
 					lschema, rschema := lresp.Contents[ir.ContentType(lct)], rresp.Contents[ir.ContentType(rct)]
 					if reflect.DeepEqual(lschema, rschema) {
+						lname, err := pascal(op.Name, lct, http.StatusText(lcode))
+						if err != nil {
+							return errors.Wrap(err, "lname")
+						}
+						rname, err := pascal(op.Name, rct, http.StatusText(rcode))
+						if err != nil {
+							return errors.Wrap(err, "rname")
+						}
+
 						candidates = append(candidates, candidate{
-							renameTo:  pascal(op.Name, lct, http.StatusText(lcode)),
+							renameTo:  lname,
 							typ:       lschema,
 							replaceCT: lct,
 							response:  lresp,
 						})
 						candidates = append(candidates, candidate{
-							renameTo:  pascal(op.Name, rct, http.StatusText(rcode)),
+							renameTo:  rname,
 							typ:       rschema,
 							replaceCT: rct,
 							response:  rresp,
@@ -143,6 +163,8 @@ func fixEqualResponses(ctx *genctx, op *ir.Operation) {
 
 		candidate.response.Contents[ir.ContentType(candidate.replaceCT)] = alias
 	}
+
+	return nil
 }
 
 func cloneResponse(r *ir.Response) *ir.Response {
@@ -164,12 +186,12 @@ func cloneResponse(r *ir.Response) *ir.Response {
 	return newR
 }
 
-func fixEqualRequests(ctx *genctx, op *ir.Operation) {
+func fixEqualRequests(ctx *genctx, op *ir.Operation) error {
 	if op.Request == nil {
-		return
+		return nil
 	}
 	if !op.Request.Type.Is(ir.KindInterface) {
-		return
+		return nil
 	}
 
 	// We can modify request contents.
@@ -199,13 +221,21 @@ func fixEqualRequests(ctx *genctx, op *ir.Operation) {
 
 			rschema := op.Request.Contents[ir.ContentType(rcontent)]
 			if reflect.DeepEqual(lschema, rschema) {
+				lname, err := pascal(op.Name, lcontent)
+				if err != nil {
+					return errors.Wrap(err, "lname")
+				}
+				rname, err := pascal(op.Name, rcontent)
+				if err != nil {
+					return errors.Wrap(err, "rname")
+				}
 				candidates = append(candidates, candidate{
-					renameTo: pascal(op.Name, lcontent),
+					renameTo: lname,
 					ctype:    lcontent,
 					t:        lschema,
 				})
 				candidates = append(candidates, candidate{
-					renameTo: pascal(op.Name, rcontent),
+					renameTo: rname,
 					ctype:    rcontent,
 					t:        rschema,
 				})
@@ -224,6 +254,8 @@ func fixEqualRequests(ctx *genctx, op *ir.Operation) {
 
 		op.Request.Contents[ir.ContentType(candidate.ctype)] = alias
 	}
+
+	return nil
 }
 
 func cloneRequest(r *ir.Request) *ir.Request {
diff --git a/gen/gen_contents.go b/gen/gen_contents.go
index 78bbb5dd..6ceeaa7d 100644
--- a/gen/gen_contents.go
+++ b/gen/gen_contents.go
@@ -7,7 +7,7 @@ import (
 	"github.com/ogen-go/ogen/jsonschema"
 )
 
-func (g *Generator) generateContents(ctx *genctx, name string, optional bool, contents map[string]*jsonschema.Schema) (map[ir.ContentType]*ir.Type, error) {
+func (g *Generator) generateContents(ctx *genctx, name string, optional bool, contents map[string]*jsonschema.Schema) (_ map[ir.ContentType]*ir.Type, err error) {
 	var (
 		result      = make(map[ir.ContentType]*ir.Type, len(contents))
 		unsupported []string
@@ -16,7 +16,10 @@ func (g *Generator) generateContents(ctx *genctx, name string, optional bool, co
 	for contentType, schema := range contents {
 		typeName := name
 		if len(contents) > 1 {
-			typeName = pascal(name, contentType)
+			typeName, err = pascal(name, contentType)
+			if err != nil {
+				return nil, errors.Wrapf(err, "name for %q", contentType)
+			}
 		}
 
 		ctx := ctx.appendPath(contentType)
diff --git a/gen/gen_operation.go b/gen/gen_operation.go
index 6755f342..2f368a5c 100644
--- a/gen/gen_operation.go
+++ b/gen/gen_operation.go
@@ -10,14 +10,21 @@ import (
 )
 
 func (g *Generator) generateOperation(ctx *genctx, spec *oas.Operation) (_ *ir.Operation, err error) {
+	var opName string
+	if spec.OperationID != "" {
+		opName, err = pascalNonEmpty(spec.OperationID)
+	} else {
+		opName, err = pascal(spec.Path.String(), strings.ToLower(spec.HTTPMethod))
+	}
+	if err != nil {
+		return nil, errors.Wrap(err, "operation name")
+	}
+	
 	op := &ir.Operation{
-		Name:        pascal(spec.Path.String(), strings.ToLower(spec.HTTPMethod)),
+		Name:        opName,
 		Description: spec.Description,
 		Spec:        spec,
 	}
-	if spec.OperationID != "" {
-		op.Name = pascal(spec.OperationID)
-	}
 
 	// Convert []oas.Parameter to []*ir.Parameter.
 	op.Params, err = g.generateParameters(ctx.appendPath("parameters"), op.Name, spec.Parameters)
diff --git a/gen/gen_parameters.go b/gen/gen_parameters.go
index dac750c9..2a3f46fa 100644
--- a/gen/gen_parameters.go
+++ b/gen/gen_parameters.go
@@ -10,7 +10,7 @@ import (
 	"github.com/ogen-go/ogen/jsonschema"
 )
 
-func (g *Generator) generateParameters(ctx *genctx, opName string, params []*oas.Parameter) ([]*ir.Parameter, error) {
+func (g *Generator) generateParameters(ctx *genctx, opName string, params []*oas.Parameter) (_ []*ir.Parameter, err error) {
 	result := make([]*ir.Parameter, 0, len(params))
 	for i, p := range params {
 		if p.In == oas.LocationCookie {
@@ -30,7 +30,12 @@ func (g *Generator) generateParameters(ctx *genctx, opName string, params []*oas
 		}
 
 		ctx := ctx.appendPath(strconv.Itoa(i), p.Name, "schema")
-		t, err := g.generateSchema(ctx, pascal(opName, p.Name), p.Schema)
+
+		paramTypeName, err := pascal(opName, p.Name)
+		if err != nil {
+			return nil, errors.Wrapf(err, "parameter type name: %q", p.Name)
+		}
+		t, err := g.generateSchema(ctx, paramTypeName, p.Schema)
 		if err != nil {
 			return nil, errors.Wrapf(err, "%q", p.Name)
 		}
@@ -49,8 +54,12 @@ func (g *Generator) generateParameters(ctx *genctx, opName string, params []*oas
 		}
 
 		t.AddFeature("uri")
+		paramName, err := pascalNonEmpty(p.Name)
+		if err != nil {
+			return nil, errors.Wrapf(err, "parameter name: %q", p.Name)
+		}
 		result = append(result, &ir.Parameter{
-			Name: pascal(p.Name),
+			Name: paramName,
 			Type: t,
 			Spec: p,
 		})
@@ -71,8 +80,15 @@ func (g *Generator) generateParameters(ctx *genctx, opName string, params []*oas
 				case inEqual && specNameEqual:
 					panic(unreachable(pp.Spec.Name))
 				case inEqual:
-					p.Name = pascalSpecial(p.Spec.Name)
-					pp.Name = pascalSpecial(pp.Spec.Name)
+					p.Name, err = pascalSpecial(p.Spec.Name)
+					if err != nil {
+						return nil, errors.Wrap(err, "parameter name")
+					}
+
+					pp.Name, err = pascalSpecial(pp.Spec.Name)
+					if err != nil {
+						return nil, errors.Wrap(err, "parameter name")
+					}
 				case specNameEqual:
 					p.Name = string(p.Spec.In) + p.Name
 					pp.Name = string(pp.Spec.In) + pp.Name
diff --git a/gen/gen_request_body.go b/gen/gen_request_body.go
index f081fa18..e93dbbe2 100644
--- a/gen/gen_request_body.go
+++ b/gen/gen_request_body.go
@@ -18,7 +18,11 @@ func (g *Generator) generateRequest(ctx *genctx, opName string, body *oas.Reques
 	var requestType *ir.Type
 	if len(contents) > 1 {
 		requestType = ir.Interface(name)
-		requestType.AddMethod(camel(name))
+		methodName, err := camel(name)
+		if err != nil {
+			return nil, errors.Wrap(err, "method name")
+		}
+		requestType.AddMethod(methodName)
 		if err := ctx.saveType(requestType); err != nil {
 			return nil, errors.Wrap(err, "save interface type")
 		}
@@ -28,7 +32,11 @@ func (g *Generator) generateRequest(ctx *genctx, opName string, body *oas.Reques
 		switch {
 		case len(contents) > 1:
 			if !t.CanHaveMethods() {
-				t = ir.Alias(pascal(name, string(contentType)), t)
+				requestName, err := pascal(name, string(contentType))
+				if err != nil {
+					return nil, errors.Wrapf(err, "request name: %q", contentType)
+				}
+				t = ir.Alias(requestName, t)
 				contents[contentType] = t
 				if err := ctx.saveType(t); err != nil {
 					return nil, errors.Wrap(err, "save alias type")
diff --git a/gen/gen_responses.go b/gen/gen_responses.go
index d35a6eec..401f6e84 100644
--- a/gen/gen_responses.go
+++ b/gen/gen_responses.go
@@ -37,10 +37,14 @@ func (g *Generator) generateResponses(ctx *genctx, opName string, responses map[
 	sort.Ints(statusCodes)
 
 	for _, code := range statusCodes {
+		respName, err := pascal(opName, http.StatusText(code))
+		if err != nil {
+			return nil, errors.Wrapf(err, "%s: %d: response name", opName, code)
+		}
+
 		var (
-			resp     = responses[strconv.Itoa(code)]
-			respName = pascal(opName, http.StatusText(code))
-			doc      = fmt.Sprintf("%s is response for %s operation.", respName, opName)
+			resp = responses[strconv.Itoa(code)]
+			doc  = fmt.Sprintf("%s is response for %s operation.", respName, opName)
 		)
 		r, err := g.responseToIR(ctx, respName, doc, resp)
 		if err != nil {
@@ -71,11 +75,13 @@ func (g *Generator) generateResponses(ctx *genctx, opName string, responses map[
 		lastWalked *ir.Type
 	)
 
-	walkResponseTypes(result, func(_ string, t *ir.Type) *ir.Type {
+	if err := walkResponseTypes(result, func(_ string, t *ir.Type) (*ir.Type, error) {
 		countTypes += 1
 		lastWalked = t
-		return t
-	})
+		return t, nil
+	}); err != nil {
+		return nil, errors.Wrap(err, "walk")
+	}
 
 	if countTypes == 1 {
 		result.Type = lastWalked
@@ -83,21 +89,31 @@ func (g *Generator) generateResponses(ctx *genctx, opName string, responses map[
 	}
 
 	iface := ir.Interface(name)
-	iface.AddMethod(camel(name))
+	methodName, err := camel(name)
+	if err != nil {
+		return nil, errors.Wrap(err, "method name")
+	}
+	iface.AddMethod(methodName)
 	if err := ctx.saveType(iface); err != nil {
 		return nil, errors.Wrap(err, "save interface type")
 	}
-	walkResponseTypes(result, func(resName string, t *ir.Type) *ir.Type {
+	if err := walkResponseTypes(result, func(resName string, t *ir.Type) (*ir.Type, error) {
 		if !t.CanHaveMethods() {
-			t = ir.Alias(pascal(opName, resName), t)
+			respName, err := pascal(opName, resName)
+			if err != nil {
+				return nil, errors.Wrapf(err, "request name: %q", resName)
+			}
+			t = ir.Alias(respName, t)
 			if err := ctx.saveType(t); err != nil {
 				panic("unreachable")
 			}
 		}
 
 		t.Implement(iface)
-		return t
-	})
+		return t, nil
+	}); err != nil {
+		return nil, errors.Wrap(err, "walk")
+	}
 
 	result.Type = iface
 	return result, nil
@@ -109,7 +125,11 @@ func (g *Generator) responseToIR(ctx *genctx, name, doc string, resp *oas.Respon
 			return r, nil
 		}
 
-		name = pascal(strings.TrimPrefix(ref, "#/components/responses/"))
+		n, err := pascal(strings.TrimPrefix(ref, "#/components/responses/"))
+		if err != nil {
+			return nil, errors.Wrapf(err, "response name: %q", ref)
+		}
+		name = n
 		doc = fmt.Sprintf("Ref: %s", ref)
 		defer func() {
 			if rerr != nil {
diff --git a/gen/generator.go b/gen/generator.go
index 818357c7..da99f5dc 100644
--- a/gen/generator.go
+++ b/gen/generator.go
@@ -103,8 +103,12 @@ func (g *Generator) makeIR(ops []*oas.Operation) error {
 			continue
 		}
 
-		fixEqualRequests(ctx, op)
-		fixEqualResponses(ctx, op)
+		if err := fixEqualRequests(ctx, op); err != nil {
+			return errors.Wrap(err, "fix requests")
+		}
+		if err := fixEqualResponses(ctx, op); err != nil {
+			return errors.Wrap(err, "fix responses")
+		}
 
 		if err := g.tstorage.merge(ctx.local); err != nil {
 			return err
diff --git a/gen/generics.go b/gen/generics.go
index 79a102be..ce7a196c 100644
--- a/gen/generics.go
+++ b/gen/generics.go
@@ -5,6 +5,7 @@ import (
 	"strings"
 
 	"github.com/go-faster/errors"
+
 	"github.com/ogen-go/ogen/internal/ir"
 )
 
@@ -69,7 +70,11 @@ func boxType(ctx *genctx, v ir.GenericVariant, t *ir.Type) (*ir.Type, error) {
 		case v.OnlyNullable():
 			t.NilSemantic = ir.NilNull
 		default:
-			t = ir.Generic(genericPostfix(t), t, v)
+			postfix, err := genericPostfix(t)
+			if err != nil {
+				return nil, errors.Wrap(err, "postfix")
+			}
+			t = ir.Generic(postfix, t, v)
 			if err := ctx.saveType(t); err != nil {
 				return nil, err
 			}
@@ -79,7 +84,11 @@ func boxType(ctx *genctx, v ir.GenericVariant, t *ir.Type) (*ir.Type, error) {
 	}
 
 	if t.CanGeneric() {
-		t = ir.Generic(genericPostfix(t), t, v)
+		postfix, err := genericPostfix(t)
+		if err != nil {
+			return nil, errors.Wrap(err, "postfix")
+		}
+		t = ir.Generic(postfix, t, v)
 		if err := ctx.saveType(t); err != nil {
 			return nil, err
 		}
@@ -93,7 +102,11 @@ func boxType(ctx *genctx, v ir.GenericVariant, t *ir.Type) (*ir.Type, error) {
 	case v.OnlyNullable():
 		return t.Pointer(ir.NilNull), nil
 	default:
-		t = ir.Generic(genericPostfix(t),
+		postfix, err := genericPostfix(t)
+		if err != nil {
+			return nil, errors.Wrap(err, "postfix")
+		}
+		t = ir.Generic(postfix,
 			t.Pointer(ir.NilNull), ir.GenericVariant{Optional: true},
 		)
 		if err := ctx.saveType(t); err != nil {
@@ -104,7 +117,7 @@ func boxType(ctx *genctx, v ir.GenericVariant, t *ir.Type) (*ir.Type, error) {
 	}
 }
 
-func genericPostfix(t *ir.Type) string {
+func genericPostfix(t *ir.Type) (string, error) {
 	name := t.NamePostfix()
 	if idx := strings.Index(name, "."); idx > 0 {
 		name = name[idx+1:]
diff --git a/gen/names.go b/gen/names.go
index a00338e2..d8a2b276 100644
--- a/gen/names.go
+++ b/gen/names.go
@@ -3,6 +3,8 @@ package gen
 import (
 	"strings"
 	"unicode"
+
+	"github.com/go-faster/errors"
 )
 
 type nameGen struct {
@@ -32,7 +34,7 @@ var namedChar = map[rune][]rune{
 	'.': []rune("Dot"),
 }
 
-func (g *nameGen) generate() string {
+func (g *nameGen) generate() (string, error) {
 	var (
 		part     []rune
 		upper    = true
@@ -45,7 +47,7 @@ func (g *nameGen) generate() string {
 		r, ok := g.next()
 		if !ok {
 			pushPart()
-			return strings.Join(g.parts, "")
+			return strings.Join(g.parts, ""), nil
 		}
 
 		if g.isAllowed(r) {
@@ -139,25 +141,52 @@ func cleanSpecial(strs ...string) string {
 	}).clean()
 }
 
-func pascal(strs ...string) string {
+func pascal(strs ...string) (string, error) {
 	return (&nameGen{
 		src: []rune(strings.Join(strs, " ")),
 	}).generate()
 }
 
-func pascalSpecial(strs ...string) string {
+func pascalSpecial(strs ...string) (string, error) {
 	return (&nameGen{
 		src:          []rune(strings.Join(strs, " ")),
 		allowSpecial: true,
 	}).generate()
 }
 
-func camel(s string) string {
-	return firstLower(pascal(s))
+func pascalNonEmpty(strs ...string) (string, error) {
+	r, err := pascal(strs...)
+	if err != nil {
+		return "", err
+	}
+	if r != "" {
+		return r, nil
+	}
+
+	r, err = pascalSpecial(strs...)
+	if err != nil {
+		return "", err
+	}
+	if r != "" {
+		return r, nil
+	}
+	return "", errors.Wrapf(&ErrNotImplemented{Name: "crypticName"}, "can't generate name for %+v", strs)
 }
 
-func camelSpecial(s ...string) string {
-	return firstLower(pascalSpecial(s...))
+func camel(s ...string) (string, error) {
+	r, err := pascal(s...)
+	if err != nil {
+		return "", err
+	}
+	return firstLower(r), nil
+}
+
+func camelSpecial(s ...string) (string, error) {
+	r, err := pascalSpecial(s...)
+	if err != nil {
+		return "", err
+	}
+	return firstLower(r), nil
 }
 
 // firstLower returns s with first rune mapped to lower case.
diff --git a/gen/schema_gen.go b/gen/schema_gen.go
index 17839920..040e6872 100644
--- a/gen/schema_gen.go
+++ b/gen/schema_gen.go
@@ -2,11 +2,10 @@ package gen
 
 import (
 	"strings"
-	"unicode"
-	"unicode/utf8"
 
 	"github.com/go-faster/errors"
 
+	"github.com/ogen-go/ogen/internal/capitalize"
 	"github.com/ogen-go/ogen/internal/ir"
 	"github.com/ogen-go/ogen/jsonschema"
 )
@@ -18,11 +17,6 @@ type schemaGen struct {
 }
 
 func variantFieldName(t *ir.Type) string {
-	capitalize := func(s string) string {
-		r, size := utf8.DecodeRuneInString(s)
-		return string(unicode.ToUpper(r)) + s[size:]
-	}
-
 	var result string
 	switch t.Kind {
 	case ir.KindArray:
@@ -34,7 +28,7 @@ func variantFieldName(t *ir.Type) string {
 	default:
 		result = t.Go()
 	}
-	return capitalize(result)
+	return capitalize.Capitalize(result)
 }
 
 func (g *schemaGen) generate(name string, schema *jsonschema.Schema) (_ *ir.Type, err error) {
@@ -50,7 +44,10 @@ func (g *schemaGen) generate(name string, schema *jsonschema.Schema) (_ *ir.Type
 			return t, nil
 		}
 
-		name = pascal(strings.TrimPrefix(ref, "#/components/schemas/"))
+		name, err = pascal(strings.TrimPrefix(ref, "#/components/schemas/"))
+		if err != nil {
+			return nil, errors.Wrapf(err, "schema name: %q", ref)
+		}
 	}
 	if name[0] >= '0' && name[0] <= '9' {
 		name = "R" + name
@@ -108,13 +105,23 @@ func (g *schemaGen) generate(name string, schema *jsonschema.Schema) (_ *ir.Type
 
 		for i := range schema.Properties {
 			prop := schema.Properties[i]
-			t, err := g.generate(pascalSpecial(name, prop.Name), prop.Schema)
+			propTypeName, err := pascalSpecial(name, prop.Name)
+			if err != nil {
+				return nil, errors.Wrapf(err, "property type name: %q", prop.Name)
+			}
+
+			t, err := g.generate(propTypeName, prop.Schema)
 			if err != nil {
 				return nil, errors.Wrapf(err, "field %s", prop.Name)
 			}
 
+			propName, err := pascalSpecial(prop.Name)
+			if err != nil {
+				return nil, errors.Wrapf(err, "property name: %q", prop.Name)
+			}
+
 			s.Fields = append(s.Fields, &ir.Field{
-				Name: pascalSpecial(prop.Name),
+				Name: propName,
 				Type: t,
 				Tag: ir.Tag{
 					JSON: prop.Name,
diff --git a/gen/schema_gen_primitive.go b/gen/schema_gen_primitive.go
index 58df7ade..7e63fbc1 100644
--- a/gen/schema_gen_primitive.go
+++ b/gen/schema_gen_primitive.go
@@ -17,7 +17,7 @@ func (g *schemaGen) primitive(name string, schema *jsonschema.Schema) (*ir.Type,
 			return nil, errors.Errorf("unsupported enum type: %q", schema.Type)
 		}
 
-		hasDuplicateNames := func() bool {
+		hasDuplicateNames, err := func() (bool, error) {
 			names := map[string]struct{}{}
 			for _, v := range schema.Enum {
 				vstr := fmt.Sprintf("%v", v)
@@ -25,15 +25,21 @@ func (g *schemaGen) primitive(name string, schema *jsonschema.Schema) (*ir.Type,
 					vstr = "Empty"
 				}
 
-				k := pascalSpecial(name, vstr)
+				k, err := pascalSpecial(name, vstr)
+				if err != nil {
+					return false, errors.Wrapf(err, "variant %q", vstr)
+				}
 				if _, ok := names[k]; ok {
-					return true
+					return true, nil
 				}
 				names[k] = struct{}{}
 			}
 
-			return false
+			return false, nil
 		}()
+		if err != nil {
+			return nil, errors.Wrap(err, "enum")
+		}
 
 		var variants []*ir.EnumVariant
 		for _, v := range schema.Enum {
@@ -46,7 +52,10 @@ func (g *schemaGen) primitive(name string, schema *jsonschema.Schema) (*ir.Type,
 			if hasDuplicateNames {
 				variantName = name + "_" + cleanSpecial(vstr)
 			} else {
-				variantName = pascalSpecial(name, vstr)
+				variantName, err = pascalSpecial(name, vstr)
+				if err != nil {
+					return nil, errors.Wrapf(err, "variant %q", vstr)
+				}
 			}
 
 			variants = append(variants, &ir.EnumVariant{
diff --git a/gen/templates.go b/gen/templates.go
index 4ebe3cc4..0fab6fe3 100644
--- a/gen/templates.go
+++ b/gen/templates.go
@@ -8,6 +8,7 @@ import (
 
 	"github.com/go-faster/errors"
 
+	"github.com/ogen-go/ogen/internal/capitalize"
 	"github.com/ogen-go/ogen/internal/ir"
 )
 
@@ -66,6 +67,7 @@ func templateFunctions() template.FuncMap {
 		},
 		"pascalSpecial": pascalSpecial,
 		"camelSpecial":  camelSpecial,
+		"capitalize":    capitalize.Capitalize,
 
 		// Helpers for recursive encoding and decoding.
 		"elem": func(t *ir.Type, v string) Elem {
diff --git a/gen/walk.go b/gen/walk.go
index 721df5b6..d07d06dd 100644
--- a/gen/walk.go
+++ b/gen/walk.go
@@ -3,25 +3,59 @@ package gen
 import (
 	"net/http"
 
+	"github.com/go-faster/errors"
+
 	"github.com/ogen-go/ogen/internal/ir"
 )
 
-func walkResponseTypes(r *ir.Response, walkFn func(name string, t *ir.Type) *ir.Type) {
+func walkResponseTypes(r *ir.Response, walkFn func(name string, t *ir.Type) (*ir.Type, error)) error {
+	do := func(prefix string, t *ir.Type, contentType ir.ContentType) (*ir.Type, error) {
+		respName, err := pascal(prefix, string(contentType))
+		if err != nil {
+			return nil, errors.Wrap(err, "generate name")
+		}
+
+		typ, err := walkFn(respName, t)
+		if err != nil {
+			return nil, errors.Wrap(err, "callback")
+		}
+
+		return typ, nil
+	}
+
 	for code, r := range r.StatusCode {
 		for contentType, t := range r.Contents {
-			r.Contents[contentType] = walkFn(pascal(http.StatusText(code), string(contentType)), t)
+			typ, err := do(http.StatusText(code), t, contentType)
+			if err != nil {
+				return errors.Wrapf(err, "%d: %q", code, contentType)
+			}
+			r.Contents[contentType] = typ
 		}
 		if r.NoContent != nil {
-			r.NoContent = walkFn(pascal(http.StatusText(code)), r.NoContent)
+			typ, err := do(http.StatusText(code), r.NoContent, "")
+			if err != nil {
+				return errors.Wrapf(err, "%d: no content", code)
+			}
+			r.NoContent = typ
 		}
 	}
 
 	if def := r.Default; def != nil {
 		for contentType, t := range def.Contents {
-			def.Contents[contentType] = walkFn(pascal("Default", string(contentType)), t)
+			typ, err := do("Default", t, contentType)
+			if err != nil {
+				return errors.Wrapf(err, "default: %q", contentType)
+			}
+			def.Contents[contentType] = typ
 		}
 		if def.NoContent != nil {
-			def.NoContent = walkFn("Default", def.NoContent)
+			typ, err := walkFn("Default", def.NoContent)
+			if err != nil {
+				return errors.Wrap(err, "callback")
+			}
+			def.NoContent = typ
 		}
 	}
+
+	return nil
 }
diff --git a/internal/capitalize/capitalize.go b/internal/capitalize/capitalize.go
new file mode 100644
index 00000000..70a3d52c
--- /dev/null
+++ b/internal/capitalize/capitalize.go
@@ -0,0 +1,13 @@
+// Package capitalize contains capitalize function.
+package capitalize
+
+import (
+	"unicode"
+	"unicode/utf8"
+)
+
+// Capitalize converts first character to upper.
+func Capitalize(s string) string {
+	r, size := utf8.DecodeRuneInString(s)
+	return string(unicode.ToUpper(r)) + s[size:]
+}
diff --git a/internal/ir/description.go b/internal/ir/description.go
index 7a72b66d..3fd47988 100644
--- a/internal/ir/description.go
+++ b/internal/ir/description.go
@@ -4,6 +4,8 @@ import (
 	"strings"
 	"unicode"
 	"unicode/utf8"
+
+	"github.com/ogen-go/ogen/internal/capitalize"
 )
 
 func splitLine(s string, limit int) (r []string) {
@@ -47,7 +49,7 @@ func prettyDoc(s string) (r []string) {
 		r = append(r, splitLine(line, lineLimit)...)
 	}
 	if len(r) > 0 {
-		r[0] = capitalize(r[0])
+		r[0] = capitalize.Capitalize(r[0])
 
 		if last := r[len(r)-1]; len(last) > 0 && last[len(last)-1] != '.' {
 			r[len(r)-1] = last + "."
diff --git a/internal/ir/json.go b/internal/ir/json.go
index 6790765d..4baf7072 100644
--- a/internal/ir/json.go
+++ b/internal/ir/json.go
@@ -1,5 +1,7 @@
 package ir
 
+import "github.com/ogen-go/ogen/internal/capitalize"
+
 // JSON returns json encoding/decoding rules for t.
 func (t *Type) JSON() JSON {
 	return JSON{
@@ -192,7 +194,7 @@ func (j JSON) Fn() string {
 	case ByteSlice:
 		return "Base64"
 	default:
-		return capitalize(j.t.Primitive.String())
+		return capitalize.Capitalize(j.t.Primitive.String())
 	}
 }
 
diff --git a/internal/ir/template_helpers.go b/internal/ir/template_helpers.go
index 9849825f..ff5966a2 100644
--- a/internal/ir/template_helpers.go
+++ b/internal/ir/template_helpers.go
@@ -3,19 +3,9 @@ package ir
 import (
 	"fmt"
 	"strings"
-	"unicode"
-)
 
-func capitalize(s string) string {
-	var v []rune
-	for i, c := range s {
-		if i == 0 {
-			c = unicode.ToUpper(c)
-		}
-		v = append(v, c)
-	}
-	return string(v)
-}
+	"github.com/ogen-go/ogen/internal/capitalize"
+)
 
 func afterDot(v string) string {
 	idx := strings.Index(v, ".")
@@ -31,7 +21,7 @@ func (t *Type) EncodeFn() string {
 	}
 	switch t.Primitive {
 	case Int, Int64, Int32, String, Bool, Float32, Float64:
-		return capitalize(t.Primitive.String())
+		return capitalize.Capitalize(t.Primitive.String())
 	case UUID, Time, IP, Duration, URL:
 		return afterDot(t.Primitive.String())
 	default:


Test patch:
diff --git a/gen/names_test.go b/gen/names_test.go
index 6f0c2e70..2cd0dd17 100644
--- a/gen/names_test.go
+++ b/gen/names_test.go
@@ -11,19 +11,21 @@ func TestNames(t *testing.T) {
 		Input   string
 		Expect  string
 		AllowMP bool
+		Error   bool
 	}{
-		{"user_id", "UserID", false},
-		{"userId", "UserId", false},
-		{"foo+bar", "FooPlusBar", true},
-		{"foo+bar", "FooBar", false},
-		{"+1", "Plus1", true},
+		{"user_id", "UserID", false, false},
+		{"userId", "UserId", false, false},
+		{"foo+bar", "FooPlusBar", true, false},
+		{"foo+bar", "FooBar", false, false},
+		{"+1", "Plus1", true, false},
 	}
 
 	for _, test := range tests {
-		out := (&nameGen{
+		out, err := (&nameGen{
 			src:          []rune(test.Input),
 			allowSpecial: test.AllowMP,
 		}).generate()
+		require.NoError(t, err)
 		require.Equal(t, test.Expect, out)
 	}
 }