Skip to main content

ferroui_reactive/
notification.rs

1//! `Notification<T>` and `NotificationKind` (`Notification.cs`).
2
3use crate::concurrency::{IScheduler, SchedulerExt};
4use crate::linq::Observable;
5use ferroui_system::{IObservable, IObserver, ObservableError};
6use std::fmt;
7use std::hash::{Hash, Hasher};
8use std::rc::Rc;
9
10/// An observer whose handlers return a result (`IObserver<TValue, TResult>`), which
11/// [`Notification::accept_observer_with_result`] returns.
12pub trait IObserverWithResult<T, R> {
13    /// Handles a value (`OnNext`).
14    fn on_next(&self, value: T) -> R;
15
16    /// Handles an error (`OnError`).
17    fn on_error(&self, error: ObservableError) -> R;
18
19    /// Handles the end of the sequence (`OnCompleted`).
20    fn on_completed(&self) -> R;
21}
22
23/// The kind of a notification (`NotificationKind`).
24#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
25pub enum NotificationKind {
26    OnNext,
27    OnError,
28    OnCompleted,
29}
30
31/// A notification of an observable sequence as a value (`Notification<T>`).
32///
33/// Two notifications are equal when they are of the same kind and their values are equal; errors
34/// compare by identity, as exceptions do upstream.
35#[derive(Clone)]
36pub enum Notification<T> {
37    /// A value (`Notification.CreateOnNext`).
38    OnNext(T),
39    /// An error (`Notification.CreateOnError`).
40    OnError(ObservableError),
41    /// The end of the sequence (`Notification.CreateOnCompleted`).
42    OnCompleted,
43}
44
45impl<T> Notification<T> {
46    /// The kind of the notification (`Kind`).
47    pub fn kind(&self) -> NotificationKind {
48        match self {
49            Self::OnNext(_) => NotificationKind::OnNext,
50            Self::OnError(_) => NotificationKind::OnError,
51            Self::OnCompleted => NotificationKind::OnCompleted,
52        }
53    }
54
55    /// Whether the notification has a value (`HasValue`).
56    pub fn has_value(&self) -> bool {
57        matches!(self, Self::OnNext(_))
58    }
59
60    /// The value of an `OnNext` notification (`Value`); `None` for the others, where upstream throws
61    /// the error of an `OnError` and `InvalidOperationException` for `OnCompleted`.
62    pub fn value(&self) -> Option<&T> {
63        match self {
64            Self::OnNext(value) => Some(value),
65            _ => None,
66        }
67    }
68
69    /// The error of an `OnError` notification (`Exception`).
70    pub fn exception(&self) -> Option<&ObservableError> {
71        match self {
72            Self::OnError(error) => Some(error),
73            _ => None,
74        }
75    }
76
77    /// Delivers the notification to an observer (`Accept(IObserver<T>)`).
78    pub fn accept(self, observer: &dyn IObserver<T>) {
79        match self {
80            Self::OnNext(value) => observer.on_next(value),
81            Self::OnError(error) => observer.on_error(error),
82            Self::OnCompleted => observer.on_completed(),
83        }
84    }
85
86    /// Delivers the notification to an observer whose handlers return a result, and returns it
87    /// (`Accept<TResult>(IObserver<T, TResult>)`).
88    pub fn accept_observer_with_result<R>(self, observer: &dyn IObserverWithResult<T, R>) -> R {
89        match self {
90            Self::OnNext(value) => observer.on_next(value),
91            Self::OnError(error) => observer.on_error(error),
92            Self::OnCompleted => observer.on_completed(),
93        }
94    }
95
96    /// Calls the function for the kind of the notification (`Accept(Func<T, TResult>, ...)`).
97    pub fn accept_fns<R>(
98        self,
99        on_next: impl FnOnce(T) -> R,
100        on_error: impl FnOnce(ObservableError) -> R,
101        on_completed: impl FnOnce() -> R,
102    ) -> R {
103        match self {
104            Self::OnNext(value) => on_next(value),
105            Self::OnError(error) => on_error(error),
106            Self::OnCompleted => on_completed(),
107        }
108    }
109}
110
111impl<T: Clone + 'static> Notification<T> {
112    /// A sequence of the one notification, delivered on the immediate scheduler (`ToObservable()`).
113    pub fn to_observable(&self) -> Rc<dyn IObservable<T>> {
114        self.to_observable_on(crate::concurrency::Scheduler::immediate())
115    }
116
117    /// A sequence of the one notification, delivered on `scheduler`: a value is followed by the end
118    /// of the sequence (`ToObservable(IScheduler)`).
119    pub fn to_observable_on(&self, scheduler: Rc<dyn IScheduler>) -> Rc<dyn IObservable<T>> {
120        let notification = self.clone();
121        Observable::create(move |observer: Rc<dyn IObserver<T>>| {
122            let notification = notification.clone();
123            scheduler.schedule_action(move || {
124                let is_next = notification.has_value();
125                notification.accept(&*observer);
126                if is_next {
127                    observer.on_completed();
128                }
129            })
130        })
131    }
132}
133
134impl<T: PartialEq> PartialEq for Notification<T> {
135    fn eq(&self, other: &Self) -> bool {
136        match (self, other) {
137            (Self::OnNext(a), Self::OnNext(b)) => a == b,
138            (Self::OnError(a), Self::OnError(b)) => Rc::ptr_eq(a, b),
139            (Self::OnCompleted, Self::OnCompleted) => true,
140            _ => false,
141        }
142    }
143}
144
145/// The hash of a notification (`GetHashCode`), consistent with its equality: a value hashes as the
146/// value; an error by its identity (the address of the error, which is what its equality
147/// compares); the end of the sequence as the name of the type of the values combined with a
148/// constant, as upstream's `typeof(T).FullName.GetHashCode() ^ 8510`.
149impl<T: Hash> Hash for Notification<T> {
150    fn hash<H: Hasher>(&self, state: &mut H) {
151        match self {
152            Self::OnNext(value) => value.hash(state),
153            Self::OnError(error) => (Rc::as_ptr(error) as *const () as usize).hash(state),
154            Self::OnCompleted => {
155                std::any::type_name::<T>().hash(state);
156                8510u32.hash(state);
157            }
158        }
159    }
160}
161
162impl<T: fmt::Debug> fmt::Debug for Notification<T> {
163    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
164        match self {
165            Self::OnNext(value) => write!(f, "OnNext({value:?})"),
166            Self::OnError(error) => write!(f, "OnError({error})"),
167            Self::OnCompleted => f.write_str("OnCompleted()"),
168        }
169    }
170}