Skip to main content

disposition_input_ir_rt/
input_diagram_merger.rs

1use disposition_input_model::{
2    entity::{EntityDescs, EntityTypes},
3    process::Processes,
4    tag::{TagNames, TagThings},
5    theme::{
6        StyleAliases, ThemeDefault, ThemeStyles, ThemeTagThingsFocus, ThemeThingDependenciesStyles,
7        ThemeTypesStyles,
8    },
9    thing::{
10        ThingCopyText, ThingDependencies, ThingHierarchy, ThingInteractions, ThingLayouts,
11        ThingNames,
12    },
13    InputDiagram,
14};
15use disposition_model_common::{entity::EntityTooltips, theme::Css};
16
17/// Merges an input diagram over another.
18#[derive(Clone, Copy, Debug)]
19pub struct InputDiagramMerger;
20
21impl InputDiagramMerger {
22    /// Merges an overlay `InputDiagram` over a base `InputDiagram`.
23    ///
24    /// The merge strategy is:
25    /// - For map-like fields: overlay values override base values for the same
26    ///   key, base values without overlay counterparts are preserved.
27    /// - For nested structures (like `ThemeDefault`): each sub-field is merged
28    ///   recursively.
29    /// - For `css`: the overlay value completely replaces the base value if
30    ///   non-empty.
31    ///
32    /// # Parameters
33    ///
34    /// * `base_diagram` - The base diagram providing default values (typically
35    ///   from `InputDiagram::base()`).
36    /// * `overlay_diagram` - The overlay diagram with user-specified values
37    ///   that take precedence.
38    ///
39    /// # Returns
40    ///
41    /// A new `InputDiagram` containing the merged result.
42    pub fn merge<'f, 'id>(
43        base_diagram: InputDiagram<'static>,
44        overlay_diagram: &'f InputDiagram<'id>,
45    ) -> InputDiagram<'id>
46    where
47        'id: 'f,
48    {
49        let things = Self::merge_thing_names(base_diagram.things, &overlay_diagram.things);
50        let thing_copy_text = Self::merge_thing_copy_text(
51            base_diagram.thing_copy_text,
52            &overlay_diagram.thing_copy_text,
53        );
54        let thing_hierarchy = Self::merge_thing_hierarchy(
55            base_diagram.thing_hierarchy,
56            &overlay_diagram.thing_hierarchy,
57        );
58        let thing_layouts =
59            Self::merge_thing_layouts(base_diagram.thing_layouts, &overlay_diagram.thing_layouts);
60        let thing_dependencies = Self::merge_thing_dependencies(
61            base_diagram.thing_dependencies,
62            &overlay_diagram.thing_dependencies,
63        );
64        let thing_interactions = Self::merge_thing_interactions(
65            base_diagram.thing_interactions,
66            &overlay_diagram.thing_interactions,
67        );
68        let processes = Self::merge_processes(base_diagram.processes, &overlay_diagram.processes);
69        let tags = Self::merge_tag_names(base_diagram.tags, &overlay_diagram.tags);
70        let tag_things =
71            Self::merge_tag_things(base_diagram.tag_things, &overlay_diagram.tag_things);
72        let entity_descs =
73            Self::merge_entity_descs(base_diagram.entity_descs, &overlay_diagram.entity_descs);
74        let entity_tooltips = Self::merge_entity_tooltips(
75            base_diagram.entity_tooltips,
76            &overlay_diagram.entity_tooltips,
77        );
78        let entity_types =
79            Self::merge_entity_types(base_diagram.entity_types, &overlay_diagram.entity_types);
80        let theme_default =
81            Self::merge_theme_default(base_diagram.theme_default, &overlay_diagram.theme_default);
82        let theme_types_styles = Self::merge_theme_types_styles(
83            base_diagram.theme_types_styles,
84            &overlay_diagram.theme_types_styles,
85        );
86        let theme_thing_dependencies_styles = Self::merge_theme_thing_dependencies_styles(
87            base_diagram.theme_thing_dependencies_styles,
88            &overlay_diagram.theme_thing_dependencies_styles,
89        );
90        let theme_tag_things_focus = Self::merge_theme_tag_things_focus(
91            base_diagram.theme_tag_things_focus,
92            &overlay_diagram.theme_tag_things_focus,
93        );
94        let render_options = overlay_diagram.render_options;
95        let css = Self::merge_css(base_diagram.css, &overlay_diagram.css);
96
97        InputDiagram {
98            things,
99            thing_copy_text,
100            thing_hierarchy,
101            thing_layouts,
102            thing_dependencies,
103            thing_interactions,
104            processes,
105            tags,
106            tag_things,
107            entity_descs,
108            entity_tooltips,
109            entity_types,
110            theme_default,
111            theme_types_styles,
112            theme_thing_dependencies_styles,
113            theme_tag_things_focus,
114            render_options,
115            css,
116        }
117    }
118
119    fn merge_thing_names<'id>(
120        base: ThingNames<'static>,
121        overlay: &ThingNames<'id>,
122    ) -> ThingNames<'id> {
123        let mut result = base;
124        overlay.iter().for_each(|(key, value)| {
125            result.insert(key.clone(), value.clone());
126        });
127        result
128    }
129
130    fn merge_thing_copy_text<'id>(
131        base: ThingCopyText<'static>,
132        overlay: &ThingCopyText<'id>,
133    ) -> ThingCopyText<'id> {
134        let mut result = base;
135        overlay.iter().for_each(|(key, value)| {
136            result.insert(key.clone(), value.clone());
137        });
138        result
139    }
140
141    fn merge_thing_hierarchy<'id>(
142        base: ThingHierarchy<'static>,
143        overlay: &ThingHierarchy<'id>,
144    ) -> ThingHierarchy<'id> {
145        // For thing_hierarchy, overlay completely replaces base for matching top-level
146        // keys.
147        //
148        // Base keys not in overlay are preserved
149        let mut result = base;
150        overlay.iter().for_each(|(key, value)| {
151            result.insert(key.clone(), value.clone());
152        });
153        result
154    }
155
156    fn merge_thing_layouts<'id>(
157        base: ThingLayouts<'static>,
158        overlay: &ThingLayouts<'id>,
159    ) -> ThingLayouts<'id> {
160        let mut result = base;
161        overlay.iter().for_each(|(key, value)| {
162            result.insert(key.clone(), *value);
163        });
164        result
165    }
166
167    fn merge_thing_dependencies<'id>(
168        base: ThingDependencies<'static>,
169        overlay: &ThingDependencies<'id>,
170    ) -> ThingDependencies<'id> {
171        let mut result = base;
172        overlay.iter().for_each(|(key, value)| {
173            result.insert(key.clone(), value.clone());
174        });
175        result
176    }
177
178    fn merge_thing_interactions<'id>(
179        base: ThingInteractions<'static>,
180        overlay: &ThingInteractions<'id>,
181    ) -> ThingInteractions<'id> {
182        let mut result = base;
183        overlay.iter().for_each(|(key, value)| {
184            result.insert(key.clone(), value.clone());
185        });
186        result
187    }
188
189    fn merge_processes<'id>(base: Processes<'static>, overlay: &Processes<'id>) -> Processes<'id> {
190        let mut result = base;
191        overlay.iter().for_each(|(key, value)| {
192            result.insert(key.clone(), value.clone());
193        });
194        result
195    }
196
197    fn merge_tag_names<'id>(base: TagNames<'static>, overlay: &TagNames<'id>) -> TagNames<'id> {
198        let mut result = base;
199        overlay.iter().for_each(|(key, value)| {
200            result.insert(key.clone(), value.clone());
201        });
202        result
203    }
204
205    fn merge_tag_things<'id>(base: TagThings<'static>, overlay: &TagThings<'id>) -> TagThings<'id> {
206        let mut result = base;
207        overlay.iter().for_each(|(key, value)| {
208            result.insert(key.clone(), value.clone());
209        });
210        result
211    }
212
213    fn merge_entity_descs<'id>(
214        base: EntityDescs<'static>,
215        overlay: &EntityDescs<'id>,
216    ) -> EntityDescs<'id> {
217        let mut result = base;
218        overlay.iter().for_each(|(key, value)| {
219            result.insert(key.clone(), value.clone());
220        });
221        result
222    }
223
224    fn merge_entity_tooltips<'id>(
225        base: EntityTooltips<'static>,
226        overlay: &EntityTooltips<'id>,
227    ) -> EntityTooltips<'id> {
228        let mut result = base;
229        overlay.iter().for_each(|(key, value)| {
230            result.insert(key.clone(), value.clone());
231        });
232        result
233    }
234
235    fn merge_entity_types<'id>(
236        base: EntityTypes<'static>,
237        overlay: &EntityTypes<'id>,
238    ) -> EntityTypes<'id> {
239        let mut result = base;
240        overlay.iter().for_each(|(key, value)| {
241            result.insert(key.clone(), value.clone());
242        });
243        result
244    }
245
246    fn merge_theme_default<'id>(
247        base: ThemeDefault<'static>,
248        overlay: &ThemeDefault<'id>,
249    ) -> ThemeDefault<'id> {
250        let style_aliases = Self::merge_style_aliases(base.style_aliases, &overlay.style_aliases);
251        let base_styles = Self::merge_theme_styles(base.base_styles, &overlay.base_styles);
252        let process_step_selected_styles = Self::merge_theme_styles(
253            base.process_step_selected_styles,
254            &overlay.process_step_selected_styles,
255        );
256
257        // Overlay's dark_mode_config takes precedence over base when
258        // the overlay specifies a non-default value.
259        let dark_mode_config = overlay.dark_mode_config;
260
261        ThemeDefault {
262            style_aliases,
263            base_styles,
264            process_step_selected_styles,
265            dark_mode_config,
266        }
267    }
268
269    fn merge_style_aliases<'id>(
270        base: StyleAliases<'static>,
271        overlay: &StyleAliases<'id>,
272    ) -> StyleAliases<'id> {
273        let mut result = base;
274        overlay.iter().for_each(|(key, value)| {
275            result.insert(key.clone(), value.clone());
276        });
277        result
278    }
279
280    fn merge_theme_styles<'id>(
281        base: ThemeStyles<'static>,
282        overlay: &ThemeStyles<'id>,
283    ) -> ThemeStyles<'id> {
284        let mut result = base;
285        overlay.iter().for_each(|(key, value)| {
286            result.insert(key.clone(), value.clone());
287        });
288        result
289    }
290
291    fn merge_theme_types_styles<'id>(
292        base: ThemeTypesStyles<'static>,
293        overlay: &ThemeTypesStyles<'id>,
294    ) -> ThemeTypesStyles<'id> {
295        let mut result = base;
296        overlay.iter().for_each(|(key, value)| {
297            result.insert(key.clone(), value.clone());
298        });
299        result
300    }
301
302    fn merge_theme_thing_dependencies_styles<'id>(
303        base: ThemeThingDependenciesStyles<'static>,
304        overlay: &ThemeThingDependenciesStyles<'id>,
305    ) -> ThemeThingDependenciesStyles<'id> {
306        let things_included_styles =
307            Self::merge_theme_styles(base.things_included_styles, &overlay.things_included_styles);
308        let things_excluded_styles =
309            Self::merge_theme_styles(base.things_excluded_styles, &overlay.things_excluded_styles);
310
311        ThemeThingDependenciesStyles {
312            things_included_styles,
313            things_excluded_styles,
314        }
315    }
316
317    fn merge_theme_tag_things_focus<'id>(
318        base: ThemeTagThingsFocus<'static>,
319        overlay: &ThemeTagThingsFocus<'id>,
320    ) -> ThemeTagThingsFocus<'id> {
321        let mut result = base;
322        overlay.iter().for_each(|(key, value)| {
323            result.insert(key.clone(), value.clone());
324        });
325        result
326    }
327
328    fn merge_css(base: Css, overlay: &Css) -> Css {
329        // If overlay has CSS, use it; otherwise use base
330        if overlay.is_empty() {
331            base
332        } else {
333            overlay.clone()
334        }
335    }
336}