# 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

| Request Format | Response Format | Condition |
|---------------|-----------------|-----------|
| 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?

| Factor | JSON | 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
