Code Generation
Turn a .zap schema into type-safe code with the zap front-end and per-language backends.
Code Generation
A .zap schema on its own is just a description. Code generation turns it into
the View/Builder types and client/server stubs your program calls. ZAP keeps
this honest with a single design rule: one parser, many backends.
How it works
schema.zap ──▶ zap (front-end) ──▶ code-generator request ──▶ backend ──▶ source
▲ parses .zap ▲ emits one languageThe canonical zap binary — built from
zap-proto/cpp-core — is the only thing
that parses schema text. It reads your .zap file (including the
whitespace-significant syntax and its brace back-compat) and emits a binary
code-generator request. A language backend consumes that request and emits
source. Backends never parse .zap themselves, so every language sees identical
schema semantics.
Backends are resolved as zap-<lang> executables on PATH. The Rust, OCaml,
Python, and Haskell runtimes all drive this same front-end.
Using the front-end
# Emit a code-generator request to stdout (what a backend reads on stdin).
zap compile -o- schema.zap
# Drive a language backend directly.
zap compile -orust schema.zap # invokes zap-rust
zap compile -opython schema.zap # invokes zap-pythonThe CLI also exposes a convenience generate verb that selects the backend and
output directory for you:
zap generate schema.zap --lang=rust --out=./gen
zap generate schema.zap --lang=go --out=./genTypeScript — zapgen
The TypeScript runtime @zap-proto/zap ships
its own self-contained generator, zapgen, as a package bin. Install the
runtime and npx zapgen is available — no separate front-end needed for the
TS-native flow:
npm install @zap-proto/zap
npx zapgen schema.zap # writes schema_zap.ts next to the input
npx zapgen -out ./src/gen schema.zap # writes into the given directory
npx zapgen --emit=openapi schema.zap # writes schema.openapi.json (OpenAPI 3.1)
npx zapgen --emit=ts,openapi schema.zap # writes both targetsFor an Echo interface, zapgen emits echo_zap.ts containing:
- one
StructViewsubclass + builder per struct, - an
EchoMethodordinal table, - an
EchoClientwith oneasyncmethod per declared method, - an abstract
EchoServerwhosedispatch(envelope)decodes the ordinal and routes to the matching handler.
The generated file imports from @zap-proto/zap and is byte-compatible with the
Go runtime over the wire, so a TypeScript client can call a Go service unchanged.
OpenAPI output
The openapi target emits one OpenAPI 3.1 document per interface. Each method
becomes a POST /<service-kebab>/<method-kebab> operation whose request and
response bodies are the JSON Schema of the method's structs; every referenced
struct lands in components.schemas. These paths match what
@zap-proto/web's httpServe mounts, so the
OpenAPI surface and the live HTTP service stay in lockstep. The directives
# @openapi:version X and # @openapi:server URL set info.version and
servers[].
Rust — zapc
The Rust backend, zapc, integrates with a
cargo build. Call it from build.rs:
fn main() {
zapc::CompilerCommand::new()
.file("schema/echo.zap")
.run()
.expect("zap schema compile");
}zapc::CompilerCommand execs the canonical zap front-end and feeds its
code-generator request to the Rust backend. To generate Rust without a cargo
build, the zap-schema crate bundles the
front-end and the Rust backend into a single zap-schema binary:
cargo install zap-schemaOrdinal stability
Generated code addresses fields and methods by ordinal, and ordinals follow
declaration order. Append-only edits are wire-safe: adding a field or method
gives it the next ordinal and regenerated peers stay compatible with deployed
ones. To insert or reorder in source while preserving the wire, pin ordinals
explicitly with @N (see Schema Language).