mirror of
https://github.com/zap-proto/zip.git
synced 2026-10-19 15:38:57 +00:00
A Rust developer writes Rust handlers, with Rust types and Rust doc comments,
and gets every projection from that source — the same deal a Go developer has
had. No .zap is written by hand and no Go program declares a Rust service's
ops.
The split falls on a measured seam. What is native per language is DECLARING an
op and RUNNING it; what is shared is the projection logic, driven from a neutral
description of the registry.
Manifest the registry with the Go taken out: ops, types, fields, wire
names, prose. App.Manifest fills one in by reflection; a
proc-macro fills one in as a Rust crate compiles.
Project* the OpenAPI document, the MCP tool list, the CLI and the ZAP
IDL, read off a Manifest. Go's own three now go through them —
the whole existing suite is the oracle — so Go keeps no private
path for them and the other languages cannot rot.
cmd/zipc the build-time projector. It reads a manifest, or the fragments
a compiler wrote, and writes the four documents.
rust/ zip in Rust: #[zip::ops] on an impl block, #[derive(zip::Wire)]
on a type, a router, a JSON binder, an HTTP door and a ZAP door.
The wire is generated by zapgen from wire.zap; nothing here
spells a byte offset by hand and nothing links Go.
The layout rule moved out of the manifest and into the projector, so the offsets
in a .zap are derived once for all three languages rather than three times;
TestProjectZAPMatchesRegistry pins that derivation against the layout the Go
encoder actually writes.
One service, written twice — corpus/info in Go and rust/examples/info in Rust,
fourteen ops each — is the gate. The OpenAPI document, the MCP tool list and the
CLI command tree come out byte-identical, and the ZAP schema agrees on every
struct, type and offset; it differs only where a field carries the declaring
language's own case convention and where the ledger names a type the way the
person who must change it would grep for it.
68 lines
1.9 KiB
Go
68 lines
1.9 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 (
|
|
"encoding/json"
|
|
"testing"
|
|
|
|
"github.com/zap-proto/zip"
|
|
"github.com/zap-proto/zip/corpus/info"
|
|
)
|
|
|
|
// The manifest lost nothing.
|
|
//
|
|
// Every projection here is produced twice — once off the Go registry, once off
|
|
// the neutral description of it — and compared byte for byte. Equality is what
|
|
// says a Rust or C++ front end that fills a manifest in has everything Go has;
|
|
// inequality names exactly the field a description cannot yet carry.
|
|
|
|
func corpus() *zip.App { return (&info.Info{Release: "luxd/1.36.178", Network: 96369}).Ops() }
|
|
|
|
func same(t *testing.T, what string, a, b any) {
|
|
t.Helper()
|
|
x, err := json.MarshalIndent(a, "", " ")
|
|
if err != nil {
|
|
t.Fatalf("%s: %v", what, err)
|
|
}
|
|
y, err := json.MarshalIndent(b, "", " ")
|
|
if err != nil {
|
|
t.Fatalf("%s: %v", what, err)
|
|
}
|
|
if string(x) != string(y) {
|
|
t.Errorf("%s: registry and manifest disagree\n--- registry\n%s\n--- manifest\n%s", what, x, y)
|
|
}
|
|
}
|
|
|
|
func TestProjectOpenAPIMatchesRegistry(t *testing.T) {
|
|
app := corpus()
|
|
same(t, "openapi", app.OpenAPISpec(), zip.ProjectOpenAPI(app.Manifest()))
|
|
}
|
|
|
|
func TestProjectMCPMatchesRegistry(t *testing.T) {
|
|
app := corpus()
|
|
same(t, "mcp", app.MCPTools(), zip.ProjectMCP(app.Manifest()))
|
|
}
|
|
|
|
func TestProjectCLIMatchesRegistry(t *testing.T) {
|
|
app := corpus()
|
|
same(t, "cli", app.Commands(), zip.ProjectCLI(app.Manifest()))
|
|
}
|
|
|
|
func TestCorpusIsFourteenOps(t *testing.T) {
|
|
if n := len(corpus().Registry()); n != 14 {
|
|
t.Fatalf("corpus has %d ops, want 14", n)
|
|
}
|
|
}
|
|
|
|
func TestProjectZAPMatchesRegistry(t *testing.T) {
|
|
app := corpus()
|
|
want := zip.ZAPSchema("info", app).String()
|
|
got := zip.ProjectZAP("info", app.Manifest()).String()
|
|
if want != got {
|
|
t.Errorf("zap: registry and manifest disagree\n--- registry\n%s\n--- manifest\n%s", want, got)
|
|
}
|
|
}
|