Files
zip/shapes_test.go
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

78 lines
2.4 KiB
Go

// SPDX-License-Identifier: BSD-3-Clause-Eco
// Copyright (C) 2026, Lux Industries Inc. All rights reserved.
// See the file LICENSE for licensing terms.
package zip_test
import (
"context"
"encoding/json"
"testing"
"github.com/zap-proto/zip"
)
// Prose written once reaches every field it describes: a promoted field carries
// the comment its embedded struct wrote, and an instantiated generic is
// published under a name a schema may have while its prose stays filed under
// the name zipdoc wrote.
// Record is what every record carries.
type Record struct {
ID string `json:"id"`
}
type purchase struct {
Record
Total int64 `json:"total"`
}
type envelope[T any] struct {
Status string `json:"status"`
Data T `json:"data"`
}
func TestShapes_ProseReachesPromotedAndGenericFields(t *testing.T) {
app := zip.New(zip.Config{AppName: "shapes", DisableStartupMessage: true})
zip.Describe("GET /v1/orders/:id", zip.Doc{
Description: "Get returns one purchase.",
Fields: map[string]string{
"Record.id": "ID addresses the record.",
"purchase.total": "Total is in cents.",
"envelope[github.com/zap-proto/zip_test.purchase].status": "Status is ok or error.",
"envelope[github.com/zap-proto/zip_test.purchase].data": "Data is the purchase.",
"reportIn.id": "ID names the purchase.",
},
})
app.Get("/v1/orders/:id", func(_ context.Context, in *reportIn) (*envelope[purchase], error) {
return &envelope[purchase]{Status: "ok"}, nil
})
b, err := json.Marshal(app.OpenAPISpec())
if err != nil {
t.Fatal(err)
}
var doc struct {
Components struct {
Schemas map[string]struct {
Properties map[string]struct {
Description string `json:"description"`
} `json:"properties"`
} `json:"schemas"`
} `json:"components"`
}
if err := json.Unmarshal(b, &doc); err != nil {
t.Fatal(err)
}
env, ok := doc.Components.Schemas["envelopePurchase"]
if !ok {
t.Fatalf("no envelopePurchase in %s", b)
}
if env.Properties["status"].Description != "Status is ok or error." || env.Properties["data"].Description != "Data is the purchase." {
t.Errorf("envelopePurchase = %+v", env)
}
o := doc.Components.Schemas["purchase"]
if o.Properties["id"].Description != "ID addresses the record." || o.Properties["total"].Description != "Total is in cents." {
t.Errorf("purchase = %+v, want the promoted id described where Record declares it", o)
}
}