#[derive(Clone, Debug, Default, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "json-schema", derive(schemars::JsonSchema))]
pub enum DataResource<T> {
#[default]
Empty,
Loading,
Loaded(T),
Failed(String),
}
impl<T> DataResource<T> {
pub fn is_empty(&self) -> bool {
matches!(self, Self::Empty)
}
pub fn is_loading(&self) -> bool {
matches!(self, Self::Loading)
}
pub fn is_loaded(&self) -> bool {
matches!(self, Self::Loaded(_))
}
pub fn is_failed(&self) -> bool {
matches!(self, Self::Failed(_))
}
pub fn data(&self) -> Option<&T> {
match self {
Self::Loaded(t) => Some(t),
_ => None,
}
}
pub fn data_mut(&mut self) -> Option<&mut T> {
match self {
Self::Loaded(t) => Some(t),
_ => None,
}
}
pub fn error(&self) -> Option<&str> {
match self {
Self::Failed(e) => Some(e),
_ => None,
}
}
pub fn map<U>(self, f: impl FnOnce(T) -> U) -> DataResource<U> {
match self {
Self::Empty => DataResource::Empty,
Self::Loading => DataResource::Loading,
Self::Loaded(t) => DataResource::Loaded(f(t)),
Self::Failed(e) => DataResource::Failed(e),
}
}
pub fn map_ref<U>(&self, f: impl FnOnce(&T) -> U) -> DataResource<U> {
match self {
Self::Empty => DataResource::Empty,
Self::Loading => DataResource::Loading,
Self::Loaded(t) => DataResource::Loaded(f(t)),
Self::Failed(e) => DataResource::Failed(e.clone()),
}
}
pub fn and_then<U>(self, f: impl FnOnce(T) -> DataResource<U>) -> DataResource<U> {
match self {
Self::Empty => DataResource::Empty,
Self::Loading => DataResource::Loading,
Self::Loaded(t) => f(t),
Self::Failed(e) => DataResource::Failed(e),
}
}
pub fn unwrap_or(self, default: T) -> T {
match self {
Self::Loaded(t) => t,
_ => default,
}
}
pub fn unwrap_or_else(self, f: impl FnOnce() -> T) -> T {
match self {
Self::Loaded(t) => t,
_ => f(),
}
}
pub fn as_ref(&self) -> DataResource<&T> {
match self {
Self::Empty => DataResource::Empty,
Self::Loading => DataResource::Loading,
Self::Loaded(t) => DataResource::Loaded(t),
Self::Failed(e) => DataResource::Failed(e.clone()),
}
}
pub fn is_settled(&self) -> bool {
matches!(self, Self::Loaded(_) | Self::Failed(_))
}
pub fn is_pending(&self) -> bool {
matches!(self, Self::Empty | Self::Loading)
}
pub fn start_loading(&mut self) -> bool
where
T: Clone,
{
if self.is_loading() {
false
} else {
*self = Self::Loading;
true
}
}
}
impl<T: Clone> DataResource<T> {
pub fn cloned(&self) -> Option<T> {
self.data().cloned()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_default_is_empty() {
let resource: DataResource<String> = DataResource::default();
assert!(resource.is_empty());
}
#[test]
fn test_state_checks() {
let empty: DataResource<i32> = DataResource::Empty;
let loading: DataResource<i32> = DataResource::Loading;
let loaded: DataResource<i32> = DataResource::Loaded(42);
let failed: DataResource<i32> = DataResource::Failed("oops".to_string());
assert!(empty.is_empty());
assert!(!empty.is_loading());
assert!(empty.is_pending());
assert!(!empty.is_settled());
assert!(!loading.is_empty());
assert!(loading.is_loading());
assert!(loading.is_pending());
assert!(!loading.is_settled());
assert!(!loaded.is_empty());
assert!(!loaded.is_loading());
assert!(loaded.is_loaded());
assert!(!loaded.is_pending());
assert!(loaded.is_settled());
assert!(!failed.is_empty());
assert!(failed.is_failed());
assert!(!failed.is_pending());
assert!(failed.is_settled());
}
#[test]
fn test_data_accessors() {
let loaded: DataResource<i32> = DataResource::Loaded(42);
let failed: DataResource<i32> = DataResource::Failed("error".to_string());
assert_eq!(loaded.data(), Some(&42));
assert_eq!(failed.data(), None);
assert_eq!(failed.error(), Some("error"));
assert_eq!(loaded.error(), None);
}
#[test]
fn test_map() {
let loaded: DataResource<i32> = DataResource::Loaded(21);
let doubled = loaded.map(|x| x * 2);
assert_eq!(doubled.data(), Some(&42));
let loading: DataResource<i32> = DataResource::Loading;
let still_loading: DataResource<i32> = loading.map(|x| x * 2);
assert!(still_loading.is_loading());
}
#[test]
fn test_and_then() {
let loaded: DataResource<i32> = DataResource::Loaded(42);
let chained = loaded.and_then(|x| DataResource::Loaded(x.to_string()));
assert_eq!(chained.data(), Some(&"42".to_string()));
let failed: DataResource<i32> = DataResource::Failed("err".to_string());
let still_failed: DataResource<String> =
failed.and_then(|x| DataResource::Loaded(x.to_string()));
assert!(still_failed.is_failed());
}
#[test]
fn test_unwrap_or() {
let loaded: DataResource<i32> = DataResource::Loaded(42);
let empty: DataResource<i32> = DataResource::Empty;
assert_eq!(loaded.unwrap_or(0), 42);
assert_eq!(empty.unwrap_or(0), 0);
}
#[test]
fn test_start_loading() {
let mut resource: DataResource<i32> = DataResource::Empty;
assert!(resource.start_loading());
assert!(resource.is_loading());
assert!(!resource.start_loading());
assert!(resource.is_loading());
}
#[test]
#[cfg(feature = "serde")]
fn test_serialize_deserialize() {
let loaded: DataResource<i32> = DataResource::Loaded(42);
let json = serde_json::to_string(&loaded).unwrap();
let restored: DataResource<i32> = serde_json::from_str(&json).unwrap();
assert_eq!(loaded, restored);
let failed: DataResource<i32> = DataResource::Failed("oops".to_string());
let json = serde_json::to_string(&failed).unwrap();
let restored: DataResource<i32> = serde_json::from_str(&json).unwrap();
assert_eq!(failed, restored);
}
}