use bytes::Bytes;
use http_body_util::Full;
use hyper::{Method, Request, Response, StatusCode};
use hyperlite::{
parse_json_body, path_param, query_params, serve, success, BoxBody, BoxError, Router,
};
use serde::{Deserialize, Serialize};
use std::net::SocketAddr;
use std::sync::{Arc, Mutex};
use uuid::Uuid;
#[derive(Clone)]
struct AppState {
counter: Arc<Mutex<u64>>,
app_name: String,
}
impl AppState {
fn new(name: impl Into<String>) -> Self {
Self {
counter: Arc::new(Mutex::new(0)),
app_name: name.into(),
}
}
}
#[derive(Clone, Serialize)]
struct User {
id: Uuid,
name: String,
email: String,
}
#[derive(Deserialize)]
struct CreateUserRequest {
name: String,
email: String,
}
#[derive(Default, Deserialize)]
struct ListUsersQuery {
#[serde(default)]
limit: Option<u32>,
#[serde(default)]
offset: Option<u32>,
}
#[derive(Serialize)]
struct StatsResponse {
app: String,
total_users_created: u64,
}
async fn get_stats_handler(
_req: Request<BoxBody>,
state: Arc<AppState>,
) -> Result<Response<Full<Bytes>>, BoxError> {
let total_users = *state.counter.lock().expect("counter poisoned");
let payload = StatsResponse {
app: state.app_name.clone(),
total_users_created: total_users,
};
Ok(success(StatusCode::OK, payload))
}
async fn create_user_handler(
req: Request<BoxBody>,
state: Arc<AppState>,
) -> Result<Response<Full<Bytes>>, BoxError> {
let body = parse_json_body::<CreateUserRequest>(req).await?;
let mut counter = state.counter.lock().expect("counter poisoned");
*counter += 1;
let user = User {
id: Uuid::new_v4(),
name: body.name,
email: body.email,
};
Ok(success(StatusCode::CREATED, user))
}
async fn list_users_handler(
req: Request<BoxBody>,
_state: Arc<AppState>,
) -> Result<Response<Full<Bytes>>, BoxError> {
let params = query_params::<ListUsersQuery>(&req)?;
let mut users = vec![
User {
id: Uuid::new_v4(),
name: "Alice".into(),
email: "alice@example.com".into(),
},
User {
id: Uuid::new_v4(),
name: "Bob".into(),
email: "bob@example.com".into(),
},
User {
id: Uuid::new_v4(),
name: "Charlie".into(),
email: "charlie@example.com".into(),
},
];
if let Some(offset) = params.offset {
let skip = offset.min(users.len() as u32) as usize;
users = users.into_iter().skip(skip).collect();
}
if let Some(limit) = params.limit {
let take = limit.min(users.len() as u32) as usize;
users.truncate(take);
}
Ok(success(StatusCode::OK, users))
}
async fn get_user_handler(
req: Request<BoxBody>,
_state: Arc<AppState>,
) -> Result<Response<Full<Bytes>>, BoxError> {
let user_id: Uuid = path_param(&req, "id")?;
let user = User {
id: user_id,
name: "Requested User".into(),
email: "requested@example.com".into(),
};
Ok(success(StatusCode::OK, user))
}
#[tokio::main]
async fn main() -> Result<(), BoxError> {
let state = AppState::new("Hyperlite Demo");
let router = Router::new(state.clone())
.route(
"/stats",
Method::GET,
Arc::new(|req, state| Box::pin(get_stats_handler(req, state))),
)
.route(
"/users",
Method::POST,
Arc::new(|req, state| Box::pin(create_user_handler(req, state))),
)
.route(
"/users",
Method::GET,
Arc::new(|req, state| Box::pin(list_users_handler(req, state))),
)
.route(
"/users/{id}",
Method::GET,
Arc::new(|req, state| Box::pin(get_user_handler(req, state))),
);
let addr: SocketAddr = "127.0.0.1:3000"
.parse()
.expect("valid socket address for example server");
println!(
"Stateful example available at http://{addr}\n • GET /stats\n • POST /users\n • GET /users\n • GET /users/:id"
);
serve(addr, router).await
}