This content originally appeared on DEV Community and was authored by Roger Torres
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);
}
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 }
// 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())
}
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)
}
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,
))
}
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);
}
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)
}
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,
))
}
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
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.
?
This content originally appeared on DEV Community and was authored by Roger Torres
Roger Torres | Sciencx (2021-04-14T12:21:26+00:00) REST API with Rust + Warp 4: PUT & DELETE. Retrieved from https://www.scien.cx/2021/04/14/rest-api-with-rust-warp-4-put-delete/
Please log in to upload a file.
There are no updates yet.
Click the Upload button above to add an update.