cratefield_testing/
sidecar.rs1use std::sync::Arc;
10use std::sync::atomic::{AtomicUsize, Ordering};
11
12use async_trait::async_trait;
13use bytes::Bytes;
14use cratefield_core::{DispatchError, Dispatcher};
15use tower::ServiceExt;
16
17#[derive(Debug, Clone, Copy, PartialEq, Eq)]
20pub enum Fault {
21 DropRequestId,
24 DuplicateRequestId,
27 RemapStatus,
29 MangleBody,
31 StripRequestHeaders,
34 Unavailable,
36 NotBound,
38}
39
40pub struct FakeSidecar {
42 binding: String,
43 router: axum::Router,
44 calls: AtomicUsize,
45 fault: Option<Fault>,
46}
47
48impl FakeSidecar {
49 #[must_use]
52 pub fn new(binding: impl Into<String>, router: axum::Router) -> Self {
53 Self {
54 binding: binding.into(),
55 router,
56 calls: AtomicUsize::new(0),
57 fault: None,
58 }
59 }
60
61 #[must_use]
63 pub fn faulty(binding: impl Into<String>, router: axum::Router, fault: Fault) -> Self {
64 Self {
65 fault: Some(fault),
66 ..Self::new(binding, router)
67 }
68 }
69
70 #[must_use]
73 pub fn calls(&self) -> usize {
74 self.calls.load(Ordering::SeqCst)
75 }
76}
77
78#[async_trait]
79impl Dispatcher for FakeSidecar {
80 fn has(&self, binding: &str) -> bool {
81 self.fault != Some(Fault::NotBound) && binding == self.binding
82 }
83
84 async fn dispatch(
85 &self,
86 binding: &str,
87 request: http::Request<Bytes>,
88 ) -> Result<http::Response<Bytes>, DispatchError> {
89 self.calls.fetch_add(1, Ordering::SeqCst);
90 if binding != self.binding {
91 return Err(DispatchError::NotBound(binding.to_owned()));
92 }
93 if self.fault == Some(Fault::Unavailable) {
94 return Err(DispatchError::Unavailable {
95 binding: binding.to_owned(),
96 reason: "fake sidecar is not answering".to_owned(),
97 });
98 }
99 let (mut parts, body) = request.into_parts();
100 if self.fault == Some(Fault::StripRequestHeaders) {
101 parts.headers.clear();
102 }
103 let inbound = http::Request::from_parts(parts, axum::body::Body::from(body));
104 let response = self
105 .router
106 .clone()
107 .oneshot(inbound)
108 .await
109 .expect("in-process router is infallible");
110 let (mut parts, body) = response.into_parts();
111 let bytes = axum::body::to_bytes(body, 8 * 1024 * 1024)
112 .await
113 .unwrap_or_default();
114 let bytes = match self.fault {
115 Some(Fault::DropRequestId) => {
116 parts.headers.remove(cratefield_core::X_REQUEST_ID);
117 bytes
118 }
119 Some(Fault::RemapStatus) => {
120 parts.status = http::StatusCode::INTERNAL_SERVER_ERROR;
121 bytes
122 }
123 Some(Fault::DuplicateRequestId) => {
124 parts.headers.append(
125 cratefield_core::X_REQUEST_ID,
126 http::HeaderValue::from_static("a-second-id-from-the-sidecar"),
127 );
128 bytes
129 }
130 Some(Fault::MangleBody) => Bytes::from_static(b"{\"not\":\"what the module said\"}"),
131 _ => bytes,
132 };
133 Ok(http::Response::from_parts(parts, bytes))
134 }
135}
136
137#[must_use]
140pub fn shared(sidecar: FakeSidecar) -> (Arc<FakeSidecar>, Arc<dyn Dispatcher>) {
141 let sidecar = Arc::new(sidecar);
142 let handle: Arc<dyn Dispatcher> = sidecar.clone();
143 (sidecar, handle)
144}