use std::sync::Arc;
use std::sync::atomic::{AtomicUsize, Ordering};
use async_trait::async_trait;
use bytes::Bytes;
use cratefield_core::{DispatchError, Dispatcher};
use tower::ServiceExt;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Fault {
DropRequestId,
DuplicateRequestId,
RemapStatus,
MangleBody,
StripRequestHeaders,
Unavailable,
NotBound,
}
pub struct FakeSidecar {
binding: String,
router: axum::Router,
calls: AtomicUsize,
fault: Option<Fault>,
}
impl FakeSidecar {
#[must_use]
pub fn new(binding: impl Into<String>, router: axum::Router) -> Self {
Self {
binding: binding.into(),
router,
calls: AtomicUsize::new(0),
fault: None,
}
}
#[must_use]
pub fn faulty(binding: impl Into<String>, router: axum::Router, fault: Fault) -> Self {
Self {
fault: Some(fault),
..Self::new(binding, router)
}
}
#[must_use]
pub fn calls(&self) -> usize {
self.calls.load(Ordering::SeqCst)
}
}
#[async_trait]
impl Dispatcher for FakeSidecar {
fn has(&self, binding: &str) -> bool {
self.fault != Some(Fault::NotBound) && binding == self.binding
}
async fn dispatch(
&self,
binding: &str,
request: http::Request<Bytes>,
) -> Result<http::Response<Bytes>, DispatchError> {
self.calls.fetch_add(1, Ordering::SeqCst);
if binding != self.binding {
return Err(DispatchError::NotBound(binding.to_owned()));
}
if self.fault == Some(Fault::Unavailable) {
return Err(DispatchError::Unavailable {
binding: binding.to_owned(),
reason: "fake sidecar is not answering".to_owned(),
});
}
let (mut parts, body) = request.into_parts();
if self.fault == Some(Fault::StripRequestHeaders) {
parts.headers.clear();
}
let inbound = http::Request::from_parts(parts, axum::body::Body::from(body));
let response = self
.router
.clone()
.oneshot(inbound)
.await
.expect("in-process router is infallible");
let (mut parts, body) = response.into_parts();
let bytes = axum::body::to_bytes(body, 8 * 1024 * 1024)
.await
.unwrap_or_default();
let bytes = match self.fault {
Some(Fault::DropRequestId) => {
parts.headers.remove(cratefield_core::X_REQUEST_ID);
bytes
}
Some(Fault::RemapStatus) => {
parts.status = http::StatusCode::INTERNAL_SERVER_ERROR;
bytes
}
Some(Fault::DuplicateRequestId) => {
parts.headers.append(
cratefield_core::X_REQUEST_ID,
http::HeaderValue::from_static("a-second-id-from-the-sidecar"),
);
bytes
}
Some(Fault::MangleBody) => Bytes::from_static(b"{\"not\":\"what the module said\"}"),
_ => bytes,
};
Ok(http::Response::from_parts(parts, bytes))
}
}
#[must_use]
pub fn shared(sidecar: FakeSidecar) -> (Arc<FakeSidecar>, Arc<dyn Dispatcher>) {
let sidecar = Arc::new(sidecar);
let handle: Arc<dyn Dispatcher> = sidecar.clone();
(sidecar, handle)
}