Files
o11y/address.go
T
zeekayandHanzo Dev 2965c80547 An address is measured from a root this package owns
The five claim verbs need the address an operation will answer at, which is a
root joined with a path. They used to ask the target for its prefix through a
struct written to answer with one — and that struct existed for a second reason
that has since gone: an operation with no declared id took a shape-derived one
qualified by the prefix of the occurrence it answered under, so a plain group
would have renamed every undeclared id. All 319 operations state their own id
now, so a declared id survives composition verbatim and the group is just a
group.

The roots are this package's own constants, so each group carries the one it was
made with and nothing can disagree about the address. The escape hatches name
their whole address and register through Raw, which says which method each one
answers.

The test that measured the gap reached zero and said so; it now holds the
property from the other side, that no operation goes undeclared.

Co-authored-by: Hanzo Dev <[email protected]>
2026-09-17 06:15:44 -07:00

85 lines
3.7 KiB
Go

package o11y
// AN OPERATION'S ADDRESS IS A FACT OF ITS REGISTRATION, NOT AN ARGUMENT OF ITS
// BODY.
//
// Every op in this table is declared at an address and then has to reach the
// runtime that implements that address. Until now it reached it by saying the
// address a second time, by hand, inside the handler:
//
// opGet(g, "/traces/:traceId", traceSpans) // the declaration
// relay(ctx, "GET", o11yRoot+"/traces/"+in.TraceID, …) // the body
//
// Two spellings of one value, 366 times over, and the second one is what got
// requested — so the first could stop agreeing with it and never be contradicted.
// It has: three of these addresses declare {traceId} where the runtime registered
// {traceID}. A router matches by POSITION, so a parameter's name is invisible to
// it and the disagreement cost nothing until something wanted to look an address
// up by name. That is exactly what the seam now does.
//
// So the address travels WITH the call. The declaration verbs (claim.go) wrap
// every op in [addressed], which puts the address the op was registered at into
// the context, and relay reads it there. The op's body states no method and no
// path at all, which is the honest end state: which endpoint was called is not
// something the body decides.
//
// The concrete path comes from [zip.Address], the inverse of the binding zip
// already performed — zip put the matched segments INTO the input, so reading
// them back out through the same rule cannot disagree with what arrived. The 119
// hand-built concatenations it replaces each re-derived that by eye.
import (
"context"
"github.com/zap-proto/zip"
)
// addr is one operation's address: the method, the public TEMPLATE that names it
// at the seam, and the CONCRETE path this particular call is for.
//
// Two spellings live here on purpose and they are two different values. The
// template is the address's NAME — "/v1/o11y/traces/{traceId}" — the string the
// document publishes and the string the runtime's registration recorded, so it is
// what resolves a handler. The path is where THIS call is going —
// "/v1/o11y/traces/abc-123" — so it is what the request carries.
type addr struct {
method string
template string
path string
}
// addrKey names the context slot an op's address rides in. A zero-size unexported
// type: unforgeable from outside this package, so nothing can plant an address an
// op was not registered at.
type addrKey struct{}
// addressed states an op's address once and carries it into the call.
//
// The template is computed at REGISTRATION — once per op for the life of the
// process — because it is a property of the declaration. Only the concrete path
// is per-call, because only it depends on the input.
//
// The full address is the target's own prefix plus the declared path, the same
// composition [claims] reads and the same one zip's registry records, so the
// three cannot name different addresses.
func addressed[In, Out any](root, method, path string, fn zip.TypedHandler[In, Out]) zip.TypedHandler[In, Out] {
pattern := root + path
template := zip.Template(pattern)
return func(ctx context.Context, in *In) (*Out, error) {
return fn(context.WithValue(ctx, addrKey{}, addr{
method: method,
template: template,
path: zip.Address(pattern, in),
}), in)
}
}
// addressOf is the address of the op whose body is running, and whether there is
// one. There is not exactly when an op's function is called directly rather than
// through the declaration that registered it — a programming error, and one worth
// naming rather than papering over with a guessed address.
func addressOf(ctx context.Context) (addr, bool) {
a, ok := ctx.Value(addrKey{}).(addr)
return a, ok
}