2024-11-08 19:46:19 +00:00
|
|
|
/// This is an example to show fetching a file from a webiste and saving to a file
|
|
|
|
///
|
|
|
|
/// The example consts of:
|
|
|
|
///
|
|
|
|
/// - The main program, in `main()` - demonstrates how to setup `kxio` for use in prod
|
|
|
|
/// - A test module - demonstrates how to use `kxio` in tests
|
|
|
|
/// - sample functions - showing how to use `kxio` the body of your program, and be testable
|
|
|
|
///
|
|
|
|
/// NOTE: running this program with `cargo run --example get` will create and delete the file
|
|
|
|
/// `example-readme.md` in the current directory.
|
2024-11-09 19:34:01 +00:00
|
|
|
use std::path::Path;
|
2024-11-09 07:52:42 +00:00
|
|
|
|
2024-11-21 08:00:15 +00:00
|
|
|
use kxio::fs::FileHandle;
|
|
|
|
|
2024-11-09 07:52:42 +00:00
|
|
|
#[tokio::main]
|
|
|
|
async fn main() -> kxio::Result<()> {
|
2024-11-08 19:46:19 +00:00
|
|
|
// Create a `Net` object for making real network requests.
|
|
|
|
let net: kxio::net::Net = kxio::net::new();
|
|
|
|
|
|
|
|
// Create a `FileSystem` object for accessing files within the current directory.
|
|
|
|
// The object created will return a `PathTraveral` error result if there is an attempt to\
|
|
|
|
// access a file outside of this directory.
|
2024-11-09 19:34:01 +00:00
|
|
|
let fs: kxio::fs::FileSystem = kxio::fs::new("./");
|
2024-11-09 07:52:42 +00:00
|
|
|
|
2024-11-08 19:46:19 +00:00
|
|
|
// The URL we will fetch - the readme for this library.
|
2024-11-09 07:52:42 +00:00
|
|
|
let url = "https://git.kemitix.net/kemitix/kxio/raw/branch/main/README.md";
|
|
|
|
|
2024-11-08 19:46:19 +00:00
|
|
|
// Create a PathBuf to a file within the directory that the `fs` object has access to.
|
|
|
|
let file_path = fs.base().join("example-readme.md");
|
|
|
|
|
|
|
|
// Create a generic handle for the file. This doesn't open the file, and always succeeds.
|
2024-11-21 08:00:15 +00:00
|
|
|
let path = fs.path(&file_path);
|
2024-11-08 19:46:19 +00:00
|
|
|
|
|
|
|
// Other options are;
|
|
|
|
// `fs.file(&file_path)` - for a file
|
|
|
|
// `fs.dir(&dir_path)` - for a directory
|
|
|
|
|
|
|
|
// Checks if the path exists (whether a file, directory, etc)
|
|
|
|
if path.exists()? {
|
2024-11-21 08:00:15 +00:00
|
|
|
eprintln!("The file {path} already exists. Aborting!");
|
2024-11-08 19:46:19 +00:00
|
|
|
return Ok(());
|
|
|
|
}
|
2024-11-09 07:52:42 +00:00
|
|
|
|
2024-11-08 19:46:19 +00:00
|
|
|
// Passes a reference to the `fs` and `net` objects for use by your program.
|
|
|
|
// Your programs should not know whether they are handling a mock or the real thing.
|
|
|
|
// Any file or network access should be made using these handlers to be properly testable.
|
2024-11-21 08:00:15 +00:00
|
|
|
let file = download_and_save_to_file(url, &file_path, &fs, &net).await?;
|
|
|
|
read_file(&file)?;
|
|
|
|
delete_file(file)?;
|
2024-11-09 07:52:42 +00:00
|
|
|
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
2024-11-08 19:46:19 +00:00
|
|
|
/// An function that uses a `FileSystem` and a `Net` object to interact with the outside world.
|
|
|
|
async fn download_and_save_to_file(
|
2024-11-09 07:52:42 +00:00
|
|
|
url: &str,
|
|
|
|
file_path: &Path,
|
2024-11-21 08:00:15 +00:00
|
|
|
// The filesystem abstraction
|
2024-11-09 07:52:42 +00:00
|
|
|
fs: &kxio::fs::FileSystem,
|
2024-11-08 19:46:19 +00:00
|
|
|
// The network abstraction
|
2024-11-09 07:52:42 +00:00
|
|
|
net: &kxio::net::Net,
|
2024-11-21 08:00:15 +00:00
|
|
|
) -> kxio::Result<FileHandle> {
|
2024-11-09 07:52:42 +00:00
|
|
|
println!("fetching: {url}");
|
2024-11-08 19:46:19 +00:00
|
|
|
|
2024-11-21 08:00:15 +00:00
|
|
|
// Makes a GET request that can be mocked in a test
|
|
|
|
let response: reqwest::Response = net.get(url).header("key", "value").send().await?;
|
|
|
|
|
|
|
|
// As you can see, we use [reqwest] under the hood.
|
2024-11-17 09:18:18 +00:00
|
|
|
//
|
2024-11-21 08:00:15 +00:00
|
|
|
// If you need to create a more complex request than the [kxio] fluent API allows, you
|
|
|
|
// can create a request using [reqwest] and pass it to [net.send(request)].
|
2024-11-08 19:46:19 +00:00
|
|
|
|
2024-11-09 07:52:42 +00:00
|
|
|
let body = response.text().await?;
|
|
|
|
println!("fetched {} bytes", body.bytes().len());
|
|
|
|
|
2024-11-08 19:46:19 +00:00
|
|
|
// Uses the file system abstraction to create a handle for a file.
|
|
|
|
let file: kxio::fs::PathReal<kxio::fs::FileMarker> = fs.file(file_path);
|
2024-11-21 08:00:15 +00:00
|
|
|
println!("writing file: {file}");
|
2024-11-08 19:46:19 +00:00
|
|
|
// Writes the body to the file.
|
2024-11-09 07:52:42 +00:00
|
|
|
file.write(body)?;
|
|
|
|
|
2024-11-21 08:00:15 +00:00
|
|
|
Ok(file)
|
2024-11-09 07:52:42 +00:00
|
|
|
}
|
|
|
|
|
2024-11-21 08:00:15 +00:00
|
|
|
/// A function that reads the file contents
|
|
|
|
fn read_file(file: &FileHandle) -> kxio::Result<()> {
|
|
|
|
println!("reading file: {file}");
|
2024-11-08 19:46:19 +00:00
|
|
|
|
|
|
|
// Creates a `Reader` which loaded the file into memory.
|
|
|
|
let reader: kxio::fs::Reader = file.reader()?;
|
|
|
|
let contents: &str = reader.as_str();
|
2024-11-09 07:52:42 +00:00
|
|
|
println!("{contents}");
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
2024-11-21 08:00:15 +00:00
|
|
|
/// A function that deletes the file
|
|
|
|
fn delete_file(file: FileHandle) -> kxio::Result<()> {
|
|
|
|
println!("deleting file: {file}");
|
|
|
|
|
|
|
|
file.remove()?;
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
2024-11-09 07:52:42 +00:00
|
|
|
#[cfg(test)]
|
|
|
|
mod tests {
|
2024-11-17 11:24:28 +00:00
|
|
|
use http::StatusCode;
|
|
|
|
|
2024-11-09 07:52:42 +00:00
|
|
|
use super::*;
|
|
|
|
|
2024-11-08 19:46:19 +00:00
|
|
|
// This test demonstrates how to use the `kxio` to test your program.
|
2024-11-09 07:52:42 +00:00
|
|
|
#[tokio::test]
|
|
|
|
async fn should_save_remote_body() {
|
|
|
|
//given
|
2024-11-08 19:46:19 +00:00
|
|
|
// Create a fake/mock network abstraction
|
|
|
|
// When `net` goes out of scope it will check that all the expected network requests (see
|
|
|
|
// `net.on(...)` below) were all made. If there are any that were not made, the test will
|
|
|
|
// be failed. If you want to avoid this, then call `net.reset()` before your test ends.
|
2024-11-10 09:44:30 +00:00
|
|
|
let mock_net: kxio::net::MockNet = kxio::net::mock();
|
2024-11-09 07:52:42 +00:00
|
|
|
let url = "http://localhost:8080";
|
2024-11-08 19:46:19 +00:00
|
|
|
|
|
|
|
// declare what response should be made for a given request
|
2024-11-17 11:24:28 +00:00
|
|
|
mock_net
|
|
|
|
.on()
|
|
|
|
.get(url)
|
|
|
|
.respond(StatusCode::OK)
|
2024-11-17 09:18:18 +00:00
|
|
|
.body("contents")
|
|
|
|
.expect("valid mock");
|
2024-11-09 07:52:42 +00:00
|
|
|
|
2024-11-08 19:46:19 +00:00
|
|
|
// Create a temporary directory that will be deleted with `fs` goes out of scope
|
2024-11-09 07:52:42 +00:00
|
|
|
let fs = kxio::fs::temp().expect("temp fs");
|
|
|
|
let file_path = fs.base().join("foo");
|
|
|
|
|
2024-11-10 09:44:30 +00:00
|
|
|
// Create a [Net] from the [MockNet] to pass to the system under tets
|
|
|
|
let net = kxio::net::Net::from(mock_net);
|
|
|
|
|
2024-11-09 07:52:42 +00:00
|
|
|
//when
|
2024-11-08 19:46:19 +00:00
|
|
|
// Pass the file sytsem and network abstractions to the code to be tested
|
2024-11-10 09:44:30 +00:00
|
|
|
download_and_save_to_file(url, &file_path, &fs, &net)
|
2024-11-09 07:52:42 +00:00
|
|
|
.await
|
|
|
|
.expect("system under test");
|
|
|
|
|
|
|
|
//then
|
2024-11-10 09:44:30 +00:00
|
|
|
// Read the file
|
2024-11-09 07:52:42 +00:00
|
|
|
let file = fs.file(&file_path);
|
|
|
|
let reader = file.reader().expect("reader");
|
|
|
|
let contents = reader.as_str();
|
|
|
|
|
|
|
|
assert_eq!(contents, "contents");
|
2024-11-10 09:44:30 +00:00
|
|
|
|
|
|
|
// not needed for this test, but should it be needed, we can avoid checking for any
|
|
|
|
// unconsumed request matches.
|
2024-11-12 07:13:59 +00:00
|
|
|
// let mock_net = kxio::net::MockNet::try_from(net).expect("recover mock");
|
|
|
|
// mock_net.reset();
|
2024-11-09 07:52:42 +00:00
|
|
|
}
|
|
|
|
}
|