use std::fmt;
use std::sync::Arc;
use std::time::Duration;
use futures_util::future::BoxFuture;
use crate::clock::Clock;
use crate::control::BuildServices;
use crate::error::Error;
use crate::region::Region;
use crate::session::SharedBackend;
pub trait Adapters: Send + Sync + fmt::Debug {
fn http_backend(&self, endpoint: &str, timeout: Duration) -> Result<SharedBackend, Error>;
fn build_services(
&self,
region: Region,
) -> BoxFuture<'_, Result<Arc<dyn BuildServices>, Error>>;
fn clock(&self) -> Arc<dyn Clock>;
fn warn(&self, warning: &str);
}
#[cfg(any(test, feature = "test-support"))]
#[cfg_attr(test, mutants::skip)] pub(crate) mod testing {
use super::*;
use crate::clock::testing::TestClock;
use crate::error::{ErrorKind, WireKind};
use crate::session::{HttpBackend, HttpRequest, HttpResponse, OpenStream};
#[derive(Default)]
pub struct TestAdapters {
clock: Arc<TestClock>,
warnings: std::sync::Mutex<Vec<String>>,
backend: Option<SharedBackend>,
}
impl std::fmt::Debug for TestAdapters {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("TestAdapters")
.field("backend", &self.backend.as_ref().map(|_| "scripted"))
.finish_non_exhaustive()
}
}
impl TestAdapters {
pub fn new() -> Self {
Self::default()
}
pub fn with_clock(clock: Arc<TestClock>) -> Self {
Self {
clock,
..Self::default()
}
}
#[must_use]
pub fn with_backend(mut self, backend: SharedBackend) -> Self {
self.backend = Some(backend);
self
}
pub fn warnings(&self) -> Vec<String> {
self.warnings
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner)
.clone()
}
}
impl Adapters for TestAdapters {
fn http_backend(&self, endpoint: &str, _timeout: Duration) -> Result<SharedBackend, Error> {
Ok(match &self.backend {
Some(backend) => Arc::clone(backend),
None => Arc::new(Inert {
endpoint: endpoint.to_string(),
}),
})
}
fn build_services(
&self,
region: Region,
) -> BoxFuture<'_, Result<Arc<dyn BuildServices>, Error>> {
Box::pin(async move {
Err(Error::new(
ErrorKind::Credentials,
format!(
"the test adapters build no STS or S3 client for {region}; pass one \
with Sandbox::with_build_services"
),
))
})
}
fn clock(&self) -> Arc<dyn Clock> {
Arc::clone(&self.clock) as Arc<dyn Clock>
}
fn warn(&self, warning: &str) {
self.warnings
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner)
.push(warning.to_string());
}
}
struct Inert {
endpoint: String,
}
impl Inert {
fn refuse(&self, request: &HttpRequest) -> Error {
Error::wire(
WireKind::Transport,
format!(
"{} {} was sent to {} through the test adapters, which reach nothing",
request.method, request.path, self.endpoint
),
)
}
}
impl HttpBackend for Inert {
fn send(&self, request: HttpRequest) -> BoxFuture<'_, Result<HttpResponse, Error>> {
let error = self.refuse(&request);
Box::pin(async move { Err(error) })
}
fn open_stream(
&self,
request: HttpRequest,
_idle_timeout: Duration,
) -> BoxFuture<'_, Result<OpenStream, Error>> {
let error = self.refuse(&request);
Box::pin(async move { Err(error) })
}
}
}