dimas_config/config.rs
1// Copyright © 2024 Stephan Kunz
2
3//! The configuration data.
4//!
5//! An Agents configuration can be defined using json5 formated files.
6//! There is a set of read methods for predefined filenames available.
7//! You can find some example files [here](https://github.com/dimas-fw/dimas/tree/main/.config)
8//!
9//! # Examples
10//! ```rust,no_run
11//! # use dimas_config::Config;
12//! # extern crate std;
13//! # fn main() -> Result<(), Box<dyn core::error::Error + Send + Sync + 'static>> {
14//! // in no-std environment:
15//! // creates a default configuration
16//! // in std environment:
17//! // creates a configuration from a file named `default.json5`
18//! // located in one of the directories listed below.
19//! // If that file does not exist, a default config will be created
20//! let config = Config::default();
21//!
22//! // use file named `filename.json5` (needs std environment)
23//! // returns an error if file does not exist or is no valid configuration file
24//! let config = Config::from_file("filename.json5")?;
25//!
26//! // methods with predefined filenames working like Config::from_file(...) (needs std environment)
27//! let config = Config::local()?; // use file named `local.json5`
28//! let config = Config::peer()?; // use file named `peer.json5`
29//! let config = Config::client()?; // use file named `client.json5`
30//! let config = Config::router()?; // use file named `router.json5`
31//!
32//! # Ok(())
33//! # }
34//! ```
35//!
36//! The methods using files will search in following directories for the file (order first to last):
37//! - current working directory
38//! - `.config` directory below current working directory
39//! - `.config` directory below home directory
40//! - local config directory (`Linux`: `$XDG_CONFIG_HOME/dimas` or `$HOME/.config/dimas` | `Windows`: `{FOLDERID_LocalAppData}/dimas` | `MacOS`: `$HOME/Library/Application Support/dimas`)
41//! - config directory (`Linux`: `$XDG_CONFIG_HOME/dimas` or `$HOME/.config/dimas` | `Windows`: `{FOLDERID_RoamingAppData}/dimas` | `MacOS`: `$HOME/Library/Application Support/dimas`)
42//!
43
44#[doc(hidden)]
45extern crate alloc;
46
47#[cfg(feature = "std")]
48extern crate std;
49
50// region: --- modules
51use crate::utils::{find_config_file, read_config_file};
52use crate::Result;
53use alloc::vec::Vec;
54#[cfg(feature = "std")]
55use tracing::{debug, warn};
56// endregion: --- modules
57
58// region: --- Session
59#[derive(Debug, Default, Clone, serde::Deserialize)]
60pub struct Session {
61 pub protocol: alloc::string::String,
62 pub name: alloc::string::String,
63 #[serde(deserialize_with = "zenoh::Config::deserialize")]
64 pub config: zenoh::Config,
65}
66// endregion: --- Session
67
68// region: --- Config
69/// Manages the configuration
70//#[repr(transparent)]
71#[derive(Debug, Clone, serde::Deserialize)]
72pub struct Config {
73 #[serde(deserialize_with = "zenoh::Config::deserialize")]
74 zenoh: zenoh::Config,
75 sessions: Option<Vec<Session>>,
76}
77
78#[cfg(not(feature = "std"))]
79impl Default for Config {
80 /// Create a default configuration
81 fn default() -> Self {
82 Self {
83 zenoh: zenoh::Config::default(),
84 }
85 }
86}
87
88#[cfg(feature = "std")]
89impl Default for Config {
90 /// Create a default configuration
91 ///
92 /// Will search for a configuration file with name "default.json5" in the directories mentioned in [`Examples`](index.html#examples).
93 /// This file should contain the wanted default configuration.
94 /// If no file is found, it will create a defined minimal default configuration.
95 /// Currently this is just a default zenoh peer configuration which connects to peers in same subnet.
96 #[allow(clippy::cognitive_complexity)]
97 fn default() -> Self {
98 match find_config_file("default.json5") {
99 Ok(path) => {
100 debug!("trying file {:?}", &path);
101 match read_config_file(&path) {
102 Ok(content) => match json5::from_str(&content) {
103 Ok(result) => result,
104 Err(error) => {
105 warn!("{}, using default dimas configuration instead", error);
106 Self {
107 zenoh: zenoh::Config::default(),
108 sessions: None,
109 }
110 }
111 },
112 Err(error) => {
113 warn!("{}, using default dimas configuration instead", error);
114 Self {
115 zenoh: zenoh::Config::default(),
116 sessions: None,
117 }
118 }
119 }
120 }
121 Err(error) => {
122 warn!("{}, using default dimas configuration instead", error);
123 Self {
124 zenoh: zenoh::Config::default(),
125 sessions: None,
126 }
127 }
128 }
129 }
130}
131
132impl Config {
133 /// Create a configuration based on file named `local.json5`.
134 ///
135 /// Will search in the directories mentioned in [`Examples`](index.html#examples).
136 /// This file should contain a configuration that only connects to entities on same host.
137 ///
138 /// # Errors
139 /// Returns a [`std::io::Error`], if file does not exist in any of the places or is not accessible.
140 #[cfg(feature = "std")]
141 pub fn local() -> Result<Self> {
142 let path = find_config_file("local.json5")?;
143 #[cfg(feature = "std")]
144 debug!("using file {:?}", &path);
145 let content = read_config_file(&path)?;
146 let cfg = json5::from_str(&content)?;
147 Ok(cfg)
148 }
149
150 /// Create a configuration based on file named `client.json5`.
151 ///
152 /// Will search in the directories mentioned in [`Examples`](index.html#examples).
153 /// This file should contain a configuration that creates an entity in client mode.
154 ///
155 /// # Errors
156 /// Returns a [`std::io::Error`], if file does not exist in any of the places or is not accessible.
157 #[cfg(feature = "std")]
158 pub fn client() -> Result<Self> {
159 let path = find_config_file("client.json5")?;
160 debug!("using file {:?}", &path);
161 let content = read_config_file(&path)?;
162 let cfg = json5::from_str(&content)?;
163 Ok(cfg)
164 }
165
166 /// Create a configuration based on file named `peer.json5`.
167 ///
168 /// Will search in the directories mentioned in [`Examples`](index.html#examples).
169 /// This file should contain a configuration that creates an entity in peer mode.
170 ///
171 /// # Errors
172 /// Returns a [`std::io::Error`], if file does not exist in any of the places or is not accessible.
173 #[cfg(feature = "std")]
174 pub fn peer() -> Result<Self> {
175 let path = find_config_file("peer.json5")?;
176 debug!("using file {:?}", &path);
177 let content = read_config_file(&path)?;
178 let cfg = json5::from_str(&content)?;
179 Ok(cfg)
180 }
181
182 /// Create a configuration based on file named `router.json5`.
183 ///
184 /// Will search in the directories mentioned in [`Examples`](index.html#examples).
185 /// This file should contain a configuration that creates an entity in router mode.
186 ///
187 /// # Errors
188 /// Returns a [`std::io::Error`], if file does not exist in any of the places or is not accessible.
189 #[cfg(feature = "std")]
190 pub fn router() -> Result<Self> {
191 let path = find_config_file("router.json5")?;
192 debug!("using file {:?}", &path);
193 let content = read_config_file(&path)?;
194
195 let cfg = json5::from_str(&content)?;
196 Ok(cfg)
197 }
198
199 /// Create a configuration based on file with given filename.
200 ///
201 /// Will search in the directories mentioned in [`Examples`](index.html#examples).
202 ///
203 /// # Errors
204 /// Returns a [`std::io::Error`], if file does not exist in any of the places or is not accessible.
205 #[cfg(feature = "std")]
206 pub fn from_file(filename: &str) -> Result<Self> {
207 let path = find_config_file(filename)?;
208 debug!("using file {:?}", &path);
209 let content = read_config_file(&path)?;
210 let cfg = json5::from_str(&content)?;
211 Ok(cfg)
212 }
213
214 /// Method to extract the zenoh configuration from [`Config`].
215 ///
216 /// Can be passed to `zenoh::open()`.
217 #[must_use]
218 pub const fn zenoh_config(&self) -> &zenoh::Config {
219 &self.zenoh
220 }
221
222 /// Method to get access to the the sessions in [`Config`].
223 ///
224 #[must_use]
225 pub const fn sessions(&self) -> &Option<Vec<Session>> {
226 &self.sessions
227 }
228}
229// endregion: --- Config
230
231#[cfg(test)]
232mod tests {
233 use super::*;
234
235 // check, that the auto traits are available
236 const fn is_normal<T: Sized + Send + Sync>() {}
237
238 #[test]
239 const fn normal_types() {
240 is_normal::<Config>();
241 }
242
243 #[test]
244 fn config_default() {
245 Config::default();
246 }
247
248 #[cfg(feature = "std")]
249 #[test]
250 fn config_local() -> Result<()> {
251 Config::local()?;
252 Ok(())
253 }
254
255 #[cfg(feature = "std")]
256 #[test]
257 fn config_router() -> Result<()> {
258 Config::router()?;
259 Ok(())
260 }
261
262 #[cfg(feature = "std")]
263 #[test]
264 fn config_peer() -> Result<()> {
265 Config::peer()?;
266 Ok(())
267 }
268
269 #[cfg(feature = "std")]
270 #[test]
271 fn config_client() -> Result<()> {
272 Config::client()?;
273 Ok(())
274 }
275
276 #[cfg(feature = "std")]
277 #[test]
278 fn config_from_file() -> Result<()> {
279 Config::from_file("default.json5")?;
280 Ok(())
281 }
282
283 #[cfg(feature = "std")]
284 #[test]
285 fn config_from_file_fails() {
286 let _ = Config::from_file("non_existent.json5").is_err();
287 }
288}