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}