Compare commits
1 commit
edf5af2e63
...
f9d8696e00
Author | SHA1 | Date | |
---|---|---|---|
f9d8696e00 |
23 changed files with 2775 additions and 537 deletions
3
.gitignore
vendored
3
.gitignore
vendored
|
@ -16,6 +16,3 @@ Cargo.lock
|
|||
|
||||
# MSVC Windows builds of rustc generate these, which store debugging information
|
||||
*.pdb
|
||||
|
||||
mutants.out/
|
||||
mutants.out.old/
|
||||
|
|
|
@ -15,6 +15,23 @@ impl Reader {
|
|||
Ok(Self { contents })
|
||||
}
|
||||
|
||||
/// Returns the contents of the file as a string.
|
||||
///
|
||||
/// ```
|
||||
/// # use kxio::fs::Result;
|
||||
/// # fn main() -> Result<()> {
|
||||
/// let fs = kxio::fs::temp()?;
|
||||
/// let path = fs.base().join("foo");
|
||||
/// let file = fs.file(&path);
|
||||
/// # file.write("new file contents")?;
|
||||
/// let contents = file.reader()?.contents();
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// ```
|
||||
pub fn contents(&self) -> &str {
|
||||
&self.contents
|
||||
}
|
||||
|
||||
/// Returns the contents of the file as a string.
|
||||
///
|
||||
/// ```
|
||||
|
|
|
@ -1,3 +1,6 @@
|
|||
//
|
||||
pub mod fs;
|
||||
pub mod net;
|
||||
|
||||
#[deprecated]
|
||||
pub mod network;
|
||||
|
|
76
src/net/mock.rs
Normal file
76
src/net/mock.rs
Normal file
|
@ -0,0 +1,76 @@
|
|||
//
|
||||
use super::{Error, Net, Result};
|
||||
|
||||
struct Plan {
|
||||
request: reqwest::Request,
|
||||
response: reqwest::Response,
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct MockNet {
|
||||
plans: Vec<Plan>,
|
||||
}
|
||||
impl MockNet {
|
||||
pub(crate) fn new() -> Self {
|
||||
Self::default()
|
||||
}
|
||||
|
||||
pub fn as_net(&self) -> Net<'_> {
|
||||
Net::mock(self)
|
||||
}
|
||||
|
||||
pub fn client(&self) -> reqwest::Client {
|
||||
Net::client()
|
||||
}
|
||||
|
||||
pub fn response(&self) -> http::response::Builder {
|
||||
http::Response::builder()
|
||||
}
|
||||
|
||||
pub fn on(&mut self, request: reqwest::RequestBuilder) -> Result<OnRequest<'_>> {
|
||||
Ok(OnRequest {
|
||||
mock: self,
|
||||
request: request.build()?,
|
||||
})
|
||||
}
|
||||
|
||||
fn _on(&mut self, request: reqwest::Request, response: reqwest::Response) {
|
||||
self.plans.push(Plan { request, response })
|
||||
}
|
||||
|
||||
pub(crate) async fn send(
|
||||
&mut self,
|
||||
request: reqwest::RequestBuilder,
|
||||
) -> Result<reqwest::Response> {
|
||||
let request = request.build()?;
|
||||
let index = self.plans.iter().position(|plan| {
|
||||
// TODO: add support or ony matching on selected criteria
|
||||
plan.request.method() == request.method()
|
||||
&& plan.request.url() == request.url()
|
||||
&& match (plan.request.body(), request.body()) {
|
||||
(None, None) => true,
|
||||
(Some(plan), Some(request)) => plan.as_bytes() == request.as_bytes(),
|
||||
_ => false,
|
||||
}
|
||||
&& plan.request.headers() == request.headers()
|
||||
});
|
||||
match index {
|
||||
Some(i) => Ok(self.plans.remove(i).response),
|
||||
None => Err(Error::UnexpectedMockRequest(request)),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn assert(&self) -> Result<()> {
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
|
||||
pub struct OnRequest<'mock> {
|
||||
mock: &'mock mut MockNet,
|
||||
request: reqwest::Request,
|
||||
}
|
||||
impl<'mock> OnRequest<'mock> {
|
||||
pub fn response(self, response: reqwest::Response) {
|
||||
self.mock._on(self.request, response)
|
||||
}
|
||||
}
|
|
@ -2,21 +2,21 @@
|
|||
//!
|
||||
//!
|
||||
|
||||
mod mock;
|
||||
mod system;
|
||||
mod result;
|
||||
|
||||
pub use result::{Error, Result};
|
||||
|
||||
pub use system::{MatchOn, Net};
|
||||
use system::{Mocked, Unmocked};
|
||||
pub use system::Net;
|
||||
|
||||
|
||||
/// Creates a new `Net`.
|
||||
pub const fn new() -> Net<Unmocked> {
|
||||
Net::<Unmocked>::new()
|
||||
pub const fn new() -> Net<'static> {
|
||||
Net::new()
|
||||
}
|
||||
|
||||
/// Creates a new `MockNet` for use in tests.
|
||||
pub fn mock() -> Net<Mocked> {
|
||||
Net::<Mocked>::new()
|
||||
pub fn mock() -> mock::MockNet {
|
||||
mock::MockNet::new()
|
||||
}
|
||||
|
|
|
@ -7,7 +7,6 @@ use derive_more::derive::From;
|
|||
pub enum Error {
|
||||
Reqwest(reqwest::Error),
|
||||
Request(String),
|
||||
#[display("Unexpected request: {0}", 0.to_string())]
|
||||
UnexpectedMockRequest(reqwest::Request),
|
||||
}
|
||||
impl std::error::Error for Error {}
|
||||
|
@ -16,7 +15,6 @@ impl Clone for Error {
|
|||
match self {
|
||||
Self::Reqwest(req) => Self::Request(req.to_string()),
|
||||
Self::Request(req) => Self::Request(req.clone()),
|
||||
Self::UnexpectedMockRequest(_) => todo!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,149 +1,30 @@
|
|||
//
|
||||
use std::marker::PhantomData;
|
||||
use super::{mock::MockNet, Error};
|
||||
|
||||
use super::{Error, Result};
|
||||
pub struct Net<'mock> {
|
||||
mock: Option<&'mock MockNet>,
|
||||
}
|
||||
impl<'mock> Net<'mock> {
|
||||
pub(crate) const fn mock(mock: &'mock MockNet) -> Self {
|
||||
Self { mock: Some(mock) }
|
||||
}
|
||||
|
||||
pub trait NetType {}
|
||||
|
||||
pub struct Mocked;
|
||||
impl NetType for Mocked {}
|
||||
pub struct Unmocked;
|
||||
impl NetType for Unmocked {}
|
||||
|
||||
type Plans = Vec<Plan>;
|
||||
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
pub enum MatchOn {
|
||||
Method,
|
||||
Url,
|
||||
Body,
|
||||
Headers,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Plan {
|
||||
request: reqwest::Request,
|
||||
response: reqwest::Response,
|
||||
match_on: Vec<MatchOn>,
|
||||
}
|
||||
impl Net<'_> {
|
||||
|
||||
pub struct Net<T: NetType> {
|
||||
_type: PhantomData<T>,
|
||||
plans: Plans,
|
||||
}
|
||||
impl Net<Unmocked> {
|
||||
pub(crate) const fn new() -> Self {
|
||||
Self {
|
||||
_type: PhantomData,
|
||||
plans: vec![],
|
||||
}
|
||||
Self { mock: None }
|
||||
}
|
||||
|
||||
pub async fn send(&mut self, request: reqwest::RequestBuilder) -> Result<reqwest::Response> {
|
||||
request.send().await.map_err(Error::from)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: NetType> Net<T> {
|
||||
pub fn client(&self) -> reqwest::Client {
|
||||
Default::default()
|
||||
}
|
||||
}
|
||||
impl Net<Mocked> {
|
||||
pub(crate) const fn new() -> Self {
|
||||
Self {
|
||||
_type: PhantomData,
|
||||
plans: vec![],
|
||||
}
|
||||
}
|
||||
pub async fn send(&mut self, request: reqwest::RequestBuilder) -> Result<reqwest::Response> {
|
||||
let request = request.build()?;
|
||||
let index = self.plans.iter().position(|plan| {
|
||||
// METHOD
|
||||
(if plan.match_on.contains(&MatchOn::Method) {
|
||||
plan.request.method() == request.method()
|
||||
} else {
|
||||
true
|
||||
})
|
||||
// URL
|
||||
&& (if plan.match_on.contains(&MatchOn::Url) {
|
||||
plan.request.url() == request.url()
|
||||
} else {
|
||||
true
|
||||
})
|
||||
// BODY
|
||||
&& (if plan.match_on.contains(&MatchOn::Body) {
|
||||
match (plan.request.body(), request.body()) {
|
||||
(None, None) => true,
|
||||
(Some(plan), Some(req)) => plan.as_bytes().eq(&req.as_bytes()),
|
||||
_ => false,
|
||||
}
|
||||
} else {
|
||||
true
|
||||
})
|
||||
// HEADERS
|
||||
&& (if plan.match_on.contains(&MatchOn::Headers) {
|
||||
plan.request.headers() == request.headers()
|
||||
} else {
|
||||
true
|
||||
})
|
||||
});
|
||||
match index {
|
||||
Some(i) => Ok(self.plans.remove(i).response),
|
||||
None => Err(Error::UnexpectedMockRequest(request)),
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates a [ResponseBuilder] to be extended and returned by a mocked network request.
|
||||
pub fn response(&self) -> http::response::Builder {
|
||||
Default::default()
|
||||
}
|
||||
|
||||
pub fn on(&mut self, request: reqwest::Request) -> OnRequest {
|
||||
OnRequest {
|
||||
net: self,
|
||||
request,
|
||||
match_on: vec![MatchOn::Method, MatchOn::Url],
|
||||
}
|
||||
}
|
||||
|
||||
fn _on(
|
||||
&mut self,
|
||||
request: reqwest::Request,
|
||||
response: reqwest::Response,
|
||||
match_on: Vec<MatchOn>,
|
||||
) {
|
||||
self.plans.push(Plan {
|
||||
request,
|
||||
response,
|
||||
match_on,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn reset(&mut self) {
|
||||
self.plans = vec![];
|
||||
}
|
||||
}
|
||||
impl<T: NetType> Drop for Net<T> {
|
||||
fn drop(&mut self) {
|
||||
assert!(self.plans.is_empty())
|
||||
}
|
||||
}
|
||||
|
||||
pub struct OnRequest<'net> {
|
||||
net: &'net mut Net<Mocked>,
|
||||
request: reqwest::Request,
|
||||
match_on: Vec<MatchOn>,
|
||||
}
|
||||
impl<'net> OnRequest<'net> {
|
||||
pub fn match_on(self, match_on: Vec<MatchOn>) -> Self {
|
||||
Self {
|
||||
net: self.net,
|
||||
request: self.request,
|
||||
match_on,
|
||||
}
|
||||
}
|
||||
pub fn respond(self, response: reqwest::Response) {
|
||||
self.net._on(self.request, response, self.match_on)
|
||||
pub fn client() -> reqwest::Client {
|
||||
reqwest::Client::new()
|
||||
}
|
||||
|
||||
pub async fn send(&self, request: reqwest::RequestBuilder) -> Result<reqwest::Response, Error> {
|
||||
match self.mock {
|
||||
Some(mock) => mock.send(request).await,
|
||||
None => request.send().await.map_err(Error::from),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
795
src/network/mock.rs
Normal file
795
src/network/mock.rs
Normal file
|
@ -0,0 +1,795 @@
|
|||
#![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<Mutex<Vec<SavedRequest>>>,
|
||||
get_responses: HashMap<NetUrl, (StatusCode, String)>,
|
||||
get_errors: HashMap<NetUrl, String>,
|
||||
post_responses: HashMap<NetUrl, (StatusCode, String)>,
|
||||
post_errors: HashMap<NetUrl, String>,
|
||||
put_responses: HashMap<NetUrl, (StatusCode, String)>,
|
||||
put_errors: HashMap<NetUrl, String>,
|
||||
patch_responses: HashMap<NetUrl, (StatusCode, String)>,
|
||||
patch_errors: HashMap<NetUrl, String>,
|
||||
delete_responses: HashMap<NetUrl, (StatusCode, String)>,
|
||||
delete_errors: HashMap<NetUrl, String>,
|
||||
propfind_responses: HashMap<NetUrl, (StatusCode, String)>,
|
||||
propfind_errors: HashMap<NetUrl, String>,
|
||||
}
|
||||
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<SavedRequest> {
|
||||
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<Reply: DeserializeOwned>(
|
||||
&self,
|
||||
net_request: NetRequest,
|
||||
) -> Result<NetResponse<Reply>, 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<Reply> = 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<NetResponse<String>, 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<NetResponse<String>, 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<NetResponse<String>, 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<MockNetwork> for Network {
|
||||
fn from(mock: MockNetwork) -> Self {
|
||||
Self::Mock(mock)
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl NetworkTrait for MockNetwork {
|
||||
async fn get<Reply: DeserializeOwned>(
|
||||
&self,
|
||||
net_request: NetRequest,
|
||||
) -> Result<NetResponse<Reply>, 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<NetResponse<String>, 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<Reply: DeserializeOwned>(
|
||||
&self,
|
||||
net_request: NetRequest,
|
||||
) -> Result<NetResponse<Reply>, 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<NetResponse<String>, 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<Reply: DeserializeOwned>(
|
||||
&self,
|
||||
net_request: NetRequest,
|
||||
) -> Result<NetResponse<Reply>, 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<NetResponse<String>, 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<Reply: DeserializeOwned + std::fmt::Debug>(
|
||||
&self,
|
||||
net_request: NetRequest,
|
||||
) -> Result<NetResponse<Reply>, 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<NetResponse<()>, 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<Reply: DeserializeOwned + std::fmt::Debug>(
|
||||
&self,
|
||||
net_request: NetRequest,
|
||||
) -> Result<NetResponse<Reply>, 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<NetResponse<String>, 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<HashMap<String, String>> = 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<NetResponse<HashMap<String, String>>, 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<String> = 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<NetResponse<String>, 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<HashMap<String, String>> = 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<NetResponse<HashMap<String, String>>, 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<HashMap<String, String>> = 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<NetResponse<HashMap<String, String>>, 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<String> = 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<NetResponse<String>, 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#"<container><foo>bar</foo></container>"#,
|
||||
);
|
||||
let net_request = NetRequest::propfind(NetUrl::new("https://caldav.org".into()))
|
||||
.response_type(ResponseType::Xml)
|
||||
.build();
|
||||
let response: NetResponse<PropfindTestResponse> = 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#"<container><foo>bar</foo></container>"#,
|
||||
);
|
||||
let net_request = NetRequest::propfind(NetUrl::new("https://caldav.org".into()))
|
||||
.response_type(ResponseType::Text)
|
||||
.build();
|
||||
let response: NetResponse<String> = block_on(net.propfind_string(net_request))?;
|
||||
assert_eq!(
|
||||
response.response_body(),
|
||||
Some("<container><foo>bar</foo></container>".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<NetResponse<PropfindTestResponse>, 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<NetResponse<String>, 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(())
|
||||
}
|
||||
}
|
36
src/network/mod.rs
Normal file
36
src/network/mod.rs
Normal file
|
@ -0,0 +1,36 @@
|
|||
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<T> = Result<T, NetworkError>;
|
66
src/network/net_auth.rs
Normal file
66
src/network/net_auth.rs
Normal file
|
@ -0,0 +1,66 @@
|
|||
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<Password> 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
|
||||
}
|
||||
}
|
462
src/network/net_request.rs
Normal file
462
src/network/net_request.rs
Normal file
|
@ -0,0 +1,462 @@
|
|||
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<NetAuth>,
|
||||
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<NetAuth>,
|
||||
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::<String>()
|
||||
)
|
||||
}
|
||||
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<NetAuth> {
|
||||
self.auth.clone()
|
||||
}
|
||||
pub fn headers(&self) -> HeaderMap {
|
||||
self.headers
|
||||
.clone()
|
||||
.try_into()
|
||||
.unwrap_or_else(|_| HeaderMap::new())
|
||||
}
|
||||
pub fn as_curl(&self) -> Result<String, serde_json::Error> {
|
||||
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<NetAuth>,
|
||||
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<T: serde::Serialize>(mut self, body: T) -> Result<Self, serde_json::Error> {
|
||||
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("<xml/>")
|
||||
.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 '<xml/>'"
|
||||
.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");
|
||||
}
|
||||
}
|
102
src/network/net_request_headers.rs
Normal file
102
src/network/net_request_headers.rs
Normal file
|
@ -0,0 +1,102 @@
|
|||
use std::{
|
||||
collections::HashMap,
|
||||
ops::{Deref, DerefMut},
|
||||
};
|
||||
|
||||
use crate::network::HeaderMap;
|
||||
|
||||
#[derive(Clone, Debug, Default, PartialEq, Eq)]
|
||||
pub struct NetRequestHeaders(HashMap<String, String>);
|
||||
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<T> From<T> for NetRequestHeaders
|
||||
where
|
||||
T: Into<HashMap<String, String>>,
|
||||
{
|
||||
fn from(x: T) -> Self {
|
||||
Self(x.into())
|
||||
}
|
||||
}
|
||||
impl TryFrom<NetRequestHeaders> for HeaderMap {
|
||||
type Error = http::Error;
|
||||
fn try_from(x: NetRequestHeaders) -> Result<Self, Self::Error> {
|
||||
Self::try_from(&x.0)
|
||||
}
|
||||
}
|
||||
impl Deref for NetRequestHeaders {
|
||||
type Target = HashMap<String, String>;
|
||||
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::<HashMap<_, _>>(),
|
||||
);
|
||||
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::<HashMap<_, _>>(),
|
||||
)
|
||||
.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<HeaderMap, http::Error> = NetRequestHeaders::from(
|
||||
[("a", "b"), ("c", "d")]
|
||||
.iter()
|
||||
.map(|(k, v)| (k.to_string(), v.to_string()))
|
||||
.collect::<HashMap<_, _>>(),
|
||||
)
|
||||
.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");
|
||||
}
|
||||
}
|
44
src/network/net_response.rs
Normal file
44
src/network/net_response.rs
Normal file
|
@ -0,0 +1,44 @@
|
|||
use crate::network::StatusCode;
|
||||
|
||||
use super::{net_request::NetUrl, RequestMethod};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct NetResponse<T> {
|
||||
method: RequestMethod,
|
||||
request_url: NetUrl,
|
||||
status_code: StatusCode,
|
||||
response_body: Option<T>,
|
||||
}
|
||||
impl<T> NetResponse<T> {
|
||||
pub fn new(
|
||||
method: RequestMethod,
|
||||
request_url: &NetUrl,
|
||||
status_code: StatusCode,
|
||||
response_body: Option<T>,
|
||||
) -> 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<T> {
|
||||
self.response_body
|
||||
}
|
||||
}
|
||||
|
||||
impl From<NetResponse<()>> for () {
|
||||
fn from(_value: NetResponse<()>) -> Self {
|
||||
// ()
|
||||
}
|
||||
}
|
174
src/network/network_env.rs
Normal file
174
src/network/network_env.rs
Normal file
|
@ -0,0 +1,174 @@
|
|||
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<T: DeserializeOwned + std::fmt::Debug>(
|
||||
&self,
|
||||
net_request: NetRequest,
|
||||
) -> Result<NetResponse<T>, 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<NetResponse<String>, 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<Reply: DeserializeOwned + std::fmt::Debug>(
|
||||
&self,
|
||||
net_request: NetRequest,
|
||||
) -> Result<NetResponse<Reply>, 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<NetResponse<String>, 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<Reply: DeserializeOwned + std::fmt::Debug>(
|
||||
&self,
|
||||
net_request: NetRequest,
|
||||
) -> Result<NetResponse<Reply>, 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<NetResponse<String>, 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<Reply: DeserializeOwned + std::fmt::Debug>(
|
||||
&self,
|
||||
net_request: NetRequest,
|
||||
) -> Result<NetResponse<Reply>, 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<NetResponse<()>, NetworkError> {
|
||||
match self {
|
||||
Self::Mock(mock) => mock.delete(net_request).await,
|
||||
Self::Real(real) => real.delete(net_request).await,
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn propfind<Reply: DeserializeOwned + std::fmt::Debug>(
|
||||
&self,
|
||||
net_request: NetRequest,
|
||||
) -> Result<NetResponse<Reply>, 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<NetResponse<String>, 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<Vec<SavedRequest>> {
|
||||
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<T: DeserializeOwned + std::fmt::Debug>(
|
||||
&self,
|
||||
net_request: NetRequest,
|
||||
) -> Result<NetResponse<T>, NetworkError>;
|
||||
|
||||
async fn get_string(
|
||||
&self,
|
||||
net_request: NetRequest,
|
||||
) -> Result<NetResponse<String>, NetworkError>;
|
||||
|
||||
async fn post_json<Reply: DeserializeOwned + std::fmt::Debug>(
|
||||
&self,
|
||||
net_request: NetRequest,
|
||||
) -> Result<NetResponse<Reply>, NetworkError>;
|
||||
|
||||
async fn post_string(
|
||||
&self,
|
||||
net_request: NetRequest,
|
||||
) -> Result<NetResponse<String>, NetworkError>;
|
||||
|
||||
async fn put_json<Reply: DeserializeOwned + std::fmt::Debug>(
|
||||
&self,
|
||||
net_request: NetRequest,
|
||||
) -> Result<NetResponse<Reply>, NetworkError>;
|
||||
|
||||
async fn put_string(
|
||||
&self,
|
||||
net_request: NetRequest,
|
||||
) -> Result<NetResponse<String>, NetworkError>;
|
||||
|
||||
async fn patch_json<Reply: DeserializeOwned + std::fmt::Debug>(
|
||||
&self,
|
||||
net_request: NetRequest,
|
||||
) -> Result<NetResponse<Reply>, NetworkError>;
|
||||
|
||||
async fn delete(&self, net_request: NetRequest) -> Result<NetResponse<()>, NetworkError>;
|
||||
|
||||
async fn propfind<Reply: DeserializeOwned + std::fmt::Debug>(
|
||||
&self,
|
||||
net_request: NetRequest,
|
||||
) -> Result<NetResponse<Reply>, NetworkError>;
|
||||
|
||||
async fn propfind_string(
|
||||
&self,
|
||||
net_request: NetRequest,
|
||||
) -> Result<NetResponse<String>, NetworkError>;
|
||||
}
|
56
src/network/network_error.rs
Normal file
56
src/network/network_error.rs
Normal file
|
@ -0,0 +1,56 @@
|
|||
#[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<T: Send>() {}
|
||||
assert_send::<NetworkError>();
|
||||
assert_send::<Result<(), NetworkError>>();
|
||||
}
|
||||
}
|
612
src/network/real.rs
Normal file
612
src/network/real.rs
Normal file
|
@ -0,0 +1,612 @@
|
|||
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<NetAuth>) -> reqwest::RequestBuilder;
|
||||
}
|
||||
impl WithAuthentiction for reqwest::RequestBuilder {
|
||||
fn auth(self, auth: Option<NetAuth>) -> 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<T: DeserializeOwned + std::fmt::Debug>(
|
||||
&self,
|
||||
request: RequestBuilder,
|
||||
net_request: NetRequest,
|
||||
) -> Result<NetResponse<T>, 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<NetResponse<String>, 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<RealNetwork> 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<T: DeserializeOwned + std::fmt::Debug>(
|
||||
&self,
|
||||
net_request: NetRequest,
|
||||
) -> Result<NetResponse<T>, 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<Reply: DeserializeOwned + std::fmt::Debug>(
|
||||
&self,
|
||||
net_request: NetRequest,
|
||||
) -> Result<NetResponse<Reply>, 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<NetResponse<String>, 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<Reply: DeserializeOwned + std::fmt::Debug>(
|
||||
&self,
|
||||
net_request: NetRequest,
|
||||
) -> Result<NetResponse<Reply>, 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<NetResponse<String>, 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<Reply: DeserializeOwned + std::fmt::Debug>(
|
||||
&self,
|
||||
net_request: NetRequest,
|
||||
) -> Result<NetResponse<Reply>, 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<NetResponse<()>, 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<Reply: DeserializeOwned + std::fmt::Debug>(
|
||||
&self,
|
||||
net_request: NetRequest,
|
||||
) -> Result<NetResponse<Reply>, 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<NetResponse<String>, 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<NetResponse<String>, 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<String, String>,
|
||||
pub url: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, serde::Deserialize)]
|
||||
pub struct PostResponse {
|
||||
pub args: HashMap<String, String>,
|
||||
pub url: String,
|
||||
pub data: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, serde::Deserialize)]
|
||||
pub struct PutResponse {
|
||||
pub args: HashMap<String, String>,
|
||||
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<GetResponse> = 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<NetResponse<String>, 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<PostResponse> = 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<NetResponse<PostResponse>, 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<PutResponse> = 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<NetResponse<String>, 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());
|
||||
}
|
||||
}
|
86
src/network/request_body.rs
Normal file
86
src/network/request_body.rs
Normal file
|
@ -0,0 +1,86 @@
|
|||
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<Self, Self::Error> {
|
||||
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<T: Serialize>(source: T) -> Result<Self, serde_json::Error> {
|
||||
serde_json::to_value(source).map(Self::Json)
|
||||
}
|
||||
pub fn content_type(&self) -> Option<String> {
|
||||
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");
|
||||
}
|
||||
}
|
26
src/network/request_method.rs
Normal file
26
src/network/request_method.rs
Normal file
|
@ -0,0 +1,26 @@
|
|||
#[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"),
|
||||
}
|
||||
}
|
||||
}
|
22
src/network/response_type.rs
Normal file
22
src/network/response_type.rs
Normal file
|
@ -0,0 +1,22 @@
|
|||
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
|
||||
pub enum ResponseType {
|
||||
Json,
|
||||
Xml,
|
||||
None,
|
||||
Text,
|
||||
}
|
||||
impl ResponseType {
|
||||
pub fn accept(&self) -> Option<String> {
|
||||
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
|
||||
}
|
||||
}
|
113
src/network/saved_request.rs
Normal file
113
src/network/saved_request.rs
Normal file
|
@ -0,0 +1,113 @@
|
|||
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);
|
||||
}
|
||||
}
|
4
src/tests/mod.rs
Normal file
4
src/tests/mod.rs
Normal file
|
@ -0,0 +1,4 @@
|
|||
//
|
||||
pub mod fs;
|
||||
|
||||
// pub mod network;
|
167
tests/fs.rs
167
tests/fs.rs
|
@ -7,55 +7,6 @@ type TestResult = Result<(), fs::Error>;
|
|||
mod path {
|
||||
use super::*;
|
||||
|
||||
mod is_link {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn create_soft_link() -> TestResult {
|
||||
let fs = fs::temp().expect("temp fs");
|
||||
|
||||
let file_path = fs.base().join("foo");
|
||||
let file = fs.file(&file_path);
|
||||
file.write("content").expect("write");
|
||||
|
||||
let link_path = fs.base().join("bar");
|
||||
let link = fs.path(&link_path);
|
||||
file.soft_link(&link).expect("soft_link");
|
||||
|
||||
let exists = link.exists().expect("exists");
|
||||
assert!(exists);
|
||||
|
||||
let is_link = link.is_link().expect("is_link");
|
||||
assert!(is_link);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn dir_is_not_a_link() {
|
||||
let fs = fs::temp().expect("temp fs");
|
||||
|
||||
let dir_path = fs.base().join("foo");
|
||||
let dir = fs.dir(&dir_path);
|
||||
dir.create().expect("create");
|
||||
|
||||
let is_link = dir.is_link().expect("is link");
|
||||
assert!(!is_link);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn file_is_not_a_link() {
|
||||
let fs = fs::temp().expect("temp fs");
|
||||
|
||||
let file_path = fs.base().join("foo");
|
||||
let file = fs.file(&file_path);
|
||||
file.write("content").expect("write");
|
||||
|
||||
let is_link = file.is_link().expect("is link");
|
||||
assert!(!is_link);
|
||||
}
|
||||
}
|
||||
|
||||
mod set_permissions {
|
||||
use super::*;
|
||||
|
||||
|
@ -404,22 +355,6 @@ mod path {
|
|||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
mod from_pathbuf {
|
||||
use std::path::PathBuf;
|
||||
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn should_convert_from_pathbuf() {
|
||||
let fs = fs::temp().expect("temp fs");
|
||||
|
||||
let src_pathbuf = fs.base().join("foo");
|
||||
let dst_pathbuf: PathBuf = fs.path(&src_pathbuf).into();
|
||||
|
||||
assert_eq!(src_pathbuf, dst_pathbuf);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
mod file {
|
||||
|
@ -440,6 +375,27 @@ mod file {
|
|||
// Ok(())
|
||||
// }
|
||||
|
||||
#[test]
|
||||
fn create_soft_link() -> TestResult {
|
||||
let fs = fs::temp().expect("temp fs");
|
||||
|
||||
let file_path = fs.base().join("foo");
|
||||
let file = fs.file(&file_path);
|
||||
file.write("content").expect("write");
|
||||
|
||||
let link_path = fs.base().join("bar");
|
||||
let link = fs.path(&link_path);
|
||||
file.soft_link(&link).expect("soft_link");
|
||||
|
||||
let exists = link.exists().expect("exists");
|
||||
assert!(exists);
|
||||
|
||||
let is_link = link.is_link().expect("is_link");
|
||||
assert!(is_link);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn create_hard_link() -> TestResult {
|
||||
let fs = fs::temp().expect("temp fs");
|
||||
|
@ -476,50 +432,6 @@ mod file {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn use_file_as_file() {
|
||||
let fs = fs::temp().expect("temp fs");
|
||||
|
||||
let file_path = fs.base().join("foo");
|
||||
let file = fs.file(&file_path);
|
||||
file.write("contents").expect("write");
|
||||
|
||||
file.remove().expect("remove");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn use_dir_as_file() {
|
||||
let fs = fs::temp().expect("temp fs");
|
||||
|
||||
let dir_path = fs.base().join("foo");
|
||||
let dir = fs.dir(&dir_path);
|
||||
dir.create().expect("create");
|
||||
|
||||
let file = fs.file(&dir_path);
|
||||
let_assert!(Err(fs::Error::NotAFile { path }) = file.remove());
|
||||
assert_eq!(path, dir_path);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn use_link_as_file() {
|
||||
let fs = fs::temp().expect("temp fs");
|
||||
|
||||
let file_path = fs.base().join("foo");
|
||||
let file = fs.file(&file_path);
|
||||
file.write("contents").expect("write");
|
||||
|
||||
let link_path = fs.base().join("bar");
|
||||
let link = fs.path(&link_path);
|
||||
|
||||
file.soft_link(&link).expect("soft_link");
|
||||
|
||||
let path = fs.file(&link_path);
|
||||
|
||||
let contents = path.reader().expect("reader").to_string();
|
||||
assert_eq!(contents, "contents");
|
||||
path.remove().expect("remove");
|
||||
}
|
||||
|
||||
mod remove {
|
||||
use super::*;
|
||||
#[test]
|
||||
|
@ -626,47 +538,20 @@ mod file {
|
|||
use super::*;
|
||||
mod to_string {
|
||||
use super::*;
|
||||
|
||||
use std::time::SystemTime;
|
||||
|
||||
#[test]
|
||||
fn read_file_to_string() {
|
||||
fn read_file_to_string() -> TestResult {
|
||||
let fs = fs::temp().expect("temp fs");
|
||||
|
||||
let path = fs.base().join("foo");
|
||||
let file = fs.file(&path);
|
||||
let line3 = SystemTime::now()
|
||||
.duration_since(SystemTime::UNIX_EPOCH)
|
||||
.expect("duration")
|
||||
.as_millis()
|
||||
.to_string();
|
||||
let contents = format!("line 1\nline 2\n{line3}");
|
||||
file.write(&contents).expect("write");
|
||||
file.write("line 1\nline 2").expect("write");
|
||||
|
||||
let reader = file.reader().expect("reader");
|
||||
let string = reader.as_str();
|
||||
let string = reader.to_string();
|
||||
|
||||
assert_eq!(string, contents);
|
||||
}
|
||||
assert_eq!(string, "line 1\nline 2");
|
||||
|
||||
#[test]
|
||||
fn read_file_lines() {
|
||||
let fs = fs::temp().expect("temp fs");
|
||||
|
||||
let path = fs.base().join("foo");
|
||||
let file = fs.file(&path);
|
||||
let line3 = SystemTime::now()
|
||||
.duration_since(SystemTime::UNIX_EPOCH)
|
||||
.expect("duration")
|
||||
.as_millis()
|
||||
.to_string();
|
||||
let contents = format!("line 1\nline 2\n{line3}");
|
||||
file.write(&contents).expect("write");
|
||||
|
||||
let reader = file.reader().expect("reader");
|
||||
let lines = reader.lines().collect::<Vec<_>>();
|
||||
|
||||
assert_eq!(lines, vec!["line 1", "line 2", &line3]);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
mod lines {
|
||||
|
|
273
tests/net.rs
273
tests/net.rs
|
@ -1,253 +1,36 @@
|
|||
use assert2::let_assert;
|
||||
//
|
||||
use kxio::net::{Error, MatchOn};
|
||||
use kxio::net::Error;
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_get_url() {
|
||||
//given
|
||||
let mut net = kxio::net::mock();
|
||||
let client = net.client();
|
||||
type TestResult = Result<(), Error>;
|
||||
|
||||
mod get {
|
||||
|
||||
use kxio::net::Net;
|
||||
|
||||
use super::*;
|
||||
|
||||
#[tokio::test]
|
||||
async fn test() -> TestResult {
|
||||
let mut net_mock = kxio::net::mock();
|
||||
|
||||
let url = "https://www.example.com";
|
||||
let request = client.get(url).build().expect("build request");
|
||||
let my_response = net
|
||||
.response()
|
||||
.status(200)
|
||||
.body("Get OK")
|
||||
.expect("request body");
|
||||
let request = net_mock.client().get(url);
|
||||
let my_response = net_mock.response().status(200).body("OK").unwrap();
|
||||
|
||||
net.on(request).respond(my_response.into());
|
||||
net_mock.on(request).response(my_response.clone().into());
|
||||
|
||||
//when
|
||||
let response = net.send(client.get(url)).await.expect("response");
|
||||
let net = net_mock.as_net();
|
||||
let client = Net::client();
|
||||
|
||||
//then
|
||||
assert_eq!(response.status(), http::StatusCode::OK);
|
||||
assert_eq!(response.bytes().await.expect("response body"), "Get OK");
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_get_wrong_url() {
|
||||
//given
|
||||
let mut net = kxio::net::mock();
|
||||
let client = net.client();
|
||||
|
||||
let url = "https://www.example.com";
|
||||
let request = client.get(url).build().expect("build request");
|
||||
let my_response = net
|
||||
.response()
|
||||
.status(200)
|
||||
.body("Get OK")
|
||||
.expect("request body");
|
||||
|
||||
net.on(request).respond(my_response.into());
|
||||
|
||||
//when
|
||||
let_assert!(Err(Error::UnexpectedMockRequest(invalid_request)) = net.send(client.get("https://some.other.url/")).await);
|
||||
|
||||
//then
|
||||
assert_eq!(invalid_request.url().to_string(), "https://some.other.url/");
|
||||
|
||||
// remove pending unmatched request - we never meant to match against it
|
||||
net.reset();
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_post_url() {
|
||||
//given
|
||||
let mut net = kxio::net::mock();
|
||||
let client = net.client();
|
||||
|
||||
let url = "https://www.example.com";
|
||||
let request = client.post(url).build().expect("build request");
|
||||
let my_response = net
|
||||
.response()
|
||||
.status(200)
|
||||
.body("Post OK")
|
||||
.expect("request body");
|
||||
|
||||
net.on(request).respond(my_response.into());
|
||||
|
||||
//when
|
||||
let response = net.send(client.post(url)).await.expect("reponse");
|
||||
|
||||
//then
|
||||
assert_eq!(response.status(), http::StatusCode::OK);
|
||||
assert_eq!(response.bytes().await.expect("response body"), "Post OK");
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_post_by_method() {
|
||||
//given
|
||||
let mut net = kxio::net::mock();
|
||||
let client = net.client();
|
||||
|
||||
let url = "https://www.example.com";
|
||||
let request = client.post(url).build().expect("build request");
|
||||
let my_response = net
|
||||
.response()
|
||||
.status(200)
|
||||
.body("Post OK")
|
||||
.expect("request body");
|
||||
|
||||
net.on(request)
|
||||
.match_on(vec![
|
||||
MatchOn::Method,
|
||||
// MatchOn::Url
|
||||
])
|
||||
.respond(my_response.into());
|
||||
|
||||
//when
|
||||
// This request is a different url - but should still match
|
||||
let response = net
|
||||
.send(client.post("https://some.other.url"))
|
||||
.await
|
||||
.expect("response");
|
||||
|
||||
//then
|
||||
assert_eq!(response.status(), http::StatusCode::OK);
|
||||
assert_eq!(response.bytes().await.expect("response body"), "Post OK");
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_post_by_url() {
|
||||
//given
|
||||
let mut net = kxio::net::mock();
|
||||
let client = net.client();
|
||||
|
||||
let url = "https://www.example.com";
|
||||
let request = client.post(url).build().expect("build request");
|
||||
let my_response = net
|
||||
.response()
|
||||
.status(200)
|
||||
.body("Post OK")
|
||||
.expect("request body");
|
||||
|
||||
net.on(request)
|
||||
.match_on(vec![
|
||||
// MatchOn::Method,
|
||||
MatchOn::Url,
|
||||
])
|
||||
.respond(my_response.into());
|
||||
|
||||
//when
|
||||
// This request is a GET, not POST - but should still match
|
||||
let response = net.send(client.get(url)).await.expect("response");
|
||||
|
||||
//then
|
||||
assert_eq!(response.status(), http::StatusCode::OK);
|
||||
assert_eq!(response.bytes().await.expect("response body"), "Post OK");
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_post_by_body() {
|
||||
//given
|
||||
let mut net = kxio::net::mock();
|
||||
let client = net.client();
|
||||
|
||||
let url = "https://www.example.com";
|
||||
let request = client
|
||||
.post(url)
|
||||
.body("match on body")
|
||||
.build()
|
||||
.expect("build request");
|
||||
let my_response = net
|
||||
.response()
|
||||
.status(200)
|
||||
.body("response body")
|
||||
.expect("request body");
|
||||
|
||||
net.on(request)
|
||||
.match_on(vec![
|
||||
// MatchOn::Method,
|
||||
// MatchOn::Url
|
||||
MatchOn::Body,
|
||||
])
|
||||
.respond(my_response.into());
|
||||
|
||||
//when
|
||||
// This request is a GET, not POST - but should still match
|
||||
let response = net
|
||||
.send(client.get("https://some.other.url").body("match on body"))
|
||||
.await
|
||||
.expect("response");
|
||||
|
||||
//then
|
||||
assert_eq!(response.status(), http::StatusCode::OK);
|
||||
assert_eq!(
|
||||
response.bytes().await.expect("response body"),
|
||||
"response body"
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_post_by_headers() {
|
||||
//given
|
||||
let mut net = kxio::net::mock();
|
||||
let client = net.client();
|
||||
|
||||
let url = "https://www.example.com";
|
||||
let request = client
|
||||
.post(url)
|
||||
.body("foo")
|
||||
.header("test", "match")
|
||||
.build()
|
||||
.expect("build request");
|
||||
let my_response = net
|
||||
.response()
|
||||
.status(200)
|
||||
.body("response body")
|
||||
.expect("request body");
|
||||
|
||||
net.on(request)
|
||||
.match_on(vec![
|
||||
// MatchOn::Method,
|
||||
// MatchOn::Url
|
||||
MatchOn::Headers,
|
||||
])
|
||||
.respond(my_response.into());
|
||||
|
||||
//when
|
||||
// This request is a GET, not POST - but should still match
|
||||
let response = net
|
||||
.send(
|
||||
client
|
||||
.get("https://some.other.url")
|
||||
.body("match on body")
|
||||
.header("test", "match"),
|
||||
)
|
||||
.await
|
||||
.expect("response");
|
||||
|
||||
//then
|
||||
assert_eq!(response.status(), http::StatusCode::OK);
|
||||
assert_eq!(
|
||||
response.bytes().await.expect("response body"),
|
||||
"response body"
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
#[should_panic]
|
||||
async fn test_unused_post() {
|
||||
//given
|
||||
let mut net = kxio::net::mock();
|
||||
let client = net.client();
|
||||
|
||||
let url = "https://www.example.com";
|
||||
let request = client.post(url).build().expect("build request");
|
||||
let my_response = net
|
||||
.response()
|
||||
.status(200)
|
||||
.body("Post OK")
|
||||
.expect("request body");
|
||||
|
||||
net.on(request).respond(my_response.into());
|
||||
|
||||
//when
|
||||
// don't send the planned request
|
||||
// let _response = net.send(client.post(url)).await.expect("send");
|
||||
|
||||
//then
|
||||
// Drop implementation for net should panic
|
||||
let response = net.send(client.get(url)).await?;
|
||||
|
||||
assert_eq!(response.status(), my_response.status());
|
||||
|
||||
net_mock.assert()?;
|
||||
|
||||
// let my_net = net::new();
|
||||
// my_net.send(request).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue