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