mirror of
https://github.com/zap-proto/zip.git
synced 2026-10-19 15:38:57 +00:00
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.
58 lines
2.3 KiB
Go
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)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|