ZAP Protocol

Protocol

ZAP schema design and message format

Protocol

ZAP uses a zero-copy binary format for message serialization. This page covers schema design, best practices, and the wire format.

ZAP Format Basics

ZAP's data serialization format requires no parsing. Data is laid out in memory exactly as it appears on the wire.

Why Zero-Copy?

FeatureBenefit
Zero-copyNo parsing overhead
Forward/backward compatibleSafe schema evolution
Strongly typedCompile-time safety
Pointer-basedEfficient for nested structures

Schema Definition

Basic Types

ZAP provides these primitive types:

struct Example
# Integers
int8Val Int8
int16Val Int16
int32Val Int32
int64Val Int64
uint8Val UInt8
uint16Val UInt16
uint32Val UInt32
uint64Val UInt64

# Floating point
float32Val Float32
float64Val Float64

# Other primitives
boolVal Bool
textVal Text
dataVal Data

Structs

Define complex types with structs:

struct Person
name Text
age Int32
email Text
address Address

struct Address
street Text
city Text
country Text
zipCode Text

Lists

Lists can contain any type:

struct Order
items List(Item)
quantities List(Int32)
tags List(Text)

Enums

Define enumerated types:

enum Status
pending
active
completed
cancelled

struct Task
title Text
status Status

Unions

Use unions for variant types:

struct Result
union
  success Data
  error Text

struct Shape
union
  circle Circle
  rectangle Rectangle
  triangle Triangle

Interface Definitions

Basic Interface

interface Calculator
add (a Float64, b Float64) -> (result Float64)
subtract (a Float64, b Float64) -> (result Float64)
multiply (a Float64, b Float64) -> (result Float64)
divide (a Float64, b Float64) -> (result Float64)

Streaming Methods

Use stream keyword for streaming responses:

interface DataService
# Unary method
getData (id Text) -> (data Data)

# Server streaming
streamData (query Query) -> stream (chunk DataChunk)

# Client streaming
uploadData stream (chunk DataChunk) -> (status UploadStatus)

# Bidirectional streaming
processStream stream (input InputChunk) -> stream (output OutputChunk)

Nested Interfaces

Interfaces can return other interfaces:

interface Database
openTable (name Text) -> (table Table)

interface Table
get (key Data) -> (value Data)
put (key Data, value Data) -> ()
delete (key Data) -> ()
scan (prefix Data) -> stream (entry Entry)

Schema Evolution

Adding Fields

Add new fields with new ordinals:

# Version 1
struct User
name Text
email Text

# Version 2 (compatible)
struct User
name Text
email Text
phone Text       # New field
verified Bool    # New field

Default Values

Provide defaults for optional fields:

struct Config
timeout UInt32 = 30
retries UInt32 = 3
debugMode Bool = false

Deprecation

Mark fields as deprecated:

struct User
name Text
email Text
oldField Text $deprecated  # Don't use in new code

Wire Format

Message Structure

┌─────────────────────────────────────┐
│ Segment Table                       │
│ ┌─────────────────────────────────┐ │
│ │ Segment Count (4 bytes)         │ │
│ │ Segment 0 Size (4 bytes)        │ │
│ │ Segment 1 Size (4 bytes)        │ │
│ │ ...                             │ │
│ └─────────────────────────────────┘ │
├─────────────────────────────────────┤
│ Segment 0 Data                      │
├─────────────────────────────────────┤
│ Segment 1 Data                      │
├─────────────────────────────────────┤
│ ...                                 │
└─────────────────────────────────────┘

ZAP Header

ZAP adds a header before the message body:

┌───────────────────────────────────────────────────┐
│ ZAP Header (40 bytes)                             │
├─────────────┬─────────────┬───────────────────────┤
│ Magic       │ Version     │ Message ID            │
│ (8 bytes)   │ (4 bytes)   │ (8 bytes)             │
├─────────────┼─────────────┼───────────────────────┤
│ Method ID   │ Flags       │ Payload Len           │
│ (4 bytes)   │ (4 bytes)   │ (4 bytes)             │
├─────────────┴─────────────┴───────────────────────┤
│ Payload Pointer + Length (8 bytes)                │
└───────────────────────────────────────────────────┘

Compression

Enable compression for large messages:

server := zap.NewServer(
    zap.WithCompression(zap.CompressionZstd),
    zap.WithCompressionThreshold(1024), // Compress if > 1KB
)

Code Generation

Generate Code

# Generate all languages
zap generate schema.zap --out=./gen

# Generate specific language
zap generate schema.zap --lang=go --out=./gen/go
zap generate schema.zap --lang=rust --out=./gen/rust
zap generate schema.zap --lang=ts --out=./gen/ts

Generated Types

For this schema:

struct Person
name Text
age Int32

Go generates:

type Person struct {
    zap.Struct
}

func (p Person) Name() (string, error)
func (p Person) SetName(v string) error
func (p Person) Age() int32
func (p Person) SetAge(v int32)

Rust generates:

pub struct Person<'a> {
    reader: zap::StructReader<'a>,
}

impl<'a> Person<'a> {
    pub fn get_name(&self) -> zap::Result<&'a str>;
    pub fn get_age(&self) -> i32;
}

Best Practices

1. Use Meaningful Field Numbers

Field numbers are permanent. Plan for future expansion:

struct User
# Core fields: 0-9
id UInt64
name Text
email Text

# Profile fields: 10-19
avatar Data
bio Text

# Settings: 20-29
theme Text
language Text

2. Prefer Structs Over Lists of Primitives

# Avoid
struct Point
coords List(Float64)  # What do indices mean?

# Prefer
struct Point
x Float64
y Float64
z Float64

3. Use Groups for Logical Organization

struct Employee
name Text

contact group
  email Text
  phone Text

employment group
  title Text
  department Text
  startDate UInt64

4. Document Your Schema

# User represents a registered user in the system.
# Users are created via the Registration service and
# authenticated via the Auth service.
struct User
# Unique identifier, assigned by the system
id UInt64

# Display name, must be 1-100 characters
name Text

# Email address, must be unique across all users
email Text

Next Steps

On this page