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}