From e21241af999e09e23097bd85673039cbdcf6db9c Mon Sep 17 00:00:00 2001 From: Paul Campbell Date: Tue, 14 Jan 2025 20:52:01 +0000 Subject: [PATCH] fix: should build with kxio dependency --- Cargo.toml | 2 + examples/kxio.rs | 161 +++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 163 insertions(+) create mode 100644 examples/kxio.rs diff --git a/Cargo.toml b/Cargo.toml index 133dad7..9ea9020 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,4 +4,6 @@ version = "0.1.0" edition = "2021" [dependencies] +kxio = "5.0" native-tls = "0.2" +tokio = { version = "1.43", features = ["full"] } diff --git a/examples/kxio.rs b/examples/kxio.rs new file mode 100644 index 0000000..2ae10bc --- /dev/null +++ b/examples/kxio.rs @@ -0,0 +1,161 @@ +/// 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 assert2::let_assert; + 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_assert!(Ok(contents) = reader.as_str()); + + assert_eq!(contents, "contents"); + + net.assert_no_unused_plans(); + + // 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(); + } +}