Skip to main content

ferroui_dynamic_data/kernel/
option_observable_extensions.rs

1//! `OptionObservableExtensions` (`Kernel/OptionObservableExtensions.cs`): the operators of
2//! sequences of options (`IObservable<Optional<T>>`).
3
4use super::OptionExtensions;
5use ferroui_reactive::{Observable, ObservableExt};
6use ferroui_system::{IObservable, IObserver, ObservableError};
7use std::rc::Rc;
8
9/// The operators of sequences of options (`OptionObservableExtensions`), implemented for
10/// `Rc<dyn IObservable<Option<T>>>`.
11///
12/// It also carries `EditDiff` of a sequence of options (`ObservableCacheEx.EditDiff(
13/// IObservable<Optional<TObject>>, ..)`), as [`edit_diff_optional`](Self::edit_diff_optional).
14pub trait OptionObservableExtensions<T: Clone + 'static> {
15    /// The sequence as the handle the operators take.
16    fn options(&self) -> Rc<dyn IObservable<Option<T>>>;
17
18    /// Each value converted by `converter`; none stays none (`Convert(Func<TSource, TDestination>)`).
19    fn convert<U: 'static>(&self, converter: impl Fn(T) -> U + 'static) -> Rc<dyn IObservable<Option<U>>> {
20        self.options().select(move |optional: Option<T>| optional.map(&converter))
21    }
22
23    /// The option that `converter` makes of each value; none stays none
24    /// (`Convert(Func<TSource, Optional<TDestination>>)`).
25    fn convert_optional<U: 'static>(&self, converter: impl Fn(T) -> Option<U> + 'static) -> Rc<dyn IObservable<Option<U>>> {
26        self.options().select(move |optional: Option<T>| optional.and_then(&converter))
27    }
28
29    /// Each value converted by `converter`, and what `fallback_converter` makes for none
30    /// (`ConvertOr`).
31    fn convert_or<U: 'static>(&self, converter: impl Fn(T) -> U + 'static, fallback_converter: impl Fn() -> U + 'static) -> Rc<dyn IObservable<U>> {
32        self.options().select(move |optional: Option<T>| match optional {
33            Some(value) => converter(value),
34            None => fallback_converter(),
35        })
36    }
37
38    /// The options, with what `fallback_operation` makes in place of none (`OrElse`).
39    fn or_else(&self, fallback_operation: impl Fn() -> Option<T> + 'static) -> Rc<dyn IObservable<Option<T>>> {
40        self.options().select(move |optional: Option<T>| match optional {
41            Some(value) => Some(value),
42            None => fallback_operation(),
43        })
44    }
45
46    /// Runs `action` with each value (`OnHasValue(Action<T>)`).
47    fn on_has_value(&self, action: impl Fn(&T) + 'static) -> Rc<dyn IObservable<Option<T>>> {
48        self.options().do_(move |optional: &Option<T>| {
49            optional.as_ref().if_has_value(|value| action(value));
50        })
51    }
52
53    /// Runs `action` with each value, and `else_action` for each none
54    /// (`OnHasValue(Action<T>, Action)`).
55    fn on_has_value_with_else(&self, action: impl Fn(&T) + 'static, else_action: impl Fn() + 'static) -> Rc<dyn IObservable<Option<T>>> {
56        self.options().do_(move |optional: &Option<T>| {
57            optional.as_ref().if_has_value(|value| action(value)).else_(&else_action);
58        })
59    }
60
61    /// Runs `action` for each none (`OnHasNoValue(Action)`).
62    fn on_has_no_value(&self, action: impl Fn() + 'static) -> Rc<dyn IObservable<Option<T>>> {
63        self.options().do_(move |optional: &Option<T>| {
64            optional.as_ref().if_has_value(|_| {}).else_(&action);
65        })
66    }
67
68    /// Runs `action` for each none, and `else_action` with each value
69    /// (`OnHasNoValue(Action, Action<T>)`).
70    fn on_has_no_value_with_else(&self, action: impl Fn() + 'static, else_action: impl Fn(&T) + 'static) -> Rc<dyn IObservable<Option<T>>> {
71        self.options().do_(move |optional: &Option<T>| {
72            optional.as_ref().if_has_value(|value| else_action(value)).else_(&action);
73        })
74    }
75
76    /// The values; the nones are left out (`SelectValues`).
77    fn select_values(&self) -> Rc<dyn IObservable<T>> {
78        self.options().where_(|optional: &Option<T>| optional.is_some()).select(|optional: Option<T>| optional.expect("filtered to values"))
79    }
80
81    /// The values, with what `value_selector` makes in place of none (`ValueOr`).
82    fn value_or(&self, value_selector: impl Fn() -> T + 'static) -> Rc<dyn IObservable<T>> {
83        self.options().select(move |optional: Option<T>| optional.unwrap_or_else(&value_selector))
84    }
85
86    /// The values, with the default value of `T` in place of none (`ValueOrDefault`).
87    fn value_or_default(&self) -> Rc<dyn IObservable<T>>
88    where
89        T: Default,
90    {
91        self.options().select(|optional: Option<T>| optional.unwrap_or_default())
92    }
93
94    /// The values; a none ends the sequence with the error of `exception_generator`
95    /// (`ValueOrThrow`).
96    fn value_or_throw(&self, exception_generator: impl Fn() -> ObservableError + 'static) -> Rc<dyn IObservable<T>> {
97        let source = self.options();
98        let exception_generator = Rc::new(exception_generator);
99        Observable::create(move |observer: Rc<dyn IObserver<T>>| {
100            let (on_next, on_error, on_completed) = (observer.clone(), observer.clone(), observer);
101            let exception_generator = exception_generator.clone();
102            source.subscribe_all(
103                move |optional: Option<T>| match optional {
104                    Some(value) => on_next.on_next(value),
105                    None => on_next.on_error(exception_generator()),
106                },
107                move |error| on_error.on_error(error),
108                move || on_completed.on_completed(),
109            )
110        })
111    }
112
113    /// The changes of a cache that holds the value of the option, keyed by `key_selector`: a
114    /// value adds it, another value with the same key updates it unless it is equal, one with
115    /// another key replaces it, and none removes it (`EditDiff(IObservable<Optional<TObject>>,
116    /// Func<TObject, TKey>)`, equality of `PartialEq`).
117    fn edit_diff_optional<K: Eq + std::hash::Hash + Clone + 'static>(
118        &self,
119        key_selector: impl Fn(&T) -> K + 'static,
120    ) -> Rc<dyn IObservable<crate::cache::ChangeSet<T, K>>>
121    where
122        T: PartialEq,
123    {
124        crate::cache::internal::edit_diff_change_set_optional(self.options(), Rc::new(key_selector), Rc::new(|a: &T, b: &T| a == b))
125    }
126
127    /// [`edit_diff_optional`](Self::edit_diff_optional) with the equality of `equality_comparer`
128    /// (`EditDiff(.., IEqualityComparer<TObject>)`).
129    fn edit_diff_optional_with_comparer<K: Eq + std::hash::Hash + Clone + 'static>(
130        &self,
131        key_selector: impl Fn(&T) -> K + 'static,
132        equality_comparer: impl Fn(&T, &T) -> bool + 'static,
133    ) -> Rc<dyn IObservable<crate::cache::ChangeSet<T, K>>> {
134        crate::cache::internal::edit_diff_change_set_optional(self.options(), Rc::new(key_selector), Rc::new(equality_comparer))
135    }
136}
137
138impl<T: Clone + 'static> OptionObservableExtensions<T> for Rc<dyn IObservable<Option<T>>> {
139    fn options(&self) -> Rc<dyn IObservable<Option<T>>> {
140        self.clone()
141    }
142}