/// These are our API handlers, the ends of each filter chain. /// Notice how thanks to using `Filter::and`, we can define a function /// with the exact arguments we'd expect from each filter in the chain. /// No tuples are needed, it's auto flattened for the functions. use anyhow::anyhow; use http_api_problem::HttpApiProblem; use std::convert::Infallible; use tracing::debug; use warp::http::StatusCode; use crate::models::{Environment, ListOptions, Todo}; use crate::problem::reject_anyhow; pub async fn list_todos( opts: ListOptions, env: Environment, ) -> Result { env.caches .list_todos .get_response(opts.clone(), || async { // Just return a JSON array of todos, applying the limit and offset. let todos = env.db.lock().await; let todos: Vec = todos .clone() .into_iter() .skip(opts.offset.unwrap_or(0)) .take(opts.limit.unwrap_or(std::usize::MAX)) .collect(); Ok(warp::reply::json(&todos)) }) .await } pub async fn get_todo(id: u64, env: Environment) -> Result { env.caches .todos .get_response(id, || async { debug!("get_todo: id={}", id); let mut vec = env.db.lock().await; // Look for the specified Todo... for todo in vec.iter_mut() { if todo.id == id { return Ok(warp::reply::json(&todo)); } } debug!(" -> todo id not found!"); // If the for loop didn't return OK, then the ID doesn't exist... Err(anyhow!(HttpApiProblem::new(format!( "Todo {} not found", id )) .set_status(StatusCode::NOT_FOUND))) }) .await } pub async fn create_todo(create: Todo, env: Environment) -> Result { debug!("create_todo: {:?}", create); let mut vec = env.db.lock().await; for todo in vec.iter() { if todo.id == create.id { debug!(" -> id already exists: {}", create.id); // Todo with id already exists, return `400 BadRequest`. return Ok(StatusCode::BAD_REQUEST); } } // No existing Todo with id, so insert and return `201 Created`. vec.push(create); env.caches.list_todos.clear().await; Ok(StatusCode::CREATED) } pub async fn update_todo( id: u64, update: Todo, env: Environment, ) -> Result { debug!("update_todo: id={}, todo={:?}", id, update); let mut vec = env.db.lock().await; // Look for the specified Todo... for todo in vec.iter_mut() { if todo.id == id { *todo = update; env.caches .todos .delete_response(id) .await .map_err(reject_anyhow)?; env.caches.list_todos.clear().await; return Ok(StatusCode::OK); } } debug!(" -> todo id not found!"); // If the for loop didn't return OK, then the ID doesn't exist... Ok(StatusCode::NOT_FOUND) } pub async fn delete_todo(id: u64, env: Environment) -> Result { debug!("delete_todo: id={}", id); let mut vec = env.db.lock().await; let len = vec.len(); vec.retain(|todo| { // Retain all Todos that aren't this id... // In other words, remove all that *are* this id... todo.id != id }); // If the vec is smaller, we found and deleted a Todo! let deleted = vec.len() != len; if deleted { env.caches .todos .delete_response(id) .await .map_err(reject_anyhow)?; env.caches.list_todos.clear().await; // respond with a `204 No Content`, which means successful, // yet no body expected... Ok(StatusCode::NO_CONTENT) } else { debug!(" -> todo id not found!"); Ok(StatusCode::NOT_FOUND) } }