# mik-sdk
[](https://crates.io/crates/mik-sdk)
[](https://docs.rs/mik-sdk)
[](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
| `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
| `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
| `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
| `sql_read!(table { ... })` | SELECT |
| `sql_create!(table { ... })` | INSERT |
| `sql_update!(table { ... })` | UPDATE |
| `sql_delete!(table { ... })` | DELETE |
### time Module
| `time::now()` | `u64` - Unix seconds |
| `time::now_millis()` | `u64` - Unix milliseconds |
| `time::now_iso()` | `String` - ISO 8601 |
### random Module
| `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
| `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 }
```
| `sql` | Yes | SQL query builder macros |
| `http-client` | Yes | HTTP client with `.send()` |
## Configuration
Environment variables for runtime limits:
| `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).