zenoh_flow_commons/
configuration.rs

1//
2// Copyright (c) 2021 - 2024 ZettaScale Technology
3//
4// This program and the accompanying materials are made available under the
5// terms of the Eclipse Public License 2.0 which is available at
6// http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0
7// which is available at https://www.apache.org/licenses/LICENSE-2.0.
8//
9// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
10//
11// Contributors:
12//   ZettaScale Zenoh Team, <zenoh@zettascale.tech>
13//
14
15use crate::merge::IMergeOverwrite;
16use serde::{Deserialize, Serialize};
17use std::{ops::Deref, sync::Arc};
18
19/// A `Configuration` is a recursive key-value structure that allows modifying the behaviour of a node without altering
20/// its implementation.
21///
22/// It is effectively a re-export of [serde_json::Value].
23///
24/// # Declaration, propagation and merging
25///
26/// Zenoh-Flow allows users to declare a configuration at 3 locations:
27/// - at the top-level of a data flow descriptor,
28/// - at the top-level of a composite operator descriptor,
29/// - in a node (be it within a data flow descriptor, a composite descriptor or in its dedicated file).
30///
31/// If a configuration is declared at a top-level it is propagated to all the nodes it includes. Hence, a declaration at
32/// the top-level of a data flow is propagated to all the nodes it contains.
33///
34/// When two configuration keys collide, the configuration with the highest order is kept. The priorities are (from
35/// highest to lowest):
36/// - the configuration in a node within a data flow descriptor,
37/// - the configuration at the top-level of a data flow descriptor,
38/// - the configuration in a node within a composite operator descriptor,
39/// - the configuration at the top-level of a composite operator descriptor,
40/// - the configuration in a dedicated file of a node.
41///
42/// Hence, configuration at the data flow level are propagating to all nodes, possibly overwriting default values. The
43/// same rules apply at the composite operator level. If a node should have a slightly different setting compared to all
44/// others, then, thanks to these priorities, only that node needs to be tweaked (either in the data flow or in the
45/// composite operator).
46///
47/// # Examples
48///
49/// - YAML
50///
51///   ```yaml
52///   configuration:
53///     name: "John Doe",
54///     age: 43,
55///     phones:
56///       - "+44 1234567"
57///       - "+44 2345678"
58///   ```
59///
60/// - JSON
61///
62///   ```json
63///   "configuration": {
64///     "name": "John Doe",
65///     "age": 43,
66///     "phones": [
67///         "+44 1234567",
68///         "+44 2345678"
69///     ]
70///   }
71///   ```
72//
73// NOTE: we take the `serde_json` representation because:
74// - JSON is the most supported representation when going online,
75// - a `serde_json::Value` can be converted to a `serde_yaml::Value` whereas the opposite is not true (YAML introduces
76//   "tags" which are not supported by JSON).
77#[derive(Default, Deserialize, Debug, Serialize, Clone, PartialEq, Eq)]
78pub struct Configuration(Arc<serde_json::Value>);
79
80impl Deref for Configuration {
81    type Target = serde_json::Value;
82
83    fn deref(&self) -> &Self::Target {
84        &self.0
85    }
86}
87
88impl IMergeOverwrite for Configuration {
89    fn merge_overwrite(self, other: Self) -> Self {
90        if self == Configuration::default() {
91            return other;
92        }
93
94        if other == Configuration::default() {
95            return self;
96        }
97
98        match (self.as_object(), other.as_object()) {
99            (Some(this), Some(other)) => {
100                let mut other = other.clone();
101                let mut this = this.clone();
102
103                other.append(&mut this);
104                Configuration(Arc::new(other.into()))
105            }
106            (_, _) => unreachable!(
107                "We are checking, when deserialising, that a Configuration is a JSON object."
108            ),
109        }
110    }
111}
112
113impl From<serde_json::Value> for Configuration {
114    fn from(value: serde_json::Value) -> Self {
115        Self(Arc::new(value))
116    }
117}
118
119#[cfg(test)]
120mod tests {
121    use super::*;
122    use serde_json::json;
123
124    #[test]
125    fn test_merge_configurations() {
126        let global = Configuration(Arc::new(
127            json!({ "a": { "nested": true }, "b": ["an", "array"] }),
128        ));
129        let local = Configuration(Arc::new(json!({ "a": { "not-nested": false }, "c": 1 })));
130
131        assert_eq!(
132            global.clone().merge_overwrite(local),
133            Configuration(Arc::new(
134                json!({ "a": { "nested": true }, "b": ["an", "array"], "c": 1 })
135            ))
136        );
137
138        assert_eq!(
139            global,
140            global.clone().merge_overwrite(Configuration::default())
141        );
142        assert_eq!(
143            global,
144            Configuration::default().merge_overwrite(global.clone())
145        );
146        assert_eq!(
147            Configuration::default(),
148            Configuration::default().merge_overwrite(Configuration::default())
149        )
150    }
151}