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// }