mirror of
https://github.com/zap-proto/zip.git
synced 2026-10-19 15:38:57 +00:00
One registry, three projections. A typed op already produced a REST route, an OpenAPI path and an MCP tool; it now also produces a command. cli.go derives `<service> <operation>` from the op's own identity, so adding a route can never rename an existing command, and LocalInvoke runs it through the op's own invoke seam — a fused binary needs no client because the command IS the handler call. clispec.go builds the same commands from a served spec, which is what lets the CLI drive a service it was not linked against. cmd/zipdoc + internal/zipdoc close the gap Go leaves: doc comments do not exist at run time, so prose reaches the spec only via a build-time AST pass. Field docs and examples are extracted from source and keyed "<Type>.<field>". Two defects found by making the projections agree: commandName treated "ends in a parameter" as the only way to address one thing, so GET /v1/paas/apps/:app/deploy became apps-deploy-LIST — named for a collection, returning one deploy. The route already says which it is past its parameter: /apps/:app/deploy is singular, /nodes/:node/jobs is plural. OpenAPI parameters carried no description while the registry had one, so the same argument had help in-process and none from the spec. Both now read the same key, and query parameters get it too. internal/jsontag exists so the reflect view of the wire-name rule and the AST view cannot drift; transport gains a dial-only https, because reaching a published API is an ordinary Mount and terminating TLS stays the ingress's job. Co-authored-by: Hanzo Dev <[email protected]>
27 lines
1004 B
Go
27 lines
1004 B
Go
// Package jsontag resolves a struct field's name on the wire.
|
|
//
|
|
// It exists so that rule lives in ONE place. zip reads it three ways — from
|
|
// reflect when building a schema, from reflect when binding path params, and
|
|
// from the AST when cmd/zipdoc extracts field documentation — and a spec that
|
|
// names a field differently from the decoder that reads it is precisely the
|
|
// drift the generator exists to kill. Different inputs, same rule.
|
|
package jsontag
|
|
|
|
import "strings"
|
|
|
|
// Name is the JSON name of a field declared as goName with the given `json`
|
|
// struct tag value (the tag's value, not the whole tag literal).
|
|
//
|
|
// The tag's name wins; an empty or options-only tag (`json:",omitempty"`)
|
|
// leaves the Go name. "-" is returned as-is rather than resolved, so a caller
|
|
// can tell "this field is omitted" from "this field is named -".
|
|
func Name(goName, tag string) string {
|
|
if i := strings.IndexByte(tag, ','); i >= 0 {
|
|
tag = tag[:i]
|
|
}
|
|
if tag == "" {
|
|
return goName
|
|
}
|
|
return tag
|
|
}
|