1#[derive(Debug, Clone, PartialEq, Eq, Default)]
38pub enum RemoteData<T, E = String> {
39 #[default]
41 NotAsked,
42 Loading,
44 Success(T),
46 Failure(E),
48}
49
50impl<T, E> RemoteData<T, E> {
51 pub fn from_result(res: Result<T, E>) -> Self {
54 match res {
55 Ok(value) => RemoteData::Success(value),
56 Err(err) => RemoteData::Failure(err),
57 }
58 }
59
60 pub fn is_loading(&self) -> bool {
62 matches!(self, RemoteData::Loading)
63 }
64
65 pub fn is_success(&self) -> bool {
67 matches!(self, RemoteData::Success(_))
68 }
69
70 pub fn is_failure(&self) -> bool {
72 matches!(self, RemoteData::Failure(_))
73 }
74
75 pub fn value(&self) -> Option<&T> {
77 match self {
78 RemoteData::Success(value) => Some(value),
79 _ => None,
80 }
81 }
82
83 pub fn error(&self) -> Option<&E> {
85 match self {
86 RemoteData::Failure(err) => Some(err),
87 _ => None,
88 }
89 }
90
91 pub fn as_ref(&self) -> RemoteData<&T, &E> {
94 match self {
95 RemoteData::NotAsked => RemoteData::NotAsked,
96 RemoteData::Loading => RemoteData::Loading,
97 RemoteData::Success(value) => RemoteData::Success(value),
98 RemoteData::Failure(err) => RemoteData::Failure(err),
99 }
100 }
101
102 pub fn map<U>(self, f: impl FnOnce(T) -> U) -> RemoteData<U, E> {
105 match self {
106 RemoteData::NotAsked => RemoteData::NotAsked,
107 RemoteData::Loading => RemoteData::Loading,
108 RemoteData::Success(value) => RemoteData::Success(f(value)),
109 RemoteData::Failure(err) => RemoteData::Failure(err),
110 }
111 }
112
113 pub fn map_err<F>(self, f: impl FnOnce(E) -> F) -> RemoteData<T, F> {
115 match self {
116 RemoteData::NotAsked => RemoteData::NotAsked,
117 RemoteData::Loading => RemoteData::Loading,
118 RemoteData::Success(value) => RemoteData::Success(value),
119 RemoteData::Failure(err) => RemoteData::Failure(f(err)),
120 }
121 }
122}
123
124#[cfg(test)]
125mod tests {
126 use super::*;
127
128 #[test]
129 fn default_is_not_asked() {
130 let data: RemoteData<i32> = RemoteData::default();
131 assert_eq!(data, RemoteData::NotAsked);
132 }
133
134 #[test]
135 fn from_result_bridges_ok_and_err() {
136 let ok: RemoteData<i32> = RemoteData::from_result(Ok(7));
137 assert_eq!(ok, RemoteData::Success(7));
138 let err: RemoteData<i32> = RemoteData::from_result(Err("boom".to_string()));
139 assert_eq!(err, RemoteData::Failure("boom".to_string()));
140 }
141
142 #[test]
143 fn predicates_and_accessors() {
144 let ok: RemoteData<i32> = RemoteData::Success(42);
145 assert!(ok.is_success() && !ok.is_loading() && !ok.is_failure());
146 assert_eq!(ok.value(), Some(&42));
147 assert_eq!(ok.error(), None);
148
149 let err: RemoteData<i32> = RemoteData::Failure("nope".to_string());
150 assert!(err.is_failure());
151 assert_eq!(err.value(), None);
152 assert_eq!(err.error(), Some(&"nope".to_string()));
153
154 let loading: RemoteData<i32> = RemoteData::Loading;
155 assert!(loading.is_loading());
156 }
157
158 #[test]
159 fn map_only_touches_success() {
160 let ok: RemoteData<i32> = RemoteData::Success(3);
161 assert_eq!(ok.map(|n| n * 2), RemoteData::Success(6));
162
163 let loading: RemoteData<i32> = RemoteData::Loading;
164 assert_eq!(loading.map(|n| n * 2), RemoteData::Loading);
165
166 let err: RemoteData<i32> = RemoteData::Failure("e".to_string());
167 assert_eq!(err.map(|n| n * 2), RemoteData::Failure("e".to_string()));
168 }
169
170 #[test]
171 fn map_err_only_touches_failure() {
172 let err: RemoteData<i32, u16> = RemoteData::Failure(404);
173 let mapped: RemoteData<i32, String> = err.map_err(|c| format!("HTTP {c}"));
174 assert_eq!(mapped, RemoteData::Failure("HTTP 404".to_string()));
175 }
176
177 #[test]
178 fn as_ref_borrows_without_moving() {
179 let ok: RemoteData<String> = RemoteData::Success("hi".to_string());
180 assert_eq!(ok.as_ref().value().map(|s| s.as_str()), Some("hi"));
181 assert!(ok.is_success());
183 }
184}