paramodel_elements/
configuration.rs1use std::collections::BTreeMap;
17
18use crate::{ParameterName, Value, name_type};
19use serde::{Deserialize, Serialize};
20
21use crate::error::ElementError;
22
23#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
33pub struct TokenExpr(String);
34
35impl TokenExpr {
36 pub fn new(s: impl Into<String>) -> Result<Self, ElementError> {
38 let s = s.into();
39 if s.is_empty() {
40 return Err(ElementError::EmptyTokenExpr);
41 }
42 Ok(Self(s))
43 }
44
45 #[must_use]
47 pub fn as_str(&self) -> &str {
48 &self.0
49 }
50
51 #[must_use]
53 pub fn into_inner(self) -> String {
54 self.0
55 }
56}
57
58impl std::fmt::Display for TokenExpr {
59 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
60 f.write_str(&self.0)
61 }
62}
63
64impl Serialize for TokenExpr {
65 fn serialize<S: serde::Serializer>(&self, s: S) -> std::result::Result<S::Ok, S::Error> {
66 s.serialize_str(&self.0)
67 }
68}
69
70impl<'de> Deserialize<'de> for TokenExpr {
71 fn deserialize<D: serde::Deserializer<'de>>(d: D) -> std::result::Result<Self, D::Error> {
72 let s = String::deserialize(d)?;
73 Self::new(s).map_err(serde::de::Error::custom)
74 }
75}
76
77#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
87#[serde(tag = "kind", rename_all = "snake_case")]
88pub enum ConfigEntry {
89 Literal {
91 value: Value,
93 },
94 Token {
96 expr: TokenExpr,
98 },
99}
100
101impl ConfigEntry {
102 #[must_use]
104 pub const fn literal(value: Value) -> Self {
105 Self::Literal { value }
106 }
107
108 #[must_use]
110 pub const fn token(expr: TokenExpr) -> Self {
111 Self::Token { expr }
112 }
113
114 #[must_use]
116 pub const fn is_token(&self) -> bool {
117 matches!(self, Self::Token { .. })
118 }
119}
120
121#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
124#[serde(transparent)]
125pub struct Configuration(BTreeMap<ParameterName, ConfigEntry>);
126
127impl Configuration {
128 #[must_use]
130 pub fn new() -> Self {
131 Self::default()
132 }
133
134 pub fn insert(
137 &mut self,
138 name: ParameterName,
139 entry: ConfigEntry,
140 ) -> Option<ConfigEntry> {
141 self.0.insert(name, entry)
142 }
143
144 #[must_use]
146 pub fn get(&self, name: &ParameterName) -> Option<&ConfigEntry> {
147 self.0.get(name)
148 }
149
150 pub fn iter(&self) -> impl Iterator<Item = (&ParameterName, &ConfigEntry)> {
152 self.0.iter()
153 }
154
155 pub fn keys(&self) -> impl Iterator<Item = &ParameterName> {
157 self.0.keys()
158 }
159
160 #[must_use]
162 pub fn len(&self) -> usize {
163 self.0.len()
164 }
165
166 #[must_use]
168 pub fn is_empty(&self) -> bool {
169 self.0.is_empty()
170 }
171}
172
173impl FromIterator<(ParameterName, ConfigEntry)> for Configuration {
174 fn from_iter<I: IntoIterator<Item = (ParameterName, ConfigEntry)>>(iter: I) -> Self {
175 Self(iter.into_iter().collect())
176 }
177}
178
179name_type! {
184 pub struct ExportName { kind: "ExportName" }
187}
188
189#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
191#[serde(transparent)]
192pub struct Exports(BTreeMap<ExportName, TokenExpr>);
193
194impl Exports {
195 #[must_use]
197 pub fn new() -> Self {
198 Self::default()
199 }
200
201 pub fn insert(&mut self, name: ExportName, expr: TokenExpr) -> Option<TokenExpr> {
204 self.0.insert(name, expr)
205 }
206
207 #[must_use]
209 pub fn get(&self, name: &ExportName) -> Option<&TokenExpr> {
210 self.0.get(name)
211 }
212
213 pub fn iter(&self) -> impl Iterator<Item = (&ExportName, &TokenExpr)> {
215 self.0.iter()
216 }
217
218 pub fn keys(&self) -> impl Iterator<Item = &ExportName> {
220 self.0.keys()
221 }
222
223 #[must_use]
225 pub fn len(&self) -> usize {
226 self.0.len()
227 }
228
229 #[must_use]
231 pub fn is_empty(&self) -> bool {
232 self.0.is_empty()
233 }
234}
235
236impl FromIterator<(ExportName, TokenExpr)> for Exports {
237 fn from_iter<I: IntoIterator<Item = (ExportName, TokenExpr)>>(iter: I) -> Self {
238 Self(iter.into_iter().collect())
239 }
240}
241
242#[cfg(test)]
243mod tests {
244 use super::*;
245 use crate::ParameterName;
246
247 fn pname(s: &str) -> ParameterName {
248 ParameterName::new(s).unwrap()
249 }
250
251 #[test]
252 fn token_expr_rejects_empty() {
253 assert!(TokenExpr::new("").is_err());
254 let t = TokenExpr::new("${self.ip}").unwrap();
255 assert_eq!(t.as_str(), "${self.ip}");
256 }
257
258 #[test]
259 fn config_entry_helpers() {
260 let lit = ConfigEntry::literal(Value::integer(pname("n"), 8, None));
261 let tok = ConfigEntry::token(TokenExpr::new("${self.ip}").unwrap());
262 assert!(!lit.is_token());
263 assert!(tok.is_token());
264 }
265
266 #[test]
267 fn configuration_iter_is_sorted_by_name() {
268 let mut c = Configuration::new();
269 c.insert(
270 pname("zebra"),
271 ConfigEntry::literal(Value::integer(pname("zebra"), 1, None)),
272 );
273 c.insert(
274 pname("apple"),
275 ConfigEntry::literal(Value::integer(pname("apple"), 2, None)),
276 );
277 let names: Vec<&str> = c.keys().map(ParameterName::as_str).collect();
278 assert_eq!(names, vec!["apple", "zebra"]);
279 }
280
281 #[test]
282 fn exports_insert_and_get() {
283 let mut e = Exports::new();
284 let n = ExportName::new("service_addr").unwrap();
285 let t = TokenExpr::new("${self.ip}:4567").unwrap();
286 e.insert(n.clone(), t.clone());
287 assert_eq!(e.get(&n), Some(&t));
288 assert_eq!(e.len(), 1);
289 }
290
291 #[test]
292 fn token_expr_serde_roundtrip() {
293 let t = TokenExpr::new("${foo.bar}").unwrap();
294 let json = serde_json::to_string(&t).unwrap();
295 assert_eq!(json, "\"${foo.bar}\"");
296 let back: TokenExpr = serde_json::from_str(&json).unwrap();
297 assert_eq!(t, back);
298 }
299
300 #[test]
301 fn token_expr_deserialise_rejects_empty() {
302 let err: Result<TokenExpr, _> = serde_json::from_str("\"\"");
303 assert!(err.is_err());
304 }
305
306 #[test]
307 fn config_entry_serde_roundtrip() {
308 let lit = ConfigEntry::literal(Value::integer(pname("n"), 8, None));
309 let json = serde_json::to_string(&lit).unwrap();
310 let back: ConfigEntry = serde_json::from_str(&json).unwrap();
311 assert_eq!(lit, back);
312 }
313}