flatbed
FlatBuffers utilities and route decorator system for the flatbed framework.
Overview
flatbed provides two main features:
- FlatBuffer Utilities: Helper functions for working with FlatBuffers
- Route Decorator System: Declarative routing with compile-time validation
Network Architecture
flatbed is designed to run behind a proxy that terminates TLS — a reverse proxy, API gateway, or service-mesh sidecar (Envoy, nginx, Linkerd, …):
- No TLS support - the proxy handles TLS termination, mTLS, and external routing
- HTTP/1.1 and HTTP/2 cleartext - Both protocols auto-negotiated
- Traffic between the proxy and the service is plaintext, which keeps the framework small
Features
FlatBuffer Utilities
Helper functions for creating and verifying FlatBuffer messages:
use ;
use PingRequest;
// Create a builder
let mut builder = new_builder;
// Verify a buffer
let is_valid = ;
// Safely read a root with verification
let request = ?;
Route Decorator System
Declarative route registration with automatic handler wrapping and compile-time validation.
Basic Usage
use ;
use ;
async
HTTP Methods
By default, routes are POST. Use the method attribute for other HTTP methods:
// GET endpoint
async
// PUT endpoint
async
// POST endpoint (explicit)
async
Handler Signature
Handlers must follow this pattern:
- Async: Must be
async fn - Input:
Request<T>orRequest<T, C>whereTis the request body type andCis optional context - Output:
Result<Response<T>, FlatbedError>orResult<Response<T>, FlatbedError<D>>for typed error details
Request Type
Request<T, C> provides access to HTTP metadata:
Response Type
Response<T> provides builder methods for HTTP responses:
// 200 OK
Ok
// 201 Created
Ok
// Custom status
Ok
// With custom headers
Ok
Error Handling
FlatbedError provides structured error responses:
// Simple error
Err
// Error with code
Err
// Error with custom headers
Err
// Error with typed details
Err
Error Response Format:
For JSON requests:
For FlatBuffer requests, errors use headers:
Content-Type: application/x-flatbuffersX-Error-Code: VALIDATION_ERRORX-Error-Message: Validation failed- Body: FlatBuffer-serialized details (if present)
Context Support
Handlers can access application context (database connections, services, etc.):
async
Route Registry
Access all registered routes at runtime:
use ;
// Validate that all routes are unique
match validate_routes
// Get the route map
let routes = get_routes;
for in routes.iter
Server Integration
Use Flatbed::run() to start the HTTP server with a boot closure:
use ;
async
The boot closure receives a clone of the FlatbedConfig and returns the application
context. The framework handles all probe signaling automatically — callers never
touch health or readiness channels directly.
Boot Lifecycle
Flatbed::run() follows a strict sequence that ensures Kubernetes probes behave
correctly at every stage, and that workers never start before the application
context is available:
Flatbed::run(config, boot)
│
├─ 1. Validate routes (fail-fast on conflicts)
├─ 2. Bind TCP listener
├─ 3. Spawn HTTP server task
├─ 4. Set healthz = true ──► /healthz → 200 (pod is alive)
│ /readyz → 503 (not ready)
│ routes → 503 (blocked)
├─ 5. Run boot(config) ───────────── user init (connect DB, kube client, etc.)
│ └─ returns AppContext
├─ 6. Store context
├─ 7. Set ready = true ──► /readyz → 200 (accepting traffic)
│ routes → 200 (serving)
└─ 8. Spawn workers ──► workers have guaranteed access to context
Why this order matters:
- healthz before boot — Kubernetes won't kill the pod during slow initialization (e.g., waiting for a database connection). The pod is alive, just not ready yet.
- Context stored before ready — Workers are spawned only after the ready signal. By storing context first, workers are guaranteed to find it when they start. This eliminates the race condition where a worker could wake up and find no context.
- Workers after ready — Background tasks (reconcilers, heartbeats) only run once the server is fully initialized.
Worker failure handling: If any worker returns an error after startup, the
framework sets healthz = false and initiates graceful shutdown. Kubernetes will
see the failing liveness probe and restart the pod.
With Telemetry
use ;
use Arc;
async
Legacy: #[flatbed::main] Macro (Deprecated)
Note: The
#[flatbed::main]macro is deprecated. UseFlatbed::run()instead for more flexibility.
use main;
async
The Flatbed::run() API provides:
- Pre-server setup flexibility (telemetry, logging, diagnostics)
- Content-type negotiation (JSON and FlatBuffer)
- X-Request-ID propagation (or auto-generation)
- Graceful shutdown on SIGTERM/SIGINT
- HTTP/1.1 and HTTP/2 cleartext support (auto-negotiated)
- Automatic route validation at startup
Built-in Endpoints
When features are enabled, the server provides built-in endpoints:
Telemetry (telemetry feature):
GET /healthz- Liveness probe (returns "OK")GET /readyz- Readiness probe (returns "Ready")GET /metrics- Prometheus metrics
OpenAPI (openapi feature):
GET /openapi.json- OpenAPI specification (latest version)GET /v{version}/openapi.json- Versioned OpenAPI specification
How It Works
The route decorator system uses procedural macros and the inventory crate to collect route registrations across your codebase at compile time.
When you write:
async
The macro generates:
- Wrapper function - Extracts HTTP metadata, deserializes body, calls your handler, serializes response
- Route registration - Submits route info to the global registry via
inventory
The registry is built at compile time and available via get_routes() at runtime.
Note: Routes are collected per-binary. Test binaries, examples, and your main application each have their own independent route registry.
Static Files and Raw Responses
static_route! serves a directory of static files (a built SPA, say)
alongside the JSON API, from the same origin. It registers through the same
inventory mechanism as #[route]:
// Declared #[route]s win; unmatched GETs are served from dist/, and unknown
// non-API paths fall back to index.html for client-side routing.
static_route!;
Files are read from the container filesystem at request time (ship the
directory in the image, e.g. COPY dist/ /app/dist). The Content-Type is
derived from the file extension; Cache-Control is no-cache for HTML and
other stable-name files (json, txt, ico, xml, webmanifest), and
public, max-age=31536000, immutable for content-hashed assets. A missing path
with an extension returns 404 rather than the HTML shell; an extensionless
miss serves the fallback.
Static bytes can't go through the typed serialization path, so they use
Response::raw, which is also available to handlers directly:
async
Response::raw(bytes, content_type) returns Response<()> and emits the bytes
verbatim under the given Content-Type, bypassing JSON/FlatBuffer
serialization.
Compile-Time Validation
Route conflicts are detected via validate_routes():
// This will return an error if two routes have the same path
// but different request/response types
match validate_routes
Always call validate_routes() at startup to catch configuration errors early. Flatbed::run() does this automatically.
Examples
See examples/routes.rs for route registration examples:
Testing
Run the test suite:
Run with all features:
Architecture
- flatbed (this crate): FlatBuffer utilities + hyper server + route registry
- flatbed_macros: Procedural macro implementation for
#[route]and#[flatbed::main] - flatbed_build: Build-time code generation for FlatBuffer schemas. See its crate docs for the supported
.fbsfield types (scalars, strings, single nested tables, and vectors of any of those — including vectors of tables).
The macro crate is separate because Rust requires proc-macro crates to only export procedural macros.
Dependencies
flatbuffers- FlatBuffer runtimeinventory- Distributed plugin registrationhttp- HTTP types (Method, StatusCode, HeaderMap)hyper/hyper-util- HTTP server (no TLS - the fronting proxy handles it)tokio- Async runtimeserde/serde_json- JSON serializationflatbed_macros- Route decorator and main macros
License
Licensed under either of Apache License, Version 2.0 or MIT license at your option. See the repository root for details.