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