diff --git a/examples/get.rs b/examples/get.rs index 0186b8a..f9a6994 100644 --- a/examples/get.rs +++ b/examples/get.rs @@ -1,46 +1,98 @@ -// example to show fetching a URL and saving to a file - -use std::path::Path; +/// 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. +use std::path::{Path, PathBuf}; #[tokio::main] async fn main() -> kxio::Result<()> { - let net = kxio::net::new(); - let fs = kxio::fs::temp()?; + // 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. + let fs: kxio::fs::FileSystem = kxio::fs::new(PathBuf::from("./")); + + // The URL we will fetch - the readme for this library. let url = "https://git.kemitix.net/kemitix/kxio/raw/branch/main/README.md"; - let file_path = fs.base().join("README.md"); - download_and_save(url, &file_path, &fs, &net).await?; + // 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"); - print_file(&file_path, &fs)?; + // Create a generic handle for the file. This doesn't open the file, and always succeeds. + let path: kxio::fs::PathReal = fs.path(&file_path); + + // 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()? { + // extracts the path from the handle + let pathbuf = path.as_pathbuf(); + eprintln!("The file {} already exists. Aborting!", pathbuf.display()); + return Ok(()); + } + + // 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. + download_and_save_to_file(url, &file_path, &fs, &net).await?; + delete_file(&file_path, &fs)?; Ok(()) } -async fn download_and_save( +/// An function that uses a `FileSystem` and a `Net` object to interact with the outside world. +async fn download_and_save_to_file( url: &str, file_path: &Path, + // The file system abstraction fs: &kxio::fs::FileSystem, + // The network abstraction net: &kxio::net::Net, ) -> kxio::Result<()> { println!("fetching: {url}"); - let request = net.client().get(url); - let response = net.send(request).await?; + + // Uses the network abstraction to create a perfectly normal `reqwest::ResponseBuilder`. + let request: reqwest::RequestBuilder = net.client().get(url); + + // Rather than calling `.build().send()?` on the request, pass it to the `net` + // This allows the `net` to either make the network request as normal, or, if we are + // under test, to handle the request as the test dictates. + // NOTE: if the `.build().send()` is called on the `request` then that WILL result in + // a real network request being made, even under test conditions. Only ever use the + // `net.send(...)` function to keep your code testable. + let response: reqwest::Response = net.send(request).await?; + let body = response.text().await?; println!("fetched {} bytes", body.bytes().len()); println!("writing file: {}", file_path.display()); - let file = fs.file(file_path); + // Uses the file system abstraction to create a handle for a file. + let file: kxio::fs::PathReal = fs.file(file_path); + // Writes the body to the file. file.write(body)?; Ok(()) } -fn print_file(file_path: &Path, fs: &kxio::fs::FileSystem) -> kxio::Result<()> { +/// An function that uses a `FileSystem` object to interact with the outside world. +fn delete_file(file_path: &Path, fs: &kxio::fs::FileSystem) -> kxio::Result<()> { println!("reading file: {}", file_path.display()); - let file = fs.file(file_path); - let reader = file.reader()?; - let contents = reader.as_str(); + + // Uses the file system abstraction to create a handle for a file. + let file: kxio::fs::PathReal = fs.file(file_path); + // Creates a `Reader` which loaded the file into memory. + let reader: kxio::fs::Reader = file.reader()?; + let contents: &str = reader.as_str(); println!("{contents}"); Ok(()) @@ -50,29 +102,39 @@ fn print_file(file_path: &Path, fs: &kxio::fs::FileSystem) -> kxio::Result<()> { mod tests { use super::*; + // This test demonstrates how to use the `kxio` to test your program. #[tokio::test] async fn should_save_remote_body() { //given - let net = kxio::net::mock(); + // 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. + let net: kxio::net::MockNet = kxio::net::mock(); let url = "http://localhost:8080"; - net.on(net.client().get(url).build().expect("request")) - .respond( - net.response() - .body("contents") - .expect("response body") - .into(), - ) + + // declare what response should be made for a given request + let response: http::Response<&str> = + net.response().body("contents").expect("response body"); + let request = net.client().get(url).build().expect("request"); + net.on(request) + // By default, the METHOD and URL must match, equivalent to: + //.match_on(vec![MatchOn::Method, MatchOn::Url]) + .respond(response.into()) .expect("mock"); + // Create a temporary directory that will be deleted with `fs` goes out of scope let fs = kxio::fs::temp().expect("temp fs"); let file_path = fs.base().join("foo"); //when - download_and_save(url, &file_path, &fs, &net.into()) + // Pass the file sytsem and network abstractions to the code to be tested + download_and_save_to_file(url, &file_path, &fs, &net.into()) .await .expect("system under test"); //then + // Open a file and read it let file = fs.file(&file_path); let reader = file.reader().expect("reader"); let contents = reader.as_str();