#[cfg(feature = "uuid")]
use ::uuid::Uuid as GeneratedUuid;
#[cfg(feature = "reqwest")]
use std::collections::BTreeMap;
#[cfg(feature = "reqwest")]
use std::fmt;
use std::io::Error as IoError;
use std::path::PathBuf;
#[cfg(feature = "reqwest")]
use std::sync::OnceLock;
use std::time::{Duration, SystemTime};
fn clock_now() -> SystemTime {
SystemTime::now()
}
async fn clock_sleep(duration: Duration) {
std::thread::sleep(duration);
}
crate::client! {
pub struct Clock as clock {
pub fn now() -> SystemTime = clock_now;
pub async fn sleep(duration: Duration) -> () = clock_sleep;
}
}
fn env_var(name: String) -> Option<String> {
std::env::var(name).ok()
}
fn env_current_dir() -> Result<PathBuf, IoError> {
std::env::current_dir()
}
fn env_temp_dir() -> PathBuf {
std::env::temp_dir()
}
crate::client! {
pub struct Env as env {
pub fn var(name: String) -> Option<String> = env_var;
pub fn current_dir() -> Result<PathBuf, IoError> = env_current_dir;
pub fn temp_dir() -> PathBuf = env_temp_dir;
}
}
fn random_u64() -> u64 {
::rand::random::<u64>()
}
fn random_bytes(len: usize) -> Vec<u8> {
let mut bytes = vec![0; len];
let mut rng = ::rand::thread_rng();
::rand::RngCore::fill_bytes(&mut rng, &mut bytes);
bytes
}
crate::client! {
pub struct Random as random {
pub fn next_u64() -> u64 = random_u64;
pub fn fill_bytes(len: usize) -> Vec<u8> = random_bytes;
}
}
fn filesystem_read(path: PathBuf) -> Result<Vec<u8>, IoError> {
std::fs::read(path)
}
fn filesystem_read_string(path: PathBuf) -> Result<String, IoError> {
std::fs::read_to_string(path)
}
fn filesystem_write(path: PathBuf, contents: Vec<u8>) -> Result<(), IoError> {
std::fs::write(path, contents)
}
fn filesystem_write_string(path: PathBuf, contents: String) -> Result<(), IoError> {
std::fs::write(path, contents)
}
crate::client! {
pub struct Filesystem as filesystem {
pub fn read(path: PathBuf) -> Result<Vec<u8>, IoError> = filesystem_read;
pub fn read_string(path: PathBuf) -> Result<String, IoError> = filesystem_read_string;
pub fn write(path: PathBuf, contents: Vec<u8>) -> Result<(), IoError> = filesystem_write;
pub fn write_string(path: PathBuf, contents: String) -> Result<(), IoError> = filesystem_write_string;
}
}
#[cfg(feature = "uuid")]
fn generate_uuid() -> GeneratedUuid {
GeneratedUuid::new_v4()
}
#[cfg(feature = "uuid")]
crate::client! {
pub struct Uuid as uuid {
pub fn generate() -> GeneratedUuid = generate_uuid;
}
}
#[cfg(feature = "reqwest")]
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum HttpMethod {
Get,
Post,
Put,
Patch,
Delete,
Head,
Options,
}
#[cfg(feature = "reqwest")]
impl HttpMethod {
fn as_reqwest(&self) -> ::reqwest::Method {
match self {
Self::Get => ::reqwest::Method::GET,
Self::Post => ::reqwest::Method::POST,
Self::Put => ::reqwest::Method::PUT,
Self::Patch => ::reqwest::Method::PATCH,
Self::Delete => ::reqwest::Method::DELETE,
Self::Head => ::reqwest::Method::HEAD,
Self::Options => ::reqwest::Method::OPTIONS,
}
}
}
#[cfg(feature = "reqwest")]
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct HttpRequest {
pub method: HttpMethod,
pub url: String,
pub headers: BTreeMap<String, String>,
pub body: Vec<u8>,
}
#[cfg(feature = "reqwest")]
impl HttpRequest {
pub fn get(url: impl Into<String>) -> Self {
Self {
method: HttpMethod::Get,
url: url.into(),
headers: BTreeMap::new(),
body: Vec::new(),
}
}
pub fn post(url: impl Into<String>, body: Vec<u8>) -> Self {
Self {
method: HttpMethod::Post,
url: url.into(),
headers: BTreeMap::new(),
body,
}
}
}
#[cfg(feature = "reqwest")]
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct HttpResponse {
pub status: u16,
pub headers: BTreeMap<String, String>,
pub body: Vec<u8>,
}
#[cfg(feature = "reqwest")]
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum HttpClientError {
BuildRequest(String),
Transport(String),
}
#[cfg(feature = "reqwest")]
impl fmt::Display for HttpClientError {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::BuildRequest(error) => write!(formatter, "failed to build HTTP request: {error}"),
Self::Transport(error) => write!(formatter, "HTTP transport error: {error}"),
}
}
}
#[cfg(feature = "reqwest")]
impl std::error::Error for HttpClientError {}
#[cfg(feature = "reqwest")]
fn reqwest_client() -> &'static ::reqwest::blocking::Client {
static CLIENT: OnceLock<::reqwest::blocking::Client> = OnceLock::new();
CLIENT.get_or_init(::reqwest::blocking::Client::new)
}
#[cfg(feature = "reqwest")]
fn execute_http_request(request: HttpRequest) -> Result<HttpResponse, HttpClientError> {
let client = reqwest_client();
let mut builder = client.request(request.method.as_reqwest(), request.url);
for (name, value) in request.headers {
builder = builder.header(name, value);
}
if !request.body.is_empty() {
builder = builder.body(request.body);
}
let response = builder
.send()
.map_err(|error| HttpClientError::Transport(error.to_string()))?;
let status = response.status().as_u16();
let mut headers = BTreeMap::new();
for (name, value) in response.headers() {
headers.insert(
name.to_string(),
value.to_str().unwrap_or_default().to_string(),
);
}
let body = response
.bytes()
.map_err(|error| HttpClientError::Transport(error.to_string()))?
.to_vec();
Ok(HttpResponse {
status,
headers,
body,
})
}
#[cfg(feature = "reqwest")]
crate::client! {
pub struct HttpClient as http_client {
pub fn execute(request: HttpRequest) -> Result<HttpResponse, HttpClientError> = |request: HttpRequest| {
execute_http_request(request)
};
pub fn get(url: String) -> Result<HttpResponse, HttpClientError> = |url: String| {
execute_http_request(HttpRequest::get(url))
};
pub fn post(url: String, body: Vec<u8>) -> Result<HttpResponse, HttpClientError> = |url: String, body: Vec<u8>| {
execute_http_request(HttpRequest::post(url, body))
};
}
}