Skip to main content

nym_config/
lib.rs

1// Copyright 2020 - Nym Technologies SA <contact@nymtech.net>
2// SPDX-License-Identifier: Apache-2.0
3
4use handlebars::{Handlebars, TemplateRenderError};
5use serde::de::DeserializeOwned;
6use serde::Serialize;
7use std::fs::{create_dir_all, File};
8use std::io::Write;
9use std::path::{Path, PathBuf};
10use std::{fs, io};
11
12pub use helpers::{parse_urls, OptionalSet};
13pub use toml::de::Error as TomlDeError;
14
15pub mod defaults;
16pub mod error;
17pub mod helpers;
18pub mod legacy_helpers;
19pub mod serde_helpers;
20
21pub const NYM_DIR: &str = ".nym";
22pub const DEFAULT_NYM_APIS_DIR: &str = "nym-api";
23pub const DEFAULT_CONFIG_DIR: &str = "config";
24pub const DEFAULT_DATA_DIR: &str = "data";
25pub const DEFAULT_CONFIG_FILENAME: &str = "config.toml";
26
27#[cfg(feature = "dirs")]
28pub fn must_get_home() -> PathBuf {
29    if let Some(home_dir) = std::env::var_os("NYM_HOME_DIR") {
30        home_dir.into()
31    } else {
32        dirs::home_dir().expect("Failed to evaluate $HOME value")
33    }
34}
35
36#[cfg(feature = "dirs")]
37pub fn may_get_home() -> Option<PathBuf> {
38    if let Some(home_dir) = std::env::var_os("NYM_HOME_DIR") {
39        Some(home_dir.into())
40    } else {
41        dirs::home_dir()
42    }
43}
44
45pub trait NymConfigTemplate: Serialize {
46    fn template(&self) -> &'static str;
47
48    fn format_to_string(&self) -> String {
49        // it is responsibility of whoever is implementing the trait to ensure the template is valid
50        Handlebars::new()
51            .render_template(self.template(), &self)
52            .unwrap()
53    }
54
55    fn format_to_writer<W: Write>(&self, writer: W) -> io::Result<()> {
56        if let Err(err) = Handlebars::new().render_template_to_write(self.template(), &self, writer)
57        {
58            match err {
59                TemplateRenderError::IOError(err, _) => return Err(err),
60                other_err => {
61                    // it is responsibility of whoever is implementing the trait to ensure the template is valid
62                    panic!("invalid template: {other_err}")
63                }
64            }
65        }
66
67        Ok(())
68    }
69}
70
71pub fn save_formatted_config_to_file<C, P>(config: &C, path: P) -> io::Result<()>
72where
73    C: NymConfigTemplate,
74    P: AsRef<Path>,
75{
76    let path = path.as_ref();
77    log::info!("saving config file to {}", path.display());
78
79    if let Some(parent) = path.parent() {
80        create_dir_all(parent)?;
81    }
82
83    let file = File::create(path)?;
84
85    // TODO: check for whether any of our configs store anything sensitive
86    // and change that to 0o644 instead
87    #[cfg(target_family = "unix")]
88    {
89        use std::os::unix::fs::PermissionsExt;
90
91        let mut perms = fs::metadata(path)?.permissions();
92        perms.set_mode(0o600);
93        fs::set_permissions(path, perms)?;
94    }
95
96    config.format_to_writer(file)
97}
98
99pub fn save_unformatted_config_to_file<C, P>(
100    config: &C,
101    path: P,
102) -> Result<(), error::NymConfigTomlError>
103where
104    C: Serialize + ?Sized,
105    P: AsRef<Path>,
106{
107    let path = path.as_ref();
108    log::info!("saving config file to {}", path.display());
109
110    if let Some(parent) = path.parent() {
111        create_dir_all(parent)?;
112    }
113
114    let mut file = File::create(path)?;
115
116    // TODO: check for whether any of our configs store anything sensitive
117    // and change that to 0o644 instead
118    #[cfg(target_family = "unix")]
119    {
120        use std::os::unix::fs::PermissionsExt;
121
122        let mut perms = fs::metadata(path)?.permissions();
123        perms.set_mode(0o600);
124        fs::set_permissions(path, perms)?;
125    }
126
127    // let serde format the TOML in whatever ugly way it chooses
128    // TODO: in https://docs.rs/toml/latest/toml/fn.to_string_pretty.html it recommends using
129    //   https://docs.rs/toml_edit/latest/toml_edit/struct.DocumentMut.html to preserve formatting
130    let toml_string = toml::to_string_pretty(config)?;
131
132    Ok(file.write_all(toml_string.as_bytes())?)
133}
134
135pub fn deserialize_config_from_toml_str<C>(raw: &str) -> Result<C, TomlDeError>
136where
137    C: DeserializeOwned,
138{
139    toml::from_str(raw)
140}
141
142pub fn read_config_from_toml_file<C, P>(path: P) -> io::Result<C>
143where
144    C: DeserializeOwned,
145    P: AsRef<Path>,
146{
147    log::trace!(
148        "trying to read config file from {}",
149        path.as_ref().display()
150    );
151    let content = fs::read_to_string(path)?;
152
153    // TODO: should we be preserving original error type instead?
154    deserialize_config_from_toml_str(&content).map_err(io::Error::other)
155}
156
157//
158//
159//
160// pub trait NymConfig: Default + Serialize + DeserializeOwned {
161//     fn template(&self) -> &'static str;
162//
163//     fn config_file_name() -> String {
164//         "config.toml".to_string()
165//     }
166//
167//     fn default_root_directory() -> PathBuf;
168//
169//     // default, most probable, implementations; can be easily overridden where required
170//     fn default_config_directory(id: &str) -> PathBuf {
171//         Self::default_root_directory()
172//             .join(id)
173//             .join(DEFAULT_CONFIG_DIR)
174//     }
175//
176//     fn default_data_directory(id: &str) -> PathBuf {
177//         Self::default_root_directory()
178//             .join(id)
179//             .join(DEFAULT_DATA_DIR)
180//     }
181//
182//     fn default_config_file_path(id: &str) -> PathBuf {
183//         Self::default_config_directory(id).join(Self::config_file_name())
184//     }
185//
186//     // We provide a second set of functions that tries to not panic.
187//
188//     fn try_default_root_directory() -> Option<PathBuf>;
189//
190//     fn try_default_config_directory(id: &str) -> Option<PathBuf> {
191//         Self::try_default_root_directory().map(|d| d.join(id).join(DEFAULT_CONFIG_DIR))
192//     }
193//
194//     fn try_default_data_directory(id: &str) -> Option<PathBuf> {
195//         Self::try_default_root_directory().map(|d| d.join(id).join(DEFAULT_DATA_DIR))
196//     }
197//
198//     fn try_default_config_file_path(id: &str) -> Option<PathBuf> {
199//         Self::try_default_config_directory(id).map(|d| d.join(Self::config_file_name()))
200//     }
201//
202//     fn root_directory(&self) -> PathBuf;
203//     fn config_directory(&self) -> PathBuf;
204//     fn data_directory(&self) -> PathBuf;
205//
206//     fn save_to_file(&self, custom_location: Option<PathBuf>) -> io::Result<()> {
207//         Ok(())
208//         // let reg = Handlebars::new();
209//         // // it's whoever is implementing the trait responsibility to make sure you can execute your own template on your data
210//         // let templated_config = reg.render_template(Self::template(), self).unwrap();
211//         //
212//         // // make sure the whole directory structure actually exists
213//         // match custom_location.clone() {
214//         //     Some(loc) => {
215//         //         if let Some(parent_dir) = loc.parent() {
216//         //             fs::create_dir_all(parent_dir)
217//         //         } else {
218//         //             Ok(())
219//         //         }
220//         //     }
221//         //     None => fs::create_dir_all(self.config_directory()),
222//         // }?;
223//         //
224//         // let location = custom_location
225//         //     .unwrap_or_else(|| self.config_directory().join(Self::config_file_name()));
226//         // log::info!("Configuration file will be saved to {:?}", location);
227//         //
228//         // cfg_if::cfg_if! {
229//         //     if #[cfg(unix)] {
230//         //         fs::write(location.clone(), templated_config)?;
231//         //         let mut perms = fs::metadata(location.clone())?.permissions();
232//         //         perms.set_mode(0o600);
233//         //         fs::set_permissions(location, perms)?;
234//         //     } else {
235//         //         fs::write(location, templated_config)?;
236//         //     }
237//         // }
238//         //
239//         // Ok(())
240//     }
241//
242//     fn load_from_file(id: &str) -> io::Result<Self> {
243//         let file = Self::default_config_file_path(id);
244//         log::trace!("Loading from file: {:#?}", file);
245//         let config_contents = fs::read_to_string(file)?;
246//
247//         toml::from_str(&config_contents)
248//             .map_err(|toml_err| io::Error::new(io::ErrorKind::Other, toml_err))
249//     }
250// }