Skip to main content

polyoxide_venue/
extensions.rs

1//! Facts only one venue has, carried on a shared record without a field of
2//! their own.
3
4use std::any::{Any, TypeId};
5use std::collections::BTreeMap;
6use std::error::Error;
7use std::fmt;
8
9use crate::{Class, Classify};
10
11/// The object-safe face of an extension value.
12trait Extension: Any + Send + Sync {
13    fn clone_box(&self) -> Box<dyn Extension>;
14    fn eq_dyn(&self, other: &dyn Extension) -> bool;
15    fn fmt_dyn(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result;
16    fn type_name_dyn(&self) -> &'static str;
17    fn as_any(&self) -> &dyn Any;
18    fn as_any_mut(&mut self) -> &mut dyn Any;
19    fn into_any(self: Box<Self>) -> Box<dyn Any>;
20}
21
22impl<T> Extension for T
23where
24    T: Clone + fmt::Debug + PartialEq + Send + Sync + 'static,
25{
26    fn clone_box(&self) -> Box<dyn Extension> {
27        Box::new(self.clone())
28    }
29
30    fn eq_dyn(&self, other: &dyn Extension) -> bool {
31        other
32            .as_any()
33            .downcast_ref::<T>()
34            .is_some_and(|other| self == other)
35    }
36
37    fn fmt_dyn(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
38        fmt::Debug::fmt(self, f)
39    }
40
41    fn type_name_dyn(&self) -> &'static str {
42        std::any::type_name::<T>()
43    }
44
45    fn as_any(&self) -> &dyn Any {
46        self
47    }
48
49    fn as_any_mut(&mut self) -> &mut dyn Any {
50        self
51    }
52
53    fn into_any(self: Box<Self>) -> Box<dyn Any> {
54        self
55    }
56}
57
58/// Facts only one venue has, carried on a shared record: at most one value of
59/// each type, read back by its type.
60///
61/// Each product defines one `<Record>Ext` type per record it fills, such as a
62/// quote's update id, and inserts one value of it. A value's type is
63/// `Clone + Debug + PartialEq + Send + Sync + 'static`, so a record that
64/// carries extensions stays `Clone`, `Debug` and `PartialEq`, and two records
65/// are equal only when their extensions hold equal values of the same types.
66/// There is no serde support: a value's type is known only to the crate that
67/// defines it.
68///
69/// ```
70/// use polyoxide_venue::Extensions;
71///
72/// #[derive(Debug, Clone, PartialEq)]
73/// struct QuoteExt {
74///     update_id: u64,
75/// }
76///
77/// let mut ext = Extensions::new();
78/// ext.insert(QuoteExt { update_id: 7 })?;
79/// assert_eq!(ext.get::<QuoteExt>(), Some(&QuoteExt { update_id: 7 }));
80/// assert!(ext.insert(QuoteExt { update_id: 8 }).is_err(), "one value per type");
81/// assert_ne!(ext, Extensions::of(QuoteExt { update_id: 8 }));
82/// # Ok::<(), polyoxide_venue::DuplicateExtension>(())
83/// ```
84///
85/// A value whose type cannot be compared is refused when the program is
86/// built:
87///
88/// ```compile_fail
89/// use polyoxide_venue::Extensions;
90///
91/// #[derive(Debug, Clone)]
92/// struct Uncomparable;
93///
94/// let mut ext = Extensions::new();
95/// ext.insert(Uncomparable).unwrap();
96/// ```
97///
98/// Nor does it serialize, since a value's type is known only to the crate that
99/// defines it:
100///
101/// ```compile_fail
102/// fn serializes<T: serde::Serialize>() {}
103/// serializes::<polyoxide_venue::Extensions>();
104/// ```
105///
106/// ```compile_fail
107/// fn deserializes<T: serde::de::DeserializeOwned>() {}
108/// deserializes::<polyoxide_venue::Extensions>();
109/// ```
110#[derive(Default)]
111pub struct Extensions {
112    values: BTreeMap<TypeId, Box<dyn Extension>>,
113}
114
115impl Extensions {
116    /// No extensions.
117    pub fn new() -> Self {
118        Self::default()
119    }
120
121    /// Extensions holding `value` alone.
122    pub fn of<T>(value: T) -> Self
123    where
124        T: Clone + fmt::Debug + PartialEq + Send + Sync + 'static,
125    {
126        let mut ext = Self::new();
127        ext.values.insert(TypeId::of::<T>(), Box::new(value));
128        ext
129    }
130
131    /// Adds `value`.
132    ///
133    /// # Errors
134    ///
135    /// [`DuplicateExtension`] when a value of `T` is already held, which is
136    /// kept unchanged.
137    pub fn insert<T>(&mut self, value: T) -> Result<(), DuplicateExtension>
138    where
139        T: Clone + fmt::Debug + PartialEq + Send + Sync + 'static,
140    {
141        let id = TypeId::of::<T>();
142        if self.values.contains_key(&id) {
143            return Err(DuplicateExtension {
144                type_name: std::any::type_name::<T>(),
145            });
146        }
147        self.values.insert(id, Box::new(value));
148        Ok(())
149    }
150
151    /// The value of `T`, when one is held.
152    pub fn get<T: 'static>(&self) -> Option<&T> {
153        self.values
154            .get(&TypeId::of::<T>())
155            .and_then(|value| value.as_ref().as_any().downcast_ref())
156    }
157
158    /// The value of `T`, to change in place, when one is held.
159    pub fn get_mut<T: 'static>(&mut self) -> Option<&mut T> {
160        self.values
161            .get_mut(&TypeId::of::<T>())
162            .and_then(|value| value.as_mut().as_any_mut().downcast_mut())
163    }
164
165    /// Takes the value of `T` out, when one is held.
166    pub fn remove<T: 'static>(&mut self) -> Option<T> {
167        self.values
168            .remove(&TypeId::of::<T>())
169            .and_then(|value| value.into_any().downcast().ok())
170            .map(|value: Box<T>| *value)
171    }
172
173    /// Whether a value of `T` is held.
174    pub fn contains<T: 'static>(&self) -> bool {
175        self.values.contains_key(&TypeId::of::<T>())
176    }
177
178    /// The types of the values held, one each.
179    pub fn type_ids(&self) -> impl Iterator<Item = TypeId> + '_ {
180        self.values.keys().copied()
181    }
182
183    /// How many values are held.
184    pub fn len(&self) -> usize {
185        self.values.len()
186    }
187
188    /// Whether no value is held.
189    pub fn is_empty(&self) -> bool {
190        self.values.is_empty()
191    }
192
193    /// Checks that every value held is of a type in `declared`, the option
194    /// types a venue reads.
195    ///
196    /// A request carries the venue's own options as extensions, and a venue
197    /// that reads only some option types refuses the rest rather than ignoring
198    /// them. An empty set always passes.
199    ///
200    /// # Errors
201    ///
202    /// [`UndeclaredExtension`] naming the first value held, in `TypeId` order,
203    /// whose type is not in `declared`.
204    ///
205    /// ```
206    /// use std::any::TypeId;
207    ///
208    /// use polyoxide_venue::Extensions;
209    ///
210    /// #[derive(Debug, Clone, PartialEq)]
211    /// struct Options;
212    ///
213    /// #[derive(Debug, Clone, PartialEq)]
214    /// struct Other;
215    ///
216    /// let ext = Extensions::of(Options);
217    /// assert!(ext.ensure_declared(&[TypeId::of::<Options>()]).is_ok());
218    /// assert!(ext.ensure_declared(&[TypeId::of::<Other>()]).is_err());
219    /// assert!(Extensions::new().ensure_declared(&[]).is_ok());
220    /// ```
221    pub fn ensure_declared(&self, declared: &[TypeId]) -> Result<(), UndeclaredExtension> {
222        match self.values.iter().find(|(id, _)| !declared.contains(id)) {
223            Some((_, value)) => Err(UndeclaredExtension {
224                type_name: value.as_ref().type_name_dyn(),
225            }),
226            None => Ok(()),
227        }
228    }
229}
230
231impl Clone for Extensions {
232    fn clone(&self) -> Self {
233        Self {
234            values: self
235                .values
236                .iter()
237                .map(|(id, value)| (*id, value.as_ref().clone_box()))
238                .collect(),
239        }
240    }
241}
242
243impl PartialEq for Extensions {
244    fn eq(&self, other: &Self) -> bool {
245        self.values.len() == other.values.len()
246            && self.values.iter().all(|(id, value)| {
247                other
248                    .values
249                    .get(id)
250                    .is_some_and(|theirs| value.as_ref().eq_dyn(theirs.as_ref()))
251            })
252    }
253}
254
255impl fmt::Debug for Extensions {
256    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
257        struct Value<'a>(&'a dyn Extension);
258
259        impl fmt::Debug for Value<'_> {
260            fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
261                self.0.fmt_dyn(f)
262            }
263        }
264
265        f.debug_set()
266            .entries(self.values.values().map(|value| Value(value.as_ref())))
267            .finish()
268    }
269}
270
271/// A value was inserted into [`Extensions`] that already held one of its type.
272/// An [`InvalidRequest`](Class::InvalidRequest): the caller's misuse.
273#[derive(Debug, Clone, PartialEq, Eq)]
274pub struct DuplicateExtension {
275    /// The type both values have.
276    pub type_name: &'static str,
277}
278
279impl fmt::Display for DuplicateExtension {
280    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
281        write!(
282            f,
283            "the record already holds an extension of type {}",
284            self.type_name
285        )
286    }
287}
288
289impl Error for DuplicateExtension {}
290
291impl Classify for DuplicateExtension {
292    fn class(&self) -> Class {
293        Class::InvalidRequest
294    }
295}
296
297/// A request carried an extension of a type its venue does not declare
298/// ([`Extensions::ensure_declared`]). An
299/// [`InvalidRequest`](Class::InvalidRequest): the caller's misuse, refused
300/// before anything is sent.
301#[derive(Debug, Clone, PartialEq, Eq)]
302#[non_exhaustive]
303pub struct UndeclaredExtension {
304    /// The type the venue does not declare.
305    pub type_name: &'static str,
306}
307
308impl fmt::Display for UndeclaredExtension {
309    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
310        write!(
311            f,
312            "the request carries `{}`, an extension this venue does not declare",
313            self.type_name
314        )
315    }
316}
317
318impl Error for UndeclaredExtension {}
319
320impl Classify for UndeclaredExtension {
321    fn class(&self) -> Class {
322        Class::InvalidRequest
323    }
324}
325
326#[cfg(test)]
327mod tests {
328    use super::*;
329
330    #[derive(Debug, Clone, PartialEq)]
331    struct QuoteExt {
332        update_id: u64,
333    }
334
335    #[derive(Debug, Clone, PartialEq)]
336    struct BookExt {
337        sequence: u64,
338    }
339
340    #[test]
341    fn extensions_are_clone_debug_default_partial_eq_send_and_sync() {
342        fn holds<T: Clone + fmt::Debug + Default + PartialEq + Send + Sync>() {}
343        holds::<Extensions>();
344    }
345
346    #[test]
347    fn a_value_is_read_back_by_its_type() {
348        let mut ext = Extensions::new();
349        assert!(ext.is_empty());
350        ext.insert(QuoteExt { update_id: 7 }).unwrap();
351        ext.insert(BookExt { sequence: 9 }).unwrap();
352        assert_eq!(ext.len(), 2);
353        assert_eq!(ext.get::<QuoteExt>(), Some(&QuoteExt { update_id: 7 }));
354        assert_eq!(ext.get::<BookExt>(), Some(&BookExt { sequence: 9 }));
355        assert_eq!(ext.get::<u64>(), None);
356        assert!(ext.contains::<QuoteExt>());
357        assert!(!ext.contains::<String>());
358    }
359
360    #[test]
361    fn inserting_a_type_already_held_is_an_error_and_keeps_the_first() {
362        let mut ext = Extensions::of(QuoteExt { update_id: 7 });
363        let err = ext.insert(QuoteExt { update_id: 8 }).unwrap_err();
364        assert!(err.type_name.ends_with("QuoteExt"), "{err}");
365        assert_eq!(err.class(), Class::InvalidRequest);
366        assert_eq!(ext.get::<QuoteExt>(), Some(&QuoteExt { update_id: 7 }));
367        assert_eq!(ext.len(), 1);
368    }
369
370    #[test]
371    fn equality_compares_the_values_not_just_the_types() {
372        let a = Extensions::of(QuoteExt { update_id: 7 });
373        assert_eq!(a, Extensions::of(QuoteExt { update_id: 7 }));
374        assert_ne!(a, Extensions::of(QuoteExt { update_id: 8 }));
375        assert_ne!(a, Extensions::of(BookExt { sequence: 7 }));
376        assert_ne!(a, Extensions::new());
377        assert_ne!(Extensions::new(), a);
378        let mut both = a.clone();
379        both.insert(BookExt { sequence: 1 }).unwrap();
380        assert_ne!(a, both);
381        assert_ne!(both, a);
382        assert_eq!(Extensions::new(), Extensions::default());
383    }
384
385    #[test]
386    fn a_clone_holds_its_own_copies() {
387        let original = Extensions::of(QuoteExt { update_id: 7 });
388        let mut copy = original.clone();
389        assert_eq!(copy, original);
390        copy.get_mut::<QuoteExt>().unwrap().update_id = 8;
391        assert_eq!(original.get::<QuoteExt>(), Some(&QuoteExt { update_id: 7 }));
392        assert_ne!(copy, original);
393    }
394
395    #[test]
396    fn the_type_ids_held_are_exposed() {
397        let mut ext = Extensions::of(QuoteExt { update_id: 7 });
398        ext.insert(BookExt { sequence: 9 }).unwrap();
399        let mut ids: Vec<TypeId> = ext.type_ids().collect();
400        let mut expected = vec![TypeId::of::<QuoteExt>(), TypeId::of::<BookExt>()];
401        ids.sort();
402        expected.sort();
403        assert_eq!(ids, expected);
404    }
405
406    #[test]
407    fn a_removed_value_comes_back_whole() {
408        let mut ext = Extensions::of(QuoteExt { update_id: 7 });
409        assert_eq!(ext.remove::<QuoteExt>(), Some(QuoteExt { update_id: 7 }));
410        assert_eq!(ext.remove::<QuoteExt>(), None);
411        assert!(ext.is_empty());
412        ext.insert(QuoteExt { update_id: 8 }).unwrap();
413        assert_eq!(ext.get::<QuoteExt>(), Some(&QuoteExt { update_id: 8 }));
414    }
415
416    #[test]
417    fn debug_shows_each_value() {
418        let ext = Extensions::of(QuoteExt { update_id: 7 });
419        assert_eq!(format!("{ext:?}"), "{QuoteExt { update_id: 7 }}");
420        assert_eq!(format!("{:?}", Extensions::new()), "{}");
421    }
422
423    #[test]
424    fn declared_extensions_and_an_empty_set_pass() {
425        let ext = Extensions::of(QuoteExt { update_id: 7 });
426        assert_eq!(ext.ensure_declared(&[TypeId::of::<QuoteExt>()]), Ok(()));
427        assert_eq!(
428            ext.ensure_declared(&[TypeId::of::<BookExt>(), TypeId::of::<QuoteExt>()]),
429            Ok(())
430        );
431        assert_eq!(Extensions::new().ensure_declared(&[]), Ok(()));
432        assert_eq!(
433            Extensions::new().ensure_declared(&[TypeId::of::<QuoteExt>()]),
434            Ok(())
435        );
436    }
437
438    #[test]
439    fn an_undeclared_value_is_refused_by_its_type_name_as_invalid_request() {
440        let mut ext = Extensions::of(QuoteExt { update_id: 7 });
441        ext.insert(BookExt { sequence: 9 }).unwrap();
442
443        let err = ext
444            .ensure_declared(&[TypeId::of::<QuoteExt>()])
445            .unwrap_err();
446        assert!(err.type_name.ends_with("BookExt"), "{err}");
447        assert_eq!(err.class(), Class::InvalidRequest);
448        assert_eq!(
449            err.to_string(),
450            format!(
451                "the request carries `{}`, an extension this venue does not declare",
452                err.type_name
453            )
454        );
455
456        let err = ext.ensure_declared(&[]).unwrap_err();
457        assert_eq!(err.class(), Class::InvalidRequest);
458    }
459
460    #[test]
461    fn the_first_undeclared_value_in_type_id_order_is_the_one_named() {
462        let mut ext = Extensions::of(QuoteExt { update_id: 7 });
463        ext.insert(BookExt { sequence: 9 }).unwrap();
464
465        let first = if TypeId::of::<QuoteExt>() < TypeId::of::<BookExt>() {
466            "QuoteExt"
467        } else {
468            "BookExt"
469        };
470        let err = ext.ensure_declared(&[]).unwrap_err();
471        assert!(err.type_name.ends_with(first), "{err}");
472    }
473
474    #[test]
475    fn declaring_a_type_the_request_lacks_does_not_excuse_one_it_has() {
476        let ext = Extensions::of(QuoteExt { update_id: 7 });
477        let err = ext.ensure_declared(&[TypeId::of::<BookExt>()]).unwrap_err();
478        assert!(err.type_name.ends_with("QuoteExt"), "{err}");
479    }
480}