zenoh_flow_commons/
configuration.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
//
// Copyright (c) 2021 - 2024 ZettaScale Technology
//
// This program and the accompanying materials are made available under the
// terms of the Eclipse Public License 2.0 which is available at
// http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0
// which is available at https://www.apache.org/licenses/LICENSE-2.0.
//
// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
//
// Contributors:
//   ZettaScale Zenoh Team, <zenoh@zettascale.tech>
//

use crate::merge::IMergeOverwrite;
use serde::{Deserialize, Serialize};
use std::{ops::Deref, sync::Arc};

/// A `Configuration` is a recursive key-value structure that allows modifying the behaviour of a node without altering
/// its implementation.
///
/// It is effectively a re-export of [serde_json::Value].
///
/// # Declaration, propagation and merging
///
/// Zenoh-Flow allows users to declare a configuration at 3 locations:
/// - at the top-level of a data flow descriptor,
/// - at the top-level of a composite operator descriptor,
/// - in a node (be it within a data flow descriptor, a composite descriptor or in its dedicated file).
///
/// If a configuration is declared at a top-level it is propagated to all the nodes it includes. Hence, a declaration at
/// the top-level of a data flow is propagated to all the nodes it contains.
///
/// When two configuration keys collide, the configuration with the highest order is kept. The priorities are (from
/// highest to lowest):
/// - the configuration in a node within a data flow descriptor,
/// - the configuration at the top-level of a data flow descriptor,
/// - the configuration in a node within a composite operator descriptor,
/// - the configuration at the top-level of a composite operator descriptor,
/// - the configuration in a dedicated file of a node.
///
/// Hence, configuration at the data flow level are propagating to all nodes, possibly overwriting default values. The
/// same rules apply at the composite operator level. If a node should have a slightly different setting compared to all
/// others, then, thanks to these priorities, only that node needs to be tweaked (either in the data flow or in the
/// composite operator).
///
/// # Examples
///
/// - YAML
///
///   ```yaml
///   configuration:
///     name: "John Doe",
///     age: 43,
///     phones:
///       - "+44 1234567"
///       - "+44 2345678"
///   ```
///
/// - JSON
///
///   ```json
///   "configuration": {
///     "name": "John Doe",
///     "age": 43,
///     "phones": [
///         "+44 1234567",
///         "+44 2345678"
///     ]
///   }
///   ```
//
// NOTE: we take the `serde_json` representation because:
// - JSON is the most supported representation when going online,
// - a `serde_json::Value` can be converted to a `serde_yaml::Value` whereas the opposite is not true (YAML introduces
//   "tags" which are not supported by JSON).
#[derive(Default, Deserialize, Debug, Serialize, Clone, PartialEq, Eq)]
pub struct Configuration(Arc<serde_json::Value>);

impl Deref for Configuration {
    type Target = serde_json::Value;

    fn deref(&self) -> &Self::Target {
        &self.0
    }
}

impl IMergeOverwrite for Configuration {
    fn merge_overwrite(self, other: Self) -> Self {
        if self == Configuration::default() {
            return other;
        }

        if other == Configuration::default() {
            return self;
        }

        match (self.as_object(), other.as_object()) {
            (Some(this), Some(other)) => {
                let mut other = other.clone();
                let mut this = this.clone();

                other.append(&mut this);
                Configuration(Arc::new(other.into()))
            }
            (_, _) => unreachable!(
                "We are checking, when deserialising, that a Configuration is a JSON object."
            ),
        }
    }
}

impl From<serde_json::Value> for Configuration {
    fn from(value: serde_json::Value) -> Self {
        Self(Arc::new(value))
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use serde_json::json;

    #[test]
    fn test_merge_configurations() {
        let global = Configuration(Arc::new(
            json!({ "a": { "nested": true }, "b": ["an", "array"] }),
        ));
        let local = Configuration(Arc::new(json!({ "a": { "not-nested": false }, "c": 1 })));

        assert_eq!(
            global.clone().merge_overwrite(local),
            Configuration(Arc::new(
                json!({ "a": { "nested": true }, "b": ["an", "array"], "c": 1 })
            ))
        );

        assert_eq!(
            global,
            global.clone().merge_overwrite(Configuration::default())
        );
        assert_eq!(
            global,
            Configuration::default().merge_overwrite(global.clone())
        );
        assert_eq!(
            Configuration::default(),
            Configuration::default().merge_overwrite(Configuration::default())
        )
    }
}