mirror of
https://github.com/tokio-rs/axum.git
synced 2025-04-26 13:56:22 +02:00
Add todos example (#38)
This commit is contained in:
parent
8faed8120f
commit
6705e79ed7
2 changed files with 165 additions and 1 deletions
|
@ -54,7 +54,7 @@ tokio = { version = "1.6.1", features = ["macros", "rt", "rt-multi-thread"] }
|
|||
tokio-postgres = "0.7.2"
|
||||
tracing = "0.1"
|
||||
tracing-subscriber = "0.2"
|
||||
uuid = { version = "0.8", features = ["serde"] }
|
||||
uuid = { version = "0.8", features = ["serde", "v4"] }
|
||||
|
||||
[dev-dependencies.tower]
|
||||
version = "0.4"
|
||||
|
|
164
examples/todos.rs
Normal file
164
examples/todos.rs
Normal file
|
@ -0,0 +1,164 @@
|
|||
//! Provides a RESTful web server managing some Todos.
|
||||
//!
|
||||
//! API will be:
|
||||
//!
|
||||
//! - `GET /todos`: return a JSON list of Todos.
|
||||
//! - `POST /todos`: create a new Todo.
|
||||
//! - `PUT /todos/:id`: update a specific Todo.
|
||||
//! - `DELETE /todos/:id`: delete a specific Todo.
|
||||
|
||||
use axum::{
|
||||
extract::{Extension, Json, Query, UrlParams},
|
||||
prelude::*,
|
||||
response::IntoResponse,
|
||||
service::ServiceExt,
|
||||
};
|
||||
use http::StatusCode;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
convert::Infallible,
|
||||
net::SocketAddr,
|
||||
sync::{Arc, RwLock},
|
||||
time::Duration,
|
||||
};
|
||||
use tower::{BoxError, ServiceBuilder};
|
||||
use tower_http::{add_extension::AddExtensionLayer, trace::TraceLayer};
|
||||
use uuid::Uuid;
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
tracing_subscriber::fmt::init();
|
||||
|
||||
let db = Db::default();
|
||||
|
||||
// Compose the routes
|
||||
let app = route("/todos", get(todos_index).post(todos_create))
|
||||
.route("/todos/:id", patch(todos_update).delete(todos_delete))
|
||||
// Add middleware to all routes
|
||||
.layer(
|
||||
ServiceBuilder::new()
|
||||
.timeout(Duration::from_secs(10))
|
||||
.layer(TraceLayer::new_for_http())
|
||||
.layer(AddExtensionLayer::new(db))
|
||||
.into_inner(),
|
||||
)
|
||||
// If the timeout fails, map the error to a response
|
||||
.handle_error(|error: BoxError| {
|
||||
let result = if error.is::<tower::timeout::error::Elapsed>() {
|
||||
Ok(StatusCode::REQUEST_TIMEOUT)
|
||||
} else {
|
||||
Err((
|
||||
StatusCode::INTERNAL_SERVER_ERROR,
|
||||
format!("Unhandled internal error: {}", error),
|
||||
))
|
||||
};
|
||||
|
||||
Ok::<_, Infallible>(result)
|
||||
})
|
||||
// Make sure all errors have been handled
|
||||
.check_infallible();
|
||||
|
||||
let addr = SocketAddr::from(([127, 0, 0, 1], 3000));
|
||||
tracing::debug!("listening on {}", addr);
|
||||
hyper::Server::bind(&addr)
|
||||
.serve(app.into_make_service())
|
||||
.await
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
// The query parameters for todos index
|
||||
#[derive(Debug, Deserialize, Default)]
|
||||
pub struct Pagination {
|
||||
pub offset: Option<usize>,
|
||||
pub limit: Option<usize>,
|
||||
}
|
||||
|
||||
async fn todos_index(
|
||||
pagination: Option<Query<Pagination>>,
|
||||
Extension(db): Extension<Db>,
|
||||
) -> impl IntoResponse {
|
||||
let todos = db.read().unwrap();
|
||||
|
||||
let Query(pagination) = pagination.unwrap_or_default();
|
||||
|
||||
let todos = todos
|
||||
.values()
|
||||
.cloned()
|
||||
.skip(pagination.offset.unwrap_or(0))
|
||||
.take(pagination.limit.unwrap_or(std::usize::MAX))
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
response::Json(todos)
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
struct CreateTodo {
|
||||
text: String,
|
||||
}
|
||||
|
||||
async fn todos_create(
|
||||
Json(input): Json<CreateTodo>,
|
||||
Extension(db): Extension<Db>,
|
||||
) -> impl IntoResponse {
|
||||
let todo = Todo {
|
||||
id: Uuid::new_v4(),
|
||||
text: input.text,
|
||||
completed: false,
|
||||
};
|
||||
|
||||
db.write().unwrap().insert(todo.id, todo.clone());
|
||||
|
||||
(StatusCode::CREATED, response::Json(todo))
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
struct UpdateTodo {
|
||||
text: Option<String>,
|
||||
completed: Option<bool>,
|
||||
}
|
||||
|
||||
async fn todos_update(
|
||||
UrlParams((id,)): UrlParams<(Uuid,)>,
|
||||
Json(input): Json<UpdateTodo>,
|
||||
Extension(db): Extension<Db>,
|
||||
) -> Result<impl IntoResponse, StatusCode> {
|
||||
let mut todo = db
|
||||
.read()
|
||||
.unwrap()
|
||||
.get(&id)
|
||||
.cloned()
|
||||
.ok_or(StatusCode::NOT_FOUND)?;
|
||||
|
||||
if let Some(text) = input.text {
|
||||
todo.text = text;
|
||||
}
|
||||
|
||||
if let Some(completed) = input.completed {
|
||||
todo.completed = completed;
|
||||
}
|
||||
|
||||
db.write().unwrap().insert(todo.id, todo.clone());
|
||||
|
||||
Ok(response::Json(todo))
|
||||
}
|
||||
|
||||
async fn todos_delete(
|
||||
UrlParams((id,)): UrlParams<(Uuid,)>,
|
||||
Extension(db): Extension<Db>,
|
||||
) -> impl IntoResponse {
|
||||
if db.write().unwrap().remove(&id).is_some() {
|
||||
StatusCode::NO_CONTENT
|
||||
} else {
|
||||
StatusCode::NOT_FOUND
|
||||
}
|
||||
}
|
||||
|
||||
type Db = Arc<RwLock<HashMap<Uuid, Todo>>>;
|
||||
|
||||
#[derive(Debug, Serialize, Clone)]
|
||||
struct Todo {
|
||||
id: Uuid,
|
||||
text: String,
|
||||
completed: bool,
|
||||
}
|
Loading…
Add table
Reference in a new issue