← back to Cli Printing Press
fix(generator): harden toCamel, flagName, types template for special chars in schema names
d8a93e0acba4f4827049ef922749783dfb09e2a4 · 2026-03-24 10:06:30 -0700 · Matt Van Horn
Files touched
M internal/generator/generator.goM internal/generator/templates/types.go.tmplM internal/openapi/parser.go
Diff
commit d8a93e0acba4f4827049ef922749783dfb09e2a4
Author: Matt Van Horn <455140+mvanhorn@users.noreply.github.com>
Date: Tue Mar 24 10:06:30 2026 -0700
fix(generator): harden toCamel, flagName, types template for special chars in schema names
---
internal/generator/generator.go | 32 +++++++++++++++++++++++++++---
internal/generator/templates/types.go.tmpl | 4 ++--
internal/openapi/parser.go | 7 +++++++
3 files changed, 38 insertions(+), 5 deletions(-)
diff --git a/internal/generator/generator.go b/internal/generator/generator.go
index 23ec9115..cb523262 100644
--- a/internal/generator/generator.go
+++ b/internal/generator/generator.go
@@ -40,6 +40,7 @@ func New(s *spec.APISpec, outputDir string) *Generator {
"add": func(a, b int) int { return a + b },
"oneline": oneline,
"flagName": flagName,
+ "safeTypeName": safeTypeName,
"exampleLine": g.exampleLine,
}
return g
@@ -163,15 +164,22 @@ func (g *Generator) renderTemplate(tmplName, outPath string, data any) error {
// Template helper functions
func toCamel(s string) string {
+ // Strip characters that are invalid in Go identifiers
+ s = strings.TrimLeft(s, "$")
parts := strings.FieldsFunc(s, func(r rune) bool {
- return r == '_' || r == '-' || r == ' '
+ return r == '_' || r == '-' || r == ' ' || r == '.' || r == '/' || r == '\\' || r == '$' || r == '#' || r == '@'
})
for i, p := range parts {
if len(p) > 0 {
parts[i] = strings.ToUpper(p[:1]) + p[1:]
}
}
- return strings.Join(parts, "")
+ result := strings.Join(parts, "")
+ // Ensure starts with letter
+ if len(result) > 0 && !unicode.IsLetter(rune(result[0])) {
+ result = "V" + result
+ }
+ return result
}
func toSnake(s string) string {
@@ -334,7 +342,25 @@ func (g *Generator) exampleLine(commandPath, endpointName string, endpoint spec.
}
func flagName(name string) string {
- return strings.ReplaceAll(name, "_", "-")
+ name = strings.TrimLeft(name, "$")
+ name = strings.NewReplacer("_", "-", "/", "-", ".", "-", "\\", "-").Replace(name)
+ return strings.Trim(name, "-")
+}
+
+func safeTypeName(name string) string {
+ name = strings.TrimLeft(name, "$")
+ name = strings.NewReplacer(".", "_", "/", "_", "\\", "_", "-", "_", " ", "_").Replace(name)
+ var b strings.Builder
+ for _, r := range name {
+ if unicode.IsLetter(r) || unicode.IsDigit(r) || r == '_' {
+ b.WriteRune(r)
+ }
+ }
+ result := b.String()
+ if len(result) > 0 && !unicode.IsLetter(rune(result[0])) {
+ result = "T" + result
+ }
+ return result
}
func envVarPlaceholder(envVar string) string {
diff --git a/internal/generator/templates/types.go.tmpl b/internal/generator/templates/types.go.tmpl
index 545f38c2..5c6b7456 100644
--- a/internal/generator/templates/types.go.tmpl
+++ b/internal/generator/templates/types.go.tmpl
@@ -1,8 +1,8 @@
package types
{{range $name, $typeDef := .Types}}
-type {{$name}} struct {
+type {{safeTypeName $name}} struct {
{{- range $typeDef.Fields}}
- {{title .Name}} {{goType .Type}} `json:"{{.Name}}"`
+ {{camel .Name}} {{goType .Type}} `json:"{{.Name}}"`
{{- end}}
}
{{end}}
diff --git a/internal/openapi/parser.go b/internal/openapi/parser.go
index 02ccbb46..51bf7031 100644
--- a/internal/openapi/parser.go
+++ b/internal/openapi/parser.go
@@ -906,11 +906,18 @@ func mapTypes(doc *openapi3.T, out *spec.APISpec) {
}
sort.Strings(names)
+ usedTypeNames := map[string]int{}
for _, name := range names {
goName := sanitizeTypeName(name)
if goName == "" {
continue
}
+ if count, exists := usedTypeNames[goName]; exists {
+ usedTypeNames[goName] = count + 1
+ goName = fmt.Sprintf("%s%d", goName, count+1)
+ } else {
+ usedTypeNames[goName] = 1
+ }
schemaRef := schemaMap[name]
schema := schemaRefValue(schemaRef)
← 2501e565 docs(plans): update gauntlet findings after parser fixes - 4
·
back to Cli Printing Press
·
docs(plans): template sanitization round 2 + pipeline E2E pl 15f3d250 →