/// 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; use kxio::fs::FileHandle; #[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 current_dir = std::env::current_dir().map_err(kxio::fs::Error::Io)?; let fs: kxio::fs::FileSystem = kxio::fs::new(current_dir); // 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 = 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()? { eprintln!("The file {path} already exists. Aborting!"); 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. let file = download_and_save_to_file(url, &file_path, &fs, &net).await?; read_file(&file)?; delete_file(file)?; 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 filesystem abstraction fs: &kxio::fs::FileSystem, // The network abstraction net: &kxio::net::Net, ) -> kxio::Result { println!("fetching: {url}"); // 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. // // 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)]. let body = response.text().await?; println!("fetched {} bytes", body.bytes().len()); // Uses the file system abstraction to create a handle for a file. let file: kxio::fs::PathReal = fs.file(file_path); println!("writing file: {file}"); // Writes the body to the file. file.write(body)?; Ok(file) } /// A function that reads the file contents fn read_file(file: &FileHandle) -> kxio::Result<()> { println!("reading file: {file}"); // 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(()) } /// A function that deletes the file fn delete_file(file: FileHandle) -> kxio::Result<()> { println!("deleting file: {file}"); file.remove()?; 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(); } }