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 #[serde(skip)]
16 Indexed(std::sync::Arc<crate::LoadedSnapshot>),
17 FullyLoaded,
18}
19
20impl LoadState {
21 pub fn into_indexed(self, layout: std::sync::Arc<crate::FieldLayout>) -> Result<Self, String> {
22 let state = match self {
23 Self::Indexed(state) => {
24 if !std::sync::Arc::ptr_eq(state.layout(), &layout) {
25 return Err("incompatible loaded-state type or layout revision".to_owned());
26 }
27 return Ok(Self::Indexed(state));
28 }
29 Self::FullyLoaded => crate::LoadedSnapshot::fully_loaded(layout),
30 Self::NotLoaded => crate::LoadedSnapshot::projection(layout, std::iter::empty()),
31 Self::Partial(fields) => {
32 crate::LoadedSnapshot::projection(layout, fields.iter().map(String::as_str))
33 }
34 Self::PartialCompact(fields) => {
35 crate::LoadedSnapshot::projection(layout, fields.iter().map(|field| field.as_ref()))
36 }
37 Self::SharedColumns(fields) => {
38 crate::LoadedSnapshot::projection(layout, fields.iter().map(String::as_str))
39 }
40 };
41 Ok(Self::Indexed(state.into_shared()))
42 }
43
44 pub fn is_loaded(&self, field_or_relation: &str) -> bool {
45 match self {
46 LoadState::NotLoaded => false,
47 LoadState::FullyLoaded => true,
48 LoadState::Partial(set) => set.contains(field_or_relation),
49 LoadState::PartialCompact(fields) => fields
50 .iter()
51 .any(|field| field.as_ref() == field_or_relation),
52 LoadState::SharedColumns(columns) => {
53 columns.iter().any(|column| column == field_or_relation)
54 }
55 LoadState::Indexed(state) => state.is_loaded(field_or_relation),
56 }
57 }
58
59 pub fn mark_loaded(&mut self, field: &str) -> Result<(), String> {
60 match self {
61 Self::Indexed(state) => {
62 *state = crate::LoadedSnapshot::with_loaded(state, field, true)?
63 }
64 Self::NotLoaded => {
65 *self = Self::PartialCompact(smallvec::smallvec![field.to_owned().into()])
66 }
67 Self::Partial(fields) => {
68 fields.insert(field.to_owned());
69 }
70 Self::PartialCompact(fields) => {
71 if !fields.iter().any(|loaded| loaded.as_ref() == field) {
72 fields.push(field.to_owned().into());
73 }
74 }
75 Self::SharedColumns(columns) => {
76 if !columns.iter().any(|loaded| loaded == field) {
77 let mut next = columns.to_vec();
78 next.push(field.to_owned());
79 *columns = next.into();
80 }
81 }
82 Self::FullyLoaded => {}
83 }
84 Ok(())
85 }
86
87 pub fn mark_unloaded(&mut self, field: &str) -> Result<(), String> {
88 match self {
89 Self::Indexed(state) => {
90 *state = crate::LoadedSnapshot::with_loaded(state, field, false)?
91 }
92 Self::Partial(fields) => {
93 fields.remove(field);
94 }
95 Self::PartialCompact(fields) => fields.retain(|loaded| loaded.as_ref() != field),
96 Self::SharedColumns(columns) => {
97 *columns = columns
98 .iter()
99 .filter(|name| name.as_str() != field)
100 .cloned()
101 .collect::<Vec<_>>()
102 .into()
103 }
104 Self::NotLoaded => {}
105 Self::FullyLoaded => {
106 return Err("clearing availability requires a generated layout".to_owned());
107 }
108 }
109 Ok(())
110 }
111}
112
113#[derive(Debug, Clone, Serialize, Deserialize)]
115pub enum EvalResult<T> {
116 Value(T),
118 Null,
120 NotLoaded {
122 failed_node: String,
123 attempted_path: String,
124 },
125}
126
127impl<T> EvalResult<T> {
128 pub fn and_then<U, F: FnOnce(T) -> EvalResult<U>>(
129 self,
130 field_name: &str,
131 f: F,
132 ) -> EvalResult<U> {
133 match self {
134 EvalResult::Value(val) => match f(val) {
135 EvalResult::NotLoaded {
136 failed_node,
137 attempted_path,
138 } => {
139 let new_path = match (attempted_path == field_name, attempted_path.is_empty()) {
140 (true, _) => attempted_path,
141 (_, true) => field_name.to_string(),
142 _ => format!("{}.{}", field_name, attempted_path),
143 };
144 EvalResult::NotLoaded {
145 failed_node,
146 attempted_path: new_path,
147 }
148 }
149 other => other,
150 },
151 EvalResult::Null => EvalResult::Null,
152 EvalResult::NotLoaded {
153 failed_node,
154 attempted_path,
155 } => EvalResult::NotLoaded {
156 failed_node,
157 attempted_path,
158 },
159 }
160 }
161
162 pub fn map<U, F: FnOnce(T) -> U>(self, f: F) -> EvalResult<U> {
163 match self {
164 EvalResult::Value(val) => EvalResult::Value(f(val)),
165 EvalResult::Null => EvalResult::Null,
166 EvalResult::NotLoaded {
167 failed_node,
168 attempted_path,
169 } => EvalResult::NotLoaded {
170 failed_node,
171 attempted_path,
172 },
173 }
174 }
175}
176
177#[cfg(test)]
178mod tests {
179 use super::*;
180
181 struct Company {
182 pub name: Option<String>,
183 pub __load_state: LoadState,
184 }
185
186 impl Company {
187 fn eval_name(&self) -> EvalResult<&str> {
188 if !self.__load_state.is_loaded("name") {
189 return EvalResult::NotLoaded {
190 failed_node: "name".to_string(),
191 attempted_path: "name".to_string(),
192 };
193 }
194 match &self.name {
195 Some(n) => EvalResult::Value(n.as_str()),
196 None => EvalResult::Null,
197 }
198 }
199 }
200
201 struct Platform {
202 pub company: Option<Box<Company>>,
203 pub __load_state: LoadState,
204 }
205
206 impl Platform {
207 fn eval_company(&self) -> EvalResult<&Company> {
208 if !self.__load_state.is_loaded("company") {
209 return EvalResult::NotLoaded {
210 failed_node: "company".to_string(),
211 attempted_path: "company".to_string(),
212 };
213 }
214 match &self.company {
215 Some(c) => EvalResult::Value(c.as_ref()),
216 None => EvalResult::Null,
217 }
218 }
219 }
220
221 struct User {
222 pub platform: Option<Box<Platform>>,
223 pub __load_state: LoadState,
224 }
225
226 impl User {
227 fn eval_platform(&self) -> EvalResult<&Platform> {
228 if !self.__load_state.is_loaded("platform") {
229 return EvalResult::NotLoaded {
230 failed_node: "platform".to_string(),
231 attempted_path: "platform".to_string(),
232 };
233 }
234 match &self.platform {
235 Some(p) => EvalResult::Value(p.as_ref()),
236 None => EvalResult::Null,
237 }
238 }
239 }
240
241 #[test]
242 fn test_eval_tracking_chain_perfect_path() {
243 let company = Company {
248 name: None,
249 __load_state: LoadState::NotLoaded,
251 };
252
253 let platform = Platform {
254 company: Some(Box::new(company)),
255 __load_state: LoadState::FullyLoaded,
257 };
258
259 let user = User {
260 platform: Some(Box::new(platform)),
261 __load_state: LoadState::FullyLoaded,
263 };
264
265 let result = user.eval_platform().and_then("platform", |p| {
267 p.eval_company().and_then("company", |c| c.eval_name())
268 });
269
270 match &result {
272 EvalResult::NotLoaded { attempted_path, .. } => {
273 assert_eq!(attempted_path, "platform.company.name");
274 println!("\n\n>>> 【系统捕获到未加载异常】 <<<\n{:#?}\n\n", result);
275 }
276 _ => panic!("Expected NotLoaded but got {:?}", result),
277 }
278 }
279
280 #[test]
281 fn test_eval_tracking_chain_middle_break() {
282 let platform = Platform {
284 company: None, __load_state: LoadState::NotLoaded, };
287
288 let user = User {
289 platform: Some(Box::new(platform)),
290 __load_state: LoadState::FullyLoaded,
291 };
292
293 let result = user.eval_platform().and_then("platform", |p| {
294 p.eval_company().and_then("company", |c| c.eval_name())
295 });
296
297 match result {
298 EvalResult::NotLoaded { attempted_path, .. } => {
299 assert_eq!(attempted_path, "platform.company");
300 println!(
301 "Success! Intercepted middle missing path: {}",
302 attempted_path
303 );
304 }
305 _ => panic!("Expected NotLoaded"),
306 }
307 }
308
309 #[test]
310 fn test_eval_tracking_chain_normal_null() {
311 let company = Company {
313 name: None, __load_state: LoadState::FullyLoaded,
315 };
316
317 let platform = Platform {
318 company: Some(Box::new(company)),
319 __load_state: LoadState::FullyLoaded,
320 };
321
322 let user = User {
323 platform: Some(Box::new(platform)),
324 __load_state: LoadState::FullyLoaded,
325 };
326
327 let result = user.eval_platform().and_then("platform", |p| {
328 p.eval_company().and_then("company", |c| c.eval_name())
329 });
330
331 match result {
332 EvalResult::Null => {
333 println!("Success! Legitimately empty (Null), not an error.");
334 }
335 _ => panic!("Expected Null"),
336 }
337 }
338}