1use serde::{Deserialize, Serialize};
2
3#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default)]
5pub enum LoadState {
6 #[default]
7 NotLoaded,
8 Partial(std::collections::HashSet<String>),
9 FullyLoaded,
10}
11
12impl LoadState {
13 pub fn is_loaded(&self, field_or_relation: &str) -> bool {
14 match self {
15 LoadState::NotLoaded => false,
16 LoadState::FullyLoaded => true,
17 LoadState::Partial(set) => set.contains(field_or_relation),
18 }
19 }
20}
21
22#[derive(Debug, Clone, Serialize, Deserialize)]
24pub enum EvalResult<T> {
25 Value(T),
27 Null,
29 NotLoaded {
31 failed_node: String,
32 attempted_path: String,
33 },
34}
35
36impl<T> EvalResult<T> {
37 pub fn and_then<U, F: FnOnce(T) -> EvalResult<U>>(
38 self,
39 field_name: &str,
40 f: F,
41 ) -> EvalResult<U> {
42 match self {
43 EvalResult::Value(val) => match f(val) {
44 EvalResult::NotLoaded {
45 failed_node,
46 attempted_path,
47 } => {
48 let new_path = match (attempted_path == field_name, attempted_path.is_empty()) {
49 (true, _) => attempted_path,
50 (_, true) => field_name.to_string(),
51 _ => format!("{}.{}", field_name, attempted_path),
52 };
53 EvalResult::NotLoaded {
54 failed_node,
55 attempted_path: new_path,
56 }
57 }
58 other => other,
59 },
60 EvalResult::Null => EvalResult::Null,
61 EvalResult::NotLoaded {
62 failed_node,
63 attempted_path,
64 } => EvalResult::NotLoaded {
65 failed_node,
66 attempted_path,
67 },
68 }
69 }
70
71 pub fn map<U, F: FnOnce(T) -> U>(self, f: F) -> EvalResult<U> {
72 match self {
73 EvalResult::Value(val) => EvalResult::Value(f(val)),
74 EvalResult::Null => EvalResult::Null,
75 EvalResult::NotLoaded {
76 failed_node,
77 attempted_path,
78 } => EvalResult::NotLoaded {
79 failed_node,
80 attempted_path,
81 },
82 }
83 }
84}
85
86#[cfg(test)]
87mod tests {
88 use super::*;
89
90 struct Company {
91 pub name: Option<String>,
92 pub __load_state: LoadState,
93 }
94
95 impl Company {
96 fn eval_name(&self) -> EvalResult<&str> {
97 if !self.__load_state.is_loaded("name") {
98 return EvalResult::NotLoaded {
99 failed_node: "name".to_string(),
100 attempted_path: "name".to_string(),
101 };
102 }
103 match &self.name {
104 Some(n) => EvalResult::Value(n.as_str()),
105 None => EvalResult::Null,
106 }
107 }
108 }
109
110 struct Platform {
111 pub company: Option<Box<Company>>,
112 pub __load_state: LoadState,
113 }
114
115 impl Platform {
116 fn eval_company(&self) -> EvalResult<&Company> {
117 if !self.__load_state.is_loaded("company") {
118 return EvalResult::NotLoaded {
119 failed_node: "company".to_string(),
120 attempted_path: "company".to_string(),
121 };
122 }
123 match &self.company {
124 Some(c) => EvalResult::Value(c.as_ref()),
125 None => EvalResult::Null,
126 }
127 }
128 }
129
130 struct User {
131 pub platform: Option<Box<Platform>>,
132 pub __load_state: LoadState,
133 }
134
135 impl User {
136 fn eval_platform(&self) -> EvalResult<&Platform> {
137 if !self.__load_state.is_loaded("platform") {
138 return EvalResult::NotLoaded {
139 failed_node: "platform".to_string(),
140 attempted_path: "platform".to_string(),
141 };
142 }
143 match &self.platform {
144 Some(p) => EvalResult::Value(p.as_ref()),
145 None => EvalResult::Null,
146 }
147 }
148 }
149
150 #[test]
151 fn test_eval_tracking_chain_perfect_path() {
152 let company = Company {
157 name: None,
158 __load_state: LoadState::NotLoaded,
160 };
161
162 let platform = Platform {
163 company: Some(Box::new(company)),
164 __load_state: LoadState::FullyLoaded,
166 };
167
168 let user = User {
169 platform: Some(Box::new(platform)),
170 __load_state: LoadState::FullyLoaded,
172 };
173
174 let result = user.eval_platform().and_then("platform", |p| {
176 p.eval_company().and_then("company", |c| c.eval_name())
177 });
178
179 match &result {
181 EvalResult::NotLoaded { attempted_path, .. } => {
182 assert_eq!(attempted_path, "platform.company.name");
183 println!("\n\n>>> 【系统捕获到未加载异常】 <<<\n{:#?}\n\n", result);
184 }
185 _ => panic!("Expected NotLoaded but got {:?}", result),
186 }
187 }
188
189 #[test]
190 fn test_eval_tracking_chain_middle_break() {
191 let platform = Platform {
193 company: None, __load_state: LoadState::NotLoaded, };
196
197 let user = User {
198 platform: Some(Box::new(platform)),
199 __load_state: LoadState::FullyLoaded,
200 };
201
202 let result = user.eval_platform().and_then("platform", |p| {
203 p.eval_company().and_then("company", |c| c.eval_name())
204 });
205
206 match result {
207 EvalResult::NotLoaded { attempted_path, .. } => {
208 assert_eq!(attempted_path, "platform.company");
209 println!(
210 "Success! Intercepted middle missing path: {}",
211 attempted_path
212 );
213 }
214 _ => panic!("Expected NotLoaded"),
215 }
216 }
217
218 #[test]
219 fn test_eval_tracking_chain_normal_null() {
220 let company = Company {
222 name: None, __load_state: LoadState::FullyLoaded,
224 };
225
226 let platform = Platform {
227 company: Some(Box::new(company)),
228 __load_state: LoadState::FullyLoaded,
229 };
230
231 let user = User {
232 platform: Some(Box::new(platform)),
233 __load_state: LoadState::FullyLoaded,
234 };
235
236 let result = user.eval_platform().and_then("platform", |p| {
237 p.eval_company().and_then("company", |c| c.eval_name())
238 });
239
240 match result {
241 EvalResult::Null => {
242 println!("Success! Legitimately empty (Null), not an error.");
243 }
244 _ => panic!("Expected Null"),
245 }
246 }
247}