Skip to main content

panopticon_core/data/
store.rs

1use crate::imports::*;
2
3/// A keyed collection of entries shared across the pipeline lifecycle.
4///
5/// The store is the single source of truth for pipeline state. It holds
6/// draft-time variables, resolved step parameters, per-step global
7/// outputs, and the values produced by return blocks. The generic
8/// parameter `T` is the entry type — most stores hold [`StoreEntry`]
9/// values, but `Store<Parameters>` is used in draft/ready phases to hold
10/// the unresolved parameter bindings for each step.
11///
12/// Keys are flat dotted strings; nesting is modelled by the
13/// [`StoreEntry::Array`] and [`StoreEntry::Map`] variants, not by the
14/// store itself. Insertion is append-only through [`insert`](Self::insert)
15/// (errors on duplicate) or replacement-allowed through
16/// [`insert_or_replace`](Self::insert_or_replace).
17#[derive(Debug, Clone)]
18pub struct Store<T = StoreEntry> {
19    entries: HashMap<String, T>,
20}
21
22impl Default for Store<StoreEntry> {
23    fn default() -> Self {
24        Store::new()
25    }
26}
27
28impl<T> Store<T> {
29    /// Constructs an empty store.
30    pub fn new() -> Self {
31        Store::<T> {
32            entries: HashMap::new(),
33        }
34    }
35
36    /// Looks up an entry by name. Returns [`StoreError::EntryNotFound`]
37    /// if the name is not in the store.
38    pub fn get<N: AsRef<str>>(&self, name: N) -> Result<&T, StoreError> {
39        let name = name.as_ref();
40        self.entries
41            .get(name)
42            .ok_or_else(|| StoreError::EntryNotFound(name.into()))
43    }
44
45    /// Inserts an entry under `name`. Returns
46    /// [`StoreError::EntryAlreadyExists`] if the name is already in use.
47    pub fn insert<N: Into<String>>(&mut self, name: N, entry: T) -> Result<(), StoreError> {
48        let name = name.into();
49        if self.entries.contains_key(&name) {
50            return Err(StoreError::EntryAlreadyExists(name));
51        }
52        self.entries.insert(name, entry);
53        Ok(())
54    }
55
56    /// Inserts an entry under `name`, overwriting any existing entry
57    /// with the same name. Used by the runtime when an operation rewrites
58    /// its own outputs.
59    pub fn insert_or_replace<N: Into<String>>(&mut self, name: N, entry: T) {
60        self.entries.insert(name.into(), entry);
61    }
62
63    /// Merges `other` into this store, optionally prefixing each
64    /// incoming key with `"{prefix}."`. Fails on the first key collision
65    /// with [`StoreError::EntryAlreadyExists`].
66    pub fn merge(&mut self, prefix_opt: Option<&str>, other: Store<T>) -> Result<(), StoreError> {
67        let key_prefix = match prefix_opt {
68            Some(prefix) => format!("{}.", prefix),
69            None => String::new(),
70        };
71        for (key, value) in other.entries {
72            let key = format!("{}{}", key_prefix, key);
73            if self.entries.contains_key(&key) {
74                return Err(StoreError::EntryAlreadyExists(key));
75            }
76            self.entries.insert(key, value);
77        }
78        Ok(())
79    }
80
81    /// Iterates over the names of every entry in the store.
82    pub fn keys(&self) -> impl Iterator<Item = &String> {
83        self.entries.keys()
84    }
85
86    /// Iterates over `(name, entry)` pairs.
87    pub fn iter(&self) -> impl Iterator<Item = (&String, &T)> {
88        self.entries.iter()
89    }
90}
91
92impl Store<StoreEntry> {
93    /// Defines a new variable entry — a [`StoreEntry::Var`] wrapping
94    /// the given [`Value`] and its derived [`Type`]. Fails with
95    /// [`StoreError::EntryAlreadyExists`] if the name is already in use.
96    pub fn define_var<N: Into<String>, V: Into<Value>>(
97        &mut self,
98        name: N,
99        value: V,
100    ) -> Result<(), StoreError> {
101        let name = name.into();
102        if self.entries.contains_key(&name) {
103            return Err(StoreError::EntryAlreadyExists(name));
104        }
105        let value = value.into();
106        let ty = value.get_type();
107        self.entries.insert(name, StoreEntry::Var { value, ty });
108        Ok(())
109    }
110
111    /// Defines a new empty array entry and returns an [`ArrayHandle`]
112    /// for populating it.
113    pub fn define_array<N: Into<String>>(
114        &mut self,
115        name: N,
116    ) -> Result<ArrayHandle<'_, StoreEntry>, StoreError> {
117        let name = name.into();
118        if self.entries.contains_key(&name) {
119            return Err(StoreError::EntryAlreadyExists(name));
120        }
121        self.entries
122            .insert(name.clone(), StoreEntry::Array(Vec::new()));
123        let data = match self.entries.get_mut(&name) {
124            Some(StoreEntry::Array(arr)) => arr,
125            _ => unreachable!(),
126        };
127        Ok(ArrayHandle::new(name, data))
128    }
129
130    /// Defines a new empty map entry and returns a [`MapHandle`] for
131    /// populating it.
132    pub fn define_map<N: Into<String>>(
133        &mut self,
134        name: N,
135    ) -> Result<MapHandle<'_, StoreEntry>, StoreError> {
136        let name = name.into();
137        if self.entries.contains_key(&name) {
138            return Err(StoreError::EntryAlreadyExists(name));
139        }
140        self.entries
141            .insert(name.clone(), StoreEntry::Map(HashMap::new()));
142        let data = match self.entries.get_mut(&name) {
143            Some(StoreEntry::Map(map)) => map,
144            _ => unreachable!(),
145        };
146        Ok(MapHandle::new(name, data))
147    }
148
149    /// Defines a new array entry and passes a handle to a closure for
150    /// population. Convenience wrapper over [`define_array`](Self::define_array)
151    /// when the nested structure would otherwise force awkward
152    /// temporaries.
153    pub fn with_array<N: Into<String>, F>(&mut self, name: N, body: F) -> Result<(), StoreError>
154    where
155        F: FnOnce(&mut ArrayHandle<StoreEntry>) -> Result<(), StoreError>,
156    {
157        let name = name.into();
158        let mut arr_handle = self.define_array(name)?;
159        body(&mut arr_handle)
160    }
161    /// Defines a new map entry and passes a handle to a closure for
162    /// population.
163    pub fn with_map<N: Into<String>, F>(&mut self, name: N, body: F) -> Result<(), StoreError>
164    where
165        F: FnOnce(&mut MapHandle<StoreEntry>) -> Result<(), StoreError>,
166    {
167        let name = name.into();
168        let mut map_handle = self.define_map(name)?;
169        body(&mut map_handle)
170    }
171}
172
173impl<T> IntoIterator for Store<T> {
174    type Item = (String, T);
175    type IntoIter = std::collections::hash_map::IntoIter<String, T>;
176
177    fn into_iter(self) -> Self::IntoIter {
178        self.entries.into_iter()
179    }
180}