#[derive(Debug, Clone, PartialEq, Eq, Default)]
pub enum RemoteData<T, E = String> {
#[default]
NotAsked,
Loading,
Success(T),
Failure(E),
}
impl<T, E> RemoteData<T, E> {
pub fn from_result(res: Result<T, E>) -> Self {
match res {
Ok(value) => RemoteData::Success(value),
Err(err) => RemoteData::Failure(err),
}
}
pub fn is_loading(&self) -> bool {
matches!(self, RemoteData::Loading)
}
pub fn is_success(&self) -> bool {
matches!(self, RemoteData::Success(_))
}
pub fn is_failure(&self) -> bool {
matches!(self, RemoteData::Failure(_))
}
pub fn value(&self) -> Option<&T> {
match self {
RemoteData::Success(value) => Some(value),
_ => None,
}
}
pub fn error(&self) -> Option<&E> {
match self {
RemoteData::Failure(err) => Some(err),
_ => None,
}
}
pub fn as_ref(&self) -> RemoteData<&T, &E> {
match self {
RemoteData::NotAsked => RemoteData::NotAsked,
RemoteData::Loading => RemoteData::Loading,
RemoteData::Success(value) => RemoteData::Success(value),
RemoteData::Failure(err) => RemoteData::Failure(err),
}
}
pub fn map<U>(self, f: impl FnOnce(T) -> U) -> RemoteData<U, E> {
match self {
RemoteData::NotAsked => RemoteData::NotAsked,
RemoteData::Loading => RemoteData::Loading,
RemoteData::Success(value) => RemoteData::Success(f(value)),
RemoteData::Failure(err) => RemoteData::Failure(err),
}
}
pub fn map_err<F>(self, f: impl FnOnce(E) -> F) -> RemoteData<T, F> {
match self {
RemoteData::NotAsked => RemoteData::NotAsked,
RemoteData::Loading => RemoteData::Loading,
RemoteData::Success(value) => RemoteData::Success(value),
RemoteData::Failure(err) => RemoteData::Failure(f(err)),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn default_is_not_asked() {
let data: RemoteData<i32> = RemoteData::default();
assert_eq!(data, RemoteData::NotAsked);
}
#[test]
fn from_result_bridges_ok_and_err() {
let ok: RemoteData<i32> = RemoteData::from_result(Ok(7));
assert_eq!(ok, RemoteData::Success(7));
let err: RemoteData<i32> = RemoteData::from_result(Err("boom".to_string()));
assert_eq!(err, RemoteData::Failure("boom".to_string()));
}
#[test]
fn predicates_and_accessors() {
let ok: RemoteData<i32> = RemoteData::Success(42);
assert!(ok.is_success() && !ok.is_loading() && !ok.is_failure());
assert_eq!(ok.value(), Some(&42));
assert_eq!(ok.error(), None);
let err: RemoteData<i32> = RemoteData::Failure("nope".to_string());
assert!(err.is_failure());
assert_eq!(err.value(), None);
assert_eq!(err.error(), Some(&"nope".to_string()));
let loading: RemoteData<i32> = RemoteData::Loading;
assert!(loading.is_loading());
}
#[test]
fn map_only_touches_success() {
let ok: RemoteData<i32> = RemoteData::Success(3);
assert_eq!(ok.map(|n| n * 2), RemoteData::Success(6));
let loading: RemoteData<i32> = RemoteData::Loading;
assert_eq!(loading.map(|n| n * 2), RemoteData::Loading);
let err: RemoteData<i32> = RemoteData::Failure("e".to_string());
assert_eq!(err.map(|n| n * 2), RemoteData::Failure("e".to_string()));
}
#[test]
fn map_err_only_touches_failure() {
let err: RemoteData<i32, u16> = RemoteData::Failure(404);
let mapped: RemoteData<i32, String> = err.map_err(|c| format!("HTTP {c}"));
assert_eq!(mapped, RemoteData::Failure("HTTP 404".to_string()));
}
#[test]
fn as_ref_borrows_without_moving() {
let ok: RemoteData<String> = RemoteData::Success("hi".to_string());
assert_eq!(ok.as_ref().value().map(|s| s.as_str()), Some("hi"));
assert!(ok.is_success());
}
}