1.阿克蘇姆設置
[dependencies]
axum = "0.7"
tokio = { version = "1", features = ["full"] }
serde = { version = "1", features = ["derive"] }
serde_json = "1"
tower = "0.4"
tower-http = { version = "0.5", features = ["cors", "trace"] }
tracing = "0.1"
tracing-subscriber = "0.3"
use axum::{routing::{get, post}, Router, Json, extract::{State, Path, Query}};
use serde::{Deserialize, Serialize};
use std::sync::Arc;
use tokio::sync::RwLock;
#[derive(Clone)]
struct AppState {
db: Arc<RwLock<Vec<Product>>>,
}
#[tokio::main]
async fn main() {
tracing_subscriber::init();
let state = AppState {
db: Arc::new(RwLock::new(Vec::new())),
};
let app = Router::new()
.route("/products", get(list_products).post(create_product))
.route("/products/{id}", get(get_product).put(update_product).delete(delete_product))
.with_state(state);
let listener = tokio::net::TcpListener::bind("0.0.0.0:3000").await.unwrap();
axum::serve(listener, app).await.unwrap();
}
2. 處理機和提取機
#[derive(Serialize, Deserialize, Clone)]
struct Product {
id: String,
name: String,
price: f64,
}
#[derive(Deserialize)]
struct CreateProduct {
name: String,
price: f64,
}
#[derive(Deserialize)]
struct Pagination {
page: Option<u32>,
limit: Option<u32>,
}
async fn list_products(
State(state): State<AppState>,
Query(pagination): Query<Pagination>,
) -> Json<Vec<Product>> {
let db = state.db.read().await;
let page = pagination.page.unwrap_or(1);
let limit = pagination.limit.unwrap_or(20);
let skip = ((page - 1) * limit) as usize;
let products: Vec<Product> = db.iter().skip(skip).take(limit as usize).cloned().collect();
Json(products)
}
async fn create_product(
State(state): State<AppState>,
Json(input): Json<CreateProduct>,
) -> (axum::http::StatusCode, Json<Product>) {
let product = Product {
id: uuid::Uuid::new_v4().to_string(),
name: input.name,
price: input.price,
};
state.db.write().await.push(product.clone());
(axum::http::StatusCode::CREATED, Json(product))
}
async fn get_product(
State(state): State<AppState>,
Path(id): Path<String>,
) -> Result<Json<Product>, AppError> {
let db = state.db.read().await;
let product = db.iter().find(|p| p.id == id)
.cloned()
.ok_or(AppError::NotFound(format!("Product {}", id)))?;
Ok(Json(product))
}
3. 錯誤處理
use axum::response::IntoResponse;
use axum::http::StatusCode;
enum AppError {
NotFound(String),
BadRequest(String),
Internal(anyhow::Error),
}
impl IntoResponse for AppError {
fn into_response(self) -> axum::response::Response {
let (status, message) = match self {
AppError::NotFound(msg) => (StatusCode::NOT_FOUND, msg),
AppError::BadRequest(msg) => (StatusCode::BAD_REQUEST, msg),
AppError::Internal(e) => (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()),
};
(status, Json(serde_json::json!({ "error": message }))).into_response()
}
}
4. 帶有 Tower 的中間件
use tower_http::cors::CorsLayer;
use tower_http::trace::TraceLayer;
let app = Router::new()
.route("/products", get(list_products))
.layer(CorsLayer::permissive())
.layer(TraceLayer::new_for_http())
.with_state(state);
下一篇: SQLx 和資料庫集成。