Files
zeekay 999e2153b3 Type every wire: bytes, forms, streams, sockets, redirects, unions
A typed op could only take one JSON value and answer one, so uploads,
signed webhooks, form posts, YAML and merge-patch bodies, PDFs, SSE,
WebSockets, OAuth redirects and upstream relays stayed untyped routes
with no schema, tool, command or SDK method. Each is now a typed op and
its REST wire is unchanged.

Requests: a zip.Body field takes the body and Content-Type exactly as
sent; an In implementing Parser decodes itself; form: fields and
*zip.File parts bind url-encoded and multipart forms; cookie: binds a
request cookie as an in: cookie parameter. Consumes names the media.

Answers: zip.Body (bytes, filename, its own status and headers, a
Reader streams chunked), Verbatim[T] (a relay documented as T), Sse[T]
(events with keep-alive), Socket[M] (an upgrade after the contract
runs; 426 without one, 501 over ZAP), Redirect, CookieCoder, Produces,
and unions: a type whose OneOf method's results are the alternatives,
with Or[A,B] as the stock one filing each under the status it states.

Projections follow from the manifest: the document publishes binary
media, form schemas, x-events, x-socket with 101, 3xx with Location
and oneOf; MCP takes bytes as base64 and returns bytes, text or
collected events as tool content, listing a socket op in _meta as no
tool; the CLI reads --body @file|-, --field name=@file and --out,
prints events as they arrive and bridges a socket to stdin and stdout.
tools/list is ProjectMCP over the manifest, the projection MCPTools
already was.

Prose reaches promoted fields (filed under the declaring struct), a
type defined over another struct, and the type arguments of zip's
kinds and OneOf results; an instantiated generic is published under
its name followed by its argument names instead of an illegal
component key. The unused reflect-only document and tool builders go.
2026-10-09 09:42:00 -07:00

58 lines
2.3 KiB
Go

package zipdoc
import "testing"
// Prose a type's fields carry has to reach the key the document reads, however
// the type reaches the op: through a generic, a type defined over another, an
// embedded struct, or one of zip's kinds.
func TestExtract_ShapesReachTheirProse(t *testing.T) {
const base = "github.com/zap-proto/zip/internal/zipdoc/testdata/"
for pkg, ops := range map[string]map[string]map[string]string{
"generic": {
"GET /v1/stores/:owner": {
"Envelope[[]" + base + "generic.Store].status": "Status is ok or error.",
"Envelope[[]" + base + "generic.Store].data": "Data is what the op answers.",
"Store.name": "Name is the store's name.",
},
"GET /v1/store/:owner": {
"Envelope[" + base + "generic.Store].status": "Status is ok or error.",
},
},
"defined": {
"GET /v1/filings/:id": {"legalFiling.name": "Name is the filing's title."},
"GET /v1/filings/:id/docket": {"docket.court": "Court is the court that holds the record."},
},
"embedded": {
"GET /v1/orders/:id": {
"Model.id": "ID addresses the record.",
"Model.created": "Created is when the record was made, RFC 3339.",
"Order.total": "Total is the order's total, in cents.",
},
},
"kinds": {
"POST /v1/stream": {"Chunk.text": "Text is the next piece of the completion.", "Prompt.stream": "Stream asks for events rather than one answer."},
"POST /v1/either": {"Chunk.text": "Text is the next piece of the completion.", "Done.answer": "Answer is the whole completion."},
"POST /v1/relay": {"Upstream.model": "Model is the model the upstream ran."},
"POST /v1/ingest": {"Event.kind": "Kind names the event.", "Batch.events": "Events are the events, oldest first."},
},
} {
p := load(t, pkg)
for key, want := range ops {
op := opByKey(t, p, key)
for field, prose := range want {
if got := op.Fields[field]; got != prose {
t.Errorf("%s %s: Fields[%q] = %q, want %q", pkg, key, field, got, prose)
}
}
// zip's own kinds are not wire shapes, so nothing of theirs is filed.
for field := range op.Fields {
for _, kind := range []string{"Sse[", "Or[", "Verbatim[", "Body."} {
if len(field) >= len(kind) && field[:len(kind)] == kind {
t.Errorf("%s %s: filed zip's own %q", pkg, key, field)
}
}
}
}
}
}