warp 0.1.5

serve the web at warp speeds
Documentation
#![deny(warnings)]
#[macro_use] extern crate log;
extern crate pretty_env_logger;
extern crate serde;
#[macro_use] extern crate serde_derive;
extern crate warp;

use std::env;
use std::sync::{Arc, Mutex};
use warp::{http::StatusCode, Filter};


/// So we don't have to tackle how different database work, we'll just use
/// a simple in-memory DB, a vector synchronized by a mutex.
type Db = Arc<Mutex<Vec<Todo>>>;

#[derive(Debug, Deserialize, Serialize)]
struct Todo {
    id: u64,
    text: String,
    completed: bool,
}

/// 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.
fn main() {
    if env::var_os("RUST_LOG").is_none() {
        // Set `RUST_LOG=todos=debug` to see debug logs,
        // this only shows access logs.
        env::set_var("RUST_LOG", "todos=info");
    }
    pretty_env_logger::init();

    // These are some `Filter`s that several of the endpoints share,
    // so we'll define them here and reuse them below...


    // Turn our "state", our db, into a Filter so we can combine it
    // easily with others...
    let db = Arc::new(Mutex::new(Vec::<Todo>::new()));
    let db = warp::any().map(move || db.clone());

    // Just the path segment "todos"...
    let todos = warp::path("todos");

    // Combined with `index`, this means nothing comes after "todos".
    // So, for example: `GET /todos`, but not `GET /todos/32`.
    let todos_index = todos.and(warp::path::index());

    // Combined with an id path parameter, for refering to a specific Todo.
    // For example, `POST /todos/32`, but not `POST /todos/32/something-more`.
    let todos_id = todos
        .and(warp::path::param::<u64>())
        .and(warp::path::index());

    // Next, we'll define each our 4 endpoints:

    // `GET /todos`
    let list = warp::get2()
        .and(todos_index)
        .and(db.clone())
        .map(list_todos);

    // `POST /todos`
    let create = warp::post2()
        .and(todos_index)
        .and(warp::body::json())
        .and(db.clone())
        .and_then(create_todo);

    // `PUT /todos/:id`
    let update = warp::put2()
        .and(todos_id)
        .and(warp::body::json())
        .and(db.clone())
        .and_then(update_todo);

    // `DELETE /todos/:id`
    let delete = warp::delete2()
        .and(todos_id)
        .and(db.clone())
        .and_then(delete_todo);


    // Combine our endpoints, since we want requests to match any of them:
    let api = list
        .or(create)
        .or(update)
        .or(delete);

    // View access logs by setting `RUST_LOG=todos`.
    let routes = api.with(warp::log("todos"));

    // Start up the server...
    warp::serve(routes)
        .run(([127, 0, 0, 1], 3030));
}

// 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.

/// GET /todos
fn list_todos(db: Db) -> impl warp::Reply {
    // Just return a JSON array of all Todos.
    warp::reply::json(&*db.lock().unwrap())
}

/// POST /todos with JSON body
fn create_todo(create: Todo, db: Db) -> Result<impl warp::Reply, warp::Rejection> {
    debug!("create_todo: {:?}", create);

    let mut vec = db
        .lock()
        .unwrap();

    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 Err(warp::reject::bad_request());
        }
    }

    // No existing Todo with id, so insert and return `201 Created`.
    vec.push(create);

    Ok(StatusCode::CREATED)
}

/// PUT /todos/:id with JSON body
fn update_todo(id: u64, update: Todo, db: Db) -> Result<impl warp::Reply, warp::Rejection> {
    debug!("update_todo: id={}, todo={:?}", id, update);
    let mut vec = db
        .lock()
        .unwrap();

    // Look for the specified Todo...
    for todo in vec.iter_mut() {
        if todo.id == id {
            *todo = update;
            return Ok(warp::reply());
        }
    }

    debug!("    -> todo id not found!");

    // If the for loop didn't return OK, then the ID doesn't exist...
    Err(warp::reject::not_found())
}

/// DELETE /todos/:id
fn delete_todo(id: u64, db: Db) -> Result<impl warp::Reply, warp::Rejection> {
    debug!("delete_todo: id={}", id);

    let mut vec = db
        .lock()
        .unwrap();

    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 {
        // respond with a `204 No Content`, which means successful,
        // yet no body expected...
        Ok(StatusCode::NO_CONTENT)
    } else {
        debug!("    -> todo id not found!");
        // Reject this request with a `404 Not Found`...
        Err(warp::reject::not_found())
    }
}