# AT-Jet
High-performance HTTP + Protobuf API framework for mobile services.
> **Not just a wrapper** - AT-Jet is an opinionated, production-ready foundation for mobile API development. See [Architecture Rationale](docs/ARCHITECTURE.md) for why this project exists.
## Why AT-Jet?
Building mobile APIs with Protobuf over HTTP requires solving the same problems repeatedly:
- Content-type negotiation
- Request body size limits
- Consistent error handling
- Client library generation
- Team coding conventions
**Without AT-Jet** (30+ lines per handler):
```rust
async fn create_user(headers: HeaderMap, body: Bytes) -> impl IntoResponse {
// Check content-type... validate size... decode proto... handle errors...
}
```
**With AT-Jet** (5 lines):
```rust
async fn create_user(ProtobufRequest(req): ProtobufRequest<CreateUserRequest>) -> ProtobufResponse<User> {
ProtobufResponse::ok(User { id: 1, name: req.name })
}
```
## Features
- **HTTP/1.1 and HTTP/2 support** via axum
- **Protobuf request/response handling** with automatic content negotiation
- **Dual-format support** - Protobuf for production, JSON for debugging (with key authorization)
- **CDN-friendly** design for global mobile users
- **Middleware support** for authentication, logging, compression
- **Prometheus metrics** (optional `metrics` feature) - HTTP request instrumentation and scrape endpoint
- **Smart tracing** - automatic log-level filtering for health/metrics endpoints
- **Tracing initialization** - unified setup with optional Jaeger/OpenTelemetry (`tracing-otel` feature)
- **JWT authentication** (optional `jwt` feature) - HMAC-based JWT middleware with Bearer token extraction
- **Session management** (optional `session` feature) - in-memory session store with TTL and idle timeout
- **Graceful shutdown** - `serve_with_shutdown()` for clean Ctrl+C handling
- **Startup banner** - pre-tracing service info output for k8s environments
- **Type-safe routing** with compile-time guarantees
- **Efficient bandwidth** - Protobuf is 60-70% smaller than JSON
- **Schema evolution** - Protobuf handles backward/forward compatibility
## Quick Start
Add to your `Cargo.toml`:
```toml
[dependencies]
at-jet = "0.6"
tokio = { version = "1", features = ["full"] }
prost = "0.13"
[build-dependencies]
prost-build = "0.13"
```
> See [Quick Start Guide](docs/QUICK_START.md) for a complete 5-minute tutorial.
### Server Example
```rust
use at_jet::prelude::*;
#[tokio::main]
async fn main() -> anyhow::Result<()> {
let server = JetServer::new()
.route("/api/users", get(list_users).post(create_user))
.route("/api/users/:id", get(get_user))
.with_cors()
.with_compression()
.with_tracing(); // Smart tracing (health/metrics at TRACE level)
server.serve("0.0.0.0:8080").await?;
Ok(())
}
async fn get_user(Path(id): Path<i32>) -> ProtobufResponse<User> {
let user = User { id, name: "John".to_string() };
ProtobufResponse::ok(user)
}
async fn list_users() -> ProtobufResponse<ListUsersResponse> {
// Return list of users
ProtobufResponse::ok(response)
}
async fn create_user(
ProtobufRequest(req): ProtobufRequest<CreateUserRequest>
) -> ProtobufResponse<User> {
// Create user from request
ProtobufResponse::created(user)
}
```
### Client Example
```rust
use at_jet::prelude::*;
#[tokio::main]
async fn main() -> anyhow::Result<()> {
// Protobuf client (production)
let client = JetClient::new("https://api.example.com")?;
// GET request
let user: User = client.get("/api/users/123").await?;
// POST request
let request = CreateUserRequest { name: "John".to_string() };
let created: User = client.post("/api/users", &request).await?;
// JSON debug client (for development)
let debug_client = JetClient::builder()
.base_url("https://api.example.com")
.debug_key("dev-debug-key")
.build()?;
// JSON requests for debugging
let user_json: UserJson = debug_client.get_json("/api/users/123").await?;
let raw_json = debug_client.get_json_raw("/api/users").await?;
println!("Response: {}", raw_json);
Ok(())
}
```
## Dual-Format Support (Protobuf + JSON)
AT-Jet supports both Protobuf (production) and JSON (debugging) formats. JSON requires authorization via debug keys to prevent accidental use in production.
### Why Require Authorization for JSON?
Protobuf provides schema evolution guarantees that JSON lacks:
- **Field numbers** enable backward/forward compatibility
- **Unknown fields** are preserved during deserialization
- **Optional fields** have well-defined defaults
If clients accidentally use JSON in production, they lose these guarantees and may break when the schema evolves. The debug key requirement ensures JSON is only used intentionally.
### Configuring Debug Keys
```rust
use at_jet::prelude::*;
#[tokio::main]
async fn main() -> anyhow::Result<()> {
// Configure authorized debug keys (call once at startup)
configure_debug_keys(vec![
"alice-dev-key".to_string(),
"bob-dev-key".to_string(),
"qa-team-key".to_string(),
]);
// Or disable JSON completely (production default)
// configure_debug_keys(vec![]);
let server = JetServer::new()
.route("/api/users", post(create_user))
.with_cors();
server.serve("0.0.0.0:8080").await?;
Ok(())
}
```
### Using Dual-Format Handlers
```rust
use at_jet::prelude::*;
// Dual-format handler - accepts both Protobuf and JSON
async fn create_user(
ApiRequest { body, format }: ApiRequest<CreateUserRequest>
) -> ApiResponse<User> {
let user = User { id: 1, name: body.name };
ApiResponse::ok(format, user) // Response format matches Accept header
}
```
### Client Usage
```bash
# Protobuf request (production - no debug key needed)
curl -X POST http://localhost:8080/api/users \
-H "Content-Type: application/x-protobuf" \
-H "Accept: application/x-protobuf" \
--data-binary @request.pb
# JSON request (debugging - requires valid debug key)
curl -X POST http://localhost:8080/api/users \
-H "Content-Type: application/json" \
-H "Accept: application/json" \
-H "X-Debug-Format: alice-dev-key" \
-d '{"name": "John"}'
# JSON request without debug key → rejected with 415 Unsupported Media Type
curl -X POST http://localhost:8080/api/users \
-H "Content-Type: application/json" \
-d '{"name": "John"}'
```
### Format Selection Rules
| Protobuf | Protobuf | Default (production) |
| JSON | JSON | Valid `X-Debug-Format` header |
| JSON | Error 415 | Missing/invalid debug key |
| Protobuf | JSON | Accept: application/json + valid debug key |
## Architecture
```
Mobile Clients (iOS, Android, Web)
│
▼
CDN (Global)
│
▼
┌─────────────┐
│ AT-Jet │ ◄── HTTP + Protobuf
│ Server │
└─────────────┘
│
▼
┌─────────────┐
│ Backend │ ◄── ZUS-RS (internal RPC)
│ Services │
└─────────────┘
```
## Why HTTP + Protobuf?
| **Size** | 100% | 30-40% (60-70% smaller) |
| **Parse Speed** | 100% | 10-20% (5-10x faster) |
| **Schema Evolution** | Manual | Built-in |
| **Type Safety** | Runtime | Compile-time |
## Running Examples
```bash
# Start server
cargo run --example basic_server
# In another terminal, run client
cargo run --example basic_client
```
## Optional: Prometheus Metrics
Enable the `metrics` feature for built-in Prometheus instrumentation:
```toml
[dependencies]
at-jet = { version = "0.6", features = ["metrics"] }
```
```rust
use at_jet::prelude::*;
use std::sync::Arc;
let guard = init_metrics(&MetricsConfig::default()).unwrap();
let server = JetServer::new()
.route("/health", get(health_check))
.route("/api/users", get(list_users))
.with_metrics(Arc::new(guard), "/metrics") // Scrape endpoint + HTTP metrics
.with_tracing();
```
This adds:
- `http_requests_total` counter (method, endpoint, status)
- `http_request_duration_seconds` histogram (method, endpoint)
- `http_active_requests` gauge (endpoint)
- Prometheus scrape endpoint at the configured path
## Optional: Tracing Initialization
Initialize tracing with optional Jaeger support:
```toml
[dependencies]
at-jet = { version = "0.6", features = ["tracing-otel"] }
```
```rust
use at_jet::prelude::*;
let config = TracingConfig {
level: "info".to_string(),
format: "json".to_string(),
jaeger_enabled: true,
jaeger_endpoint: "http://jaeger:14268/api/traces".to_string(),
service_name: "my-service".to_string(),
..Default::default()
};
let _guard = init_tracing(&config); // Keep guard alive!
```
`TracingConfig` and `init_tracing()` are always available. Jaeger support requires the `tracing-otel` feature.
## Optional: JWT Authentication
Enable JWT middleware for protected routes:
```toml
[dependencies]
at-jet = { version = "0.6", features = ["jwt"] }
```
```rust
use at_jet::prelude::*;
let config = JwtConfig {
secret: "base64-encoded-secret".to_string(),
..Default::default()
};
let server = JetServer::new()
.route("/api/protected", get(handler))
.layer(JwtAuthLayer::new(&config));
```
## Optional: Session Management
In-memory session store with TTL and idle timeout:
```toml
[dependencies]
at-jet = { version = "0.6", features = ["session"] }
```
```rust
use at_jet::prelude::*;
use std::collections::HashMap;
let store = SessionStore::new(28800, 900); // 8h TTL, 15min idle
let token = store.create("user@example.com".into(), HashMap::new()).await;
```
## Documentation
- [Quick Start](docs/QUICK_START.md) - Get running in 5 minutes
- [User Guide](docs/USER_GUIDE.md) - Complete feature documentation
- [Architecture Rationale](docs/ARCHITECTURE.md) - Why AT-Jet exists and design decisions
- [API Docs](https://docs.rs/at-jet) - Generated API documentation
- [CLAUDE.md](CLAUDE.md) - Development guidance and coding conventions
## License
MIT OR Apache-2.0