/// 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; #[tokio::main] async fn main() -> kxio::Result<()> { // 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("./"); // The URL we will fetch - the readme for this library. let url = "https://git.kemitix.net/kemitix/kxio/raw/branch/main/README.md"; // 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. 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(()) } /// 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}"); // 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. // `kxio::net::Response` is an alias for `reqwest::Response`. let response: kxio::net::Response = net.get(url).header("key", "value").send().await?; // Other options: // Uses the network abstraction to create a perfectly normal `reqwest::ResponseBuilder`. // `kxio::net::RequestBuilder` is an alias. // let response = net.send(net.client().get(url)).await?; // // let response = net.post(url).body("{}").send().await?; let body = response.text().await?; println!("fetched {} bytes", body.bytes().len()); println!("writing file: {}", file_path.display()); // 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(()) } /// 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()); // 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(()) } #[cfg(test)] mod tests { use http::StatusCode; use super::*; // This test demonstrates how to use the `kxio` to test your program. #[tokio::test] async fn should_save_remote_body() { //given // 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 mock_net: kxio::net::MockNet = kxio::net::mock(); let url = "http://localhost:8080"; // declare what response should be made for a given request mock_net .on() .get(url) .respond(StatusCode::OK) .body("contents") .expect("valid 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"); // Create a [Net] from the [MockNet] to pass to the system under tets let net = kxio::net::Net::from(mock_net); //when // Pass the file sytsem and network abstractions to the code to be tested download_and_save_to_file(url, &file_path, &fs, &net) .await .expect("system under test"); //then // Read the file let file = fs.file(&file_path); let reader = file.reader().expect("reader"); let contents = reader.as_str(); assert_eq!(contents, "contents"); // not needed for this test, but should it be needed, we can avoid checking for any // unconsumed request matches. // let mock_net = kxio::net::MockNet::try_from(net).expect("recover mock"); // mock_net.reset(); } }