#![allow(clippy::all)]
#![allow(dead_code)]
use armature::prelude::*;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::sync::{Arc, RwLock};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct User {
pub id: u64,
pub name: String,
pub email: String,
#[serde(default)]
pub active: bool,
}
#[derive(Debug, Deserialize)]
pub struct CreateUserRequest {
pub name: String,
pub email: String,
}
#[derive(Debug, Deserialize)]
pub struct UpdateUserRequest {
pub name: Option<String>,
pub email: Option<String>,
pub active: Option<bool>,
}
#[derive(Debug, Serialize)]
pub struct PagedResponse<T> {
pub data: Vec<T>,
pub total: usize,
pub page: usize,
pub per_page: usize,
}
#[derive(Debug, Clone, Default)]
pub struct UserRepository {
users: Arc<RwLock<HashMap<u64, User>>>,
next_id: Arc<RwLock<u64>>,
}
impl UserRepository {
pub fn new() -> Self {
Self::default()
}
pub fn with_seed_data() -> Self {
let repo = Self::new();
let users = vec![
User {
id: 1,
name: "Alice".to_string(),
email: "alice@example.com".to_string(),
active: true,
},
User {
id: 2,
name: "Bob".to_string(),
email: "bob@example.com".to_string(),
active: true,
},
User {
id: 3,
name: "Charlie".to_string(),
email: "charlie@example.com".to_string(),
active: false,
},
];
{
let mut store = repo.users.write().unwrap();
for user in users {
store.insert(user.id, user);
}
*repo.next_id.write().unwrap() = 4;
}
repo
}
pub fn find_all(&self, page: usize, per_page: usize) -> PagedResponse<User> {
let store = self.users.read().unwrap();
let total = store.len();
let data: Vec<User> = store
.values()
.skip((page - 1) * per_page)
.take(per_page)
.cloned()
.collect();
PagedResponse {
data,
total,
page,
per_page,
}
}
pub fn find_by_id(&self, id: u64) -> Option<User> {
self.users.read().unwrap().get(&id).cloned()
}
pub fn create(&self, name: String, email: String) -> User {
let mut next_id = self.next_id.write().unwrap();
let id = *next_id;
*next_id += 1;
let user = User {
id,
name,
email,
active: true,
};
self.users.write().unwrap().insert(id, user.clone());
user
}
pub fn update(&self, id: u64, update: UpdateUserRequest) -> Option<User> {
let mut store = self.users.write().unwrap();
if let Some(user) = store.get_mut(&id) {
if let Some(name) = update.name {
user.name = name;
}
if let Some(email) = update.email {
user.email = email;
}
if let Some(active) = update.active {
user.active = active;
}
Some(user.clone())
} else {
None
}
}
pub fn delete(&self, id: u64) -> bool {
self.users.write().unwrap().remove(&id).is_some()
}
}
#[derive(Debug, Clone)]
pub struct UserService {
repository: UserRepository,
}
impl UserService {
pub fn new(repository: UserRepository) -> Self {
Self { repository }
}
pub fn list_users(&self, page: usize, per_page: usize) -> PagedResponse<User> {
let page = page.max(1);
let per_page = per_page.clamp(1, 100);
self.repository.find_all(page, per_page)
}
pub fn get_user(&self, id: u64) -> std::result::Result<User, String> {
self.repository
.find_by_id(id)
.ok_or_else(|| format!("User with id {} not found", id))
}
pub fn create_user(&self, req: CreateUserRequest) -> std::result::Result<User, String> {
if req.name.trim().is_empty() {
return Err("Name is required".to_string());
}
if req.email.trim().is_empty() {
return Err("Email is required".to_string());
}
if !req.email.contains('@') {
return Err("Invalid email format".to_string());
}
Ok(self.repository.create(req.name, req.email))
}
pub fn update_user(
&self,
id: u64,
req: UpdateUserRequest,
) -> std::result::Result<User, String> {
if let Some(ref email) = req.email {
if !email.contains('@') {
return Err("Invalid email format".to_string());
}
}
self.repository
.update(id, req)
.ok_or_else(|| format!("User with id {} not found", id))
}
pub fn delete_user(&self, id: u64) -> std::result::Result<(), String> {
if self.repository.delete(id) {
Ok(())
} else {
Err(format!("User with id {} not found", id))
}
}
}
static USER_SERVICE: std::sync::OnceLock<UserService> = std::sync::OnceLock::new();
fn get_user_service() -> &'static UserService {
USER_SERVICE.get().expect("UserService not initialized")
}
#[controller("/api/users")]
#[derive(Default, Clone)]
struct UserController;
#[routes]
impl UserController {
#[get("")]
async fn list_users(req: HttpRequest) -> Result<HttpResponse, Error> {
let page: usize = req.query("page").and_then(|p| p.parse().ok()).unwrap_or(1);
let per_page: usize = req
.query("per_page")
.and_then(|p| p.parse().ok())
.unwrap_or(10);
let result = get_user_service().list_users(page, per_page);
HttpResponse::json(&result)
}
#[get("/:id")]
async fn get_user(req: HttpRequest) -> Result<HttpResponse, Error> {
let id: u64 = req
.param("id")
.and_then(|id| id.parse().ok())
.ok_or_else(|| Error::bad_request("Invalid user ID"))?;
match get_user_service().get_user(id) {
Ok(user) => HttpResponse::json(&user),
Err(msg) => Err(Error::not_found(msg)),
}
}
#[post("")]
async fn create_user(req: HttpRequest) -> Result<HttpResponse, Error> {
let body: CreateUserRequest = req
.json()
.map_err(|e| Error::bad_request(format!("Invalid JSON: {}", e)))?;
match get_user_service().create_user(body) {
Ok(user) => {
let mut response = HttpResponse::created();
response = response.with_json(&user)?;
Ok(response)
}
Err(msg) => Err(Error::validation(msg)),
}
}
#[put("/:id")]
async fn update_user(req: HttpRequest) -> Result<HttpResponse, Error> {
let id: u64 = req
.param("id")
.and_then(|id| id.parse().ok())
.ok_or_else(|| Error::bad_request("Invalid user ID"))?;
let body: UpdateUserRequest = req
.json()
.map_err(|e| Error::bad_request(format!("Invalid JSON: {}", e)))?;
match get_user_service().update_user(id, body) {
Ok(user) => HttpResponse::json(&user),
Err(msg) => Err(Error::not_found(msg)),
}
}
#[delete("/:id")]
async fn delete_user(req: HttpRequest) -> Result<HttpResponse, Error> {
let id: u64 = req
.param("id")
.and_then(|id| id.parse().ok())
.ok_or_else(|| Error::bad_request("Invalid user ID"))?;
match get_user_service().delete_user(id) {
Ok(()) => Ok(HttpResponse::no_content()),
Err(msg) => Err(Error::not_found(msg)),
}
}
}
#[module(
controllers: [UserController]
)]
#[derive(Default)]
struct AppModule;
#[tokio::main]
async fn main() {
println!("Starting CRUD API example");
let repository = UserRepository::with_seed_data();
let service = UserService::new(repository);
USER_SERVICE
.set(service)
.expect("Failed to set user service");
println!("Server running at http://127.0.0.1:3000");
println!("Try these endpoints:");
println!(" GET /api/users - List all users");
println!(" GET /api/users/1 - Get user by ID");
println!(" POST /api/users - Create a user");
println!(" PUT /api/users/1 - Update a user");
println!(" DELETE /api/users/1 - Delete a user");
let app = Application::create::<AppModule>().await;
app.listen(3000).await.unwrap();
}