diff --git a/Cargo.toml b/Cargo.toml index 183f02a..7b2c872 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,18 +14,15 @@ unexpected_cfgs = { level = "warn", check-cfg = ['cfg(tarpaulin_include)'] } # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -async-trait = "0.1" -derive_more = { version = "1.0", features = [ "from", "display", "constructor" ] } +derive_more = { version = "1.0", features = [ + "from", + "display", + "constructor" +] } http = "1.1" path-clean = "1.0" reqwest = "0.12" -secrecy = "0.10" -serde = { version = "1.0", features = ["derive"] } -serde_json = "1.0" -serde-xml-rs = "0.6" tempfile = "3.10" -thiserror = "2.0" -tracing = "0.1" [dev-dependencies] assert2 = "0.3" diff --git a/src/lib.rs b/src/lib.rs index 82273b6..1d24bc8 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,6 +1,3 @@ // pub mod fs; pub mod net; - -#[deprecated] -pub mod network; diff --git a/src/network/mock.rs b/src/network/mock.rs deleted file mode 100644 index 2416ef1..0000000 --- a/src/network/mock.rs +++ /dev/null @@ -1,795 +0,0 @@ -#![cfg(not(tarpaulin_include))] - -use serde::de::DeserializeOwned; -use tracing::{event, Level}; - -use std::collections::HashMap; -use std::sync::{Arc, Mutex}; - -use crate::network::StatusCode; - -use super::network_env::NetworkTrait; -use super::{ - NetRequest, NetResponse, NetUrl, Network, NetworkError, RequestBody, RequestMethod, - ResponseType, SavedRequest, -}; - -#[derive(Debug, Clone)] -pub struct MockNetwork { - requests: Arc>>, - get_responses: HashMap, - get_errors: HashMap, - post_responses: HashMap, - post_errors: HashMap, - put_responses: HashMap, - put_errors: HashMap, - patch_responses: HashMap, - patch_errors: HashMap, - delete_responses: HashMap, - delete_errors: HashMap, - propfind_responses: HashMap, - propfind_errors: HashMap, -} -impl MockNetwork { - pub fn new() -> Self { - Self { - requests: Arc::new(Mutex::new(Vec::new())), - get_responses: HashMap::new(), - get_errors: HashMap::new(), - post_responses: HashMap::new(), - post_errors: HashMap::new(), - put_responses: HashMap::new(), - put_errors: HashMap::new(), - patch_responses: HashMap::new(), - patch_errors: HashMap::new(), - delete_responses: HashMap::new(), - delete_errors: HashMap::new(), - propfind_responses: HashMap::new(), - propfind_errors: HashMap::new(), - } - } - pub fn requests(&self) -> Vec { - unsafe { self.requests.lock().unwrap_unchecked().clone() } - } - pub fn add_get_response(&mut self, url: &str, status: StatusCode, body: &str) { - self.get_responses - .insert(NetUrl::new(url.to_string()), (status, body.to_string())); - } - pub fn add_get_error(&mut self, url: &str, error: &str) { - self.get_errors - .insert(NetUrl::new(url.to_string()), error.to_string()); - } - pub fn add_post_response(&mut self, url: &str, status: StatusCode, body: &str) { - self.post_responses - .insert(NetUrl::new(url.to_string()), (status, body.to_string())); - } - pub fn add_post_error(&mut self, url: &str, error: &str) { - self.post_errors - .insert(NetUrl::new(url.to_string()), error.to_string()); - } - pub fn add_put_response(&mut self, url: &str, status: StatusCode, body: &str) { - self.put_responses - .insert(NetUrl::new(url.to_string()), (status, body.to_string())); - } - pub fn add_put_error(&mut self, url: &str, error: &str) { - self.put_errors - .insert(NetUrl::new(url.to_string()), error.to_string()); - } - pub fn add_patch_response(&mut self, url: &str, status: StatusCode, body: &str) { - self.patch_responses - .insert(NetUrl::new(url.to_string()), (status, body.to_string())); - } - pub fn add_patch_error(&mut self, url: &str, error: &str) { - self.patch_errors - .insert(NetUrl::new(url.to_string()), error.to_string()); - } - pub fn add_delete_response(&mut self, url: &str, status: StatusCode, body: &str) { - self.delete_responses - .insert(NetUrl::new(url.to_string()), (status, body.to_string())); - } - pub fn add_delete_error(&mut self, url: &str, error: &str) { - self.delete_errors - .insert(NetUrl::new(url.to_string()), error.to_string()); - } - pub fn add_propfind_response(&mut self, url: &str, status: StatusCode, body: &str) { - self.propfind_responses - .insert(NetUrl::new(url.to_string()), (status, body.to_string())); - } - pub fn add_propfind_error(&mut self, url: &str, error: &str) { - self.propfind_errors - .insert(NetUrl::new(url.to_string()), error.to_string()); - } - fn save_request(&self, method: RequestMethod, url: &str, body: RequestBody) { - unsafe { - self.requests - .lock() - .unwrap_unchecked() - .push(SavedRequest::new(method, url, body)); - } - } - - #[tracing::instrument(skip_all)] - fn call( - &self, - net_request: NetRequest, - ) -> Result, NetworkError> { - tracing::info!("MockNetworkEnv::call({:?})", net_request); - let method = net_request.method(); - let url = net_request.url(); - let body = net_request.body(); - let response_type = net_request.response_type(); - self.save_request(method, url, body.clone()); - let errors = match method { - RequestMethod::Get => &self.get_errors, - RequestMethod::Post => &self.post_errors, - RequestMethod::Put => &self.put_errors, - RequestMethod::Patch => &self.patch_errors, - RequestMethod::Propfind => &self.propfind_errors, - RequestMethod::Delete => &self.delete_errors, - }; - if let Some(error) = errors.get(url) { - event!( - Level::INFO, - "MockNetworkEnv::{}({}) -> error: {}", - method, - **url, - error - ); - Err(NetworkError::RequestError( - method, - StatusCode::INTERNAL_SERVER_ERROR, - url.clone(), - )) - } else { - let responses = match method { - RequestMethod::Get => &self.get_responses, - RequestMethod::Post => &self.post_responses, - RequestMethod::Put => &self.put_responses, - RequestMethod::Patch => &self.patch_responses, - RequestMethod::Propfind => &self.propfind_responses, - RequestMethod::Delete => &self.delete_responses, - }; - let (status, response) = responses.get(url).ok_or_else(|| { - tracing::error!(?method, ?url, "unexpected request"); - NetworkError::MockError(method, StatusCode::NOT_IMPLEMENTED, url.clone()) - })?; - if status.is_client_error() || status.is_server_error() { - event!( - Level::INFO, - "MockNetworkEnv::{}({}) -> error: {}", - method, - url, - response - ); - Err(NetworkError::RequestError(method, *status, url.clone())) - } else { - event!( - Level::INFO, - "MockNetworkEnv::{}({}) -> response: {}", - method, - url, - response - ); - let response_body: Option = match response_type { - ResponseType::Json => Some(serde_json::from_str(response)?), - ResponseType::Xml => Some(serde_xml_rs::from_str(response)?), - ResponseType::None => None, - ResponseType::Text => { - // use call_string() instead - Err(NetworkError::UnexpectedMockRequest { - method, - request_url: url.clone(), - })? - } - }; - Ok(NetResponse::new(method, url, *status, response_body)) - } - } - } - - fn call_string(&self, net_request: NetRequest) -> Result, NetworkError> { - let method = net_request.method(); - let url = net_request.url(); - let body = net_request.body(); - - match body { - RequestBody::String(_) => Ok(()), - RequestBody::None => Ok(()), - _ => Err(NetworkError::InvalidRequestBody), - }?; - - self.save_request(method, url, body.clone()); - event!(Level::INFO, "MockNetworkEnv::{}({})", method, url); - self.check_for_error(method, url).map_or_else( - || self.as_response(method, url, &net_request), - |error| as_server_error(method, url, error), - ) - } - - fn as_response( - &self, - method: RequestMethod, - url: &NetUrl, - net_request: &NetRequest, - ) -> Result, NetworkError> { - event!(Level::INFO, "url: {}", url); - let (status, response) = self.check_for_response(method, url).ok_or_else(|| { - NetworkError::MockError(method, StatusCode::NOT_IMPLEMENTED, url.clone()) - })?; - event!(Level::INFO, "status: {:?}", status); - let response_body = match net_request.response_type() { - ResponseType::None => None, - _ => { - let response_body: String = response.to_string(); - event!(Level::INFO, "response_body: {:?}", response_body); - Some(response_body) - } - }; - Ok(NetResponse::new(method, url, *status, response_body)) - } - - fn check_for_response( - &self, - method: RequestMethod, - url: &NetUrl, - ) -> Option<&(StatusCode, String)> { - (match method { - RequestMethod::Get => &self.get_responses, - RequestMethod::Post => &self.post_responses, - RequestMethod::Put => &self.put_responses, - RequestMethod::Patch => &self.patch_responses, - RequestMethod::Propfind => &self.propfind_responses, - RequestMethod::Delete => &self.delete_responses, - }) - .get(url) - } - - fn check_for_error(&self, method: RequestMethod, url: &NetUrl) -> Option<&String> { - (match method { - RequestMethod::Get => &self.get_errors, - RequestMethod::Post => &self.post_errors, - RequestMethod::Put => &self.put_errors, - RequestMethod::Patch => &self.patch_errors, - RequestMethod::Propfind => &self.propfind_errors, - RequestMethod::Delete => &self.delete_errors, - }) - .get(url) - } -} - -fn as_server_error( - method: RequestMethod, - url: &NetUrl, - error: &String, -) -> Result, NetworkError> { - event!( - Level::INFO, - "MockNetworkEnv::{}({}) -> error: {}", - method, - url, - error - ); - Err(NetworkError::RequestError( - method, - StatusCode::INTERNAL_SERVER_ERROR, - url.clone(), - )) -} -impl Default for MockNetwork { - fn default() -> Self { - Self::new() - } -} -impl From for Network { - fn from(mock: MockNetwork) -> Self { - Self::Mock(mock) - } -} - -#[async_trait::async_trait] -impl NetworkTrait for MockNetwork { - async fn get( - &self, - net_request: NetRequest, - ) -> Result, NetworkError> { - assert_eq!( - net_request.method(), - RequestMethod::Get, - "get method must be RequestMethod::Get" - ); - self.call(net_request) - } - - async fn get_string( - &self, - net_request: NetRequest, - ) -> Result, NetworkError> { - assert_eq!( - net_request.method(), - RequestMethod::Get, - "get_string method must be RequestMethod::Get" - ); - assert_eq!( - net_request.response_type(), - ResponseType::Text, - "get_string response_type must be ResponseType::Text" - ); - self.call_string(net_request) - } - - async fn post_json( - &self, - net_request: NetRequest, - ) -> Result, NetworkError> { - assert_eq!( - net_request.method(), - RequestMethod::Post, - "post method must be RequestMethod::Post" - ); - assert!( - matches!(net_request.body(), RequestBody::Json(_)), - "request body must be RequestBody::Json" - ); - self.call(net_request) - } - - async fn post_string( - &self, - net_request: NetRequest, - ) -> Result, NetworkError> { - assert_eq!( - net_request.method(), - RequestMethod::Post, - "post_string method must be RequestMethod::Post" - ); - assert_eq!( - net_request.response_type(), - ResponseType::Text, - "post_string response_type must be ResponseType::Text" - ); - self.call_string(net_request) - } - - async fn put_json( - &self, - net_request: NetRequest, - ) -> Result, NetworkError> { - assert_eq!( - net_request.method(), - RequestMethod::Put, - "put method must be RequestMethod::Put" - ); - assert!( - matches!(net_request.body(), RequestBody::Json(_)), - "request body must be RequestBody::Json" - ); - self.call(net_request) - } - - async fn put_string( - &self, - net_request: NetRequest, - ) -> Result, NetworkError> { - assert_eq!( - net_request.method(), - RequestMethod::Put, - "put_string method must be RequestMethod::Put" - ); - assert_eq!( - net_request.response_type(), - ResponseType::Text, - "put_string response_type must be ResponseType::Text" - ); - self.call_string(net_request) - } - - async fn patch_json( - &self, - net_request: NetRequest, - ) -> Result, NetworkError> { - assert_eq!( - net_request.method(), - RequestMethod::Patch, - "patch method must be RequestMethod::Patch" - ); - assert!( - matches!(net_request.body(), RequestBody::Json(_)), - "request body must be RequestBody::Json" - ); - self.call(net_request) - } - - #[tracing::instrument(skip(self))] - async fn delete(&self, net_request: NetRequest) -> Result, NetworkError> { - assert_eq!( - net_request.method(), - RequestMethod::Delete, - "delete method must be RequestMethod::Delete" - ); - assert_eq!( - net_request.response_type(), - ResponseType::None, - "delete response_type must be ResponseType::None" - ); - self.call(net_request) - } - - async fn propfind( - &self, - net_request: NetRequest, - ) -> Result, NetworkError> { - assert_eq!( - net_request.method(), - RequestMethod::Propfind, - "propfind method must be RequestMethod::Propfind" - ); - assert_eq!( - net_request.response_type(), - ResponseType::Xml, - "propfind response_type must be ResponseType::Xml" - ); - assert_eq!( - net_request.body(), - &RequestBody::None, - "delete body must be RequestBody::None" - ); - self.call(net_request) - } - - async fn propfind_string( - &self, - net_request: NetRequest, - ) -> Result, NetworkError> { - assert_eq!( - net_request.method(), - RequestMethod::Propfind, - "propfind_string method must be RequestMethod::Propfind" - ); - assert_eq!( - net_request.response_type(), - ResponseType::Text, - "propfind_string response_type must be ResponseType::Text" - ); - assert_eq!( - net_request.body(), - &RequestBody::None, - "delete body must be RequestBody::None" - ); - self.call_string(net_request) - } -} - -#[cfg(test)] -mod tests { - - use crate::network::{NetResponse, NetworkError, RequestMethod}; - - use super::*; - - use reqwest::StatusCode; - - use pretty_assertions::assert_eq; - use serde_json::json; - use tokio_test::block_on; - - #[test_log::test] - fn test_mock_network_env_get() -> Result<(), NetworkError> { - let mut net = MockNetwork::new(); - net.add_get_response("https://httpbin.org", StatusCode::OK, r#"{"foo": "bar"}"#); - let net_request = NetRequest::get(NetUrl::new("https://httpbin.org".into())).build(); - let response: NetResponse> = block_on(net.get(net_request))?; - assert_eq!( - response - .response_body() - .and_then(|body| body.get("foo").cloned()), - Some("bar".to_string()) - ); - assert_eq!( - net.requests(), - vec![SavedRequest::new( - RequestMethod::Get, - "https://httpbin.org", - RequestBody::None - )] - ); - Ok(()) - } - - #[test_log::test] - fn test_mock_network_env_get_error() -> Result<(), NetworkError> { - let mut net = MockNetwork::new(); - net.add_get_error("https://httpbin.org", "error"); - let net_request = NetRequest::get(NetUrl::new("https://httpbin.org".into())).build(); - let result: Result>, NetworkError> = - block_on(net.get(net_request)); - assert!(result.is_err()); - assert_eq!( - net.requests(), - vec![SavedRequest::new( - RequestMethod::Get, - "https://httpbin.org", - RequestBody::None - )] - ); - Ok(()) - } - - #[test_log::test] - fn test_mock_network_env_get_string() -> Result<(), NetworkError> { - let mut net = MockNetwork::new(); - net.add_get_response("https://httpbin.org", StatusCode::OK, r#"{"foo":"bar"}"#); - let net_request = NetRequest::get(NetUrl::new("https://httpbin.org".into())) - .response_type(ResponseType::Text) - .build(); - let response: NetResponse = block_on(net.get_string(net_request))?; - assert_eq!( - response.response_body(), - Some(json!({"foo":"bar"}).to_string()) - ); - assert_eq!( - net.requests(), - vec![SavedRequest::new( - RequestMethod::Get, - "https://httpbin.org", - RequestBody::None - )] - ); - Ok(()) - } - - #[test_log::test] - fn test_mock_network_env_get_string_error() { - let mut net = MockNetwork::new(); - net.add_get_error("https://httpbin.org", "error"); - let net_request = NetRequest::get(NetUrl::new("https://httpbin.org".into())) - .response_type(ResponseType::Text) - .build(); - let result: Result, NetworkError> = - block_on(net.get_string(net_request)); - assert!(result.is_err()); - assert_eq!( - net.requests(), - vec![SavedRequest::new( - RequestMethod::Get, - "https://httpbin.org", - RequestBody::None - )] - ); - } - - #[test_log::test] - fn test_mock_network_env_post() -> Result<(), NetworkError> { - let mut net = MockNetwork::new(); - net.add_post_response("https://httpbin.org", StatusCode::OK, r#"{"foo": "bar"}"#); - let net_request = NetRequest::post(NetUrl::new("https://httpbin.org".into())) - .json_body(json!({}))? - .build(); - let response: NetResponse> = block_on(net.post_json(net_request))?; - assert_eq!( - response - .response_body() - .and_then(|body| body.get("foo").cloned()), - Some("bar".to_string()) - ); - assert_eq!( - net.requests(), - vec![SavedRequest::new( - RequestMethod::Post, - "https://httpbin.org", - RequestBody::Json(json!({})) - )] - ); - Ok(()) - } - - #[test_log::test] - fn test_mock_network_env_post_error() -> Result<(), NetworkError> { - let mut net = MockNetwork::new(); - net.add_post_error("https://httpbin.org", "error"); - let net_request = NetRequest::post(NetUrl::new("https://httpbin.org".into())) - .json_body(json!({}))? - .build(); - let result: Result>, NetworkError> = - block_on(net.post_json(net_request)); - assert!(result.is_err()); - assert_eq!( - net.requests(), - vec![SavedRequest::new( - RequestMethod::Post, - "https://httpbin.org", - RequestBody::Json(json!({})) - )] - ); - Ok(()) - } - - #[test_log::test] - fn test_mock_network_env_put_json() -> Result<(), NetworkError> { - let mut net = MockNetwork::new(); - net.add_put_response("https://httpbin.org", StatusCode::OK, r#"{"foo": "bar"}"#); - let net_request = NetRequest::put(NetUrl::new("https://httpbin.org".into())) - .json_body(json!({}))? - .build(); - let response: NetResponse> = block_on(net.put_json(net_request))?; - assert_eq!( - response - .response_body() - .and_then(|body| body.get("foo").cloned()), - Some("bar".to_string()) - ); - assert_eq!( - net.requests(), - vec![SavedRequest::new( - RequestMethod::Put, - "https://httpbin.org", - RequestBody::Json(json!({})) - )] - ); - Ok(()) - } - - #[test_log::test] - fn test_mock_network_env_put_json_error() -> Result<(), NetworkError> { - let mut net = MockNetwork::new(); - net.add_put_error("https://httpbin.org", "error"); - let net_request = NetRequest::put(NetUrl::new("https://httpbin.org".into())) - .json_body(json!({}))? - .build(); - let result: Result>, NetworkError> = - block_on(net.put_json(net_request)); - assert!(result.is_err()); - assert_eq!( - net.requests(), - vec![SavedRequest::new( - RequestMethod::Put, - "https://httpbin.org", - RequestBody::Json(json!({})) - )] - ); - Ok(()) - } - - #[test_log::test] - fn test_mock_network_env_put_string() -> Result<(), NetworkError> { - let mut net = MockNetwork::new(); - net.add_put_response("https://httpbin.org", StatusCode::OK, r#"{"foo":"bar"}"#); - let net_request = NetRequest::put(NetUrl::new("https://httpbin.org".into())) - .string_body("PLAIN-TEXT".to_string()) - .build(); - let response: NetResponse = block_on(net.put_string(net_request))?; - assert_eq!( - response.response_body(), - Some(json!({"foo":"bar"}).to_string()) - ); - assert_eq!( - net.requests(), - vec![SavedRequest::new( - RequestMethod::Put, - "https://httpbin.org", - RequestBody::String("PLAIN-TEXT".to_string()), - )] - ); - Ok(()) - } - - #[test_log::test] - fn test_mock_network_env_put_string_error() { - let mut net = MockNetwork::new(); - net.add_put_error("https://httpbin.org", "error"); - let net_request = NetRequest::put(NetUrl::new("https://httpbin.org".into())) - .string_body("PLAIN-TEXT".to_string()) - .build(); - let result: Result, NetworkError> = - block_on(net.put_string(net_request)); - assert!(result.is_err()); - assert_eq!( - net.requests(), - vec![SavedRequest::new( - RequestMethod::Put, - "https://httpbin.org", - RequestBody::String("PLAIN-TEXT".to_string()) - )] - ); - } - - #[derive(Debug, PartialEq, Eq, serde::Deserialize)] - struct PropfindTestResponse { - pub foo: String, - } - - #[test_log::test] - fn test_mock_network_env_propfind() -> Result<(), NetworkError> { - let mut net = MockNetwork::new(); - net.add_propfind_response( - "https://caldav.org", - StatusCode::OK, - r#"bar"#, - ); - let net_request = NetRequest::propfind(NetUrl::new("https://caldav.org".into())) - .response_type(ResponseType::Xml) - .build(); - let response: NetResponse = block_on(net.propfind(net_request))?; - assert_eq!( - response.response_body().map(|body| body.foo), - Some("bar".to_string()) - ); - assert_eq!( - net.requests(), - vec![SavedRequest::new( - RequestMethod::Propfind, - "https://caldav.org", - RequestBody::None - )] - ); - Ok(()) - } - - #[test_log::test] - fn test_mock_network_env_propfind_string() -> Result<(), NetworkError> { - let mut net = MockNetwork::new(); - net.add_propfind_response( - "https://caldav.org", - StatusCode::OK, - r#"bar"#, - ); - let net_request = NetRequest::propfind(NetUrl::new("https://caldav.org".into())) - .response_type(ResponseType::Text) - .build(); - let response: NetResponse = block_on(net.propfind_string(net_request))?; - assert_eq!( - response.response_body(), - Some("bar".to_string()) - ); - assert_eq!( - net.requests(), - vec![SavedRequest::new( - RequestMethod::Propfind, - "https://caldav.org", - RequestBody::None - )] - ); - Ok(()) - } - - #[test_log::test] - fn test_mock_network_env_propfind_error() -> Result<(), NetworkError> { - let mut net = MockNetwork::new(); - net.add_propfind_error("https://caldav.org", "error"); - let net_request = NetRequest::propfind(NetUrl::new("https://caldav.org".into())) - .response_type(ResponseType::Xml) - .build(); - let result: Result, NetworkError> = - block_on(net.propfind(net_request)); - assert!(result.is_err()); - assert_eq!( - net.requests(), - vec![SavedRequest::new( - RequestMethod::Propfind, - "https://caldav.org", - RequestBody::None - )] - ); - Ok(()) - } - - #[test_log::test] - fn test_mock_network_env_propfind_string_error() -> Result<(), NetworkError> { - let mut net = MockNetwork::new(); - net.add_propfind_error("https://caldav.org", "error"); - let net_request = NetRequest::propfind(NetUrl::new("https://caldav.org".into())) - .response_type(ResponseType::Text) - .build(); - let result: Result, NetworkError> = - block_on(net.propfind_string(net_request)); - assert!(result.is_err()); - assert_eq!( - net.requests(), - vec![SavedRequest::new( - RequestMethod::Propfind, - "https://caldav.org", - RequestBody::None - )] - ); - Ok(()) - } -} diff --git a/src/network/mod.rs b/src/network/mod.rs deleted file mode 100644 index 495d1ce..0000000 --- a/src/network/mod.rs +++ /dev/null @@ -1,36 +0,0 @@ -mod mock; -mod net_auth; -mod net_request; -mod net_request_headers; -mod net_response; -mod network_env; -mod network_error; -mod real; -mod request_body; -mod request_method; -mod response_type; -mod saved_request; - -pub use mock::MockNetwork; -pub use net_auth::NetAuth; -pub use net_auth::NetAuthPassword; -pub use net_auth::NetAuthUsername; -pub use net_request::NetRequest; -pub use net_request::NetRequestLogging; -pub use net_request::NetUrl; -pub use net_request_headers::NetRequestHeaders; -pub use net_response::NetResponse; -pub use network_env::Network; -pub use network_error::NetworkError; -pub use real::RealNetwork; -pub use request_body::RequestBody; -pub use request_method::RequestMethod; -pub use response_type::ResponseType; -pub use saved_request::SavedRequest; - -pub use reqwest::header::HeaderMap; -pub use reqwest::Error as ReqwestError; -pub use reqwest::StatusCode; -pub use serde_json::json; - -pub type NetworkResult = Result; diff --git a/src/network/net_auth.rs b/src/network/net_auth.rs deleted file mode 100644 index 055fec1..0000000 --- a/src/network/net_auth.rs +++ /dev/null @@ -1,66 +0,0 @@ -use std::fmt::{Display, Formatter}; - -#[derive(Debug, Clone)] -pub struct Password(secrecy::SecretString); -impl Password { - pub fn new(password: String) -> Self { - Self(secrecy::SecretString::from(password)) - } - pub fn expose_password(&self) -> &str { - secrecy::ExposeSecret::expose_secret(&self.0) - } -} - -#[derive(Debug, Clone)] -pub struct NetAuthUsername(String); -impl NetAuthUsername { - pub const fn new(username: String) -> Self { - Self(username) - } - pub fn as_str(&self) -> &str { - &self.0 - } -} -impl Display for NetAuthUsername { - fn fmt(&self, f: &mut Formatter) -> std::fmt::Result { - write!(f, "{}", self.0) - } -} - -/// "Password for HTTP authentication"); -#[derive(Debug, Clone)] -pub struct NetAuthPassword(Password); -impl NetAuthPassword { - pub fn new(password: String) -> Self { - Self(Password::new(password)) - } - pub fn expose_password(&self) -> &str { - self.0.expose_password() - } - // pub const fn as_str(&self) -> &str { - // "********" - // } -} -impl From for NetAuthPassword { - fn from(password: Password) -> Self { - Self(password) - } -} -// new_type_display!(NetAuthPassword); - -#[derive(Debug, Clone)] -pub struct NetAuth { - username: NetAuthUsername, - password: NetAuthPassword, -} -impl NetAuth { - pub const fn new(username: NetAuthUsername, password: NetAuthPassword) -> Self { - Self { username, password } - } - pub const fn username(&self) -> &NetAuthUsername { - &self.username - } - pub const fn password(&self) -> &NetAuthPassword { - &self.password - } -} diff --git a/src/network/net_request.rs b/src/network/net_request.rs deleted file mode 100644 index af1ce53..0000000 --- a/src/network/net_request.rs +++ /dev/null @@ -1,462 +0,0 @@ -use std::fmt::Display; -use std::fmt::Formatter; -use std::ops::Deref; - -use crate::network::HeaderMap; -use crate::network::NetAuth; -use crate::network::RequestBody; -use crate::network::RequestMethod; -use crate::network::ResponseType; - -use super::NetRequestHeaders; - -#[derive(Debug, Default, Clone, PartialEq, Eq, Hash)] -pub struct NetUrl(String); -impl NetUrl { - pub const fn new(url: String) -> Self { - Self(url) - } - pub fn as_str(&self) -> &str { - &self.0 - } -} -impl Deref for NetUrl { - type Target = String; - fn deref(&self) -> &Self::Target { - &self.0 - } -} -impl Display for NetUrl { - fn fmt(&self, f: &mut Formatter) -> std::fmt::Result { - write!(f, "{}", self.0) - } -} - -#[derive(Debug, Copy, Clone, PartialEq, Eq)] -pub enum NetRequestLogging { - None, - Request, - Response, - Both, -} -impl Default for NetRequestLogging { - fn default() -> Self { - Self::None - } -} - -#[derive(Debug)] -pub struct NetRequest { - method: RequestMethod, - url: NetUrl, - body: RequestBody, - response_type: ResponseType, - auth: Option, - headers: NetRequestHeaders, - log: NetRequestLogging, -} -impl NetRequest { - pub fn get(net_url: NetUrl) -> NetRequestBuilder { - NetRequestBuilder::default() - .url(net_url) - .header("Accept", "application/json") - } - pub fn post(net_url: NetUrl) -> NetRequestBuilder { - NetRequestBuilder::default() - .method(RequestMethod::Post) - .url(net_url) - } - pub fn put(net_url: NetUrl) -> NetRequestBuilder { - NetRequestBuilder::default() - .method(RequestMethod::Put) - .url(net_url) - } - - pub fn patch(net_url: NetUrl) -> NetRequestBuilder { - NetRequestBuilder::default() - .method(RequestMethod::Patch) - .url(net_url) - } - - pub fn delete(net_url: NetUrl) -> NetRequestBuilder { - NetRequestBuilder::default() - .method(RequestMethod::Delete) - .response_type(ResponseType::None) - .url(net_url) - } - - pub fn propfind(net_url: NetUrl) -> NetRequestBuilder { - NetRequestBuilder::default() - .method(RequestMethod::Propfind) - .response_type(ResponseType::None) - .url(net_url) - } - - pub const fn new( - method: RequestMethod, - url: NetUrl, - headers: NetRequestHeaders, - body: RequestBody, - response_type: ResponseType, - auth: Option, - log: NetRequestLogging, - ) -> Self { - Self { - method, - url, - headers, - body, - response_type, - auth, - log, - } - } - pub fn as_trace(&self) -> String { - format!( - "{} {}", - self.method, - self.url.as_str().chars().take(90).collect::() - ) - } - pub const fn method(&self) -> RequestMethod { - self.method - } - pub const fn url(&self) -> &NetUrl { - &self.url - } - pub const fn body(&self) -> &RequestBody { - &self.body - } - pub const fn response_type(&self) -> ResponseType { - self.response_type - } - pub const fn log(&self) -> NetRequestLogging { - self.log - } - pub fn auth(&self) -> Option { - self.auth.clone() - } - pub fn headers(&self) -> HeaderMap { - self.headers - .clone() - .try_into() - .unwrap_or_else(|_| HeaderMap::new()) - } - pub fn as_curl(&self) -> Result { - let mut curl = format!("curl -X {} {}", self.method, *self.url); - if let Some(accept) = &self.response_type.accept() { - if self.headers.get("Accept").is_none() { - curl.push_str(&format!(" -H 'Accept: {}'", accept)); - } - } - let mut headers = vec![]; - for (key, value) in self.headers.iter() { - headers.push(format!(" -H '{}: {}'", key, value)); - } - headers.sort(); - for header in headers { - curl.push_str(&header); - } - if let Some(auth) = &self.auth() { - curl.push_str(&format!( - " -u {}:{}", - auth.username(), - auth.password().expose_password() - )); - } - if self.method == RequestMethod::Post || self.method == RequestMethod::Put { - let body = String::try_from(&self.body)?; - curl.push_str(&format!(" -d '{}'", body)); - } - Ok(curl) - } -} - -#[derive(Default)] -pub struct NetRequestBuilder { - method: RequestMethod, - url: NetUrl, - headers: NetRequestHeaders, - body: RequestBody, - response_type: ResponseType, - auth: Option, - log: NetRequestLogging, -} -impl NetRequestBuilder { - pub fn build(self) -> NetRequest { - NetRequest::new( - self.method, - self.url, - self.headers, - self.body, - self.response_type, - self.auth, - self.log, - ) - } - - pub fn method(mut self, method: RequestMethod) -> Self { - assert_ne!(method, RequestMethod::default()); - self.method = method; - if self.method == RequestMethod::Get || self.method == RequestMethod::Delete { - self.body = RequestBody::None; - } - self - } - - fn url(mut self, url: NetUrl) -> Self { - self.url = url; - self - } - - pub fn header(mut self, key: &str, value: &str) -> Self { - self.headers = self.headers.with(key, value); - self - } - - pub fn headers(mut self, new_headers: NetRequestHeaders) -> Self { - assert_ne!(new_headers, NetRequestHeaders::default()); - for (key, value) in new_headers.iter() { - self.headers = self.headers.with(key, value); - } - self - } - - pub fn body(mut self, body: RequestBody) -> Self { - match body { - RequestBody::Json(_) => { - self.headers = self.headers.with("Content-Type", "application/json") - } - RequestBody::Xml(_) => { - self.headers = self.headers.with("Content-Type", "application/xml") - } - _ => (), - } - self.body = body; - self - } - - pub fn string_body(mut self, body: String) -> Self { - self.body = RequestBody::String(body); - self.header("Content-Type", "text/plain") - .response_type(ResponseType::Text) - } - - pub fn json_body(mut self, body: T) -> Result { - self.body = RequestBody::json(body)?; - Ok(self.header("Content-Type", "application/json")) - } - - pub fn xml_body(mut self, body: &str) -> Self { - self.body = RequestBody::Xml(body.to_string()); - self.header("Content-Type", "application/xml") - } - - pub fn response_type(mut self, response_type: ResponseType) -> Self { - assert_ne!(response_type, ResponseType::default()); - self.response_type = response_type; - match response_type.accept() { - Some(accept) => self.header("Accept", accept.as_str()), - None => { - self.headers.remove("Accept"); - self - } - } - } - - pub fn auth(mut self, auth: NetAuth) -> Self { - self.auth = Some(auth); - self - } - - pub const fn log(mut self, log: NetRequestLogging) -> Self { - self.log = log; - self - } -} - -#[cfg(test)] -mod tests { - use assert2::let_assert; - use serde_json::json; - - use crate::network::net_auth::{NetAuthPassword, NetAuthUsername}; - - use super::*; - - #[test_log::test] - fn test_as_curl_no_auth() { - let request = NetRequest::get(NetUrl("https://httpbin.org/get".to_string())).build(); - let_assert!(Ok(curl) = request.as_curl()); - assert_eq!( - curl, - "curl -X GET https://httpbin.org/get -H 'Accept: application/json'".to_string() - ); - } - #[test_log::test] - fn test_as_curl_auth() { - let request = NetRequest::get(NetUrl("https://httpbin.org/get".to_string())) - .auth(NetAuth::new( - NetAuthUsername::new("user".into()), - NetAuthPassword::new("pass".into()), - )) - .build(); - let_assert!(Ok(curl) = request.as_curl()); - assert_eq!( - curl, - "curl -X GET https://httpbin.org/get -H 'Accept: application/json' -u user:pass" - .to_string() - ); - } - #[test_log::test] - fn test_as_curl_no_auth_post_json() -> Result<(), serde_json::Error> { - let request = NetRequest::post(NetUrl("https://httpbin.org/post".to_string())) - .json_body(json!({ - "args": {}, - "url": "https://httpbin.org/post", - }))? - .build(); - let_assert!(Ok(curl) = request.as_curl()); - assert_eq!( - curl, - "curl -X POST https://httpbin.org/post -H 'Accept: application/json' -H 'Content-Type: application/json' -d '{\"args\":{},\"url\":\"https://httpbin.org/post\"}'".to_string() - ); - Ok(()) - } - #[test_log::test] - fn test_as_curl_no_auth_post_text() { - let request = NetRequest::post(NetUrl("https://httpbin.org/post".to_string())) - .string_body("body".to_string()) - .build(); - let_assert!(Ok(curl) = request.as_curl()); - assert_eq!( - curl, - "curl -X POST https://httpbin.org/post -H 'Accept: text/plain' -H 'Content-Type: text/plain' -d 'body'".to_string() - ); - } - - #[test_log::test] - fn test_as_curl_no_auth_put() -> Result<(), serde_json::Error> { - let request = NetRequest::put(NetUrl("https://httpbin.org/put".to_string())) - .json_body(json!({ - "args": {}, - "url": "https://httpbin.org/put", - }))? - .build(); - let_assert!(Ok(curl) = request.as_curl()); - assert_eq!( - curl, - "curl -X PUT https://httpbin.org/put -H 'Accept: application/json' -H 'Content-Type: application/json' -d '{\"args\":{},\"url\":\"https://httpbin.org/put\"}'".to_string() - ); - Ok(()) - } - - #[test_log::test] - fn test_as_curl_no_auth_delete() { - let request = NetRequest::delete(NetUrl("https://httpbin.org/delete".to_string())).build(); - let_assert!(Ok(curl) = request.as_curl()); - assert_eq!( - curl, - "curl -X DELETE https://httpbin.org/delete".to_string() - ); - } - - #[test_log::test] - fn test_as_curl_no_auth_put_xml() { - let request = NetRequest::put(NetUrl("https://httpbin.org/put".to_string())) - .xml_body("") - .build(); - let_assert!(Ok(curl) = request.as_curl()); - assert_eq!( - curl, - "curl -X PUT https://httpbin.org/put -H 'Accept: application/json' -H 'Content-Type: application/xml' -d ''" - .to_string() - ); - } - - #[test_log::test] - fn test_as_curl_accept_json() { - let request = NetRequest::get(NetUrl("https://httpbin.org/get".to_string())).build(); - let_assert!(Ok(curl) = request.as_curl()); - assert_eq!( - curl, - "curl -X GET https://httpbin.org/get -H 'Accept: application/json'".to_string() - ); - } - - #[test_log::test] - fn test_as_curl_accept_xml() { - let request = NetRequest::get(NetUrl("https://httpbin.org/get".to_string())) - .response_type(ResponseType::Xml) - .build(); - let_assert!(Ok(curl) = request.as_curl()); - assert_eq!( - curl, - "curl -X GET https://httpbin.org/get -H 'Accept: application/xml'".to_string() - ); - } - - #[test_log::test] - fn test_as_curl_accept_text() { - let request = NetRequest::get(NetUrl("https://httpbin.org/get".to_string())) - .response_type(ResponseType::Text) - .build(); - let_assert!(Ok(curl) = request.as_curl()); - assert_eq!( - curl, - "curl -X GET https://httpbin.org/get -H 'Accept: text/plain'".to_string() - ); - } - - #[test_log::test] - fn text_as_curl_accept_none() { - let request = NetRequest::get(NetUrl("https://httpbin.org/get".to_string())) - .response_type(ResponseType::None) - .build(); - let_assert!(Ok(curl) = request.as_curl()); - assert_eq!(curl, "curl -X GET https://httpbin.org/get".to_string()); - } - - #[test_log::test] - fn test_as_curl_with_headers() { - let request = NetRequest::get(NetUrl("https://httpbin.org/get".to_string())) - .header("header1", "value1") - .header("header2", "value2") - .build(); - let_assert!(Ok(curl) = request.as_curl()); - assert!(curl.contains("-H 'header1: value1'")); - assert!(curl.contains("-H 'header2: value2'")); - assert!(curl.starts_with("curl -X GET https://httpbin.org/get")); - } - - #[test_log::test] - fn test_net_request_headers() { - let request = NetRequest::get(NetUrl("https://httpbin.org/get".to_string())) - .header("header1", "value1") - .header("header2", "value2") - .build(); - let headers = request.headers(); - assert!(headers.len() >= 2); - let_assert!(Some(value1) = headers.get("header1")); - assert_eq!(value1, "value1"); - let_assert!(Some(value2) = headers.get("header2")); - assert_eq!(value2, "value2"); - } - - #[test_log::test] - fn test_as_curl_propfind() { - let request = - NetRequest::propfind(NetUrl("https://httpbin.org/propfind".to_string())).build(); - let_assert!(Ok(curl) = request.as_curl()); - assert_eq!( - curl, - "curl -X PROPFIND https://httpbin.org/propfind".to_string() - ); - } - - #[test_log::test] - fn test_as_trace() { - let request = NetRequest::get(NetUrl("https://httpbin.org/get".to_string())).build(); - assert_eq!(request.as_trace(), "GET https://httpbin.org/get"); - } -} diff --git a/src/network/net_request_headers.rs b/src/network/net_request_headers.rs deleted file mode 100644 index a851e87..0000000 --- a/src/network/net_request_headers.rs +++ /dev/null @@ -1,102 +0,0 @@ -use std::{ - collections::HashMap, - ops::{Deref, DerefMut}, -}; - -use crate::network::HeaderMap; - -#[derive(Clone, Debug, Default, PartialEq, Eq)] -pub struct NetRequestHeaders(HashMap); -impl NetRequestHeaders { - pub fn new() -> Self { - Self::default() //(HashMap::new()) - } - pub fn with(mut self, key: &str, value: &str) -> Self { - self.0.insert(key.into(), value.into()); - self - } -} -impl From for NetRequestHeaders -where - T: Into>, -{ - fn from(x: T) -> Self { - Self(x.into()) - } -} -impl TryFrom for HeaderMap { - type Error = http::Error; - fn try_from(x: NetRequestHeaders) -> Result { - Self::try_from(&x.0) - } -} -impl Deref for NetRequestHeaders { - type Target = HashMap; - fn deref(&self) -> &Self::Target { - &self.0 - } -} -impl DerefMut for NetRequestHeaders { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.0 - } -} - -#[cfg(test)] -mod tests { - - use assert2::let_assert; - use pretty_assertions::assert_eq; - - use super::*; - - #[test_log::test] - fn test_net_request_headers_from_hash_map() { - let nrh = NetRequestHeaders::from( - [("a", "b"), ("c", "d")] - .iter() - .map(|(k, v)| (k.to_string(), v.to_string())) - .collect::>(), - ); - assert_eq!(nrh.len(), 2); - let_assert!(Some(a) = nrh.get("a")); - assert_eq!(a, "b"); - let_assert!(Some(c) = nrh.get("c")); - assert_eq!(c, "d"); - } - - #[test_log::test] - fn test_net_request_headers_with_new_entry() { - let nrh = NetRequestHeaders::from( - [("a", "b"), ("c", "d")] - .iter() - .map(|(k, v)| (k.to_string(), v.to_string())) - .collect::>(), - ) - .with("e", "f"); - assert_eq!(nrh.len(), 3); - let_assert!(Some(a) = nrh.get("a")); - assert_eq!(a, "b"); - let_assert!(Some(c) = nrh.get("c")); - assert_eq!(c, "d"); - let_assert!(Some(e) = nrh.get("e")); - assert_eq!(e, "f"); - } - - #[test_log::test] - fn test_net_request_headers_try_into_header_map() { - let result: Result = NetRequestHeaders::from( - [("a", "b"), ("c", "d")] - .iter() - .map(|(k, v)| (k.to_string(), v.to_string())) - .collect::>(), - ) - .try_into(); - let_assert!(Ok(hm) = result); - assert_eq!(hm.len(), 2); - let_assert!(Some(a) = hm.get("a")); - assert_eq!(a, "b"); - let_assert!(Some(c) = hm.get("c")); - assert_eq!(c, "d"); - } -} diff --git a/src/network/net_response.rs b/src/network/net_response.rs deleted file mode 100644 index c65dfed..0000000 --- a/src/network/net_response.rs +++ /dev/null @@ -1,44 +0,0 @@ -use crate::network::StatusCode; - -use super::{net_request::NetUrl, RequestMethod}; - -#[derive(Debug)] -pub struct NetResponse { - method: RequestMethod, - request_url: NetUrl, - status_code: StatusCode, - response_body: Option, -} -impl NetResponse { - pub fn new( - method: RequestMethod, - request_url: &NetUrl, - status_code: StatusCode, - response_body: Option, - ) -> Self { - Self { - method, - request_url: request_url.clone(), - status_code, - response_body, - } - } - pub const fn method(&self) -> &RequestMethod { - &self.method - } - pub const fn request_url(&self) -> &NetUrl { - &self.request_url - } - pub const fn status_code(&self) -> StatusCode { - self.status_code - } - pub fn response_body(self) -> Option { - self.response_body - } -} - -impl From> for () { - fn from(_value: NetResponse<()>) -> Self { - // () - } -} diff --git a/src/network/network_env.rs b/src/network/network_env.rs deleted file mode 100644 index 3dc42fc..0000000 --- a/src/network/network_env.rs +++ /dev/null @@ -1,174 +0,0 @@ -use async_trait::async_trait; -use serde::de::DeserializeOwned; - -use crate::network::{MockNetwork, NetRequest, NetworkError, RealNetwork, SavedRequest}; - -use super::NetResponse; - -#[derive(Debug, Clone)] -pub enum Network { - Mock(MockNetwork), - Real(RealNetwork), -} -impl Network { - pub fn new_mock() -> Self { - Self::Mock(MockNetwork::default()) - } - - pub fn new_real() -> Self { - Self::Real(RealNetwork::default()) - } - - pub async fn get( - &self, - net_request: NetRequest, - ) -> Result, NetworkError> { - match self { - Self::Mock(mock) => mock.get(net_request).await, - Self::Real(real) => real.get(net_request).await, - } - } - - pub async fn get_string( - &self, - net_request: NetRequest, - ) -> Result, NetworkError> { - match self { - Self::Mock(mock) => mock.get_string(net_request).await, - Self::Real(real) => real.get_string(net_request).await, - } - } - - pub async fn post_json( - &self, - net_request: NetRequest, - ) -> Result, NetworkError> { - match self { - Self::Mock(mock) => mock.post_json(net_request).await, - Self::Real(real) => real.post_json(net_request).await, - } - } - - pub async fn post_string( - &self, - net_request: NetRequest, - ) -> Result, NetworkError> { - match self { - Self::Mock(mock) => mock.post_string(net_request).await, - Self::Real(real) => real.post_string(net_request).await, - } - } - - pub async fn put_json( - &self, - net_request: NetRequest, - ) -> Result, NetworkError> { - match self { - Self::Mock(mock) => mock.put_json(net_request).await, - Self::Real(real) => real.put_json(net_request).await, - } - } - - pub async fn put_string( - &self, - net_request: NetRequest, - ) -> Result, NetworkError> { - match self { - Self::Mock(mock) => mock.put_string(net_request).await, - Self::Real(real) => real.put_string(net_request).await, - } - } - - pub async fn patch_json( - &self, - net_request: NetRequest, - ) -> Result, NetworkError> { - match self { - Self::Mock(mock) => mock.patch_json(net_request).await, - Self::Real(real) => real.patch_json(net_request).await, - } - } - - pub async fn delete(&self, net_request: NetRequest) -> Result, NetworkError> { - match self { - Self::Mock(mock) => mock.delete(net_request).await, - Self::Real(real) => real.delete(net_request).await, - } - } - - pub async fn propfind( - &self, - net_request: NetRequest, - ) -> Result, NetworkError> { - match self { - Self::Mock(mock) => mock.propfind(net_request).await, - Self::Real(real) => real.propfind(net_request).await, - } - } - - pub async fn propfind_string( - &self, - net_request: NetRequest, - ) -> Result, NetworkError> { - match self { - Self::Mock(mock) => mock.propfind_string(net_request).await, - Self::Real(real) => real.propfind_string(net_request).await, - } - } - - pub fn mocked_requests(&self) -> Option> { - match self { - Self::Mock(mock) => Some(mock.requests()), - Self::Real(_) => None, - } - } -} -#[async_trait] -pub(super) trait NetworkTrait: Sync + Send + Clone + std::fmt::Debug { - async fn get( - &self, - net_request: NetRequest, - ) -> Result, NetworkError>; - - async fn get_string( - &self, - net_request: NetRequest, - ) -> Result, NetworkError>; - - async fn post_json( - &self, - net_request: NetRequest, - ) -> Result, NetworkError>; - - async fn post_string( - &self, - net_request: NetRequest, - ) -> Result, NetworkError>; - - async fn put_json( - &self, - net_request: NetRequest, - ) -> Result, NetworkError>; - - async fn put_string( - &self, - net_request: NetRequest, - ) -> Result, NetworkError>; - - async fn patch_json( - &self, - net_request: NetRequest, - ) -> Result, NetworkError>; - - async fn delete(&self, net_request: NetRequest) -> Result, NetworkError>; - - async fn propfind( - &self, - net_request: NetRequest, - ) -> Result, NetworkError>; - - async fn propfind_string( - &self, - net_request: NetRequest, - ) -> Result, NetworkError>; -} diff --git a/src/network/network_error.rs b/src/network/network_error.rs deleted file mode 100644 index 7e80f63..0000000 --- a/src/network/network_error.rs +++ /dev/null @@ -1,56 +0,0 @@ -#[derive(thiserror::Error, Debug)] -pub enum NetworkError { - #[error(transparent)] - Reqwest(#[from] crate::network::ReqwestError), - - #[error(transparent)] - SerdeJson(#[from] serde_json::Error), - - #[error(transparent)] - SerdeXml(#[from] serde_xml_rs::Error), - - #[error(transparent)] - HttpMethod(#[from] http::method::InvalidMethod), - - #[error(transparent)] - Http(#[from] http::Error), - - #[error("{0} failed: {1} ({2})")] - RequestFailed(super::RequestMethod, super::StatusCode, super::NetUrl), - - #[error("{0} failed: {1} ({2})")] - MockError(super::RequestMethod, super::StatusCode, super::NetUrl), - - #[error("missing response body")] - MissingResponseBody, - - #[error("unexpected mock request: {method:?} {request_url}")] - UnexpectedMockRequest { - method: super::RequestMethod, - request_url: super::NetUrl, - }, - - #[error("{0} failed: {1} ({2})")] - RequestError(super::RequestMethod, super::StatusCode, super::NetUrl), - - #[error("invalid response type")] - InvalidResponseType, - - #[error("invalid request body")] - InvalidRequestBody, - - #[error("response body is empty")] - EmptyResponseBody, -} - -#[cfg(test)] -mod tests { - use super::NetworkError; - - #[test] - const fn test_network_error_is_send() { - const fn assert_send() {} - assert_send::(); - assert_send::>(); - } -} diff --git a/src/network/real.rs b/src/network/real.rs deleted file mode 100644 index 6a28b1c..0000000 --- a/src/network/real.rs +++ /dev/null @@ -1,612 +0,0 @@ -use reqwest::RequestBuilder; -use serde::de::DeserializeOwned; -use tracing::{event, Level}; - -use super::{ - network_env::NetworkTrait, NetAuth, NetRequest, NetRequestLogging, NetResponse, Network, - NetworkError, RequestMethod, ResponseType, StatusCode, -}; - -trait WithAuthentiction { - fn auth(self, auth: Option) -> reqwest::RequestBuilder; -} -impl WithAuthentiction for reqwest::RequestBuilder { - fn auth(self, auth: Option) -> reqwest::RequestBuilder { - if let Some(auth) = auth { - self.basic_auth( - auth.username().to_string(), - Some(auth.password().expose_password()), - ) - } else { - self - } - } -} - -trait WithResponseType { - fn response_type(self, response_type: ResponseType) -> Self; -} -impl WithResponseType for reqwest::RequestBuilder { - fn response_type(self, response_type: ResponseType) -> Self { - match response_type { - ResponseType::Json => self.header(reqwest::header::ACCEPT, "application/json"), - ResponseType::Xml => self.header(reqwest::header::ACCEPT, "application/xml"), - ResponseType::Text => self.header(reqwest::header::ACCEPT, "text/plain"), - ResponseType::None => self, - } - } -} - -#[derive(Debug, Clone)] -pub struct RealNetwork { - client: reqwest::Client, -} -impl RealNetwork { - #[cfg(not(tarpaulin_include))] - pub fn new() -> Self { - Self { - client: reqwest::Client::new(), - } - } - - async fn send( - &self, - request: RequestBuilder, - net_request: NetRequest, - ) -> Result, NetworkError> { - let response = request.send().await?; - let status_code = response.status(); - let text = response.text().await?; - if status_code.is_success() { - tracing::trace!(status = ?status_code); - } else { - tracing::error!("Request failed"); - self.log_request(&net_request); - self.log_response(&net_request, &status_code, text.as_str()); - return Err(NetworkError::RequestFailed( - net_request.method(), - status_code, - net_request.url().clone(), - )); - } - match net_request.response_type() { - ResponseType::Json => serde_json::from_str(text.as_str()) - .map(Some) - .map_err(NetworkError::from), - ResponseType::Xml => serde_xml_rs::from_str(text.as_str()) - .map(Some) - .map_err(NetworkError::from), - ResponseType::Text => { - panic!("text response type not implemented - use send_for_text(...) instead") - } - ResponseType::None => Ok(None), - } - .map(|response_body| { - match net_request.log() { - NetRequestLogging::None => (), - NetRequestLogging::Request => self.log_request(&net_request), - NetRequestLogging::Response => { - self.log_response(&net_request, &status_code, text.as_str()); - } - NetRequestLogging::Both => { - self.log_request(&net_request); - self.log_response(&net_request, &status_code, text.as_str()); - } - }; - NetResponse::new( - net_request.method(), - net_request.url(), - status_code, - response_body, - ) - }) - } - - async fn send_for_text( - &self, - request: RequestBuilder, - net_request: NetRequest, - ) -> Result, NetworkError> { - let response = request.send().await?; - let status_code = response.status(); - if status_code.is_success() { - tracing::trace!(status = ?status_code); - } else { - tracing::error!(status = ?status_code, request = ?net_request); - } - match net_request.response_type() { - ResponseType::Text => { - let text = response.text().await?; - Ok(Some(text)) - } - _ => panic!("text response type not implemented - use send(...) instead"), - } - .map(|response_body| { - NetResponse::new( - net_request.method(), - net_request.url(), - status_code, - response_body, - ) - }) - } - - fn log_request(&self, net_request: &NetRequest) { - tracing::info!(?net_request, "RealNetworkEnv::request"); - } - - fn log_response( - &self, - net_request: &NetRequest, - status_code: &StatusCode, - response_body: &str, - ) { - tracing::info!( - ?net_request, - status = ?status_code, - ?response_body, - "RealNetworkEnv::response" - ); - } -} - -impl Default for RealNetwork { - fn default() -> Self { - Self::new() - } -} -impl From for Network { - fn from(real: RealNetwork) -> Self { - Self::Real(real) - } -} - -#[cfg(not(tarpaulin_include))] -#[async_trait::async_trait] -impl NetworkTrait for RealNetwork { - #[tracing::instrument(skip_all, fields(request = %net_request.as_trace()), level = "trace")] - async fn get( - &self, - net_request: NetRequest, - ) -> Result, NetworkError> { - tracing::debug!("RealNetworkEnv::get({:?})", net_request); - let url = net_request.url(); - let auth = net_request.auth(); - let response_type = net_request.response_type(); - let headers = net_request.headers(); - - let request = self - .client - .get(url.to_string()) - .auth(auth) - .response_type(response_type) - .headers(headers); - self.send(request, net_request).await - } - - #[tracing::instrument(skip_all, fields(request = %net_request.as_trace()), level = "trace")] - async fn post_json( - &self, - net_request: NetRequest, - ) -> Result, NetworkError> { - // event!(Level::INFO, "RealNetworkEnv::post_json({:?})", net_request); - let url = net_request.url(); - let body = net_request.body(); - let auth = net_request.auth(); - let response_type = net_request.response_type(); - let headers = net_request.headers(); - - let body = String::try_from(body)?; - - let request = self - .client - .post(url.to_string()) - .body(body) - .auth(auth) - .response_type(response_type) - .headers(headers); - self.send(request, net_request).await - } - - #[tracing::instrument(skip_all, fields(request = %net_request.as_trace()), level = "trace")] - async fn post_string( - &self, - net_request: NetRequest, - ) -> Result, NetworkError> { - // event!(Level::INFO, "RealNetworkEnv::post_string({:?})", net_request); - let url = net_request.url(); - let body = net_request.body(); - let auth = net_request.auth(); - let response_type = net_request.response_type(); - let headers = net_request.headers(); - - let body = String::try_from(body)?; - - let request = self - .client - .post(url.to_string()) - .body(body) - .auth(auth) - .response_type(response_type) - .headers(headers); - - self.send_for_text(request, net_request).await - } - - #[tracing::instrument(skip_all, fields(request = %net_request.as_trace()), level = "trace")] - async fn put_json( - &self, - net_request: NetRequest, - ) -> Result, NetworkError> { - // event!(Level::INFO, "RealNetworkEnv::put_json({:?})", net_request); - let url = net_request.url(); - let body = net_request.body(); - let auth = net_request.auth(); - let response_type = net_request.response_type(); - let headers = net_request.headers(); - - let body = String::try_from(body)?; - - let request = self - .client - .put(url.to_string()) - .body(body) - .auth(auth) - .response_type(response_type) - .headers(headers); - - self.send(request, net_request).await - } - - #[tracing::instrument(skip_all, fields(request = %net_request.as_trace()), level = "trace")] - async fn put_string( - &self, - net_request: NetRequest, - ) -> Result, NetworkError> { - // event!(Level::INFO, "RealNetworkEnv::put_string({:?})", net_request); - let url = net_request.url(); - let body = net_request.body(); - let auth = net_request.auth(); - let response_type = net_request.response_type(); - let headers = net_request.headers(); - - let body = String::try_from(body)?; - - let request = self - .client - .put(url.to_string()) - .body(body) - .auth(auth) - .response_type(response_type) - .headers(headers); - - self.send_for_text(request, net_request).await - } - - #[tracing::instrument(skip_all, fields(request = %net_request.as_trace()), level = "trace")] - async fn patch_json( - &self, - net_request: NetRequest, - ) -> Result, NetworkError> { - // event!(Level::INFO, "RealNetworkEnv::put_json({:?})", net_request); - let url = net_request.url(); - let body = net_request.body(); - let auth = net_request.auth(); - let response_type = net_request.response_type(); - let headers = net_request.headers(); - - let body = String::try_from(body)?; - - let request = self - .client - .patch(url.to_string()) - .body(body) - .auth(auth) - .response_type(response_type) - .headers(headers); - - self.send(request, net_request).await - } - - #[tracing::instrument(skip_all, fields(request = %net_request.as_trace()), level = "trace")] - async fn delete(&self, net_request: NetRequest) -> Result, NetworkError> { - // event!(Level::INFO, "RealNetworkEnv::delete({:?})", net_request); - let url = net_request.url(); - let auth = net_request.auth(); - let response_type = net_request.response_type(); - let headers = net_request.headers(); - - let request = self - .client - .delete(url.to_string()) - .auth(auth) - .response_type(response_type) - .headers(headers); - - self.send(request, net_request).await - } - - #[tracing::instrument(skip_all, fields(request = %net_request.as_trace()), level = "trace")] - async fn propfind( - &self, - net_request: NetRequest, - ) -> Result, NetworkError> { - // event!(Level::INFO, "RealNetworkEnv::propfind({:?})", net_request); - let url = net_request.url(); - let auth = net_request.auth(); - let response_type = net_request.response_type(); - let headers = net_request.headers(); - - let request = self - .client - .request(reqwest::Method::from_bytes(b"PROPFIND")?, url.to_string()) - .auth(auth) - .response_type(response_type) - .headers(headers); - - let response = request.send().await?; - - let status_code = response.status(); - event!(Level::TRACE, status = %status_code); - let body = match response_type { - ResponseType::Xml => serde_xml_rs::from_str(response.text().await?.as_str()), - _ => Err(NetworkError::InvalidResponseType)?, - }; - body.map_err(Into::into).map(|response_body| { - NetResponse::new(RequestMethod::Propfind, url, status_code, response_body) - }) - } - - #[tracing::instrument(skip_all, fields(request = %net_request.as_trace()), level = "trace")] - async fn get_string( - &self, - net_request: NetRequest, - ) -> Result, NetworkError> { - // event!(Level::INFO, "RealNetworkEnv::get_string({:?})", net_request); - let url = net_request.url(); - let auth = net_request.auth(); - let response_type = net_request.response_type(); - let headers = net_request.headers(); - - let request = self - .client - .get(url.to_string()) - .auth(auth) - .response_type(response_type) - .headers(headers); - - self.send_for_text(request, net_request).await - } - - #[tracing::instrument(skip_all, fields(request = %net_request.as_trace()), level = "trace")] - async fn propfind_string( - &self, - net_request: NetRequest, - ) -> Result, NetworkError> { - // event!(Level::INFO, "RealNetworkEnv::propfind_string({:?})", net_request); - let url = net_request.url(); - let auth = net_request.auth(); - let response_type = net_request.response_type(); - let headers = net_request.headers(); - - let request = self - .client - .request(reqwest::Method::from_bytes(b"PROPFIND")?, url.to_string()) - .auth(auth) - .response_type(response_type) - .headers(headers); - - self.send_for_text(request, net_request).await - } -} - -#[cfg(test)] -mod tests { - - use super::*; - - use crate::network::{ - net_auth::{NetAuthPassword, NetAuthUsername}, - NetUrl, NetworkError, StatusCode, - }; - - use assert2::let_assert; - use pretty_assertions::assert_eq; - use std::collections::HashMap; - use tokio_test::block_on; - - #[derive(Debug, PartialEq, Eq, serde::Deserialize)] - pub struct GetResponse { - pub args: HashMap, - pub url: String, - } - - #[derive(Debug, PartialEq, Eq, serde::Deserialize)] - pub struct PostResponse { - pub args: HashMap, - pub url: String, - pub data: String, - } - - #[derive(Debug, PartialEq, Eq, serde::Deserialize)] - pub struct PutResponse { - pub args: HashMap, - pub url: String, - pub data: String, - } - - #[test_log::test] - fn test_with_authentication_none() { - let client = reqwest::Client::new(); - let request = client.get("https://httpbin.org/get").auth(None); - let_assert!(Ok(build) = request.build()); - assert!(build.headers().is_empty()); - } - - #[test_log::test] - fn test_with_authentication_some() { - let client = reqwest::Client::new(); - let request = client - .get("https://httpbin.org/get") - .auth(Some(NetAuth::new( - NetAuthUsername::new("user".into()), - NetAuthPassword::new("pass".into()), - ))); - let_assert!(Ok(build) = request.build()); - let headers = build.headers(); - let_assert!(Some(authorization) = headers.get(reqwest::header::AUTHORIZATION)); - assert_eq!(authorization, "Basic dXNlcjpwYXNz"); - } - - #[test_log::test] - fn test_with_response_type_json() { - let client = reqwest::Client::new(); - let request = client - .get("https://httpbin.org/get") - .response_type(ResponseType::Json); - let_assert!(Ok(request) = request.build()); - let headers = request.headers(); - let_assert!(Some(accept) = headers.get(reqwest::header::ACCEPT)); - assert_eq!(accept, "application/json"); - } - - #[test_log::test] - fn test_with_response_type_xml() { - let client = reqwest::Client::new(); - let request = client - .get("https://httpbin.org/get") - .response_type(ResponseType::Xml); - let_assert!(Ok(request) = request.build()); - let headers = request.headers(); - let_assert!(Some(accept) = headers.get(reqwest::header::ACCEPT)); - assert_eq!(accept, "application/xml"); - } - - #[test_log::test] - fn test_with_response_type_text() { - let client = reqwest::Client::new(); - let request = client - .get("https://httpbin.org/get") - .response_type(ResponseType::Text); - let_assert!(Ok(request) = request.build()); - let headers = request.headers(); - let_assert!(Some(accept) = headers.get(reqwest::header::ACCEPT)); - assert_eq!(accept, "text/plain"); - } - - #[test_log::test] - fn test_with_response_type_none() { - let client = reqwest::Client::new(); - let request = client - .get("https://httpbin.org/get") - .response_type(ResponseType::None); - let_assert!(Ok(request) = request.build()); - let headers = request.headers(); - assert!(headers.get(reqwest::header::ACCEPT).is_none()); - } - - #[test_log::test] - #[ignore] - fn test_real_network_env_get() -> Result<(), NetworkError> { - let env = RealNetwork::new(); - let net_request = - NetRequest::get(NetUrl::new("https://httpbin.org/get?arg=baz".into())).build(); - let response: NetResponse = block_on(env.get(net_request))?; - let_assert!(Some(body) = response.response_body()); - assert_eq!( - body.args.get("arg"), - Some(&"baz".to_string()), - "args from body" - ); - Ok(()) - } - - #[test_log::test] - #[ignore] - fn test_real_network_env_get_error() { - let env = RealNetwork::new(); - let net_request = - NetRequest::get(NetUrl::new("https://httpbin.org/status/400".into())).build(); - let result: Result, NetworkError> = block_on(env.get(net_request)); - assert!(result.is_err(), "response is not a String"); - } - - #[test_log::test] - #[ignore] - fn test_real_network_env_post_json() -> Result<(), NetworkError> { - let env = RealNetwork::new(); - let body = serde_json::json!({"foo":"bar"}); - let net_request = NetRequest::post(NetUrl::new("https://httpbin.org/post?arg=baz".into())) - .json_body(body)? - .build(); - let response: NetResponse = block_on(env.post_json(net_request))?; - let_assert!(Some(body) = response.response_body()); - assert_eq!( - body.args.get("arg"), - Some(&"baz".to_string()), - "args from body" - ); - assert_eq!(body.data, "{\"foo\":\"bar\"}".to_string(), "data from body"); - Ok(()) - } - - #[test_log::test] - #[ignore] - fn test_real_network_env_post_json_error() -> Result<(), NetworkError> { - let env = RealNetwork::new(); - let net_request = - NetRequest::post(NetUrl::new("https://httpbin.org/status/400".into())).build(); - let response: Result, NetworkError> = - block_on(env.post_json(net_request)); - match response { - Ok(_) => panic!("expected error"), - Err(e) => match e { - NetworkError::MockError(method, status, _url) => { - assert_eq!(method, RequestMethod::Post); - assert_eq!(status, StatusCode::BAD_REQUEST) - } - _ => panic!("unexpected error type"), - }, - } - Ok(()) - } - - #[test_log::test] - #[ignore] - fn test_real_network_env_put() -> Result<(), NetworkError> { - let env = RealNetwork::new(); - let body = serde_json::json!({"foo":"bar"}); - let net_request = NetRequest::put(NetUrl::new("https://httpbin.org/put?arg=baz".into())) - .json_body(body)? - .build(); - let response: NetResponse = block_on(env.put_json(net_request))?; - let_assert!(Some(body) = response.response_body()); - assert_eq!( - body.args.get("arg"), - Some(&"baz".to_string()), - "args from body" - ); - assert_eq!(body.data, "{\"foo\":\"bar\"}".to_string(), "data from body"); - Ok(()) - } - - #[test_log::test] - #[ignore] - fn test_real_network_env_put_error() { - let env = RealNetwork::new(); - let net_request = - NetRequest::put(NetUrl::new("https://httpbin.org/status/400".into())).build(); - let result: Result, NetworkError> = block_on(env.put_json(net_request)); - assert!(result.is_err(), "response is not a String"); - } - - #[test_log::test] - #[ignore] - fn test_real_network_env_delete() { - let env = RealNetwork::new(); - let net_request = - NetRequest::delete(NetUrl::new("https://httpbin.org/delete?arg=baz".into())).build(); - let result = block_on(env.delete(net_request)); - assert!(result.is_ok()); - } -} diff --git a/src/network/request_body.rs b/src/network/request_body.rs deleted file mode 100644 index bd2a8a8..0000000 --- a/src/network/request_body.rs +++ /dev/null @@ -1,86 +0,0 @@ -use serde::Serialize; - -#[derive(Clone, Debug, PartialEq, Eq)] -pub enum RequestBody { - None, - String(String), - Json(serde_json::Value), - Xml(String), // no separate type for XML, so store as string -} -impl Default for RequestBody { - fn default() -> Self { - Self::None - } -} -impl TryFrom<&RequestBody> for String { - type Error = serde_json::Error; - fn try_from(value: &RequestBody) -> Result { - match value { - RequestBody::None => Ok("".to_string()), - RequestBody::String(s) => Ok(s.to_string()), - RequestBody::Json(json) => serde_json::to_string(&json), - RequestBody::Xml(s) => Ok(s.to_string()), - } - } -} -impl RequestBody { - pub fn json(source: T) -> Result { - serde_json::to_value(source).map(Self::Json) - } - pub fn content_type(&self) -> Option { - match self { - Self::None => None, - Self::String(_) => Some("text/plain".to_string()), - Self::Json(_) => Some("application/json".to_string()), - Self::Xml(_) => Some("application/xml".to_string()), - } - } -} - -#[cfg(test)] -mod tests { - - use assert2::let_assert; - use pretty_assertions::assert_eq; - use serde_json::json; - - use super::*; - - #[test_log::test] - fn test_request_body_json() { - let_assert!(Ok(json) = RequestBody::json("hello")); - assert_eq!( - json, - RequestBody::Json(serde_json::Value::String("hello".to_string())) - ); - } - - #[test_log::test] - fn test_request_body_content_type() { - assert_eq!(RequestBody::None.content_type(), None); - assert_eq!( - RequestBody::String("".to_string()).content_type(), - Some("text/plain".to_string()) - ); - assert_eq!( - RequestBody::Json(json!("")).content_type(), - Some("application/json".to_string()) - ); - assert_eq!( - RequestBody::Xml("".to_string()).content_type(), - Some("application/xml".to_string()) - ); - } - - #[test_log::test] - fn test_request_body_try_from() { - let_assert!(Ok(value) = String::try_from(&RequestBody::None)); - assert_eq!(value, ""); - let_assert!(Ok(value) = String::try_from(&RequestBody::String("hello".to_string()))); - assert_eq!(value, "hello"); - let_assert!(Ok(value) = String::try_from(&RequestBody::Json(json!("hello")))); - assert_eq!(value, "\"hello\""); - let_assert!(Ok(value) = String::try_from(&RequestBody::Xml("hello".to_string()))); - assert_eq!(value, "hello"); - } -} diff --git a/src/network/request_method.rs b/src/network/request_method.rs deleted file mode 100644 index 4461c63..0000000 --- a/src/network/request_method.rs +++ /dev/null @@ -1,26 +0,0 @@ -#[derive(Debug, PartialEq, Eq, Clone, Copy)] -pub enum RequestMethod { - Get, - Post, - Put, - Patch, - Delete, - Propfind, -} -impl Default for RequestMethod { - fn default() -> Self { - Self::Get - } -} -impl std::fmt::Display for RequestMethod { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - Self::Get => write!(f, "GET"), - Self::Post => write!(f, "POST"), - Self::Put => write!(f, "PUT"), - Self::Patch => write!(f, "PATCH"), - Self::Delete => write!(f, "DELETE"), - Self::Propfind => write!(f, "PROPFIND"), - } - } -} diff --git a/src/network/response_type.rs b/src/network/response_type.rs deleted file mode 100644 index c855f29..0000000 --- a/src/network/response_type.rs +++ /dev/null @@ -1,22 +0,0 @@ -#[derive(Debug, PartialEq, Eq, Clone, Copy)] -pub enum ResponseType { - Json, - Xml, - None, - Text, -} -impl ResponseType { - pub fn accept(&self) -> Option { - match self { - Self::Json => Some("application/json".to_string()), - Self::Text => Some("text/plain".to_string()), - Self::Xml => Some("application/xml".to_string()), - Self::None => None, - } - } -} -impl Default for ResponseType { - fn default() -> Self { - Self::Json - } -} diff --git a/src/network/saved_request.rs b/src/network/saved_request.rs deleted file mode 100644 index e2ef412..0000000 --- a/src/network/saved_request.rs +++ /dev/null @@ -1,113 +0,0 @@ -use super::{RequestBody, RequestMethod}; - -#[derive(Clone, Debug, PartialEq, Eq)] -pub struct SavedRequest { - method: RequestMethod, - url: String, - body: RequestBody, -} -impl SavedRequest { - pub fn new(method: RequestMethod, url: &str, body: RequestBody) -> Self { - Self { - method, - url: url.to_string(), - body, - } - } - pub const fn method(&self) -> RequestMethod { - self.method - } - pub fn url(&self) -> &str { - &self.url - } - pub const fn body(&self) -> &RequestBody { - &self.body - } -} - -#[cfg(test)] -mod tests { - use assert2::let_assert; - - use super::*; - - #[test_log::test] - fn test_saved_request() { - let request = - SavedRequest::new(RequestMethod::Get, "https://httpbin.org", RequestBody::None); - assert_eq!(request.method, RequestMethod::Get); - assert_eq!(request.url, "https://httpbin.org"); - assert_eq!(request.body, RequestBody::None); - } - - #[test_log::test] - fn test_saved_request_body() { - let request = SavedRequest::new( - RequestMethod::Get, - "https://httpbin.org", - RequestBody::String("body".to_string()), - ); - let_assert!(RequestBody::String(body) = request.body()); - assert_eq!(body, "body"); - } - - #[test_log::test] - fn test_saved_request_eq() { - let request1 = SavedRequest::new( - RequestMethod::Get, - "https://httpbin.org", - RequestBody::String("body".to_string()), - ); - let request2 = SavedRequest::new( - RequestMethod::Get, - "https://httpbin.org", - RequestBody::String("body".to_string()), - ); - assert_eq!(request1, request2); - } - - #[test_log::test] - fn test_saved_request_ne_method() { - let request1 = SavedRequest::new( - RequestMethod::Get, - "https://httpbin.org", - RequestBody::String("body".to_string()), - ); - let request2 = SavedRequest::new( - RequestMethod::Post, - "https://httpbin.org", - RequestBody::String("body".to_string()), - ); - assert_ne!(request1, request2); - } - - #[test_log::test] - fn test_saved_request_ne_url() { - let request1 = SavedRequest::new( - RequestMethod::Get, - "https://httpbin.org", - RequestBody::String("body".to_string()), - ); - let request2 = SavedRequest::new( - RequestMethod::Get, - "https://httpbin.org2", - RequestBody::String("body".to_string()), - ); - assert_ne!(request1, request2); - } - - #[test_log::test] - fn test_saved_request_ne_body() { - let request1 = SavedRequest::new( - RequestMethod::Get, - "https://httpbin.org", - RequestBody::String("body".to_string()), - ); - let request2 = SavedRequest::new( - RequestMethod::Get, - "https://httpbin.org", - RequestBody::String("body2".to_string()), - ); - assert_ne!(request1, request2); - } -}