Logo

dev-resources.site

for different kinds of informations.

REST API with Rust + Warp 4: PUT & DELETE

Published at
4/14/2021
Categories
rust
warp
rest
api
Author
rogertorres
Categories
4 categories in total
rust
open
warp
open
rest
open
api
open
Author
11 person written this
rogertorres
open
REST API with Rust + Warp 4: PUT & DELETE

That's it, the last methods. In the beginning, I thought it would be the end of the series, but then I realized it needed an additional post, regarding how to manually test it using curl. However, before that, there are still two more methods to be coded. But don't worry, both combined are probably easier than the single ones we handled so far.


Warp 4, Mr. Sulu.

The code for this part is available here.

The PUT method is a mix between insert and change: it creates an entry when the data is not there and updates it when it is.

This behavior is already met by our HashSet; this is exactly how the insert() function works. However, we got to know if we are inserting or changing because the status that is returned got to be different:

  • Code 201 when it is created
  • Code 200 when it is updated

Where did I get this from? It is written here.

With that in mind, I wrote this code:

#[tokio::test]
async fn try_update() {
    let db = models::new_db();
    let api = filters::update_sim(db);

    let response = request()
        .method("PUT")
        .path("/holodeck/1")
        .json(&models::Name{ new_name: String::from("The Big Goodbye")})
        .reply(&api)
        .await;

    assert_eq!(response.status(), StatusCode::CREATED);

    let response = request()
        .method("PUT")
        .path("/holodeck/1")
        .json(&models::Name{ new_name: String::from("The Short Hello")})
        .reply(&api)
        .await;

    assert_eq!(response.status(), StatusCode::OK);
}
Enter fullscreen mode Exit fullscreen mode

How did I knew that 201 was StatusCode::CREATED and 200 was StatusCode::OK? Here.

As you can see, the request is made by sending the parameter id ("1", in this case). Different from GET, this parameter is mandatory. And because the id is already being sent in the URI, the body only contains the name. The reasoning behind this is also in the aforementioned rfc.

Because of this, I implemented a new struct and a new function to get the JSON body.

#[derive(Debug, Deserialize, Serialize)]
pub struct NewName{ pub name: String }
Enter fullscreen mode Exit fullscreen mode
// This code is inside the mod "filters"
fn json_body_put() -> impl Filter<Extract = (models::NewName,), Error = warp::Rejection> + Clone {
    warp::body::content_length_limit(1024 * 16).and(warp::body::json())
}
Enter fullscreen mode Exit fullscreen mode

This is certainly a suboptimal way of doing this. But let's move on anyway; I am saving the excuses about the poor execution of things to the last part.

Now, the filter and the handler.

// This is inside the mod "filters"
pub fn update_sim(db: models::Db) -> impl Filter<Extract = impl warp::Reply, Error = warp::Rejection> + Clone {
    let db_map = warp::any()
        .map(move || db.clone());

    warp::path!("holodeck" / u64)
        .and(warp::put())
        .and(json_body_put())
        .and(db_map)
        .and_then(handlers::handle_update_sim)
}
Enter fullscreen mode Exit fullscreen mode
pub async fn handle_update_sim(id: u64, new: models::NewName, db: models::Db) -> Result<impl warp::Reply, Infallible> {
    // Replaced entry
    if let Some(_) = db.lock().await.replace(Simulation{id, name: new.name}){
        return Ok(warp::reply::with_status(
            format!("Simulation #{} was updated.\n", id), 
            StatusCode::OK,
        ));
    }

    // Create entry
    Ok(warp::reply::with_status(
        format!("Simulation #{} was inserted.\n", id), 
        StatusCode::CREATED,
    ))
}
Enter fullscreen mode Exit fullscreen mode

And that's it!


Red alert

To warp wrap things up, the DELETE method.

As usual, the request is quite simple: it sends the id as a parameter and no body. As a response, we expect code 200 (OK) including a "representation describing the status".

#[tokio::test]
async fn try_delete() {
    let simulation = models::Simulation{
        id: 1, 
        name: String::from("The Big Goodbye!"),
    };

    let db = models::new_db();
    db.lock().await.insert(simulation);

    let api = filters::delete_sim(db);

    let response = request()
        .method("DELETE")
        .path("/holodeck/1")
        .reply(&api)
        .await;

        assert_eq!(response.status(), StatusCode::OK);
}
Enter fullscreen mode Exit fullscreen mode

Hopefully, nothing about the filter implementation seems strange to you:

pub fn delete(db: models::Db) -> impl Filter<Extract = impl warp::Reply, Error = warp::Rejection> + Clone {
    let db_map = warp::any()
        .map(move || db.clone());

    warp::path!("holodeck" / u64)
        .and(warp::delete())
        .and(db_map)
        .and_then(handlers::handle_delete_sim)
}
Enter fullscreen mode Exit fullscreen mode

Since it is the first time we're deleting data, the handler has a unique behavior, but also nothing very different from what has been done so far.

pub async fn handle_delete_sim(id: u64, db: models::Db) -> Result<impl warp::Reply, Infallible> {
    if db.lock().await.remove(&Simulation{id, name: String::new(),}){
        return Ok(warp::reply::with_status(
            format!("Simulation #{} was deleted", id), 
            StatusCode::OK,
        ))
    };

    Ok(warp::reply::with_status(
        format!("No data was deleted."),
        StatusCode::OK,
    ))
}
Enter fullscreen mode Exit fullscreen mode

That should do...

$ cargo test

running 5 tests
test tests::try_delete ... ok
test tests::try_create ... ok
test tests::try_list ... ok
test tests::try_create_duplicates ... ok
test tests::try_update ... ok

test result: ok. 5 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out
Enter fullscreen mode Exit fullscreen mode

And it did!


In the next episode of Engaging Warp...

Finally, the last part. We will serve what has been built and curl against it.

🖖

warp Article's
29 articles in total
Favicon
Warp AI Terminal: A Beginner’s Guide to the Future of Command Line Interfaces
Favicon
OSX Sequoia & Operation not permitted (os error 1)
Favicon
iTerm on steroids: Why I've Switched to Warp
Favicon
warp adoption guide: Overview, examples, and alternatives
Favicon
Warp terminal on WSL is AMAZING
Favicon
How to use Cloudflare Warp as a socks proxy on your local computer
Favicon
Dank Owl - custom themes in Warp terminal and neovim
Favicon
Reasons to change the default terminal to Warp
Favicon
How Warp Works
Favicon
The terminal is on life support. Is it worth saving?
Favicon
How we designed themes for the terminal - a peek into our process
Favicon
Warp’s product principles for reinventing the terminal
Favicon
How to hire engineers at an early stage startup
Favicon
Code-first vs. Product-first
Favicon
The Biggest Mistake I See Engineers Make
Favicon
Multiply by 𝝅
Favicon
Coding
Favicon
Planning
Favicon
Host Interns
Favicon
ทำไมเรายังไม่ Warp (terminal)
Favicon
Rust Warp: Use Cookie for Authorization
Favicon
REST API with Rust + Warp 1: Introduction
Favicon
REST API with Rust + Warp 3: GET
Favicon
REST API with Rust + Warp 2: POST
Favicon
REST API with Rust + Warp 4: PUT & DELETE
Favicon
REST API with Rust + Warp 5: Beyond test utilities
Favicon
Validating JSON input in Rust web services
Favicon
Dotnet Warp to further optimize .Net self-contained single executable file.
Favicon
My First Rust Web Service (And Other Firsts)

Featured ones: