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.
78 lines
2.4 KiB
Go
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)
|
|
}
|
|
}
|