feat(net): add tracing
This commit is contained in:
parent
6e5ea556a9
commit
436ad890d8
4 changed files with 64 additions and 44 deletions
|
@ -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()
|
||||||
}
|
}
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
//
|
//
|
||||||
|
|
||||||
use std::{
|
use std::{
|
||||||
cell::RefCell,
|
cell::RefCell,
|
||||||
collections::HashMap,
|
collections::HashMap,
|
||||||
|
@ -33,36 +32,38 @@ 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();
|
||||||
self.match_request.iter().all(|criteria| match criteria {
|
let is_match =
|
||||||
MatchRequest::Body(body) => {
|
self.match_request.iter().all(|criteria| match criteria {
|
||||||
request.body().and_then(reqwest::Body::as_bytes) == Some(body)
|
MatchRequest::Body(body) => {
|
||||||
}
|
request.body().and_then(reqwest::Body::as_bytes) == Some(body)
|
||||||
MatchRequest::Header { name, value } => {
|
}
|
||||||
request
|
MatchRequest::Header { name, value } => {
|
||||||
.headers()
|
request
|
||||||
.iter()
|
.headers()
|
||||||
.any(|(request_header_name, request_header_value)| {
|
.iter()
|
||||||
let Ok(request_header_value) = request_header_value.to_str() else {
|
.any(|(request_header_name, request_header_value)| {
|
||||||
return false;
|
let Ok(request_header_value) = request_header_value.to_str() else {
|
||||||
};
|
return false;
|
||||||
request_header_name.as_str() == name && request_header_value == value
|
};
|
||||||
})
|
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::Method(method) => request.method() == http::Method::from(method),
|
||||||
MatchRequest::Host(host) => url.host_str() == Some(host),
|
MatchRequest::Scheme(scheme) => url.scheme() == scheme,
|
||||||
MatchRequest::Path(path) => url.path() == path,
|
MatchRequest::Host(host) => url.host_str() == Some(host),
|
||||||
MatchRequest::Fragment(fragment) => url.fragment() == Some(fragment),
|
MatchRequest::Path(path) => url.path() == path,
|
||||||
MatchRequest::Query { name, value } => {
|
MatchRequest::Fragment(fragment) => url.fragment() == Some(fragment),
|
||||||
url.query_pairs()
|
MatchRequest::Query { name, value } => url.query_pairs().into_iter().any(
|
||||||
.into_iter()
|
|(request_query_name, request_query_value)| {
|
||||||
.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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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();
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue