Skip to main content

ravenrs_app/model/
layers.rs

1//! Layers of differences over the generated model.
2
3use serde_json::{Map, Value as Json};
4
5use super::node::ModelNode;
6use crate::ModelError;
7
8/// The key that marks a node in a difference as created by it. Any other node a
9/// difference names has to exist already.
10const NEW_NODE: &str = "$new";
11
12/// A layer of the model, from the lowest to the highest. The highest to set a value wins.
13#[derive(Clone, Debug, PartialEq, Eq)]
14#[cfg_attr(feature = "live", ravenrs_live::expose(part = "variants"))]
15pub enum ModelLayer {
16    /// What a module adds, by its name; modules stack in the order they first appear.
17    Module(String),
18    /// What the administrator of an installation set.
19    Administrator,
20    /// What the person using the application set.
21    User,
22}
23
24#[cfg_attr(feature = "live", ravenrs_live::expose)]
25impl ModelLayer {
26    /// Where it goes among layers: modules first, the person last.
27    fn rank(&self) -> u8 {
28        match self {
29            Self::Module(_) => 0,
30            Self::Administrator => 1,
31            Self::User => 2,
32        }
33    }
34}
35
36/// A difference over the layers below it, as JSON: an object is a node, by id, and anything else
37/// is a setting.
38///
39/// ```json
40/// { "Views": { "Account_ListView": { "Columns": { "opening": { "Index": -1 } } } } }
41/// ```
42///
43/// A node that this difference itself creates has `"$new": true`.
44#[derive(Clone, Debug, Default, PartialEq)]
45#[cfg_attr(feature = "live", ravenrs_live::expose(part = "fields"))]
46pub struct Diff(Map<String, Json>);
47
48#[cfg_attr(feature = "live", ravenrs_live::expose)]
49impl Diff {
50    /// A difference that changes nothing.
51    pub fn new() -> Self {
52        Self::default()
53    }
54
55    /// Reads it from JSON text.
56    pub fn parse(text: &str) -> Result<Self, ModelError> {
57        match serde_json::from_str(text) {
58            Ok(Json::Object(map)) => Ok(Self(map)),
59            Ok(_) => Err(ModelError("a model difference has to be a JSON object".into())),
60            Err(e) => Err(ModelError(format!("a model difference is not valid JSON: {e}"))),
61        }
62    }
63
64    /// Writes it as JSON text, ready for people to read.
65    pub fn to_json(&self) -> String {
66        serde_json::to_string_pretty(&self.0).unwrap_or_else(|_| "{}".to_owned())
67    }
68
69    /// Puts this over it, node by node.
70    pub fn merge(&mut self, over: &Diff) {
71        fn merged(into: &mut Map<String, Json>, over: &Map<String, Json>) {
72            for (name, value) in over {
73                match (into.get_mut(name), value) {
74                    (Some(Json::Object(mine)), Json::Object(theirs)) => merged(mine, theirs),
75                    _ => {
76                        into.insert(name.clone(), value.clone());
77                    }
78                }
79            }
80        }
81        merged(&mut self.0, &over.0);
82    }
83
84    /// Whether it changes anything.
85    pub fn is_empty(&self) -> bool {
86        self.0.is_empty()
87    }
88
89    fn set(&mut self, path: &[&str], name: &str, value: Json) {
90        let mut map = &mut self.0;
91        for id in path {
92            let entry = map.entry((*id).to_owned()).or_insert_with(|| Json::Object(Map::new()));
93            if !entry.is_object() {
94                *entry = Json::Object(Map::new());
95            }
96            let Json::Object(next) = entry else { return };
97            map = next;
98        }
99        map.insert(name.to_owned(), value);
100    }
101
102    /// Takes the setting off, and with it whatever nodes this leaves empty.
103    fn clear(&mut self, path: &[&str], name: &str) {
104        fn walk(map: &mut Map<String, Json>, path: &[&str], name: &str) {
105            match path.split_first() {
106                None => {
107                    map.remove(name);
108                }
109                Some((id, rest)) => {
110                    if let Some(Json::Object(next)) = map.get_mut(*id) {
111                        walk(next, rest, name);
112                        if next.is_empty() {
113                            map.remove(*id);
114                        }
115                    }
116                }
117            }
118        }
119        walk(&mut self.0, path, name);
120    }
121}
122
123/// A part of a difference that has no node to land on, as after the entity it
124/// refers to is gone. It is kept, and comes back into use if the node does.
125#[derive(Clone, Debug, PartialEq)]
126#[cfg_attr(feature = "live", ravenrs_live::expose(part = "fields"))]
127pub struct UnusableNode {
128    /// The layer that holds it.
129    pub layer: ModelLayer,
130    /// The ids that lead to it.
131    pub path: Vec<String>,
132    /// What it wanted to change.
133    pub difference: Json,
134}
135
136/// The model as it stands, with all its layers applied.
137#[derive(Clone, Debug, PartialEq)]
138#[cfg_attr(feature = "live", ravenrs_live::expose(part = "fields"))]
139pub struct MergedModel {
140    /// The tree to read settings from.
141    pub root: ModelNode,
142    /// What had nowhere to go.
143    pub unusable: Vec<UnusableNode>,
144}
145
146fn apply(
147    node: &mut ModelNode,
148    difference: &Map<String, Json>,
149    layer: &ModelLayer,
150    path: &mut Vec<String>,
151    unusable: &mut Vec<UnusableNode>,
152) {
153    for (key, value) in difference {
154        match value {
155            Json::Object(inner) => {
156                let creates = inner.get(NEW_NODE).and_then(Json::as_bool).unwrap_or(false);
157                if creates && node.get(key).is_none() {
158                    node.children.push(ModelNode::new(key));
159                }
160                path.push(key.clone());
161                match node.get_mut(key) {
162                    Some(child) => apply(child, inner, layer, path, unusable),
163                    None => unusable.push(UnusableNode {
164                        layer: layer.clone(),
165                        path: path.clone(),
166                        difference: value.clone(),
167                    }),
168                }
169                path.pop();
170            }
171            _ if key == NEW_NODE => {}
172            _ => {
173                node.properties.insert(key.clone(), value.clone());
174            }
175        }
176    }
177}
178
179/// The generated model and the layers over it. Taking a setting off a layer brings back what
180/// the layer below says.
181#[derive(Clone, Debug, PartialEq)]
182#[cfg_attr(feature = "live", ravenrs_live::expose(part = "fields"))]
183pub struct ModelLayers {
184    zero: ModelNode,
185    layers: Vec<(ModelLayer, Diff)>,
186}
187
188#[cfg_attr(feature = "live", ravenrs_live::expose)]
189impl ModelLayers {
190    /// Layers over this generated model, all empty.
191    pub fn new(zero: ModelNode) -> Self {
192        Self { zero, layers: Vec::new() }
193    }
194
195    fn layer_mut(&mut self, layer: &ModelLayer) -> &mut Diff {
196        let at = match self.layers.iter().position(|(l, _)| l == layer) {
197            Some(at) => at,
198            None => {
199                let at = self.layers.partition_point(|(l, _)| l.rank() <= layer.rank());
200                self.layers.insert(at, (layer.clone(), Diff::new()));
201                at
202            }
203        };
204        &mut self.layers[at].1
205    }
206
207    /// A setting at this path of ids, on this layer.
208    pub fn set(&mut self, layer: &ModelLayer, path: &[&str], name: &str, value: Json) {
209        self.layer_mut(layer).set(path, name, value);
210    }
211
212    /// Takes the setting off this layer, so the one below answers again.
213    pub fn clear(&mut self, layer: &ModelLayer, path: &[&str], name: &str) {
214        self.layer_mut(layer).clear(path, name);
215    }
216
217    /// Puts a whole difference on this layer, replacing what it held.
218    pub fn put(&mut self, layer: &ModelLayer, difference: Diff) {
219        *self.layer_mut(layer) = difference;
220    }
221
222    /// What this layer holds.
223    pub fn difference(&self, layer: &ModelLayer) -> Option<&Diff> {
224        self.layers.iter().find(|(l, _)| l == layer).map(|(_, d)| d)
225    }
226
227    /// The model, from the bottom up. What finds no node goes to the unusable list, and stays
228    /// in its difference.
229    pub fn merged(&self) -> MergedModel {
230        let mut root = self.zero.clone();
231        let mut unusable = Vec::new();
232        for (layer, difference) in &self.layers {
233            apply(&mut root, &difference.0, layer, &mut Vec::new(), &mut unusable);
234        }
235        MergedModel { root, unusable }
236    }
237
238    /// Reads this layer from a store, if the store has it.
239    pub fn load(&mut self, layer: &ModelLayer, store: &dyn super::ModelStore) -> Result<(), ModelError> {
240        if let Some(difference) = store.load()? {
241            self.put(layer, difference);
242        }
243        Ok(())
244    }
245
246    /// Writes this layer to a store.
247    pub fn save(&self, layer: &ModelLayer, store: &dyn super::ModelStore) -> Result<(), ModelError> {
248        store.save(self.difference(layer).unwrap_or(&Diff::new()))
249    }
250}
251
252#[cfg(test)]
253mod tests {
254    use super::*;
255
256    fn zero() -> ModelNode {
257        ModelNode::new("Application")
258            .child(ModelNode::new("Views").child(ModelNode::new("A_ListView").with("Caption", "zero")))
259    }
260
261    const PATH: [&str; 2] = ["Views", "A_ListView"];
262
263    fn caption(layers: &ModelLayers) -> String {
264        let merged = layers.merged();
265        merged.root.at(&PATH).and_then(|n| n.text("Caption")).unwrap_or("").to_owned()
266    }
267
268    #[test]
269    fn the_highest_layer_wins_whatever_order_they_came_in() {
270        let mut layers = ModelLayers::new(zero());
271        layers.set(&ModelLayer::User, &PATH, "Caption", "user".into());
272        layers.set(&ModelLayer::Administrator, &PATH, "Caption", "admin".into());
273        layers.set(&ModelLayer::Module("m".into()), &PATH, "Caption", "module".into());
274        assert_eq!(caption(&layers), "user");
275    }
276
277    #[test]
278    fn clearing_brings_back_the_layer_below() {
279        let mut layers = ModelLayers::new(zero());
280        layers.set(&ModelLayer::Administrator, &PATH, "Caption", "admin".into());
281        layers.set(&ModelLayer::User, &PATH, "Caption", "user".into());
282        layers.clear(&ModelLayer::User, &PATH, "Caption");
283        assert_eq!(caption(&layers), "admin");
284        layers.clear(&ModelLayer::Administrator, &PATH, "Caption");
285        assert_eq!(caption(&layers), "zero");
286        assert_eq!(layers.difference(&ModelLayer::User), Some(&Diff::new()), "nothing is left behind");
287    }
288
289    #[test]
290    fn modules_stack_in_the_order_they_first_came() {
291        let mut layers = ModelLayers::new(zero());
292        layers.set(&ModelLayer::Module("z".into()), &PATH, "Caption", "z".into());
293        layers.set(&ModelLayer::Module("a".into()), &PATH, "Caption", "a".into());
294        assert_eq!(caption(&layers), "a");
295    }
296
297    #[test]
298    fn a_setting_with_no_node_is_kept_and_comes_back() {
299        let mut layers = ModelLayers::new(zero());
300        layers.set(&ModelLayer::User, &["Views", "Gone_ListView"], "Caption", "kept".into());
301        let merged = layers.merged();
302        assert_eq!(merged.unusable.len(), 1);
303        assert_eq!(merged.unusable[0].path, ["Views", "Gone_ListView"]);
304        assert_eq!(merged.unusable[0].layer, ModelLayer::User);
305        // Saved with the rest, so a new version that has the node finds it.
306        let saved = layers.difference(&ModelLayer::User).expect("a layer").to_json();
307        let mut later = ModelLayers::new(zero());
308        later.put(&ModelLayer::User, Diff::parse(&saved).expect("valid"));
309        assert_eq!(later.merged().unusable.len(), 1, "still nowhere to go");
310        let with_node = {
311            let mut root = zero();
312            root.get_mut("Views").expect("views").children.push(ModelNode::new("Gone_ListView"));
313            let mut l = ModelLayers::new(root);
314            l.put(&ModelLayer::User, Diff::parse(&saved).expect("valid"));
315            l.merged()
316        };
317        assert!(with_node.unusable.is_empty());
318        assert_eq!(
319            with_node.root.at(&["Views", "Gone_ListView"]).and_then(|n| n.text("Caption")),
320            Some("kept")
321        );
322    }
323
324    #[test]
325    fn a_difference_can_create_a_node() {
326        let difference = Diff::parse(r#"{ "Views": { "B_ListView": { "$new": true, "Caption": "b" } } }"#);
327        let mut layers = ModelLayers::new(zero());
328        layers.put(&ModelLayer::Administrator, difference.expect("valid"));
329        let merged = layers.merged();
330        assert!(merged.unusable.is_empty());
331        let node = merged.root.at(&["Views", "B_ListView"]).expect("the new node");
332        assert_eq!(node.text("Caption"), Some("b"));
333        assert!(!node.properties.contains_key(NEW_NODE), "the mark is not a setting");
334    }
335
336    #[test]
337    fn bad_json_says_so() {
338        assert!(Diff::parse("[1]").is_err_and(|e| e.0.contains("object")));
339        assert!(Diff::parse("{").is_err_and(|e| e.0.contains("not valid")));
340    }
341}