This counter example is part of the Rocket series.
Dependencies
Besides Rocker we also use the tempdir crate to create a temporary directory for our data file.
examples/rocket/single-counter-in-text-file/Cargo.toml
[package]
name = "single-counter-in-text-file"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
rocket = "0.5"
tempdir = "0.3"
The main code
The code from the example on the web all set the return value of the route function to be &'static str
. In order to make this example work I had to change that to String
.
We use a file called "counter.txt" in the current working directory, which, if we run the application using cargo run
then is the same as the folder of the crate.
We allow the user (who runs the application) to override this by setting an environment variable called COUNTER_PATH
. This will be used in the tests to set the location
of the "counter.txt" in some temporary folder.
examples/rocket/single-counter-in-text-file/src/main.rs
#[macro_use]
extern crate rocket;
use std::fs::File;
use std::io::Write;
#[get("/")]
fn index() -> String {
let file = match std::env::var("COUNTER_PATH") {
Ok(val) => std::path::PathBuf::from(val),
Err(_) => {
let current_dir = std::env::current_dir().unwrap();
current_dir.join("counter.txt")
}
};
let counter: u32 = if file.exists() {
std::fs::read_to_string(&file)
.unwrap()
.trim_end()
.parse()
.unwrap()
} else {
0
};
let counter = counter + 1;
let mut fh = File::create(file).unwrap();
writeln!(&mut fh, "{}", counter).unwrap();
format!("Counter is {}", counter)
}
#[launch]
fn rocket() -> _ {
rocket::build().mount("/", routes![index])
}
#[cfg(test)]
mod tests;
The test
In the test we create a temporary folder and we place the "counter.txt" file there. This will ensure that running the test does not interfere with the application.
We set the environment variable COUNTER_PATH
to this path that is consulted in the application itself.
We send two get
requests to see the counter increasing.
examples/rocket/single-counter-in-text-file/src/tests.rs
use rocket::http::Status;
use rocket::local::blocking::Client;
use tempdir::TempDir;
#[test]
fn test_counte() {
let tmp_dir = TempDir::new("counter").unwrap();
std::env::set_var("COUNTER_PATH", tmp_dir.path().join("counter.txt"));
let client = Client::tracked(super::rocket()).unwrap();
let response = client.get("/").dispatch();
assert_eq!(response.status(), Status::Ok);
assert_eq!(response.into_string(), Some("Counter is 1".into()));
let response = client.get("/").dispatch();
assert_eq!(response.status(), Status::Ok);
assert_eq!(response.into_string(), Some("Counter is 2".into()));
}
Error handling - unwrap
In this example I did not deal much with error handling and called unwrap
in many places. This was done to be able to focus on a working example without the extra code
to deal with the - in this case - unlikely events. If you are interested in improving the error handling check out the article about unwrap.
No atomic file operation
Anothe issue with this solution is that the read and write operations on the file are not atomic. In a more real-world situation we would need to make sure that two requests can't update the counter at the same time.