mik-sdk 0.1.2

Ergonomic macros for WASI HTTP handlers - ok!, error!, json!
Documentation
# mik-sdk

[![Crates.io](https://img.shields.io/crates/v/mik-sdk.svg)](https://crates.io/crates/mik-sdk)
[![Documentation](https://docs.rs/mik-sdk/badge.svg)](https://docs.rs/mik-sdk)
[![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE-MIT)

Ergonomic SDK for building WASI HTTP handlers with pure Rust.

> **v0.1.x** - Published and usable, but evolving. The API may change between minor versions.

## Features

- **Type-Safe Routing** - `routes!` macro with path, query, and body extraction
- **Derive Macros** - `#[derive(Type)]`, `#[derive(Query)]`, `#[derive(Path)]`
- **Response Helpers** - `ok!`, `error!` with RFC 7807 support
- **SQL Builder** - `sql_read!`, `sql_create!` with cursor pagination
- **Minimal** - ~200KB composed component size

## Quick Start

```rust
use mik_sdk::prelude::*;

// Define typed inputs with derive macros
#[derive(Type)]
pub struct HelloResponse {
    pub greeting: String,
    pub name: String,
}

#[derive(Path)]
pub struct HelloPath {
    pub name: String,
}

#[derive(Query)]
pub struct SearchQuery {
    pub q: Option<String>,
    #[field(default = 1)]
    pub page: u32,
    #[field(default = 10, max = 100)]
    pub limit: u32,
}

// Define routes with typed inputs
routes! {
    GET "/" => home,
    GET "/hello/{name}" => hello(path: HelloPath) -> HelloResponse,
    GET "/search" => search(query: SearchQuery),
}

fn home(_req: &Request) -> Response {
    ok!({ "message": "Welcome!" })
}

fn hello(path: HelloPath, _req: &Request) -> Response {
    ok!({
        "greeting": format!("Hello, {}!", path.name),
        "name": path.name
    })
}

fn search(query: SearchQuery, _req: &Request) -> Response {
    ok!({
        "query": query.q,
        "page": query.page,
        "limit": query.limit
    })
}
```

## Core Macros

### Response Macros

```rust
ok!({ "data": value })                    // 200 OK with JSON
error! { status: 404, title: "Not Found" } // RFC 7807 error
created!("/users/123", { "id": "123" })   // 201 Created with Location
no_content!()                              // 204 No Content
```

### Routing

```rust
routes! {
    GET "/" => home,
    GET "/users/{id}" => get_user(path: Id) -> User,
    POST "/users" => create_user(body: CreateInput) -> User,
    GET "/search" => search(query: SearchQuery),
}
```

### DX Macros

```rust
guard!(!name.is_empty(), 400, "Name required");      // Early return validation
let user = ensure!(find_user(id), 404, "Not found"); // Unwrap or return error
```

For JSON body parsing, use typed inputs with `#[derive(Type)]` - the body is parsed automatically in the route handler.

### SQL Builder

```rust
let (sql, params) = sql_read!(users {
    select: [id, name, email],
    filter: { active: true },
    order: [-created_at, id],
    after: cursor,
    limit: 20,
});
```

### HTTP Client

```rust
// Simple request
let resp = fetch!(GET "https://api.example.com/users").send()?;

// POST with JSON body
let resp = fetch!(POST "https://api.example.com/users", json: {
    "name": "Alice"
}).send()?;

// SSRF protection for user-provided URLs
let resp = fetch!(GET &user_url)
    .deny_private_ips()  // Blocks localhost, 10.x, 192.168.x, etc.
    .send()?;
```

## Request Helpers

```rust
req.param("id")          // Path parameter: Option<&str>
req.query("page")        // Query parameter: Option<&str>
req.header("auth")       // Header (case-insensitive): Option<&str>
req.body()               // Raw body: Option<&[u8]>
req.text()               // Body as UTF-8: Option<&str>
req.is_json()            // Content-Type is JSON: bool
req.is_html()            // Content-Type is HTML: bool
req.is_form()            // Content-Type is form: bool
req.accepts("json")      // Accept header check: bool
```

## Type Inference

Variables work directly in `ok!` and `json!` macros via the `ToJson` trait:

```rust
ok!({
    "name": name,       // String → JSON string
    "age": age,         // i32 → JSON integer
    "score": score,     // Option<f64> → JSON number or null
    "tags": tags        // Vec<&str> → JSON array
})
```

Type hints available for explicit control: `str()`, `int()`, `float()`, `bool()`

## API Reference

### Modules

| Module        | Purpose                        |
| ------------- | ------------------------------ |
| `json`        | JSON building and lazy parsing |
| `time`        | UTC timestamps and ISO 8601    |
| `random`      | UUIDs, tokens, random bytes    |
| `log`         | Structured logging to stderr   |
| `env`         | Environment variable access    |
| `http_client` | Outbound HTTP requests         |
| `status`      | HTTP status code constants     |

### Response Macros

| Macro                    | Status | Description          |
| ------------------------ | ------ | -------------------- |
| `ok!({ ... })`           | 200    | JSON response        |
| `created!(loc, { ... })` | 201    | With Location header |
| `accepted!()`            | 202    | Accepted             |
| `no_content!()`          | 204    | No Content           |
| `redirect!(url)`         | 302    | Redirect             |
| `bad_request!(msg)`      | 400    | Bad Request          |
| `forbidden!(msg)`        | 403    | Forbidden            |
| `not_found!(msg)`        | 404    | Not Found            |
| `conflict!(msg)`         | 409    | Conflict             |
| `error! { ... }`         | any    | RFC 7807             |

### DX Macros

| Macro                        | Purpose                  |
| ---------------------------- | ------------------------ |
| `guard!(cond, status, msg)`  | Early return if false    |
| `ensure!(expr, status, msg)` | Unwrap or return error   |
| `fetch!(METHOD url, ...)`    | HTTP client request      |
| `ids!(collection)`           | Extract IDs for batching |

### SQL Macros

| Macro                        | Purpose |
| ---------------------------- | ------- |
| `sql_read!(table { ... })`   | SELECT  |
| `sql_create!(table { ... })` | INSERT  |
| `sql_update!(table { ... })` | UPDATE  |
| `sql_delete!(table { ... })` | DELETE  |

### time Module

| Function             | Returns                   |
| -------------------- | ------------------------- |
| `time::now()`        | `u64` - Unix seconds      |
| `time::now_millis()` | `u64` - Unix milliseconds |
| `time::now_iso()`    | `String` - ISO 8601       |

### random Module

| Function           | Returns                    |
| ------------------ | -------------------------- |
| `random::uuid()`   | `String` - UUID v4         |
| `random::hex(n)`   | `String` - n bytes as hex  |
| `random::bytes(n)` | `Vec<u8>` - n random bytes |
| `random::u64()`    | `u64` - Random integer     |

### Request Methods

| Method              | Returns             |
| ------------------- | ------------------- |
| `param(name)`       | `Option<&str>`      |
| `query(name)`       | `Option<&str>`      |
| `query_all(name)`   | `&[String]`         |
| `header(name)`      | `Option<&str>`      |
| `header_all(name)`  | `Vec<&str>`         |
| `trace_id()`        | `Option<&str>`      |
| `body()`            | `Option<&[u8]>`     |
| `text()`            | `Option<&str>`      |
| `json()`            | `Option<JsonValue>` |
| `json_with(parser)` | `Option<T>`         |
| `form(name)`        | `Option<&str>`      |
| `form_all(name)`    | `&[String]`         |
| `is_json()`         | `bool`              |
| `is_form()`         | `bool`              |
| `is_html()`         | `bool`              |
| `accepts(mime)`     | `bool`              |
| `has_body()`        | `bool`              |
| `content_type()`    | `Option<&str>`      |

### Logging

```rust
// Format-string style
log::info!("User {} logged in", id);
log::warn!("Cache miss: {}", key);
log::error!("Failed: {}", err);
log::debug!("Debug: {:?}", data);  // Compiled out in release

// Structured style (JSON output)
log!(info, "user created", id: user_id, email: &email);
```

## Feature Flags

```toml
[dependencies]
mik-sdk = "0.1"  # Includes sql + http-client by default

# Minimal build
mik-sdk = { version = "0.1", default-features = false }
```

| Feature       | Default | Description                |
| ------------- | ------- | -------------------------- |
| `sql`         | Yes     | SQL query builder macros   |
| `http-client` | Yes     | HTTP client with `.send()` |

## Configuration

Environment variables for runtime limits:

| Variable            | Default | Description                         |
| ------------------- | ------- | ----------------------------------- |
| `MIK_MAX_JSON_SIZE` | 1 MB    | Maximum JSON input size for parsing |
| `MIK_MAX_BODY_SIZE` | 10 MB   | Maximum request body size (bridge)  |

## Requirements

- Rust 1.89+ (Edition 2024)
- Target: `wasm32-wasip2`
- Build tool: `cargo-component`

## License

Licensed under MIT license. See [LICENSE-MIT](LICENSE-MIT).