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 = if attempted_path == field_name {
47 attempted_path
48 } else if attempted_path.is_empty() {
49 field_name.to_string()
50 } else {
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 { failed_node, attempted_path } => {
62 let new_path = if attempted_path.is_empty() {
63 field_name.to_string()
64 } else {
65 format!("{}.{}", attempted_path, field_name)
66 };
67 EvalResult::NotLoaded {
68 failed_node,
69 attempted_path: new_path
70 }
71 },
72 }
73 }
74
75 pub fn map<U, F: FnOnce(T) -> U>(self, f: F) -> EvalResult<U> {
76 match self {
77 EvalResult::Value(val) => EvalResult::Value(f(val)),
78 EvalResult::Null => EvalResult::Null,
79 EvalResult::NotLoaded { failed_node, attempted_path } => EvalResult::NotLoaded { failed_node, attempted_path },
80 }
81 }
82}
83
84#[cfg(test)]
85mod tests {
86 use super::*;
87 use std::collections::HashSet;
88
89 struct Company {
90 pub name: Option<String>,
91 pub __load_state: LoadState,
92 }
93
94 impl Company {
95 fn eval_name(&self) -> EvalResult<&str> {
96 if !self.__load_state.is_loaded("name") {
97 EvalResult::NotLoaded { failed_node: "name".to_string(), attempted_path: "name".to_string() }
98 } else {
99 match &self.name {
100 Some(n) => EvalResult::Value(n.as_str()),
101 None => EvalResult::Null,
102 }
103 }
104 }
105 }
106
107 struct Platform {
108 pub company: Option<Box<Company>>,
109 pub __load_state: LoadState,
110 }
111
112 impl Platform {
113 fn eval_company(&self) -> EvalResult<&Company> {
114 if !self.__load_state.is_loaded("company") {
115 EvalResult::NotLoaded { failed_node: "company".to_string(), attempted_path: "company".to_string() }
116 } else {
117 match &self.company {
118 Some(c) => EvalResult::Value(c.as_ref()),
119 None => EvalResult::Null,
120 }
121 }
122 }
123 }
124
125 struct User {
126 pub platform: Option<Box<Platform>>,
127 pub __load_state: LoadState,
128 }
129
130 impl User {
131 fn eval_platform(&self) -> EvalResult<&Platform> {
132 if !self.__load_state.is_loaded("platform") {
133 EvalResult::NotLoaded { failed_node: "platform".to_string(), attempted_path: "platform".to_string() }
134 } else {
135 match &self.platform {
136 Some(p) => EvalResult::Value(p.as_ref()),
137 None => EvalResult::Null,
138 }
139 }
140 }
141 }
142
143 #[test]
144 fn test_eval_tracking_chain_perfect_path() {
145 let company = Company {
150 name: None,
151 __load_state: LoadState::NotLoaded,
153 };
154
155 let platform = Platform {
156 company: Some(Box::new(company)),
157 __load_state: LoadState::FullyLoaded,
159 };
160
161 let user = User {
162 platform: Some(Box::new(platform)),
163 __load_state: LoadState::FullyLoaded,
165 };
166
167 let result = user.eval_platform()
169 .and_then("platform", |p| p.eval_company().and_then("company", |c| c.eval_name()));
170
171 match &result {
173 EvalResult::NotLoaded { missing_path } => {
174 assert_eq!(missing_path, "platform.company.name");
175 println!("\n\n>>> 【系统捕获到未加载异常】 <<<\n{:#?}\n\n", result);
176 }
177 _ => panic!("Expected NotLoaded but got {:?}", result),
178 }
179 }
180
181 #[test]
182 fn test_eval_tracking_chain_middle_break() {
183 let platform = Platform {
185 company: None, __load_state: LoadState::NotLoaded, };
188
189 let user = User {
190 platform: Some(Box::new(platform)),
191 __load_state: LoadState::FullyLoaded,
192 };
193
194 let result = user.eval_platform()
195 .and_then("platform", |p| p.eval_company().and_then("company", |c| c.eval_name()));
196
197 match result {
198 EvalResult::NotLoaded { missing_path } => {
199 assert_eq!(missing_path, "platform.company");
200 println!("Success! Intercepted middle missing path: {}", missing_path);
201 }
202 _ => panic!("Expected NotLoaded"),
203 }
204 }
205
206 #[test]
207 fn test_eval_tracking_chain_normal_null() {
208 let company = Company {
210 name: None, __load_state: LoadState::FullyLoaded,
212 };
213
214 let platform = Platform {
215 company: Some(Box::new(company)),
216 __load_state: LoadState::FullyLoaded,
217 };
218
219 let user = User {
220 platform: Some(Box::new(platform)),
221 __load_state: LoadState::FullyLoaded,
222 };
223
224 let result = user.eval_platform()
225 .and_then("platform", |p| p.eval_company().and_then("company", |c| c.eval_name()));
226
227 match result {
228 EvalResult::Null => {
229 println!("Success! Legitimately empty (Null), not an error.");
230 }
231 _ => panic!("Expected Null"),
232 }
233 }
234}