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())
)
}
}