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