react_rs_core/
resource.rs1use 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}