From 436ad890d8094707133396835c1b184272387848 Mon Sep 17 00:00:00 2001 From: Paul Campbell Date: Wed, 4 Dec 2024 22:38:44 +0000 Subject: [PATCH] feat(net): add tracing --- src/net/mod.rs | 1 + src/net/result.rs | 6 ++- src/net/system.rs | 95 ++++++++++++++++++++++++++++------------------- tests/net.rs | 6 +-- 4 files changed, 64 insertions(+), 44 deletions(-) diff --git a/src/net/mod.rs b/src/net/mod.rs index 8d28277..cd87a70 100644 --- a/src/net/mod.rs +++ b/src/net/mod.rs @@ -161,6 +161,7 @@ pub const fn new() -> Net { } /// Creates a new `MockNet` for use in tests. +#[tracing::instrument] pub fn mock() -> MockNet { Default::default() } diff --git a/src/net/result.rs b/src/net/result.rs index 754ee5c..fa1bb66 100644 --- a/src/net/result.rs +++ b/src/net/result.rs @@ -23,8 +23,10 @@ pub enum Error { Url(url::ParseError), /// There was network request that doesn't match any that were expected - #[display("Unexpected request: {0}", 0.to_string())] - UnexpectedMockRequest(Request), + #[display("Unexpected request: {} {}", request.method(), request.url())] + UnexpectedMockRequest { + request: Request, + }, /// There was an error accessing the list of expected requests. RwLockLocked, diff --git a/src/net/system.rs b/src/net/system.rs index 3619609..b3ca2d5 100644 --- a/src/net/system.rs +++ b/src/net/system.rs @@ -1,5 +1,4 @@ // - use std::{ cell::RefCell, collections::HashMap, @@ -33,36 +32,38 @@ struct Plan { response: reqwest::Response, } impl Plan { + #[tracing::instrument] fn matches(&self, request: &Request) -> bool { let url = request.url(); - self.match_request.iter().all(|criteria| match criteria { - MatchRequest::Body(body) => { - request.body().and_then(reqwest::Body::as_bytes) == Some(body) - } - MatchRequest::Header { name, value } => { - request - .headers() - .iter() - .any(|(request_header_name, request_header_value)| { - let Ok(request_header_value) = request_header_value.to_str() else { - return false; - }; - request_header_name.as_str() == name && request_header_value == value - }) - } - MatchRequest::Method(method) => request.method() == http::Method::from(method), - MatchRequest::Scheme(scheme) => url.scheme() == scheme, - MatchRequest::Host(host) => url.host_str() == Some(host), - MatchRequest::Path(path) => url.path() == path, - MatchRequest::Fragment(fragment) => url.fragment() == Some(fragment), - MatchRequest::Query { name, value } => { - url.query_pairs() - .into_iter() - .any(|(request_query_name, request_query_value)| { + let is_match = + self.match_request.iter().all(|criteria| match criteria { + MatchRequest::Body(body) => { + request.body().and_then(reqwest::Body::as_bytes) == Some(body) + } + MatchRequest::Header { name, value } => { + request + .headers() + .iter() + .any(|(request_header_name, request_header_value)| { + let Ok(request_header_value) = request_header_value.to_str() else { + return false; + }; + request_header_name.as_str() == name && request_header_value == value + }) + } + MatchRequest::Method(method) => request.method() == http::Method::from(method), + MatchRequest::Scheme(scheme) => url.scheme() == scheme, + MatchRequest::Host(host) => url.host_str() == Some(host), + MatchRequest::Path(path) => url.path() == path, + MatchRequest::Fragment(fragment) => url.fragment() == Some(fragment), + MatchRequest::Query { name, value } => url.query_pairs().into_iter().any( + |(request_query_name, request_query_value)| { request_query_name.as_ref() == name && request_query_value.as_ref() == value - }) - } - }) + }, + ), + }); + tracing::debug!(?is_match); + is_match } } impl Display for Plan { @@ -129,17 +130,16 @@ impl Net { /// # Ok(()) /// # } /// ``` + #[tracing::instrument(skip_all)] pub async fn send(&self, request: impl Into) -> Result { + let request: RequestBuilder = request.into(); + tracing::debug!(?request); let Some(plans) = &self.plans else { - return request.into().send().await.map_err(Error::from); + tracing::debug!("no plans - sending real request"); + return request.send().await.map_err(Error::from); }; - let request = request.into().build()?; - eprintln!( - "? {} {} {:?}", - request.method(), - request.url(), - request.headers() - ); + tracing::debug!("build request"); + let request = request.build()?; let index = plans .lock() .await @@ -150,15 +150,19 @@ impl Net { match index { Some(i) => { let plan = plans.lock().await.borrow_mut().remove(i); - eprintln!("- matched: {plan}"); let response = plan.response; if response.status().is_success() { + tracing::debug!(?request, "matched success response"); Ok(response) } else { + tracing::debug!(?request, "matched error response"); Err(crate::net::Error::ResponseError { response }) } } - None => Err(Error::UnexpectedMockRequest(request)), + None => { + tracing::warn!(?request, "unexpected mock request"); + Err(Error::UnexpectedMockRequest { request }) + } } } @@ -376,14 +380,17 @@ impl<'net> ReqBuilder<'net> { /// # Ok(()) /// # } /// ``` + #[tracing::instrument(skip_all)] pub async fn send(self) -> Result { let client = self.net.client(); // URL let mut url = self.url; + tracing::trace!(?url); // Query Parameters if !self.query.is_empty() { url.push('?'); for (i, (name, value)) in self.query.into_iter().enumerate() { + tracing::trace!(?name, ?value, "query parameters"); if i > 0 { url.push('&'); } @@ -391,6 +398,7 @@ impl<'net> ReqBuilder<'net> { url.push('='); url.push_str(&value); } + tracing::trace!(?url, "with query parameters"); } // Method let mut req = match self.method { @@ -409,7 +417,7 @@ impl<'net> ReqBuilder<'net> { if let Some(bytes) = self.body { req = req.body(bytes); } - + tracing::debug!(?req); self.net.send(req).await } @@ -588,6 +596,7 @@ impl MockNet { /// # } /// ``` pub fn reset(&self) { + tracing::debug!("reset plans"); self.plans.take(); } } @@ -602,19 +611,23 @@ impl From for Net { } impl Drop for MockNet { + #[tracing::instrument] fn drop(&mut self) { let unused = self.plans.take(); if unused.is_empty() { + tracing::trace!("no unused expected requests"); return; // all good } panic_with_unused_plans(unused); } } impl Drop for Net { + #[tracing::instrument] fn drop(&mut self) { if let Some(plans) = &self.plans { let unused = plans.try_lock().expect("lock plans").take(); if unused.is_empty() { + tracing::trace!("no unused expected requests"); return; // all good } panic_with_unused_plans(unused); @@ -672,9 +685,11 @@ impl std::error::Error for MockError {} pub trait WhenState {} +#[derive(Debug)] pub struct WhenBuildRequest; impl WhenState for WhenBuildRequest {} +#[derive(Debug)] pub struct WhenBuildResponse; impl WhenState for WhenBuildResponse {} @@ -737,6 +752,7 @@ impl<'net> WhenRequest<'net, WhenBuildRequest> { self._url(NetMethod::Patch, url) } + #[tracing::instrument(skip_all)] fn _url(mut self, method: NetMethod, url: impl Into) -> Self { self.match_on.push(MatchRequest::Method(method)); match Url::parse(&url.into()) { @@ -774,6 +790,7 @@ impl<'net> WhenRequest<'net, WhenBuildRequest> { self.error.replace(err.into()); } } + tracing::debug!(match_on = ?self.match_on, error = ?self.error); self } diff --git a/tests/net.rs b/tests/net.rs index 48bd2c2..867a2f9 100644 --- a/tests/net.rs +++ b/tests/net.rs @@ -214,12 +214,12 @@ async fn test_get_wrong_url() { //when let_assert!( - Err(Error::UnexpectedMockRequest(invalid_request)) = + Err(Error::UnexpectedMockRequest { request }) = net.send(client.get("https://some.other.url/")).await ); //then - assert_eq!(invalid_request.url().to_string(), "https://some.other.url/"); + assert_eq!(request.url().to_string(), "https://some.other.url/"); // remove pending unmatched request - we never meant to match against it let mock_net = MockNet::try_from(net).await.expect("recover net"); @@ -329,7 +329,7 @@ async fn test_post_by_header_wrong_value() { .await; //then - let_assert!(Err(kxio::net::Error::UnexpectedMockRequest(_)) = response); + let_assert!(Err(kxio::net::Error::UnexpectedMockRequest { request: _ }) = response); MockNet::try_from(net).await.expect("recover mock").reset(); }