1use std::future::Future;
4use std::marker::PhantomData;
5use std::sync::Arc;
6
7use futures::future::BoxFuture;
8use serde::de::DeserializeOwned;
9use serde::Serialize;
10
11use crate::codec::{Codec, JsonCodec};
12use crate::engine::DialCache;
13use crate::error::{BoxError, ConfigError, Error};
14use crate::identity::{normalize_args, ArgValue, Identity, IntoKeyId};
15use crate::operation::{
16 downcast_value, erase_load, Comparator, ErasedOperation, JsonDefault, MismatchDiffHook,
17 MismatchEntry, Operation, OperationMetadata, Preview, RecoveryPredicate, SourceBudget,
18};
19use crate::policy::Policy;
20use crate::scope::Scope;
21
22#[derive(Debug, Clone, PartialEq, Default)]
24pub struct KeySpec {
25 pub id: String,
27 pub args: Vec<(String, ArgValue)>,
30}
31
32impl KeySpec {
33 pub fn new(id: impl IntoKeyId) -> Self {
36 KeySpec {
37 id: id.into_key_id(),
38 args: Vec::new(),
39 }
40 }
41
42 pub fn arg(mut self, name: impl Into<String>, value: impl Into<ArgValue>) -> Self {
48 self.args.push((name.into(), value.into()));
49 self
50 }
51}
52
53impl From<String> for KeySpec {
54 fn from(id: String) -> Self {
55 KeySpec::new(id)
56 }
57}
58
59impl From<&str> for KeySpec {
60 fn from(id: &str) -> Self {
61 KeySpec::new(id)
62 }
63}
64
65macro_rules! key_from_number {
66 ($($t:ty),*) => { $(impl From<$t> for KeySpec { fn from(id: $t) -> Self { KeySpec::new(id) } })* };
67}
68key_from_number!(i8, i16, i32, i64, i128, isize, u8, u16, u32, u64, u128, usize, f64, f32);
69
70impl From<&str> for ArgValue {
71 fn from(value: &str) -> Self {
72 ArgValue::Str(value.to_string())
73 }
74}
75
76impl From<String> for ArgValue {
77 fn from(value: String) -> Self {
78 ArgValue::Str(value)
79 }
80}
81
82impl<T: Clone + Into<ArgValue>> From<&T> for ArgValue {
83 fn from(value: &T) -> Self {
84 value.clone().into()
85 }
86}
87
88impl From<bool> for ArgValue {
89 fn from(value: bool) -> Self {
90 ArgValue::Bool(value)
91 }
92}
93
94macro_rules! integer_arg_value {
95 ($($t:ty),*) => { $(impl From<$t> for ArgValue {
96 fn from(value: $t) -> Self { ArgValue::Int(i64::from(value)) }
97 })* };
98}
99integer_arg_value!(i8, i16, i32, i64, u8, u16, u32);
100
101macro_rules! wide_integer_arg_value {
102 ($($t:ty),*) => { $(impl From<$t> for ArgValue {
103 fn from(value: $t) -> Self {
104 match i64::try_from(value) {
105 Ok(value) => ArgValue::Int(value),
106 Err(_) => ArgValue::BigInt(value.to_string()),
107 }
108 }
109 })* };
110}
111wide_integer_arg_value!(i128, isize, u64, u128, usize);
112
113impl From<f64> for ArgValue {
114 fn from(value: f64) -> Self {
115 ArgValue::Number(value)
116 }
117}
118
119impl From<f32> for ArgValue {
120 fn from(value: f32) -> Self {
121 ArgValue::Number(f64::from(value))
122 }
123}
124
125impl<T: Into<ArgValue>> From<Option<T>> for ArgValue {
126 fn from(value: Option<T>) -> Self {
127 match value {
128 Some(value) => value.into(),
129 None => ArgValue::Absent,
130 }
131 }
132}
133
134type KeyFn<Args> = Arc<dyn Fn(&Args) -> KeySpec + Send + Sync>;
135type SourceFn<Args, T> =
136 Arc<dyn Fn(Scope, Args) -> BoxFuture<'static, Result<T, BoxError>> + Send + Sync>;
137
138pub struct UseCaseBuilder<Args, T> {
142 cache: DialCache,
143 key_type: String,
144 use_case: String,
145 tracked: bool,
146 policy: Policy,
147 budget: SourceBudget,
148 codec: Option<Arc<dyn Codec<T>>>,
149 comparator: Option<Comparator<T>>,
150 should_recover: Option<RecoveryPredicate>,
151 preview: Option<Preview<T>>,
152 mismatch_diff: Option<MismatchDiffHook<T>>,
153 key: Option<KeyFn<Args>>,
154 source: Option<SourceFn<Args, T>>,
155}
156
157impl<Args, T> std::fmt::Debug for UseCaseBuilder<Args, T> {
158 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
159 f.debug_struct("UseCaseBuilder")
160 .field("key_type", &self.key_type)
161 .field("use_case", &self.use_case)
162 .finish_non_exhaustive()
163 }
164}
165
166impl<Args, T> UseCaseBuilder<Args, T>
167where
168 Args: Clone + Send + Sync + 'static,
169 T: Send + Sync + 'static,
170{
171 pub(crate) fn new(cache: DialCache, key_type: String, use_case: String) -> Self {
172 UseCaseBuilder {
173 cache,
174 key_type,
175 use_case,
176 tracked: false,
177 policy: Policy::default(),
178 budget: SourceBudget::Default,
179 codec: None,
180 comparator: None,
181 should_recover: None,
182 preview: None,
183 mismatch_diff: None,
184 key: None,
185 source: None,
186 }
187 }
188
189 pub fn policy(mut self, policy: Policy) -> Self {
191 self.policy = policy;
192 self
193 }
194
195 pub fn tracked(mut self, tracked: bool) -> Self {
197 self.tracked = tracked;
198 self
199 }
200
201 pub fn budget(mut self, budget: SourceBudget) -> Self {
203 self.budget = budget;
204 self
205 }
206
207 pub fn codec(mut self, codec: Arc<dyn Codec<T>>) -> Self {
209 self.codec = Some(codec);
210 self
211 }
212
213 pub fn comparator(
215 mut self,
216 comparator: impl Fn(&T, &T) -> bool + Send + Sync + 'static,
217 ) -> Self {
218 self.comparator = Some(Arc::new(comparator));
219 self
220 }
221
222 pub fn should_recover(
224 mut self,
225 predicate: impl Fn(&Error) -> bool + Send + Sync + 'static,
226 ) -> Self {
227 self.should_recover = Some(Arc::new(predicate));
228 self
229 }
230
231 pub fn preview(
236 mut self,
237 preview: impl Fn(&T) -> Option<String> + Send + Sync + 'static,
238 ) -> Self {
239 self.preview = Some(Arc::new(preview));
240 self
241 }
242
243 pub fn mismatch_diff(
247 mut self,
248 hook: impl Fn(&T, &T) -> Vec<MismatchEntry> + Send + Sync + 'static,
249 ) -> Self {
250 self.mismatch_diff = Some(Arc::new(hook));
251 self
252 }
253
254 pub fn key(mut self, key: impl Fn(&Args) -> KeySpec + Send + Sync + 'static) -> Self {
257 self.key = Some(Arc::new(key));
258 self
259 }
260
261 pub fn source<F, Fut>(mut self, source: F) -> Self
265 where
266 F: Fn(Scope, Args) -> Fut + Send + Sync + 'static,
267 Fut: Future<Output = Result<T, BoxError>> + Send + 'static,
268 {
269 self.source = Some(Arc::new(move |scope, args| Box::pin(source(scope, args))));
270 self
271 }
272
273 fn finish(
274 self,
275 codec: Arc<dyn Codec<T>>,
276 comparator: Comparator<T>,
277 json: Option<JsonDefault<T>>,
278 ) -> Result<UseCase<Args, T>, ConfigError> {
279 let key = self
280 .key
281 .ok_or_else(|| ConfigError::invalid("DialCache use case needs a key selector"))?;
282 let source = self
283 .source
284 .ok_or_else(|| ConfigError::invalid("DialCache use case needs a source"))?;
285 self.policy
286 .validate()
287 .map_err(|e| ConfigError::invalid(e.to_string()))?;
288 if let SourceBudget::Millis(ms) = self.budget {
289 if !(1..=crate::limits::MAX_DEADLINE_MS).contains(&ms) {
290 return Err(ConfigError::invalid(
291 "DialCache source budget is outside its domain",
292 ));
293 }
294 }
295 self.cache.register_use_case(&self.use_case)?;
296 let (identity, metadata) = Operation {
297 identity: Identity::new(self.key_type, "", self.use_case).tracked(self.tracked),
298 policy: self.policy,
299 budget: self.budget,
300 codec,
301 comparator,
302 should_recover: self.should_recover,
303 preview: self
305 .preview
306 .or_else(|| json.as_ref().map(JsonDefault::preview)),
307 mismatch_diff: self.mismatch_diff,
308 json,
309 }
310 .erase();
311 Ok(UseCase {
312 inner: Arc::new(UseCaseInner {
313 cache: self.cache,
314 identity,
315 metadata,
316 key,
317 source,
318 }),
319 _args: PhantomData,
320 })
321 }
322
323 pub fn register_custom(self) -> Result<UseCase<Args, T>, ConfigError> {
326 let codec = self
327 .codec
328 .clone()
329 .ok_or_else(|| ConfigError::invalid("DialCache use case needs a codec"))?;
330 let comparator = self
331 .comparator
332 .clone()
333 .ok_or_else(|| ConfigError::invalid("DialCache use case needs a comparator"))?;
334 self.finish(codec, comparator, None)
335 }
336}
337
338impl<Args, T> UseCaseBuilder<Args, T>
339where
340 Args: Clone + Send + Sync + 'static,
341 T: Serialize + DeserializeOwned + PartialEq + Send + Sync + 'static,
342{
343 pub fn register(self) -> Result<UseCase<Args, T>, ConfigError> {
347 let codec = self.codec.clone().unwrap_or_else(|| Arc::new(JsonCodec));
348 let comparator = self
349 .comparator
350 .clone()
351 .unwrap_or_else(|| Arc::new(|a: &T, b: &T| a == b));
352 self.finish(codec, comparator, Some(JsonDefault::new()))
353 }
354}
355
356struct UseCaseInner<Args, T> {
357 cache: DialCache,
358 identity: Identity,
359 metadata: Arc<OperationMetadata>,
360 key: KeyFn<Args>,
361 source: SourceFn<Args, T>,
362}
363
364pub struct UseCase<Args, T> {
368 inner: Arc<UseCaseInner<Args, T>>,
369 _args: PhantomData<fn(Args)>,
370}
371
372impl<Args, T> Clone for UseCase<Args, T> {
373 fn clone(&self) -> Self {
374 UseCase {
375 inner: self.inner.clone(),
376 _args: PhantomData,
377 }
378 }
379}
380
381impl<Args, T> std::fmt::Debug for UseCase<Args, T> {
382 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
383 f.debug_struct("UseCase")
384 .field("key_type", &self.inner.identity.key_type)
385 .field("use_case", &self.inner.identity.use_case)
386 .finish()
387 }
388}
389
390impl<Args, T> UseCase<Args, T>
391where
392 Args: Clone + Send + Sync + 'static,
393 T: Send + Sync + 'static,
394{
395 pub fn use_case(&self) -> &str {
397 &self.inner.identity.use_case
398 }
399
400 pub fn key_type(&self) -> &str {
402 &self.inner.identity.key_type
403 }
404
405 pub async fn get(&self, scope: &Scope, args: Args) -> Result<Arc<T>, Error> {
407 let inner = self.inner.clone();
408 let identity = inner.identity.clone();
409 let provider_identity = identity.clone();
410 let provider_args = args.clone();
411 let provider_inner = inner.clone();
412 let identity_provider = Arc::new(move || -> Result<Identity, BoxError> {
413 let spec = (provider_inner.key)(&provider_args);
414 let args = normalize_args(spec.args.clone())?;
415 Ok(Identity {
416 id: spec.id,
417 args,
418 ..provider_identity.clone()
419 })
420 });
421 let source_inner = inner.clone();
422 let load = erase_load(move |scope: Scope| (source_inner.source)(scope, args.clone()));
423 let erased = ErasedOperation {
424 identity,
425 identity_provider: Some(identity_provider),
426 metadata: inner.metadata.clone(),
427 load,
428 };
429 let value = inner.cache.execute(scope, erased).await?;
430 downcast_value::<T>(value)
431 }
432
433 pub async fn get_uncached(&self, args: Args) -> Result<Arc<T>, Error> {
436 self.get(&Scope::outside(), args).await
437 }
438}
439
440impl DialCache {
441 pub fn use_case<Args, T>(
444 &self,
445 key_type: impl Into<String>,
446 use_case: impl Into<String>,
447 ) -> UseCaseBuilder<Args, T>
448 where
449 Args: Clone + Send + Sync + 'static,
450 T: Send + Sync + 'static,
451 {
452 UseCaseBuilder::new(self.clone(), key_type.into(), use_case.into())
453 }
454}