1#[derive(Debug, Clone, PartialEq, Eq, Default)]
8pub enum Loadable<T, E = String> {
9 #[default]
10 Idle,
11 Loading,
12 Ready(T),
13 Empty,
14 Unavailable(String),
15 Error(E),
16}
17
18impl<T, E> Loadable<T, E> {
19 pub fn value(&self) -> Option<&T> {
20 match self {
21 Self::Ready(value) => Some(value),
22 _ => None,
23 }
24 }
25
26 pub fn is_loading(&self) -> bool {
27 matches!(self, Self::Loading)
28 }
29
30 pub fn map<U>(self, map: impl FnOnce(T) -> U) -> Loadable<U, E> {
31 match self {
32 Self::Idle => Loadable::Idle,
33 Self::Loading => Loadable::Loading,
34 Self::Ready(value) => Loadable::Ready(map(value)),
35 Self::Empty => Loadable::Empty,
36 Self::Unavailable(reason) => Loadable::Unavailable(reason),
37 Self::Error(error) => Loadable::Error(error),
38 }
39 }
40}
41
42#[derive(Debug, Clone, PartialEq, Eq)]
47pub struct AsyncValue<T, E = String> {
48 pub value: Option<T>,
49 pub status: AsyncStatus<E>,
50}
51
52#[derive(Debug, Clone, PartialEq, Eq, Default)]
54pub enum AsyncStatus<E = String> {
55 #[default]
56 Idle,
57 Loading,
58 Refreshing,
59 Ready,
60 Empty,
61 Unavailable(String),
62 Error(E),
63}
64
65impl<T, E> AsyncValue<T, E> {
66 pub fn loading() -> Self {
67 Self {
68 value: None,
69 status: AsyncStatus::Loading,
70 }
71 }
72
73 pub fn ready(value: T) -> Self {
74 Self {
75 value: Some(value),
76 status: AsyncStatus::Ready,
77 }
78 }
79
80 pub fn refresh(&mut self) {
81 self.status = AsyncStatus::Refreshing;
82 }
83
84 pub fn fail_refresh(&mut self, error: E) {
85 self.status = AsyncStatus::Error(error);
86 }
87
88 pub fn is_stale(&self) -> bool {
89 self.value.is_some()
90 && matches!(self.status, AsyncStatus::Refreshing | AsyncStatus::Error(_))
91 }
92}
93
94#[cfg(test)]
95mod tests {
96 use super::*;
97
98 #[test]
99 fn a_refresh_failure_does_not_erase_real_data() {
100 let mut value = AsyncValue::<_, &str>::ready(vec!["real"]);
101 value.refresh();
102 value.fail_refresh("offline");
103 assert_eq!(value.value.as_deref(), Some(["real"].as_slice()));
104 assert!(value.is_stale());
105 }
106
107 #[test]
108 fn unavailable_is_not_empty() {
109 let unavailable: Loadable<Vec<u8>> = Loadable::Unavailable("unsupported".into());
110 assert_ne!(unavailable, Loadable::Empty);
111 }
112}