Rust Axum 웹서버

CRUD 기능, JSON 처리, 그리고 체계적인 에러 처리를 포함한 Axum 웹 프레임워크 기반의 서버 템플릿입니다.

Gist
use axum::{
    extract::{Path, State},
    http::StatusCode,
    response::{IntoResponse, Response},
    routing::{get, post},
    Json, Router,
};
use serde::{Deserialize, Serialize};
use std::sync::{Arc, RwLock};
use tower_http::trace::TraceLayer;

#[derive(Debug, Serialize, Deserialize, Clone)]
struct User {
    id: u64,
    username: String,
    email: String,
}

#[derive(Deserialize)]
struct CreateUser {
    username: String,
    email: String,
}

struct AppState {
    users: RwLock<Vec<User>>,
}

enum AppError {
    UserNotFound(u64),
    Internal(String),
}

impl IntoResponse for AppError {
    fn into_response(self) -> Response {
        let (status, message) = match self {
            AppError::UserNotFound(id) => (StatusCode::NOT_FOUND, format!("User {} not found", id)),
            AppError::Internal(err) => (StatusCode::INTERNAL_SERVER_ERROR, err),
        };

        (status, Json(serde_json::json!({ "error": message }))).into_response()
    }
}

async fn get_users(State(state): State<Arc<AppState>>) -> impl IntoResponse {
    let users = state.users.read().unwrap();
    Json(users.clone())
}

async fn create_user(
    State(state): State<Arc<AppState>>,
    Json(payload): Json<CreateUser>,
) -> impl IntoResponse {
    let mut users = state.users.write().unwrap();
    let id = users.len() as u64 + 1;
    let user = User {
        id,
        username: payload.username,
        email: payload.email,
    };
    users.push(user.clone());
    (StatusCode::CREATED, Json(user))
}

async fn get_user(
    Path(id): Path<u64>,
    State(state): State<Arc<AppState>>,
) -> Result<impl IntoResponse, AppError> {
    let users = state.users.read().unwrap();
    let user = users.iter().find(|u| u.id == id).cloned();

    match user {
        Some(user) => Ok(Json(user)),
        None => Err(AppError::UserNotFound(id)),
    }
}

#[tokio::main]
async fn main() {
    tracing_subscriber::fmt::init();

    let state = Arc::new(AppState {
        users: RwLock::new(vec![]),
    });

    let app = Router::new()
        .route("/users", get(get_users).post(create_user))
        .route("/users/:id", get(get_user))
        .layer(TraceLayer::new_for_http())
        .with_state(state);

    let listener = tokio::net::TcpListener::bind("0.0.0.0:3000").await.unwrap();
    println!("listening on {}", listener.local_addr().unwrap());
    axum::serve(listener, app).await.unwrap();
}