Skip to content

Types

mik-sdk uses derive macros to define type-safe inputs (path, query, body) and outputs. These types are automatically parsed, validated, and documented in the OpenAPI schema.

MacroPurposeJSON Serialization
#[derive(Type)]Request/response bodyYes
#[derive(Query)]Query string parametersNo
#[derive(Path)]URL path parametersNo

Use #[derive(Type)] for JSON request bodies and response types:

#[derive(Type)]
pub struct CreateUserInput {
pub name: String,
pub email: String,
pub age: Option<i32>,
}
#[derive(Type)]
pub struct User {
pub id: String,
pub name: String,
pub email: String,
}
routes! {
POST "/users" => create_user(body: CreateUserInput) -> User,
}
fn create_user(body: CreateUserInput, _req: &Request) -> Response {
let id = random::uuid();
ok!({
"id": id,
"name": body.name,
"email": body.email
})
}
TypeJSON TypeExample
Stringstring"hello"
i32, i64number42
u32, u64number42
f32, f64number3.14
boolbooleantrue
Option<T>T or nullnull or value
Vec<T>array[1, 2, 3]
Nested Typeobject{ "name": "Alice" }
Enum (see below)string"active"

Add constraints and metadata with #[field(...)]:

#[derive(Type)]
pub struct CreatePostInput {
// Length constraints
#[field(min = 1, max = 200)]
pub title: String,
// Rename JSON field
#[field(rename = "bodyContent")]
pub body: String,
// Format hint (for OpenAPI)
#[field(format = "email")]
pub author_email: String,
// Pattern validation (for OpenAPI)
#[field(pattern = "^[a-z0-9-]+$")]
pub slug: String,
// Documentation
#[field(docs = "Tags for categorization")]
pub tags: Vec<String>,
}
AttributeApplies ToDescription
minString, Vec, numbersMinimum length/value/items
maxString, Vec, numbersMaximum length/value/items
formatStringOpenAPI format hint
patternStringRegex pattern
renameAnyJSON field name
docsAnyOpenAPI description

Use #[derive(Query)] for URL query parameters:

#[derive(Query)]
pub struct SearchQuery {
// Optional parameter
pub q: Option<String>,
// With default value
#[field(default = 1)]
pub page: u32,
// With default and max constraint
#[field(default = 20, max = 100)]
pub limit: u32,
}
routes! {
GET "/users" => list_users(query: SearchQuery),
}
fn list_users(query: SearchQuery, _req: &Request) -> Response {
ok!({
"search": query.q,
"page": query.page,
"limit": query.limit
})
}

Request: GET /users?q=alice&page=2&limit=50

AttributeDescription
defaultDefault value if parameter is missing
maxMaximum allowed value
minMinimum allowed value

Use #[derive(Path)] for URL path parameters:

#[derive(Path)]
pub struct UserPath {
pub id: String, // Matches {id} in route
}
#[derive(Path)]
pub struct OrgUserPath {
pub org_id: String, // Matches {org_id}
pub user_id: String, // Matches {user_id}
}
routes! {
GET "/users/{id}" => get_user(path: UserPath),
GET "/orgs/{org_id}/users/{user_id}" => get_org_user(path: OrgUserPath),
}
fn get_user(path: UserPath, _req: &Request) -> Response {
ok!({ "id": path.id })
}
fn get_org_user(path: OrgUserPath, _req: &Request) -> Response {
ok!({
"org_id": path.org_id,
"user_id": path.user_id
})
}

Use #[derive(Type)] on enums to serialize them as JSON strings. Only unit variants (no fields) are supported.

#[derive(Type)]
pub enum Status {
Active, // serializes as "active"
Inactive, // serializes as "inactive"
Pending, // serializes as "pending"
}
#[derive(Type)]
pub enum Priority {
#[field(rename = "HIGH")]
High,
#[field(rename = "MEDIUM")]
Medium,
#[field(rename = "LOW")]
Low,
}
  • Variants are converted from PascalCase to snake_case by default (SuperAdmin"super_admin")
  • Use #[field(rename = "...")] on variants to customize the JSON string
  • Invalid values return a helpful error listing all valid options
  • OpenAPI schema is generated as { "type": "string", "enum": ["active", "inactive", "pending"] }

Enums can be used as fields in other types:

#[derive(Type)]
pub struct Task {
pub id: String,
pub title: String,
pub status: Status,
pub priority: Priority,
}
#[derive(Type)]
pub struct CreateTaskInput {
pub title: String,
pub status: Option<Status>, // Optional enum field
pub priority: Priority,
}
routes! {
POST "/tasks" => create_task(body: CreateTaskInput) -> Task,
}
fn create_task(body: CreateTaskInput, _req: &Request) -> Response {
ok!({
"id": random::uuid(),
"title": body.title,
"status": body.status.unwrap_or(Status::Pending),
"priority": body.priority
})
}

Example request:

{
"title": "Review PR",
"status": "active",
"priority": "HIGH"
}

Example response:

{
"id": "550e8400-e29b-41d4-a716-446655440000",
"title": "Review PR",
"status": "active",
"priority": "HIGH"
}

Handlers can receive multiple typed inputs:

#[derive(Path)]
pub struct TaskPath {
pub id: String,
}
#[derive(Query)]
pub struct TaskQuery {
#[field(default = false)]
pub include_history: bool,
}
#[derive(Type)]
pub struct UpdateTaskInput {
pub title: Option<String>,
pub status: Option<Status>,
}
routes! {
GET "/tasks/{id}" => get_task(path: TaskPath, query: TaskQuery),
PUT "/tasks/{id}" => update_task(path: TaskPath, body: UpdateTaskInput),
}
fn get_task(path: TaskPath, query: TaskQuery, _req: &Request) -> Response {
ok!({
"id": path.id,
"include_history": query.include_history
})
}
fn update_task(path: TaskPath, body: UpdateTaskInput, _req: &Request) -> Response {
ok!({
"id": path.id,
"updated_title": body.title,
"updated_status": body.status
})
}

When parsing or validation fails, mik-sdk returns an RFC 7807 Problem Details response:

{
"type": "urn:problem:validation",
"title": "Validation Error",
"status": 400,
"detail": "Invalid request body",
"errors": [
{ "field": "name", "message": "field is required" },
{ "field": "email", "message": "invalid format" }
]
}

For enums, invalid values produce a helpful error:

{
"type": "urn:problem:validation",
"title": "Validation Error",
"status": 400,
"detail": "Invalid request body",
"errors": [
{
"field": "status",
"message": "unknown enum variant \"invalid\". Valid values: \"active\", \"inactive\", \"pending\""
}
]
}