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