1use std::any::TypeId;
4use std::sync::Arc;
5
6use futures::future::BoxFuture;
7use serde::de::DeserializeOwned;
8use serde::Serialize;
9use serde_json::Value;
10
11use crate::codec::{Codec, JsonCodec, Payload};
12use crate::error::{BoxError, Error};
13use crate::identity::Identity;
14use crate::limits::DEFAULT_FALLBACK_TIMEOUT_MS;
15use crate::local::StoredValue;
16use crate::policy::Policy;
17use crate::scope::Scope;
18use crate::shadow_log::{JsonRender, ValueRenderer};
19
20#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
22pub enum SourceBudget {
23 #[default]
25 Default,
26 Unbounded,
29 Millis(u64),
31}
32
33impl SourceBudget {
34 pub(crate) fn millis(self) -> Option<u64> {
35 match self {
36 SourceBudget::Default => Some(DEFAULT_FALLBACK_TIMEOUT_MS),
37 SourceBudget::Unbounded => None,
38 SourceBudget::Millis(ms) => Some(ms),
39 }
40 }
41}
42
43pub type RecoveryPredicate = Arc<dyn Fn(&Error) -> bool + Send + Sync>;
46pub type Comparator<T> = Arc<dyn Fn(&T, &T) -> bool + Send + Sync>;
48pub type Preview<T> = Arc<dyn Fn(&T) -> Option<String> + Send + Sync>;
57pub type MismatchDiffHook<T> = Arc<dyn Fn(&T, &T) -> Vec<MismatchEntry> + Send + Sync>;
66
67#[derive(Debug, Clone, PartialEq, Serialize)]
72pub struct MismatchEntry {
73 pub path: String,
75 #[serde(skip_serializing_if = "Option::is_none")]
77 pub cached: Option<Value>,
78 #[serde(skip_serializing_if = "Option::is_none")]
80 pub source: Option<Value>,
81}
82
83pub struct Operation<T> {
89 pub identity: Identity,
91 pub policy: Policy,
93 pub budget: SourceBudget,
95 pub codec: Arc<dyn Codec<T>>,
97 pub comparator: Comparator<T>,
99 pub should_recover: Option<RecoveryPredicate>,
102 pub preview: Option<Preview<T>>,
112 pub mismatch_diff: Option<MismatchDiffHook<T>>,
117 pub(crate) json: Option<JsonDefault<T>>,
120}
121
122pub(crate) struct JsonDefault<T> {
128 render: JsonRender,
129 preview: Preview<T>,
130}
131
132impl<T: Serialize + Send + Sync + 'static> JsonDefault<T> {
133 pub(crate) fn new() -> Self {
134 JsonDefault {
135 render: JsonRender::of::<T>(),
136 preview: Arc::new(crate::preview::json_preview::<T>),
137 }
138 }
139}
140
141impl<T> JsonDefault<T> {
142 pub(crate) fn preview(&self) -> Preview<T> {
144 self.preview.clone()
145 }
146}
147
148impl<T> Clone for JsonDefault<T> {
149 fn clone(&self) -> Self {
150 JsonDefault {
151 render: self.render,
152 preview: self.preview.clone(),
153 }
154 }
155}
156
157impl<T> Clone for Operation<T> {
158 fn clone(&self) -> Self {
159 Self {
160 identity: self.identity.clone(),
161 policy: self.policy.clone(),
162 budget: self.budget,
163 codec: self.codec.clone(),
164 comparator: self.comparator.clone(),
165 should_recover: self.should_recover.clone(),
166 preview: self.preview.clone(),
167 mismatch_diff: self.mismatch_diff.clone(),
168 json: self.json.clone(),
169 }
170 }
171}
172
173impl<T> std::fmt::Debug for Operation<T> {
174 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
175 f.debug_struct("Operation")
176 .field("identity", &self.identity)
177 .field("policy", &self.policy)
178 .field("budget", &self.budget)
179 .finish_non_exhaustive()
180 }
181}
182
183impl<T: Serialize + DeserializeOwned + PartialEq + Send + Sync + 'static> Operation<T> {
184 pub fn new(identity: Identity) -> Self {
191 let json = JsonDefault::new();
192 Operation {
193 identity,
194 policy: Policy::default(),
195 budget: SourceBudget::Default,
196 codec: Arc::new(JsonCodec),
197 comparator: Arc::new(|a: &T, b: &T| a == b),
198 should_recover: None,
199 preview: Some(json.preview()),
200 mismatch_diff: None,
201 json: Some(json),
202 }
203 }
204}
205
206impl<T: Send + Sync + 'static> Operation<T> {
207 pub fn with_codec(
215 identity: Identity,
216 codec: Arc<dyn Codec<T>>,
217 comparator: impl Fn(&T, &T) -> bool + Send + Sync + 'static,
218 ) -> Self {
219 Operation {
220 identity,
221 policy: Policy::default(),
222 budget: SourceBudget::Default,
223 codec,
224 comparator: Arc::new(comparator),
225 should_recover: None,
226 preview: None,
227 mismatch_diff: None,
228 json: None,
229 }
230 }
231
232 pub fn policy(mut self, policy: Policy) -> Self {
234 self.policy = policy;
235 self
236 }
237
238 pub fn budget(mut self, budget: SourceBudget) -> Self {
240 self.budget = budget;
241 self
242 }
243
244 pub fn should_recover(
247 mut self,
248 predicate: impl Fn(&Error) -> bool + Send + Sync + 'static,
249 ) -> Self {
250 self.should_recover = Some(Arc::new(predicate));
251 self
252 }
253
254 pub fn preview(
259 mut self,
260 preview: impl Fn(&T) -> Option<String> + Send + Sync + 'static,
261 ) -> Self {
262 self.preview = Some(Arc::new(preview));
263 self
264 }
265
266 pub fn mismatch_diff(
269 mut self,
270 hook: impl Fn(&T, &T) -> Vec<MismatchEntry> + Send + Sync + 'static,
271 ) -> Self {
272 self.mismatch_diff = Some(Arc::new(hook));
273 self
274 }
275
276 pub(crate) fn erase(self) -> (Identity, Arc<OperationMetadata>) {
277 let codec = self.codec;
278 let comparator = self.comparator;
279 let value_renderer = match (self.preview, self.json) {
282 (Some(preview), Some(json)) if Arc::ptr_eq(&preview, &json.preview) => {
283 Some(ValueRenderer::Json(json.render))
284 }
285 (preview, _) => preview.map(|preview| {
286 ValueRenderer::Preview(Arc::new(move |value: &StoredValue| {
287 value.downcast_ref::<T>().and_then(|v| preview(v))
288 }))
289 }),
290 };
291 let mismatch_diff = self.mismatch_diff;
292 let metadata = OperationMetadata {
293 value_type: TypeId::of::<T>(),
294 policy: self.policy,
295 budget: self.budget,
296 codec: Arc::new(TypedCodec { codec }),
297 compare: Arc::new(move |a: &StoredValue, b: &StoredValue| {
298 let (Some(a), Some(b)) = (a.downcast_ref::<T>(), b.downcast_ref::<T>()) else {
299 return Err("shadow comparison received a value of another type".into());
300 };
301 Ok(comparator(a, b))
302 }),
303 should_recover: self.should_recover,
304 value_renderer,
305 mismatch_diff: mismatch_diff.map(|hook| {
306 Arc::new(move |cached: &StoredValue, source: &StoredValue| {
307 let typed = "compared values have the operation's type";
309 hook(
310 cached.downcast_ref::<T>().expect(typed),
311 source.downcast_ref::<T>().expect(typed),
312 )
313 }) as ErasedMismatchDiff
314 }),
315 };
316 (self.identity, Arc::new(metadata))
317 }
318}
319
320pub(crate) type ErasedLoad =
322 Arc<dyn Fn(Scope) -> BoxFuture<'static, Result<StoredValue, BoxError>> + Send + Sync>;
323pub(crate) type ErasedCompare =
324 Arc<dyn Fn(&StoredValue, &StoredValue) -> Result<bool, BoxError> + Send + Sync>;
325pub(crate) type ErasedPreview = Arc<dyn Fn(&StoredValue) -> Option<String> + Send + Sync>;
326pub(crate) type ErasedMismatchDiff =
327 Arc<dyn Fn(&StoredValue, &StoredValue) -> Vec<MismatchEntry> + Send + Sync>;
328pub(crate) type IdentityProvider = Arc<dyn Fn() -> Result<Identity, BoxError> + Send + Sync>;
329
330pub(crate) struct OperationMetadata {
332 pub value_type: TypeId,
333 pub policy: Policy,
334 pub budget: SourceBudget,
335 pub codec: Arc<dyn ErasedCodec>,
336 pub compare: ErasedCompare,
337 pub should_recover: Option<RecoveryPredicate>,
338 pub value_renderer: Option<ValueRenderer>,
341 pub mismatch_diff: Option<ErasedMismatchDiff>,
342}
343
344pub(crate) struct ErasedOperation {
346 pub identity: Identity,
347 pub identity_provider: Option<IdentityProvider>,
348 pub metadata: Arc<OperationMetadata>,
349 pub load: ErasedLoad,
350}
351
352pub(crate) trait ErasedCodec: Send + Sync {
354 fn encode(&self, value: StoredValue) -> BoxFuture<'_, Result<Payload, BoxError>>;
355 fn decode(&self, payload: Payload) -> BoxFuture<'_, Result<StoredValue, BoxError>>;
356}
357
358struct TypedCodec<T> {
359 codec: Arc<dyn Codec<T>>,
360}
361
362impl<T: Send + Sync + 'static> ErasedCodec for TypedCodec<T> {
363 fn encode(&self, value: StoredValue) -> BoxFuture<'_, Result<Payload, BoxError>> {
364 match Arc::downcast::<T>(value) {
365 Ok(value) => self.codec.encode_owned(value),
366 Err(_) => Box::pin(std::future::ready(Err(
367 "codec received a value of another type".into(),
368 ))),
369 }
370 }
371
372 fn decode(&self, payload: Payload) -> BoxFuture<'_, Result<StoredValue, BoxError>> {
373 let decoding = self.codec.decode(payload);
374 Box::pin(async move { decoding.await.map(|value| Arc::new(value) as StoredValue) })
375 }
376}
377
378pub(crate) fn erase_load<T, F, Fut>(load: F) -> ErasedLoad
380where
381 T: Send + Sync + 'static,
382 F: Fn(Scope) -> Fut + Send + Sync + 'static,
383 Fut: std::future::Future<Output = Result<T, BoxError>> + Send + 'static,
384{
385 Arc::new(move |scope: Scope| {
386 let future = load(scope);
387 Box::pin(async move { future.await.map(|value| Arc::new(value) as StoredValue) })
388 })
389}
390
391pub(crate) fn downcast_value<T: Send + Sync + 'static>(
393 value: StoredValue,
394) -> Result<Arc<T>, Error> {
395 Arc::downcast::<T>(value).map_err(|_| {
396 Error::Config(crate::error::ConfigError::invalid(format!(
397 "cached value is not a {}",
398 std::any::type_name::<T>()
399 )))
400 })
401}