feat: Add Debug, Clone, Default, PartialEq, Eq, Send, Sync to as many or our types as possible.
Some checks failed
Rust / build (map[name:stable]) (push) Failing after 4m1s
Rust / build (map[name:nightly]) (push) Failing after 6m34s

- adds tokio::sync as a dependency to provide an async Mutex for Clone of Net and MockNet

## ƒs

- adds `Clone` to `DirItem`
- adds `Default`, `PartialEq` and `Eq` to `FileSystem`, `PathMarker`, `FileMarker` and `DirMarker`
- adds `Default` to `PathReal`
- adds `Clone`, `Debug`, `Default`, `PartialEq` and `Eq` to `Reader`

## net

- `MockNet::try_from` now returns a `Future`, so should be `await`ed
- adds `Debug` to `Plan`
- adds `Debug` and `Default` to `Net`
- adds `Debug`, `Clone` and `Default` to `MockNet`
- adds `Debug`, `Clone`, `PartialEq` and `Eq` to `MatchRequest`
- adds `Debug` and `Clone` to `WhenRequest`
This commit is contained in:
Paul Campbell 2024-11-16 08:46:07 +00:00
parent d664090648
commit 5e0e7c2d71
9 changed files with 81 additions and 28 deletions

View file

@ -24,6 +24,7 @@ path-clean = "1.0"
reqwest = { version = "0.12", features = [ "json" ] } reqwest = { version = "0.12", features = [ "json" ] }
url = "2.5" url = "2.5"
tempfile = "3.10" tempfile = "3.10"
tokio = { version = "1.41", features = [ "sync" ] }
[dev-dependencies] [dev-dependencies]
assert2 = "0.3" assert2 = "0.3"

View file

@ -7,7 +7,7 @@ use std::{
use super::Error; use super::Error;
/// Represents an item in a directory /// Represents an item in a directory
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord)] #[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
pub enum DirItem { pub enum DirItem {
File(PathBuf), File(PathBuf),
Dir(PathBuf), Dir(PathBuf),

View file

@ -98,3 +98,25 @@ pub fn new(base: impl Into<PathBuf>) -> FileSystem {
pub fn temp() -> Result<temp::TempFileSystem> { pub fn temp() -> Result<temp::TempFileSystem> {
temp::TempFileSystem::new() temp::TempFileSystem::new()
} }
#[cfg(test)]
mod tests {
use super::*;
fn is_normal<T: Sized + Send + Sync + Unpin>() {}
#[test]
fn normal_types() {
is_normal::<FileSystem>();
is_normal::<DirItem>();
is_normal::<DirItemIterator>();
is_normal::<Reader>();
is_normal::<PathReal<PathMarker>>();
is_normal::<DirMarker>();
is_normal::<FileMarker>();
is_normal::<PathMarker>();
is_normal::<DirHandle>();
is_normal::<FileHandle>();
is_normal::<PathHandle<PathMarker>>();
}
}

View file

@ -12,24 +12,24 @@ use super::{DirHandle, FileHandle, PathHandle};
pub trait PathType {} pub trait PathType {}
/// Path marker for the type of [PathReal]. /// Path marker for the type of [PathReal].
#[derive(Clone, Debug)] #[derive(Clone, Debug, Default, PartialEq, Eq)]
pub struct PathMarker; pub struct PathMarker;
impl PathType for PathMarker {} impl PathType for PathMarker {}
/// File marker for the type of [PathReal]. /// File marker for the type of [PathReal].
#[derive(Clone, Debug)] #[derive(Clone, Debug, Default, PartialEq, Eq)]
pub struct FileMarker; pub struct FileMarker;
impl PathType for FileMarker {} impl PathType for FileMarker {}
/// Dir marker for the type of [PathReal]. /// Dir marker for the type of [PathReal].
#[derive(Clone, Debug)] #[derive(Clone, Debug, Default, PartialEq, Eq)]
pub struct DirMarker; pub struct DirMarker;
impl PathType for DirMarker {} impl PathType for DirMarker {}
/// Represents a path in the filesystem. /// Represents a path in the filesystem.
/// ///
/// It can be a simple path, or it can be a file or a directory. /// It can be a simple path, or it can be a file or a directory.
#[derive(Clone, Debug)] #[derive(Clone, Debug, Default)]
pub struct PathReal<T: PathType> { pub struct PathReal<T: PathType> {
base: PathBuf, base: PathBuf,
path: PathBuf, path: PathBuf,

View file

@ -6,6 +6,7 @@ use crate::fs::Result;
use super::Error; use super::Error;
/// A reader for a file. /// A reader for a file.
#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub struct Reader { pub struct Reader {
contents: String, contents: String,
} }

View file

@ -9,7 +9,7 @@ use super::{
}; };
/// Represents to base of a section of a file system. /// Represents to base of a section of a file system.
#[derive(Clone, Debug)] #[derive(Clone, Debug, Default, PartialEq, Eq)]
pub struct FileSystem { pub struct FileSystem {
base: PathBuf, base: PathBuf,
} }

View file

@ -134,10 +134,14 @@
//! //!
//! ```rust //! ```rust
//! # use kxio::net; //! # use kxio::net;
//! # #[tokio::main]
//! # async fn main() -> net::Result<()> {
//! # let mock_net = net::mock(); //! # let mock_net = net::mock();
//! # let net = net::Net::from(mock_net); //! # let net = net::Net::from(mock_net);
//! let mock_net = kxio::net::MockNet::try_from(net).expect("recover mock"); //! let mock_net = kxio::net::MockNet::try_from(net).await.expect("recover mock");
//! mock_net.reset(); //! mock_net.reset();
//! # Ok(())
//! # }
//! ```` //! ````
//! //!
//! ## Error Handling //! ## Error Handling

View file

@ -1,7 +1,9 @@
// //
use std::{cell::RefCell, rc::Rc};
use std::{cell::RefCell, ops::Deref, rc::Rc, sync::Arc};
use reqwest::{Body, Client}; use reqwest::{Body, Client};
use tokio::sync::Mutex;
use crate::net::{Method, Request, RequestBuilder, Response, Url}; use crate::net::{Method, Request, RequestBuilder, Response, Url};
@ -29,6 +31,7 @@ pub enum MatchOn {
/// A planned request and the response to return /// A planned request and the response to return
/// ///
/// Contains a list of the criteria that a request must meet before being considered a match. /// Contains a list of the criteria that a request must meet before being considered a match.
#[derive(Debug)]
struct Plan { struct Plan {
match_request: Vec<MatchRequest>, match_request: Vec<MatchRequest>,
response: Response, response: Response,
@ -57,9 +60,9 @@ impl Plan {
} }
/// An abstraction for the network /// An abstraction for the network
#[derive(Clone)] #[derive(Debug, Clone, Default)]
pub struct Net { pub struct Net {
plans: Option<Rc<RefCell<Plans>>>, plans: Option<Arc<Mutex<RefCell<Plans>>>>,
} }
impl Net { impl Net {
/// Creates a new unmocked [Net] for creating real network requests. /// Creates a new unmocked [Net] for creating real network requests.
@ -68,9 +71,9 @@ impl Net {
} }
/// Creats a new [MockNet] for use in tests. /// Creats a new [MockNet] for use in tests.
pub(super) const fn mock() -> MockNet { pub(super) fn mock() -> MockNet {
MockNet { MockNet {
plans: RefCell::new(vec![]), plans: Rc::new(RefCell::new(vec![])),
} }
} }
} }
@ -114,27 +117,28 @@ impl Net {
}; };
let request = request.into().build()?; let request = request.into().build()?;
let index = plans let index = plans
.lock()
.await
.deref()
.borrow() .borrow()
.iter() .iter()
.position(|plan| plan.matches(&request)); .position(|plan| plan.matches(&request));
match index { match index {
Some(i) => { Some(i) => {
let response = plans.borrow_mut().remove(i).response; let response = plans.lock().await.borrow_mut().remove(i).response;
Ok(response) Ok(response)
} }
None => Err(Error::UnexpectedMockRequest(request)), None => Err(Error::UnexpectedMockRequest(request)),
} }
} }
} }
impl TryFrom<Net> for MockNet { impl MockNet {
type Error = super::Error; pub async fn try_from(net: Net) -> std::result::Result<Self, super::Error> {
fn try_from(net: Net) -> std::result::Result<Self, Self::Error> {
match &net.plans { match &net.plans {
Some(plans) => Ok(MockNet { Some(plans) => Ok(MockNet {
plans: RefCell::new(plans.take()), plans: Rc::new(RefCell::new(plans.lock().await.take())),
}), }),
None => Err(Self::Error::NetIsNotAMock), None => Err(super::Error::NetIsNotAMock),
} }
} }
} }
@ -150,7 +154,8 @@ impl TryFrom<Net> for MockNet {
/// ```rust /// ```rust
/// use kxio::net::{Method, Url}; /// use kxio::net::{Method, Url};
/// # use kxio::net::Result; /// # use kxio::net::Result;
/// # fn run() -> Result<()> { /// # #[tokio::main]
/// # async fn run() -> Result<()> {
/// let mock_net = kxio::net::mock(); /// let mock_net = kxio::net::mock();
/// let client = mock_net.client(); /// let client = mock_net.client();
/// // define an expected requet, and the response that should be returned /// // define an expected requet, and the response that should be returned
@ -162,13 +167,14 @@ impl TryFrom<Net> for MockNet {
/// ///
/// // In some rare cases you don't want to assert that all expected requests were made. /// // In some rare cases you don't want to assert that all expected requests were made.
/// // You should recover the `MockNet` from the `Net` and `MockNet::reset` it. /// // You should recover the `MockNet` from the `Net` and `MockNet::reset` it.
/// let mock_net = kxio::net::MockNet::try_from(net)?; /// let mock_net = kxio::net::MockNet::try_from(net).await?;
/// mock_net.reset(); // only if explicitly needed /// mock_net.reset(); // only if explicitly needed
/// # Ok(()) /// # Ok(())
/// # } /// # }
/// ``` /// ```
#[derive(Debug, Clone, Default)]
pub struct MockNet { pub struct MockNet {
plans: RefCell<Plans>, plans: Rc<RefCell<Plans>>,
} }
impl MockNet { impl MockNet {
/// Helper to create a default [Client]. /// Helper to create a default [Client].
@ -222,10 +228,11 @@ impl MockNet {
/// ///
/// ```rust /// ```rust
/// # use kxio::net::Result; /// # use kxio::net::Result;
/// # fn run() -> Result<()> { /// # #[tokio::main]
/// # async fn run() -> Result<()> {
/// # let mock_net = kxio::net::mock(); /// # let mock_net = kxio::net::mock();
/// # let net: kxio::net::Net = mock_net.into(); /// # let net: kxio::net::Net = mock_net.into();
/// let mock_net = kxio::net::MockNet::try_from(net)?; /// let mock_net = kxio::net::MockNet::try_from(net).await?;
/// mock_net.reset(); // only if explicitly needed /// mock_net.reset(); // only if explicitly needed
/// # Ok(()) /// # Ok(())
/// # } /// # }
@ -239,7 +246,7 @@ impl From<MockNet> for Net {
Self { Self {
// keep the original `inner` around to allow it's Drop impelmentation to run when we go // keep the original `inner` around to allow it's Drop impelmentation to run when we go
// out of scope at the end of the test // out of scope at the end of the test
plans: Some(Rc::new(RefCell::new(mock_net.plans.take()))), plans: Some(Arc::new(Mutex::new(RefCell::new(mock_net.plans.take())))),
} }
} }
} }
@ -252,11 +259,12 @@ impl Drop for MockNet {
impl Drop for Net { impl Drop for Net {
fn drop(&mut self) { fn drop(&mut self) {
if let Some(plans) = &self.plans { if let Some(plans) = &self.plans {
assert!(plans.borrow().is_empty()) assert!(plans.try_lock().expect("lock plans").take().is_empty())
} }
} }
} }
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum MatchRequest { pub enum MatchRequest {
Method(Method), Method(Method),
Url(Url), Url(Url),
@ -264,6 +272,7 @@ pub enum MatchRequest {
Body(String), Body(String),
} }
#[derive(Debug, Clone)]
pub struct WhenRequest<'net> { pub struct WhenRequest<'net> {
net: &'net MockNet, net: &'net MockNet,
match_on: Vec<MatchRequest>, match_on: Vec<MatchRequest>,
@ -302,3 +311,19 @@ impl<'net> WhenRequest<'net> {
} }
} }
} }
#[cfg(test)]
mod tests {
use super::*;
fn is_normal<T: Sized + Send + Sync + Unpin>() {}
#[test]
fn normal_types() {
is_normal::<Net>();
// is_normal::<MockNet>(); // only used in test setup - no need to be Send or Sync
is_normal::<MatchRequest>();
is_normal::<Plan>();
// is_normal::<WhenRequest>(); // only used in test setup - no need to be Send or Sync
}
}

View file

@ -62,7 +62,7 @@ async fn test_get_wrong_url() {
assert_eq!(invalid_request.url().to_string(), "https://some.other.url/"); assert_eq!(invalid_request.url().to_string(), "https://some.other.url/");
// remove pending unmatched request - we never meant to match against it // remove pending unmatched request - we never meant to match against it
let mock_net = MockNet::try_from(net).expect("recover net"); let mock_net = MockNet::try_from(net).await.expect("recover net");
mock_net.reset(); mock_net.reset();
} }
@ -210,7 +210,7 @@ async fn test_post_by_header_wrong_value() {
//then //then
let_assert!(Err(kxio::net::Error::UnexpectedMockRequest(_)) = response); let_assert!(Err(kxio::net::Error::UnexpectedMockRequest(_)) = response);
MockNet::try_from(net).expect("recover mock").reset(); MockNet::try_from(net).await.expect("recover mock").reset();
} }
#[tokio::test] #[tokio::test]