Files
zeekayandHanzo Dev 2a0c388a29 one generator: typed ops to OpenAPI, MCP, CLI and three SDKs
A zip app already projects one typed-op registry into a document, a tool
list, a command tree and a Go SDK, but nothing writes them together, so a
fleet ends up describing the same API in a docs pipeline, an SDK pipeline
and a hand-kept manifest. This writes all of them from one app, and adds
the two languages zip does not reach.

The document is the intermediate form for every client leg, so generating
from a live app and generating from the document that app wrote are one
pipeline rather than two to keep level. Proven byte-identical on egress.

Undeclared routes are filtered out of every projection. zip's own
extraction calls leak them — measured against v1.36.38: an undeclared op
is in OpenAPISpec, MCPTools and Commands while the live MCP door drops
it — which would publish a retired address as an SDK method.

The generated Rust and C++ are compiled and run by the tests, not merely
written.

Co-authored-by: Hanzo Dev <[email protected]>
2026-09-01 11:16:02 -07:00

70 lines
1.8 KiB
Go

package opgen
// Spelling one name four ways.
//
// An operation has ONE identity, the operationId, and four languages that each
// insist on their own casing for it. Every rename happens here, from that one
// token, so a method in the Rust client and a method in the C++ client are the
// same word in two accents rather than two words.
import (
"strings"
"unicode"
)
// words splits an identifier however it was written — vault_seal, vaultSeal,
// vault-seal, vault.seal — into its words, lowercased.
func words(s string) []string {
var out []string
var cur strings.Builder
var prev rune
for i, r := range s {
switch {
case r == '_' || r == '-' || r == '.' || r == ' ' || r == '/':
if cur.Len() > 0 {
out = append(out, strings.ToLower(cur.String()))
cur.Reset()
}
case i > 0 && unicode.IsUpper(r) && !unicode.IsUpper(prev):
if cur.Len() > 0 {
out = append(out, strings.ToLower(cur.String()))
cur.Reset()
}
cur.WriteRune(r)
default:
cur.WriteRune(r)
}
prev = r
}
if cur.Len() > 0 {
out = append(out, strings.ToLower(cur.String()))
}
return out
}
// title is VaultSeal: the type name a language that names types that way wants.
func title(s string) string {
var b strings.Builder
for _, w := range words(s) {
b.WriteString(strings.ToUpper(w[:1]))
b.WriteString(w[1:])
}
if b.Len() == 0 {
return s
}
return b.String()
}
// snake is vault_seal: the method name Rust and C++ both want.
func snake(s string) string { return strings.Join(words(s), "_") }
// safe keeps an identifier out of a language's keyword set by suffixing it,
// which is the one rename that cannot collide with another field: a document
// that had both `type` and `type_` would already be two names.
func safe(name string, reserved map[string]bool) string {
if reserved[name] {
return name + "_"
}
return name
}