Skip to main content

state_engine/
state.rs

1use serde_json::Value;
2use std::collections::{HashMap, HashSet};
3use crate::manifest::Manifest;
4use crate::common::pool::{StateValueList, STATE_OFFSET_KEY, STATE_MASK_KEY};
5use crate::common::bit;
6use crate::store::Store;
7use crate::load::Load;
8use crate::ports::provided::StateError;
9
10pub struct State<'a> {
11    manifest: Manifest,
12    state_values: StateValueList,
13    store: Store<'a>,
14    load: Load<'a>,
15    max_recursion: usize,
16    called_keys: HashSet<String>,
17}
18
19impl<'a> State<'a> {
20    /// Creates a new State with the given manifest directory and load handler.
21    ///
22    /// # Examples
23    ///
24    /// ```
25    /// use state_engine::State;
26    /// use state_engine::load::Load;
27    ///
28    /// let state = State::new("./examples/manifest", Load::new());
29    /// ```
30    pub fn new(manifest_dir: &str, load: Load<'a>) -> Self {
31        Self {
32            manifest: Manifest::new(manifest_dir),
33            state_values: StateValueList::new(),
34            store: Store::new(),
35            load,
36            max_recursion: 20,
37            called_keys: HashSet::new(),
38        }
39    }
40
41    pub fn with_in_memory(mut self, client: &'a mut dyn crate::ports::required::InMemoryClient) -> Self {
42        self.store = self.store.with_in_memory(client);
43        self
44    }
45
46    pub fn with_kvs_client(mut self, client: &'a mut dyn crate::ports::required::KVSClient) -> Self {
47        self.store = self.store.with_kvs_client(client);
48        self
49    }
50
51    /// Splits "file.path" into ("file", "path").
52    fn split_key<'k>(key: &'k str) -> (&'k str, &'k str) {
53        match key.find('.') {
54            Some(pos) => (&key[..pos], &key[pos + 1..]),
55            None => (key, ""),
56        }
57    }
58
59    /// Resolves a yaml value record to a Value (any type, for connection etc.).
60    /// For non-template single placeholder: returns the resolved Value as-is (including Object).
61    /// For string-compatible values: delegates to resolve_value_to_string.
62    fn resolve_value(&mut self, value_idx: u16) -> Option<Value> {
63        crate::fn_log!("State", "resolve_value", &value_idx.to_string());
64        let vo = self.manifest.values.get(value_idx)?;
65        let is_template = bit::get(vo[0], bit::VO_OFFSET_IS_TEMPLATE, bit::VO_MASK_IS_TEMPLATE) == 1;
66        let is_path = bit::get(vo[0], bit::VO_OFFSET_T0_IS_PATH, bit::VO_MASK_IS_PATH) == 1;
67        let dyn_idx = bit::get(vo[0], bit::VO_OFFSET_T0_DYNAMIC, bit::VO_MASK_DYNAMIC) as u16;
68
69        if is_path && dyn_idx != 0 && !is_template {
70            let path_segments = self.manifest.path_map.get(dyn_idx)?.to_vec();
71            let path_key: String = path_segments.iter()
72                .filter_map(|&seg_idx| self.manifest.dynamic.get(seg_idx).map(|s| s.to_string()))
73                .collect::<Vec<_>>()
74                .join(".");
75            return self.get(&path_key).ok().flatten();
76        }
77
78        self.resolve_value_to_string(value_idx).map(Value::String)
79    }
80
81    /// Resolves a yaml value record to a String (for use in store/load config keys).
82    fn resolve_value_to_string(&mut self, value_idx: u16) -> Option<String> {
83        crate::fn_log!("State", "resolve_value_to_string", &value_idx.to_string());
84        let vo = self.manifest.values.get(value_idx)?;
85
86        let is_template = bit::get(vo[0], bit::VO_OFFSET_IS_TEMPLATE, bit::VO_MASK_IS_TEMPLATE) == 1;
87
88        const TOKEN_OFFSETS: [(u32, u32); 6] = [
89            (bit::VO_OFFSET_T0_IS_PATH, bit::VO_OFFSET_T0_DYNAMIC),
90            (bit::VO_OFFSET_T1_IS_PATH, bit::VO_OFFSET_T1_DYNAMIC),
91            (bit::VO_OFFSET_T2_IS_PATH, bit::VO_OFFSET_T2_DYNAMIC),
92            (bit::VO_OFFSET_T3_IS_PATH, bit::VO_OFFSET_T3_DYNAMIC),
93            (bit::VO_OFFSET_T4_IS_PATH, bit::VO_OFFSET_T4_DYNAMIC),
94            (bit::VO_OFFSET_T5_IS_PATH, bit::VO_OFFSET_T5_DYNAMIC),
95        ];
96
97        let mut result = String::new();
98
99        for (i, (off_is_path, off_dynamic)) in TOKEN_OFFSETS.iter().enumerate() {
100            let word = if i < 3 { 0 } else { 1 };
101            let is_path = bit::get(vo[word], *off_is_path, bit::VO_MASK_IS_PATH) == 1;
102            let dyn_idx = bit::get(vo[word], *off_dynamic, bit::VO_MASK_DYNAMIC) as u16;
103
104            if dyn_idx == 0 {
105                break;
106            }
107
108            if is_path {
109                let path_segments = self.manifest.path_map.get(dyn_idx)?.to_vec();
110                let path_key: String = path_segments.iter()
111                    .filter_map(|&seg_idx| self.manifest.dynamic.get(seg_idx).map(|s| s.to_string()))
112                    .collect::<Vec<_>>()
113                    .join(".");
114                crate::fn_log!("State", "resolve/get", &path_key);
115                let resolved = self.get(&path_key).ok().flatten();
116                crate::fn_log!("State", "resolve/got", if resolved.is_some() { "Some" } else { "None" });
117                let resolved = resolved?;
118                let s = match &resolved {
119                    Value::String(s) => s.clone(),
120                    Value::Number(n) => n.to_string(),
121                    Value::Bool(b) => b.to_string(),
122                    _ => return None,
123                };
124                result.push_str(&s);
125            } else {
126                let s = self.manifest.dynamic.get(dyn_idx)?.to_string();
127                result.push_str(&s);
128            }
129
130            if !is_template {
131                break;
132            }
133        }
134
135        Some(result)
136    }
137
138    /// Builds a store/load config HashMap from a meta record index.
139    fn build_config(&mut self, meta_idx: u16) -> Option<HashMap<String, Value>> {
140        crate::fn_log!("State", "build_config", &meta_idx.to_string());
141        let record = self.manifest.keys.get(meta_idx)?;
142        let child_idx = bit::get(record, bit::OFFSET_CHILD, bit::MASK_CHILD) as u16;
143        if child_idx == 0 { return None; }
144        let has_children = bit::get(record, bit::OFFSET_HAS_CHILDREN, bit::MASK_HAS_CHILDREN);
145        let children = if has_children == 1 {
146            self.manifest.children_map.get(child_idx)?.to_vec()
147        } else {
148            vec![child_idx]
149        };
150
151        let mut config = HashMap::new();
152
153        for &child_idx in &children {
154            let record = match self.manifest.keys.get(child_idx) {
155                Some(r) => r,
156                None => continue,
157            };
158            let prop   = bit::get(record, bit::OFFSET_PROP,   bit::MASK_PROP)   as u8;
159            let client = bit::get(record, bit::OFFSET_CLIENT, bit::MASK_CLIENT) as u8;
160            let is_leaf = bit::get(record, bit::OFFSET_IS_LEAF, bit::MASK_IS_LEAF) == 1;
161            let value_idx = if is_leaf {
162                bit::get(record, bit::OFFSET_CHILD, bit::MASK_CHILD) as u16
163            } else { 0 };
164
165            if client != 0 {
166                config.insert("client".to_string(), Value::Number(client.into()));
167                continue;
168            }
169
170            let prop_name = match prop as u64 {
171                bit::PROP_KEY        => "key",
172                bit::PROP_CONNECTION => "connection",
173                bit::PROP_MAP        => "map",
174                bit::PROP_TTL        => "ttl",
175                bit::PROP_TABLE      => "table",
176                bit::PROP_WHERE      => "where",
177                _ => continue,
178            };
179
180            if prop_name == "map" {
181                if let Some(map_val) = self.build_map_config(child_idx) {
182                    config.insert("map".to_string(), map_val);
183                }
184            } else if prop_name == "connection" {
185                if value_idx != 0 {
186                    if let Some(v) = self.resolve_value(value_idx) {
187                        config.insert("connection".to_string(), v);
188                    }
189                }
190            } else if value_idx != 0 {
191                if let Some(s) = self.resolve_value_to_string(value_idx) {
192                    config.insert(prop_name.to_string(), Value::String(s));
193                }
194            }
195        }
196
197        Some(config)
198    }
199
200    /// Builds a map config object from a map prop record's children.
201    fn build_map_config(&self, map_idx: u16) -> Option<Value> {
202        let record = self.manifest.keys.get(map_idx)?;
203        let child_idx = bit::get(record, bit::OFFSET_CHILD, bit::MASK_CHILD) as u16;
204        if child_idx == 0 { return Some(Value::Object(serde_json::Map::new())); }
205
206        let has_children = bit::get(record, bit::OFFSET_HAS_CHILDREN, bit::MASK_HAS_CHILDREN);
207        let children = if has_children == 1 {
208            self.manifest.children_map.get(child_idx)?.to_vec()
209        } else {
210            vec![child_idx]
211        };
212
213        let mut map = serde_json::Map::new();
214        for &c in &children {
215            let child = self.manifest.keys.get(c)?;
216            let dyn_idx   = bit::get(child, bit::OFFSET_DYNAMIC, bit::MASK_DYNAMIC) as u16;
217            let value_idx = bit::get(child, bit::OFFSET_CHILD,   bit::MASK_CHILD)   as u16;
218            let is_path   = bit::get(child, bit::OFFSET_IS_PATH, bit::MASK_IS_PATH) == 1;
219
220            let key_str = if is_path {
221                let segs = self.manifest.path_map.get(dyn_idx)?;
222                segs.iter()
223                    .filter_map(|&s| self.manifest.dynamic.get(s).map(|x| x.to_string()))
224                    .collect::<Vec<_>>()
225                    .join(".")
226            } else {
227                self.manifest.dynamic.get(dyn_idx)?.to_string()
228            };
229
230            let val_vo = self.manifest.values.get(value_idx)?;
231            let col_dyn = bit::get(val_vo[0], bit::VO_OFFSET_T0_DYNAMIC, bit::VO_MASK_DYNAMIC) as u16;
232            let col_str = self.manifest.dynamic.get(col_dyn)?.to_string();
233
234            map.insert(key_str, Value::String(col_str));
235        }
236
237        Some(Value::Object(map))
238    }
239
240    /// Finds a state_values record index by key_index.
241    fn find_state_value(&self, key_idx: u16) -> Option<u16> {
242        let mut i = 1u16;
243        loop {
244            let record = self.state_values.get_record(i)?;
245            if record == 0 { i += 1; continue; }
246            let k = ((record >> STATE_OFFSET_KEY) & (STATE_MASK_KEY as u32)) as u16;
247            if k == key_idx { return Some(i); }
248            i += 1;
249        }
250    }
251
252    /// Returns the value for `key`, checking state cache → _store → _load in order.
253    ///
254    /// # Examples
255    ///
256    /// ```
257    /// use state_engine::State;
258    /// use state_engine::load::Load;
259    /// use state_engine::InMemoryClient;
260    /// use serde_json::{json, Value};
261    ///
262    /// struct MockInMemory { data: std::collections::HashMap<String, Value> }
263    /// impl MockInMemory { fn new() -> Self { Self { data: Default::default() } } }
264    /// impl InMemoryClient for MockInMemory {
265    ///     fn get(&self, key: &str) -> Option<Value> { self.data.get(key).cloned() }
266    ///     fn set(&mut self, key: &str, value: Value) { self.data.insert(key.to_string(), value); }
267    ///     fn delete(&mut self, key: &str) -> bool { self.data.remove(key).is_some() }
268    /// }
269    ///
270    /// let mut client = MockInMemory::new();
271    /// let mut state = State::new("./examples/manifest", Load::new())
272    ///     .with_in_memory(&mut client);
273    ///
274    /// // set then get
275    /// state.set("connection.common", json!({"host": "localhost"}), None).unwrap();
276    /// assert!(state.get("connection.common").unwrap().is_some());
277    /// ```
278    pub fn get(&mut self, key: &str) -> Result<Option<Value>, StateError> {
279        crate::fn_log!("State", "get", key);
280        if self.called_keys.len() >= self.max_recursion {
281            return Err(StateError::RecursionLimitExceeded);
282        }
283        if self.called_keys.contains(&key.to_string()) {
284            return Err(StateError::RecursionLimitExceeded);
285        }
286
287        self.called_keys.insert(key.to_string());
288
289        let (file, path) = Self::split_key(key);
290        let file = file.to_string();
291        let path = path.to_string();
292
293        if let Err(e) = self.manifest.load(&file) {
294            self.called_keys.remove(key);
295            return Err(StateError::ManifestLoadFailed(e.to_string()));
296        }
297
298        let key_idx = match self.manifest.find(&file, &path) {
299            Some(idx) => idx,
300            None => {
301                self.called_keys.remove(key);
302                return Err(StateError::KeyNotFound(key.to_string()));
303            }
304        };
305
306        // check state_values cache
307        if let Some(sv_idx) = self.find_state_value(key_idx) {
308            let val = self.state_values.get_value(sv_idx).cloned();
309            self.called_keys.remove(key);
310            return Ok(val);
311        }
312
313        let meta = self.manifest.get_meta(&file, &path);
314
315        // check if _load client is State (load-only, no store read)
316        let has_state_client = meta.load.and_then(|load_idx| {
317            self.manifest.keys.get(load_idx)
318                .map(|r| bit::get(r, bit::OFFSET_CLIENT, bit::MASK_CLIENT) == bit::CLIENT_STATE)
319        }).unwrap_or(false);
320
321        if !has_state_client {
322            if let Some(store_idx) = meta.store {
323                if let Some(config) = self.build_config(store_idx) {
324                    if let Some(value) = self.store.get(&config) {
325                        self.state_values.push(key_idx, value.clone());
326                        self.called_keys.remove(key);
327                        return Ok(Some(value));
328                    }
329                }
330            }
331        }
332
333        // try _load
334        let result = if let Some(load_idx) = meta.load {
335            if let Some(mut config) = self.build_config(load_idx) {
336                if !config.contains_key("client") {
337                    self.called_keys.remove(key);
338                    return Ok(None);
339                }
340
341                // unqualify map keys for Load
342                if let Some(Value::Object(map_obj)) = config.get("map").cloned() {
343                    let mut unqualified = serde_json::Map::new();
344                    for (qk, v) in map_obj {
345                        let field = qk.rfind('.').map_or(qk.as_str(), |p| &qk[p+1..]);
346                        unqualified.insert(field.to_string(), v);
347                    }
348                    config.insert("map".to_string(), Value::Object(unqualified));
349                }
350
351                match self.load.handle(&config) {
352                    Ok(loaded) => {
353                        if let Some(store_idx) = meta.store {
354                            if let Some(store_config) = self.build_config(store_idx) {
355                                if self.store.set(&store_config, loaded.clone(), None) {
356                                    self.state_values.push(key_idx, loaded.clone());
357                                }
358                            }
359                        } else {
360                            self.state_values.push(key_idx, loaded.clone());
361                        }
362                        Ok(Some(loaded))
363                    }
364                    Err(e) => Err(StateError::LoadFailed(e)),
365                }
366            } else { Ok(None) }
367        } else { Ok(None) };
368
369        self.called_keys.remove(key);
370        result
371    }
372
373    /// Writes `value` to the _store backend for `key`.
374    ///
375    /// # Examples
376    ///
377    /// ```
378    /// # use state_engine::State;
379    /// # use state_engine::load::Load;
380    /// # use state_engine::InMemoryClient;
381    /// # use serde_json::{json, Value};
382    /// # struct MockInMemory { data: std::collections::HashMap<String, Value> }
383    /// # impl MockInMemory { fn new() -> Self { Self { data: Default::default() } } }
384    /// # impl InMemoryClient for MockInMemory {
385    /// #     fn get(&self, key: &str) -> Option<Value> { self.data.get(key).cloned() }
386    /// #     fn set(&mut self, key: &str, value: Value) { self.data.insert(key.to_string(), value); }
387    /// #     fn delete(&mut self, key: &str) -> bool { self.data.remove(key).is_some() }
388    /// # }
389    /// let mut client = MockInMemory::new();
390    /// let mut state = State::new("./examples/manifest", Load::new())
391    ///     .with_in_memory(&mut client);
392    ///
393    /// assert!(state.set("connection.common", json!({"host": "localhost"}), None).unwrap());
394    /// ```
395    pub fn set(&mut self, key: &str, value: Value, ttl: Option<u64>) -> Result<bool, StateError> {
396        crate::fn_log!("State", "set", key);
397        let (file, path) = Self::split_key(key);
398        let file = file.to_string();
399        let path = path.to_string();
400
401        if let Err(e) = self.manifest.load(&file) {
402            return Err(StateError::ManifestLoadFailed(e.to_string()));
403        }
404
405        let key_idx = match self.manifest.find(&file, &path) {
406            Some(idx) => idx,
407            None => return Err(StateError::KeyNotFound(key.to_string())),
408        };
409
410        let meta = self.manifest.get_meta(&file, &path);
411
412        if let Some(store_idx) = meta.store {
413            if let Some(config) = self.build_config(store_idx) {
414                let ok = self.store.set(&config, value.clone(), ttl);
415                if ok {
416                    if let Some(sv_idx) = self.find_state_value(key_idx) {
417                        self.state_values.update(sv_idx, value);
418                    } else {
419                        self.state_values.push(key_idx, value);
420                    }
421                }
422                return Ok(ok);
423            }
424        }
425        Ok(false)
426    }
427
428    /// Removes the value for `key` from the _store backend.
429    ///
430    /// # Examples
431    ///
432    /// ```
433    /// # use state_engine::State;
434    /// # use state_engine::load::Load;
435    /// # use state_engine::InMemoryClient;
436    /// # use serde_json::{json, Value};
437    /// # struct MockInMemory { data: std::collections::HashMap<String, Value> }
438    /// # impl MockInMemory { fn new() -> Self { Self { data: Default::default() } } }
439    /// # impl InMemoryClient for MockInMemory {
440    /// #     fn get(&self, key: &str) -> Option<Value> { self.data.get(key).cloned() }
441    /// #     fn set(&mut self, key: &str, value: Value) { self.data.insert(key.to_string(), value); }
442    /// #     fn delete(&mut self, key: &str) -> bool { self.data.remove(key).is_some() }
443    /// # }
444    /// let mut client = MockInMemory::new();
445    /// let mut state = State::new("./examples/manifest", Load::new())
446    ///     .with_in_memory(&mut client);
447    ///
448    /// state.set("connection.common", json!({"host": "localhost"}), None).unwrap();
449    /// assert!(state.delete("connection.common").unwrap());
450    /// // delete後はstoreにデータがなく、_loadも試みるが今回はEnvClientなしのため値なし
451    /// assert!(state.get("connection.common").is_err() || state.get("connection.common").unwrap().is_none());
452    /// ```
453    pub fn delete(&mut self, key: &str) -> Result<bool, StateError> {
454        crate::fn_log!("State", "delete", key);
455        let (file, path) = Self::split_key(key);
456        let file = file.to_string();
457        let path = path.to_string();
458
459        if let Err(e) = self.manifest.load(&file) {
460            return Err(StateError::ManifestLoadFailed(e.to_string()));
461        }
462
463        let key_idx = match self.manifest.find(&file, &path) {
464            Some(idx) => idx,
465            None => return Err(StateError::KeyNotFound(key.to_string())),
466        };
467
468        let meta = self.manifest.get_meta(&file, &path);
469
470        if let Some(store_idx) = meta.store {
471            if let Some(config) = self.build_config(store_idx) {
472                let ok = self.store.delete(&config);
473                if ok {
474                    if let Some(sv_idx) = self.find_state_value(key_idx) {
475                        self.state_values.remove(sv_idx);
476                    }
477                }
478                return Ok(ok);
479            }
480        }
481        Ok(false)
482    }
483
484    /// Returns `true` if a value exists for `key` in state cache or _store.
485    /// Does not trigger _load.
486    ///
487    /// # Examples
488    ///
489    /// ```
490    /// # use state_engine::State;
491    /// # use state_engine::load::Load;
492    /// # use state_engine::InMemoryClient;
493    /// # use serde_json::{json, Value};
494    /// # struct MockInMemory { data: std::collections::HashMap<String, Value> }
495    /// # impl MockInMemory { fn new() -> Self { Self { data: Default::default() } } }
496    /// # impl InMemoryClient for MockInMemory {
497    /// #     fn get(&self, key: &str) -> Option<Value> { self.data.get(key).cloned() }
498    /// #     fn set(&mut self, key: &str, value: Value) { self.data.insert(key.to_string(), value); }
499    /// #     fn delete(&mut self, key: &str) -> bool { self.data.remove(key).is_some() }
500    /// # }
501    /// let mut client = MockInMemory::new();
502    /// let mut state = State::new("./examples/manifest", Load::new())
503    ///     .with_in_memory(&mut client);
504    ///
505    /// assert!(!state.exists("connection.common").unwrap());
506    /// state.set("connection.common", json!({"host": "localhost"}), None).unwrap();
507    /// assert!(state.exists("connection.common").unwrap());
508    /// ```
509    pub fn exists(&mut self, key: &str) -> Result<bool, StateError> {
510        crate::fn_log!("State", "exists", key);
511        let (file, path) = Self::split_key(key);
512        let file = file.to_string();
513        let path = path.to_string();
514
515        if let Err(e) = self.manifest.load(&file) {
516            return Err(StateError::ManifestLoadFailed(e.to_string()));
517        }
518
519        let key_idx = match self.manifest.find(&file, &path) {
520            Some(idx) => idx,
521            None => return Err(StateError::KeyNotFound(key.to_string())),
522        };
523
524        if let Some(sv_idx) = self.find_state_value(key_idx) {
525            return Ok(!self.state_values.get_value(sv_idx)
526                .map_or(true, |v| v.is_null()));
527        }
528
529        let meta = self.manifest.get_meta(&file, &path);
530        if let Some(store_idx) = meta.store {
531            if let Some(config) = self.build_config(store_idx) {
532                return Ok(self.store.get(&config).is_some());
533            }
534        }
535        Ok(false)
536    }
537}