zenoh_flow_commons/
vars.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::IMergeOverwrite;
16use serde::{Deserialize, Serialize};
17use std::collections::HashMap;
18use std::error::Error;
19use std::ops::Deref;
20use std::rc::Rc;
21
22/// `Vars` is an internal structure that we use to expand the "moustache variables" in a descriptor file.
23///
24/// Moustache variables take the form: `{{ var }}` where the number of spaces after the `{{` and before the `}}` do
25/// not matter.
26///
27/// We first parse the descriptor file to only extract the `vars` section and build a `HashMap` out of it.
28///
29/// We then load the descriptor file as a template and "render" it, substituting every "moustache variable" with its
30/// corresponding value in the HashMap.
31///
32/// # Declaration, propagation and merging
33///
34/// Zenoh-Flow allows users to declare `vars` at 3 locations:
35/// - at the top-level of a data flow descriptor,
36/// - at the top-level of a composite operator descriptor,
37/// - at the top-level of a node descriptor (not contained within a data flow or composite operator descriptor).
38///
39/// The `vars` are propagated to all "contained" descriptors. For instance, a data flow descriptor that references a
40/// composite operator whose descriptor resides in a separate file will have its `vars` propagated there.
41///
42/// At the same time, if a "contained" descriptor also has a `vars` section, that section will be merged and all
43/// duplicated keys overwritten with the values of the "container" descriptor.
44///
45/// This allows defining default values for substitutions in leaf descriptors and overwriting them if necessary.
46///
47/// # Example (YAML)
48///
49/// Declaration within a descriptor:
50///
51/// ```yaml
52///   vars:
53///     BUILD: debug
54///     DLL_EXT: so
55/// ```
56///
57/// Its usage within the descriptor:
58///
59/// ```yaml
60///   sources:
61///     - id: my-source
62///       library: "file:///zenoh-flow/target/{{ BUILD }}/libmy_source.{{ DLL_EXT }}"
63/// ```
64#[derive(Debug, Default, Clone, Serialize, Deserialize, PartialEq)]
65pub struct Vars {
66    #[serde(default)]
67    vars: Rc<HashMap<Rc<str>, Rc<str>>>,
68}
69
70impl Deref for Vars {
71    type Target = HashMap<Rc<str>, Rc<str>>;
72
73    fn deref(&self) -> &Self::Target {
74        &self.vars
75    }
76}
77
78impl IMergeOverwrite for Vars {
79    fn merge_overwrite(self, other: Self) -> Self {
80        let mut merged = (*other.vars).clone();
81        merged.extend((*self.vars).clone());
82
83        Self {
84            vars: Rc::new(merged),
85        }
86    }
87}
88
89impl<T: AsRef<str>, U: AsRef<str>, const N: usize> From<[(T, U); N]> for Vars {
90    fn from(value: [(T, U); N]) -> Self {
91        Self {
92            vars: Rc::new(
93                value
94                    .into_iter()
95                    .map(|(k, v)| (k.as_ref().into(), v.as_ref().into()))
96                    .collect::<HashMap<Rc<str>, Rc<str>>>(),
97            ),
98        }
99    }
100}
101
102impl<T: AsRef<str>, U: AsRef<str>> From<Vec<(T, U)>> for Vars {
103    fn from(value: Vec<(T, U)>) -> Self {
104        Self {
105            vars: Rc::new(
106                value
107                    .into_iter()
108                    .map(|(k, v)| (k.as_ref().into(), v.as_ref().into()))
109                    .collect::<HashMap<Rc<str>, Rc<str>>>(),
110            ),
111        }
112    }
113}
114
115/// Parse a single [Var](Vars) from a string of the format "KEY=VALUE".
116///
117/// Note that if several "=" characters are present in the string, only the first one will be considered as a separator
118/// and the others will be treated as being part of the VALUE.
119///
120/// # Errors
121///
122/// This function will return an error if no "=" character was found.
123pub fn parse_vars<T, U>(
124    s: &str,
125) -> std::result::Result<(T, U), Box<dyn Error + Send + Sync + 'static>>
126where
127    T: std::str::FromStr,
128    T::Err: Error + Send + Sync + 'static,
129    U: std::str::FromStr,
130    U::Err: Error + Send + Sync + 'static,
131{
132    let pos = s
133        .find('=')
134        .ok_or_else(|| format!("invalid KEY=value: no `=` found in `{s}`"))?;
135    Ok((s[..pos].parse()?, s[pos + 1..].parse()?))
136}