Skip to content

Common Patterns

A collection of patterns for common tasks in mik-sdk handlers.

All examples assume these imports:

#[allow(warnings)]
mod bindings;
use bindings::exports::mik::core::handler::{self, Guest, Response};
use mik_sdk::prelude::*;
fn fetch_external_data(api_key: &str) -> Response {
let response = fetch!(GET "https://api.example.com/data",
headers: {
"Authorization": format!("Bearer {}", api_key),
"Accept": "application/json"
},
timeout: 5000
).send();
match response {
Ok(resp) if resp.is_success() => {
ok!({ "data": resp.json() })
}
Ok(resp) => {
error! {
status: 502,
title: "Upstream Error",
detail: format!("API returned {}", resp.status())
}
}
Err(_) => {
error! {
status: 503,
title: "Service Unavailable",
detail: "Could not reach external API"
}
}
}
}

Track failures and short-circuit when service is down:

fn call_with_fallback(primary_url: &str, fallback: &str) -> Response {
// Try primary
let primary = fetch!(GET primary_url, timeout: 2000).send();
if let Ok(resp) = primary {
if resp.is_success() {
return ok!({ "source": "primary", "data": resp.json() });
}
}
log::warn!("Primary service failed, trying fallback");
// Try fallback
let fallback = fetch!(GET fallback, timeout: 5000).send();
match fallback {
Ok(resp) if resp.is_success() => {
ok!({ "source": "fallback", "data": resp.json() })
}
_ => {
error! {
status: 503,
title: "Service Unavailable",
detail: "All services are unavailable"
}
}
}
}
fn transform_api_response(resp: &http_client::Response) -> Option<Response> {
let parsed = resp.json()?;
// Extract and transform fields
let items = parsed.get("results");
let total = parsed.path_int(&["meta", "total"]).unwrap_or(0);
Some(ok!({
"items": items,
"count": total
}))
}
fn aggregate_user_data(user_id: &str, req: &Request) -> Response {
// Parallel requests to different services
let trace_id = req.trace_id_or("");
let trace_opt = if trace_id.is_empty() { None } else { Some(trace_id) };
let profile = fetch!(GET format!("http://profiles:8080/users/{}", user_id))
.with_trace_id(trace_opt)
.send();
let orders = fetch!(GET format!("http://orders:8080/users/{}/orders", user_id))
.with_trace_id(trace_opt)
.send();
let profile_data = match profile {
Ok(r) if r.is_success() => r.json(),
_ => None,
};
let order_data = match orders {
Ok(r) if r.is_success() => r.json(),
_ => None,
};
ok!({
"profile": profile_data,
"orders": order_data
})
}
fn get_with_cache(key: &str, fetch_fn: impl Fn() -> Option<String>) -> Response {
// Check cache first
let cache_key = format!("http://cache:8080/{}", key);
let cached = fetch!(GET &cache_key).send();
if let Ok(resp) = cached {
if resp.is_success() {
log::debug!("Cache hit for {}", key);
return ok!({ "data": resp.json(), "cached": true });
}
}
// Cache miss - fetch data
log::debug!("Cache miss for {}", key);
let data = ensure!(fetch_fn(), 404, "Data not found");
// Store in cache (fire and forget)
let _ = fetch!(PUT &cache_key,
headers: { "Content-Type": "application/json" },
body: data.as_bytes()
).send();
ok!({ "data": data, "cached": false })
}
fn validate_request(req: &Request) -> Result<UserContext, Response> {
let auth = req.header_or("authorization", "");
let token = auth.strip_prefix("Bearer ")
.ok_or_else(|| error! {
status: 401,
title: "Unauthorized",
detail: "Missing Authorization header"
})?;
// Call auth service to validate token
let auth_response = fetch!(GET "http://auth:8080/validate",
headers: { "Authorization": format!("Bearer {}", token) }
).send();
match auth_response {
Ok(resp) if resp.is_success() => {
let parsed = resp.json().ok_or_else(|| error! {
status: 500,
title: "Internal Error",
detail: "Failed to parse auth response"
})?;
Ok(UserContext {
user_id: parsed.path_str(&["user_id"]).unwrap_or_default(),
role: parsed.path_str(&["role"]).unwrap_or_default(),
})
}
_ => Err(error! {
status: 401,
title: "Unauthorized",
detail: "Invalid token"
}),
}
}
fn with_auth<F>(req: &Request, handler: F) -> Response
where
F: FnOnce(UserContext, &Request) -> Response,
{
match validate_request(req) {
Ok(user) => handler(user, req),
Err(response) => response,
}
}
fn protected_endpoint(req: &Request) -> Response {
with_auth(req, |user, req| {
ok!({ "user_id": user.user_id })
})
}
fn handler_inner(req: &Request) -> Result<Response, HandlerError> {
let parsed = req.json().ok_or(HandlerError::InvalidJson)?;
let name = parsed.path_str(&["name"]).ok_or(HandlerError::MissingField("name"))?;
Ok(ok!({ "name": name }))
}
fn handler(req: &Request) -> Response {
match handler_inner(req) {
Ok(response) => response,
Err(HandlerError::MissingBody) => bad_request!("Request body required"),
Err(HandlerError::InvalidJson) => bad_request!("Invalid JSON"),
Err(HandlerError::MissingField(f)) => bad_request!(format!("{} is required", f)),
}
}
fn check_rate_limit(client_id: &str) -> bool {
let response = fetch!(POST "http://rate-limiter:8080/check",
json: { "client_id": client_id, "limit": 100 }
).send();
match response {
Ok(resp) => {
if let Some(parsed) = resp.json() {
parsed.path_bool(&["allowed"]).unwrap_or(false)
} else {
true // Allow on parse error
}
}
Err(_) => true, // Allow if rate limiter is down
}
}
fn rate_limited_endpoint(req: &Request) -> Response {
let client_id = req.header_or("x-client-id", "anonymous");
if !check_rate_limit(client_id) {
return error! {
status: 429,
title: "Too Many Requests",
detail: "Rate limit exceeded"
};
}
ok!({ "allowed": true })
}
fn handle_webhook(body: WebhookInput, req: &Request) -> Response {
// Verify webhook signature
let signature = req.header_or("x-webhook-signature", "");
guard!(!signature.is_empty(), 401, "Missing signature");
// Process based on event type
match body.event_type.as_str() {
"user.created" => handle_user_created(&body.data),
"user.deleted" => handle_user_deleted(&body.data),
"order.completed" => handle_order_completed(&body.data),
_ => {
log::warn!("Unknown webhook event: {}", body.event_type);
accepted!()
}
}
}
fn handle_user_created(data: &JsonValue) -> Response {
log!(info, "user created webhook", user_id: data.path_str(&["id"]).unwrap_or("unknown"));
accepted!()
}
routes! {
GET "/health" => health_check,
GET "/health/ready" => readiness_check,
GET "/health/live" => liveness_check,
}
fn health_check(_req: &Request) -> Response {
ok!({
"status": "healthy",
"timestamp": time::now_iso()
})
}
fn readiness_check(_req: &Request) -> Response {
// Check dependencies
let db_ok = check_database();
let cache_ok = check_cache();
if db_ok && cache_ok {
ok!({
"status": "ready",
"checks": {
"database": "ok",
"cache": "ok"
}
})
} else {
error! {
status: 503,
title: "Not Ready",
detail: "Some dependencies are unavailable"
}
}
}
fn liveness_check(_req: &Request) -> Response {
ok!({ "status": "alive" })
}
fn check_database() -> bool {
fetch!(GET "http://db-proxy:8080/ping", timeout: 1000)
.send()
.map(|r| r.is_success())
.unwrap_or(false)
}
fn check_cache() -> bool {
fetch!(GET "http://cache:8080/ping", timeout: 1000)
.send()
.map(|r| r.is_success())
.unwrap_or(false)
}