use std::{
borrow::Cow,
env,
fs::{create_dir_all, File},
future::Future,
io::{Read, Write},
path::{Path, PathBuf},
sync::Arc,
task::{Context, Poll},
};
use bytes::Bytes;
use crossbeam_utils::sync::{Parker, Unparker};
use futures_timer::Delay;
use futures_util::{pin_mut, task::ArcWake};
use serde::{Deserialize, Serialize, Serializer};
use std::{cell::Cell, time::Duration};
pub(crate) fn update_cell<T: Sized + Default, F: FnOnce(&mut T)>(v: &Cell<T>, f: F) {
let mut vv = v.take();
f(&mut vv);
v.set(vv);
}
#[doc(hidden)]
pub(crate) async fn with_retry<T, U, F, Fut>(retries: usize, f: F) -> Result<T, U>
where
F: Fn() -> Fut,
Fut: Future<Output = Result<T, U>>,
{
let mut result = (f)().await;
for i in 1..=retries {
if result.is_ok() {
return result;
} else {
Delay::new(Duration::from_secs(1 * i as u64)).await;
}
result = (f)().await;
}
result
}
#[doc(hidden)]
pub(crate) fn read_env(name: &str, default: &str) -> String {
match std::env::var(name) {
Ok(value) => value,
Err(_) => default.to_string(),
}
}
#[doc(hidden)]
pub trait Join: Future {
fn join(self) -> <Self as Future>::Output;
}
impl<F: Future> Join for F {
fn join(self) -> <Self as Future>::Output {
struct ThreadWaker(Unparker);
impl ArcWake for ThreadWaker {
fn wake_by_ref(arc_self: &Arc<Self>) {
arc_self.0.unpark();
}
}
let parker = Parker::new();
let waker = futures_util::task::waker(Arc::new(ThreadWaker(parker.unparker().clone())));
let mut context = Context::from_waker(&waker);
let future = self;
pin_mut!(future);
loop {
match future.as_mut().poll(&mut context) {
Poll::Ready(output) => return output,
Poll::Pending => parker.park(),
}
}
}
}
pub fn get_test_resource_file_path(relative_resource_path: &str) -> Result<PathBuf, String> {
match env::var("CARGO_MANIFEST_DIR") {
Ok(manifest_path) => Ok(Path::new(&manifest_path).join(relative_resource_path)),
Err(e) => Err(e.to_string()),
}
}
pub async fn write_file<P: AsRef<Path>>(
resource_path: P,
content: &Bytes,
create_dir: bool,
) -> Result<PathBuf, Box<dyn std::error::Error>> {
let mut path = resource_path.as_ref().to_path_buf();
if path.is_relative() {
let current_dir = env::current_dir()?;
path = current_dir.join(path);
}
if create_dir {
if let Some(parent) = path.parent() {
create_dir_all(parent)?;
}
}
let mut file = File::create(&path)?;
file.write_all(content)?;
file.flush()?;
Ok(path)
}
#[cfg(test)]
mod test {
use crate::common::util::{with_retry, Join};
#[test]
fn with_retry_error_test() {
let result: Result<(), &str> = with_retry(1, || async {
return Err("test error");
})
.join();
assert_eq!(result.is_err(), true);
assert_eq!(result.err().unwrap(), "test error")
}
}
#[derive(Serialize, Deserialize, Clone, PartialEq, Eq)]
pub struct HttpMockBytes(pub Bytes);
impl HttpMockBytes {
pub fn to_vec(&self) -> Vec<u8> {
self.0.to_vec()
}
pub fn to_bytes(&self) -> Bytes {
self.0.clone()
}
pub fn is_empty(&self) -> bool {
self.0.is_empty()
}
pub fn is_blank(&self) -> bool {
self.is_empty() || self.0.iter().all(|&b| b.is_ascii_whitespace())
}
pub fn contains_str(&self, substring: &str) -> bool {
if substring.is_empty() {
return true;
}
self.0
.as_ref()
.windows(substring.as_bytes().len())
.any(|window| window == substring.as_bytes())
}
pub fn contains_slice(&self, slice: &[u8]) -> bool {
self.0
.as_ref()
.windows(slice.len())
.any(|window| window == slice)
}
pub fn contains_vec(&self, vec: &Vec<u8>) -> bool {
self.0
.as_ref()
.windows(vec.len())
.any(|window| window == vec.as_slice())
}
pub fn to_maybe_lossy_str(&self) -> Cow<str> {
return match std::str::from_utf8(&self.0) {
Ok(valid_str) => Cow::Borrowed(valid_str),
Err(_) => Cow::Owned(String::from_utf8_lossy(&self.0).to_string()),
};
}
}
impl From<Bytes> for HttpMockBytes {
fn from(value: Bytes) -> Self {
Self(value)
}
}
impl From<&Bytes> for HttpMockBytes {
fn from(value: &Bytes) -> Self {
Self(value.clone())
} }
impl From<bytes::BytesMut> for HttpMockBytes {
fn from(value: bytes::BytesMut) -> Self {
Self(value.freeze())
}
}
impl From<Vec<u8>> for HttpMockBytes {
fn from(value: Vec<u8>) -> Self {
Self(Bytes::from(value))
}
}
impl From<&[u8]> for HttpMockBytes {
fn from(value: &[u8]) -> Self {
Self(Bytes::copy_from_slice(value))
}
}
impl From<String> for HttpMockBytes {
fn from(value: String) -> Self {
Self(Bytes::from(value))
}
}
impl From<&str> for HttpMockBytes {
fn from(value: &str) -> Self {
Self(Bytes::copy_from_slice(value.as_bytes()))
}
}
impl<'a> From<Cow<'a, str>> for HttpMockBytes {
fn from(value: Cow<'a, str>) -> Self {
match value {
Cow::Borrowed(s) => Self::from(s),
Cow::Owned(s) => Self::from(s),
}
}
}
impl<'a> From<Cow<'a, [u8]>> for HttpMockBytes {
fn from(value: Cow<'a, [u8]>) -> Self {
match value {
Cow::Borrowed(b) => Self::from(b),
Cow::Owned(v) => Self::from(v),
}
}
}
impl From<HttpMockBytes> for Bytes {
fn from(value: HttpMockBytes) -> Bytes {
value.0
}
}
impl AsRef<[u8]> for HttpMockBytes {
fn as_ref(&self) -> &[u8] {
self.0.as_ref()
}
}
impl std::borrow::Borrow<[u8]> for HttpMockBytes {
fn borrow(&self) -> &[u8] {
self.0.as_ref()
}
}
impl PartialEq<[u8]> for HttpMockBytes {
fn eq(&self, other: &[u8]) -> bool {
self.0.as_ref() == other
}
}
impl PartialEq<&[u8]> for HttpMockBytes {
fn eq(&self, other: &&[u8]) -> bool {
self.0.as_ref() == *other
}
}
impl PartialEq<Vec<u8>> for HttpMockBytes {
fn eq(&self, other: &Vec<u8>) -> bool {
self.0.as_ref() == other.as_slice()
}
}
impl PartialEq<Bytes> for HttpMockBytes {
fn eq(&self, other: &Bytes) -> bool {
&self.0 == other
}
}
impl PartialEq<&str> for HttpMockBytes {
fn eq(&self, other: &&str) -> bool {
self.0.as_ref() == other.as_bytes()
}
}
impl PartialEq<String> for HttpMockBytes {
fn eq(&self, other: &String) -> bool {
self.0.as_ref() == other.as_bytes()
}
}
impl std::fmt::Display for HttpMockBytes {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match std::str::from_utf8(&self.0) {
Ok(s) => write!(f, "{s}"),
Err(_) => write!(f, "{}", base64::encode(&self.0)),
}
}
}
impl std::fmt::Debug for HttpMockBytes {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match std::str::from_utf8(&self.0) {
Ok(s) => f.debug_tuple("HttpMockBytes").field(&s).finish(),
Err(_) => f
.debug_tuple("HttpMockBytes")
.field(&format!(
"<{} bytes, b64:{}>",
self.0.len(),
base64::encode(&self.0)
))
.finish(),
}
}
}
pub fn title_case(s: &str) -> String {
let mut result = String::new();
let mut capitalize_next = true;
for c in s.chars() {
if c.is_whitespace() {
capitalize_next = true;
result.push(c);
} else if capitalize_next {
result.push(c.to_uppercase().next().unwrap());
capitalize_next = false;
} else {
result.push(c.to_lowercase().next().unwrap());
}
}
result
}
pub fn is_none_or_empty<T>(option: &Option<Vec<T>>) -> bool {
match option {
None => true,
Some(vec) => vec.is_empty(),
}
}
pub fn read_file<P: AsRef<Path>>(absolute_resource_path: P) -> Result<Vec<u8>, String> {
let mut buffer = Vec::new();
let len = File::open(&absolute_resource_path)
.and_then(|mut f| f.read_to_end(&mut buffer))
.map_err(|e| e.to_string())?;
tracing::trace!(
"Read {} bytes from file {:?}",
&len,
&absolute_resource_path
.as_ref()
.as_os_str()
.to_str()
.expect("Invalid file path")
);
Ok(buffer)
}