Skip to main content

react_rs_core/
resource.rs

1use crate::signal::{create_signal, ReadSignal, WriteSignal};
2
3#[derive(Debug, Clone, PartialEq)]
4pub enum ResourceState<T> {
5    Loading,
6    Ready(T),
7    Error(String),
8}
9
10impl<T> ResourceState<T> {
11    pub fn is_loading(&self) -> bool {
12        matches!(self, ResourceState::Loading)
13    }
14
15    pub fn is_ready(&self) -> bool {
16        matches!(self, ResourceState::Ready(_))
17    }
18
19    pub fn is_error(&self) -> bool {
20        matches!(self, ResourceState::Error(_))
21    }
22
23    pub fn data(&self) -> Option<&T> {
24        match self {
25            ResourceState::Ready(d) => Some(d),
26            _ => None,
27        }
28    }
29
30    pub fn error(&self) -> Option<&str> {
31        match self {
32            ResourceState::Error(e) => Some(e),
33            _ => None,
34        }
35    }
36}
37
38pub struct Resource<T: Clone + 'static> {
39    state: ReadSignal<ResourceState<T>>,
40    set_state: WriteSignal<ResourceState<T>>,
41}
42
43impl<T: Clone + 'static> Resource<T> {
44    pub fn state(&self) -> ReadSignal<ResourceState<T>> {
45        self.state.clone()
46    }
47
48    pub fn read(&self) -> ResourceState<T> {
49        self.state.get()
50    }
51
52    pub fn loading(&self) -> bool {
53        self.state.get().is_loading()
54    }
55
56    pub fn data(&self) -> Option<T> {
57        match self.state.get() {
58            ResourceState::Ready(d) => Some(d),
59            _ => None,
60        }
61    }
62
63    pub fn set_ready(&self, data: T) {
64        self.set_state.set(ResourceState::Ready(data));
65    }
66
67    pub fn set_error(&self, error: impl Into<String>) {
68        self.set_state.set(ResourceState::Error(error.into()));
69    }
70
71    pub fn set_loading(&self) {
72        self.set_state.set(ResourceState::Loading);
73    }
74}
75
76impl<T: Clone + 'static> Clone for Resource<T> {
77    fn clone(&self) -> Self {
78        Self {
79            state: self.state.clone(),
80            set_state: self.set_state.clone(),
81        }
82    }
83}
84
85pub fn create_resource<T: Clone + 'static>() -> Resource<T> {
86    let (state, set_state) = create_signal(ResourceState::Loading);
87    Resource { state, set_state }
88}
89
90pub fn create_resource_with<T: Clone + 'static>(initial: T) -> Resource<T> {
91    let (state, set_state) = create_signal(ResourceState::Ready(initial));
92    Resource { state, set_state }
93}
94
95#[cfg(test)]
96mod tests {
97    use super::*;
98
99    #[test]
100    fn test_resource_initial_loading() {
101        let resource = create_resource::<String>();
102        assert!(resource.loading());
103        assert!(resource.data().is_none());
104    }
105
106    #[test]
107    fn test_resource_set_ready() {
108        let resource = create_resource::<String>();
109        resource.set_ready("hello".to_string());
110        assert!(!resource.loading());
111        assert_eq!(resource.data(), Some("hello".to_string()));
112    }
113
114    #[test]
115    fn test_resource_set_error() {
116        let resource = create_resource::<String>();
117        resource.set_error("network error");
118        assert!(resource.read().is_error());
119        assert_eq!(resource.read().error(), Some("network error"));
120    }
121
122    #[test]
123    fn test_resource_with_initial() {
124        let resource = create_resource_with(42);
125        assert!(!resource.loading());
126        assert_eq!(resource.data(), Some(42));
127    }
128
129    #[test]
130    fn test_resource_state_transitions() {
131        let resource = create_resource::<Vec<String>>();
132        assert!(resource.loading());
133
134        resource.set_ready(vec!["a".to_string()]);
135        assert_eq!(resource.data().unwrap().len(), 1);
136
137        resource.set_loading();
138        assert!(resource.loading());
139
140        resource.set_error("timeout");
141        assert!(resource.read().is_error());
142    }
143}