feat(net): add tracing
All checks were successful
Test / build (map[name:nightly]) (push) Successful in 3m38s
Test / build (map[name:stable]) (push) Successful in 5m4s
Release Please / Release-plz (push) Successful in 38s

This commit is contained in:
Paul Campbell 2024-12-04 22:38:44 +00:00
parent 6e5ea556a9
commit 436ad890d8
4 changed files with 64 additions and 44 deletions

View file

@ -161,6 +161,7 @@ pub const fn new() -> Net {
} }
/// Creates a new `MockNet` for use in tests. /// Creates a new `MockNet` for use in tests.
#[tracing::instrument]
pub fn mock() -> MockNet { pub fn mock() -> MockNet {
Default::default() Default::default()
} }

View file

@ -23,8 +23,10 @@ pub enum Error {
Url(url::ParseError), Url(url::ParseError),
/// There was network request that doesn't match any that were expected /// There was network request that doesn't match any that were expected
#[display("Unexpected request: {0}", 0.to_string())] #[display("Unexpected request: {} {}", request.method(), request.url())]
UnexpectedMockRequest(Request), UnexpectedMockRequest {
request: Request,
},
/// There was an error accessing the list of expected requests. /// There was an error accessing the list of expected requests.
RwLockLocked, RwLockLocked,

View file

@ -1,5 +1,4 @@
// //
use std::{ use std::{
cell::RefCell, cell::RefCell,
collections::HashMap, collections::HashMap,
@ -33,8 +32,10 @@ struct Plan {
response: reqwest::Response, response: reqwest::Response,
} }
impl Plan { impl Plan {
#[tracing::instrument]
fn matches(&self, request: &Request) -> bool { fn matches(&self, request: &Request) -> bool {
let url = request.url(); let url = request.url();
let is_match =
self.match_request.iter().all(|criteria| match criteria { self.match_request.iter().all(|criteria| match criteria {
MatchRequest::Body(body) => { MatchRequest::Body(body) => {
request.body().and_then(reqwest::Body::as_bytes) == Some(body) request.body().and_then(reqwest::Body::as_bytes) == Some(body)
@ -55,14 +56,14 @@ impl Plan {
MatchRequest::Host(host) => url.host_str() == Some(host), MatchRequest::Host(host) => url.host_str() == Some(host),
MatchRequest::Path(path) => url.path() == path, MatchRequest::Path(path) => url.path() == path,
MatchRequest::Fragment(fragment) => url.fragment() == Some(fragment), MatchRequest::Fragment(fragment) => url.fragment() == Some(fragment),
MatchRequest::Query { name, value } => { MatchRequest::Query { name, value } => url.query_pairs().into_iter().any(
url.query_pairs() |(request_query_name, request_query_value)| {
.into_iter()
.any(|(request_query_name, request_query_value)| {
request_query_name.as_ref() == name && request_query_value.as_ref() == value request_query_name.as_ref() == name && request_query_value.as_ref() == value
}) },
} ),
}) });
tracing::debug!(?is_match);
is_match
} }
} }
impl Display for Plan { impl Display for Plan {
@ -129,17 +130,16 @@ impl Net {
/// # Ok(()) /// # Ok(())
/// # } /// # }
/// ``` /// ```
#[tracing::instrument(skip_all)]
pub async fn send(&self, request: impl Into<RequestBuilder>) -> Result<Response> { pub async fn send(&self, request: impl Into<RequestBuilder>) -> Result<Response> {
let request: RequestBuilder = request.into();
tracing::debug!(?request);
let Some(plans) = &self.plans else { 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()?; tracing::debug!("build request");
eprintln!( let request = request.build()?;
"? {} {} {:?}",
request.method(),
request.url(),
request.headers()
);
let index = plans let index = plans
.lock() .lock()
.await .await
@ -150,15 +150,19 @@ impl Net {
match index { match index {
Some(i) => { Some(i) => {
let plan = plans.lock().await.borrow_mut().remove(i); let plan = plans.lock().await.borrow_mut().remove(i);
eprintln!("- matched: {plan}");
let response = plan.response; let response = plan.response;
if response.status().is_success() { if response.status().is_success() {
tracing::debug!(?request, "matched success response");
Ok(response) Ok(response)
} else { } else {
tracing::debug!(?request, "matched error response");
Err(crate::net::Error::ResponseError { 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(()) /// # Ok(())
/// # } /// # }
/// ``` /// ```
#[tracing::instrument(skip_all)]
pub async fn send(self) -> Result<Response> { pub async fn send(self) -> Result<Response> {
let client = self.net.client(); let client = self.net.client();
// URL // URL
let mut url = self.url; let mut url = self.url;
tracing::trace!(?url);
// Query Parameters // Query Parameters
if !self.query.is_empty() { if !self.query.is_empty() {
url.push('?'); url.push('?');
for (i, (name, value)) in self.query.into_iter().enumerate() { for (i, (name, value)) in self.query.into_iter().enumerate() {
tracing::trace!(?name, ?value, "query parameters");
if i > 0 { if i > 0 {
url.push('&'); url.push('&');
} }
@ -391,6 +398,7 @@ impl<'net> ReqBuilder<'net> {
url.push('='); url.push('=');
url.push_str(&value); url.push_str(&value);
} }
tracing::trace!(?url, "with query parameters");
} }
// Method // Method
let mut req = match self.method { let mut req = match self.method {
@ -409,7 +417,7 @@ impl<'net> ReqBuilder<'net> {
if let Some(bytes) = self.body { if let Some(bytes) = self.body {
req = req.body(bytes); req = req.body(bytes);
} }
tracing::debug!(?req);
self.net.send(req).await self.net.send(req).await
} }
@ -588,6 +596,7 @@ impl MockNet {
/// # } /// # }
/// ``` /// ```
pub fn reset(&self) { pub fn reset(&self) {
tracing::debug!("reset plans");
self.plans.take(); self.plans.take();
} }
} }
@ -602,19 +611,23 @@ impl From<MockNet> for Net {
} }
impl Drop for MockNet { impl Drop for MockNet {
#[tracing::instrument]
fn drop(&mut self) { fn drop(&mut self) {
let unused = self.plans.take(); let unused = self.plans.take();
if unused.is_empty() { if unused.is_empty() {
tracing::trace!("no unused expected requests");
return; // all good return; // all good
} }
panic_with_unused_plans(unused); panic_with_unused_plans(unused);
} }
} }
impl Drop for Net { impl Drop for Net {
#[tracing::instrument]
fn drop(&mut self) { fn drop(&mut self) {
if let Some(plans) = &self.plans { if let Some(plans) = &self.plans {
let unused = plans.try_lock().expect("lock plans").take(); let unused = plans.try_lock().expect("lock plans").take();
if unused.is_empty() { if unused.is_empty() {
tracing::trace!("no unused expected requests");
return; // all good return; // all good
} }
panic_with_unused_plans(unused); panic_with_unused_plans(unused);
@ -672,9 +685,11 @@ impl std::error::Error for MockError {}
pub trait WhenState {} pub trait WhenState {}
#[derive(Debug)]
pub struct WhenBuildRequest; pub struct WhenBuildRequest;
impl WhenState for WhenBuildRequest {} impl WhenState for WhenBuildRequest {}
#[derive(Debug)]
pub struct WhenBuildResponse; pub struct WhenBuildResponse;
impl WhenState for WhenBuildResponse {} impl WhenState for WhenBuildResponse {}
@ -737,6 +752,7 @@ impl<'net> WhenRequest<'net, WhenBuildRequest> {
self._url(NetMethod::Patch, url) self._url(NetMethod::Patch, url)
} }
#[tracing::instrument(skip_all)]
fn _url(mut self, method: NetMethod, url: impl Into<String>) -> Self { fn _url(mut self, method: NetMethod, url: impl Into<String>) -> Self {
self.match_on.push(MatchRequest::Method(method)); self.match_on.push(MatchRequest::Method(method));
match Url::parse(&url.into()) { match Url::parse(&url.into()) {
@ -774,6 +790,7 @@ impl<'net> WhenRequest<'net, WhenBuildRequest> {
self.error.replace(err.into()); self.error.replace(err.into());
} }
} }
tracing::debug!(match_on = ?self.match_on, error = ?self.error);
self self
} }

View file

@ -214,12 +214,12 @@ async fn test_get_wrong_url() {
//when //when
let_assert!( let_assert!(
Err(Error::UnexpectedMockRequest(invalid_request)) = Err(Error::UnexpectedMockRequest { request }) =
net.send(client.get("https://some.other.url/")).await net.send(client.get("https://some.other.url/")).await
); );
//then //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 // remove pending unmatched request - we never meant to match against it
let mock_net = MockNet::try_from(net).await.expect("recover net"); let mock_net = MockNet::try_from(net).await.expect("recover net");
@ -329,7 +329,7 @@ async fn test_post_by_header_wrong_value() {
.await; .await;
//then //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(); MockNet::try_from(net).await.expect("recover mock").reset();
} }