mirror of
https://github.com/rustdesk/rustdesk.git
synced 2026-04-05 17:41:30 +03:00
move confy here
This commit is contained in:
300
libs/confy/src/lib.rs
Normal file
300
libs/confy/src/lib.rs
Normal file
@@ -0,0 +1,300 @@
|
||||
//! Zero-boilerplate configuration management
|
||||
//!
|
||||
//! ## Why?
|
||||
//!
|
||||
//! There are a lot of different requirements when
|
||||
//! selecting, loading and writing a config,
|
||||
//! depending on the operating system and other
|
||||
//! environment factors.
|
||||
//!
|
||||
//! In many applications this burden is left to you,
|
||||
//! the developer of an application, to figure out
|
||||
//! where to place the configuration files.
|
||||
//!
|
||||
//! This is where `confy` comes in.
|
||||
//!
|
||||
//! ## Idea
|
||||
//!
|
||||
//! `confy` takes care of figuring out operating system
|
||||
//! specific and environment paths before reading and
|
||||
//! writing a configuration.
|
||||
//!
|
||||
//! It gives you easy access to a configuration file
|
||||
//! which is mirrored into a Rust `struct` via [serde].
|
||||
//! This way you only need to worry about the layout of
|
||||
//! your configuration, not where and how to store it.
|
||||
//!
|
||||
//! [serde]: https://docs.rs/crates/serde
|
||||
//!
|
||||
//! `confy` uses the [`Default`] trait in Rust to automatically
|
||||
//! create a new configuration, if none is available to read
|
||||
//! from yet.
|
||||
//! This means that you can simply assume your application
|
||||
//! to have a configuration, which will be created with
|
||||
//! default values of your choosing, without requiring
|
||||
//! any special logic to handle creation.
|
||||
//!
|
||||
//! [`Default`]: https://doc.rust-lang.org/std/default/trait.Default.html
|
||||
//!
|
||||
//! ```rust,no_run
|
||||
//! use serde_derive::{Serialize, Deserialize};
|
||||
//!
|
||||
//! #[derive(Serialize, Deserialize)]
|
||||
//! struct MyConfig {
|
||||
//! version: u8,
|
||||
//! api_key: String,
|
||||
//! }
|
||||
//!
|
||||
//! /// `MyConfig` implements `Default`
|
||||
//! impl ::std::default::Default for MyConfig {
|
||||
//! fn default() -> Self { Self { version: 0, api_key: "".into() } }
|
||||
//! }
|
||||
//!
|
||||
//! fn main() -> Result<(), confy::ConfyError> {
|
||||
//! let cfg = confy::load("my-app-name")?;
|
||||
//! Ok(())
|
||||
//! }
|
||||
//! ```
|
||||
//!
|
||||
//! Updating the configuration is then done via the [`store`] function.
|
||||
//!
|
||||
//! [`store`]: fn.store.html
|
||||
//!
|
||||
|
||||
mod utils;
|
||||
use utils::*;
|
||||
|
||||
use directories::ProjectDirs;
|
||||
use serde::{de::DeserializeOwned, Serialize};
|
||||
use std::error::Error;
|
||||
use std::fmt;
|
||||
use std::fs::{self, File, OpenOptions};
|
||||
use std::io::{ErrorKind::NotFound, Write};
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
#[cfg(not(any(feature = "toml_conf", feature = "yaml_conf")))]
|
||||
compile_error!("Exactly one config language feature must be enabled to use \
|
||||
confy. Please enable one of either the `toml_conf` or `yaml_conf` \
|
||||
features.");
|
||||
|
||||
#[cfg(all(feature = "toml_conf", feature = "yaml_conf"))]
|
||||
compile_error!("Exactly one config language feature must be enabled to compile \
|
||||
confy. Please disable one of either the `toml_conf` or `yaml_conf` features. \
|
||||
NOTE: `toml_conf` is a default feature, so disabling it might mean switching off \
|
||||
default features for confy in your Cargo.toml");
|
||||
|
||||
#[cfg(all(feature = "toml_conf", not(feature = "yaml_conf")))]
|
||||
const EXTENSION: &str = "toml";
|
||||
|
||||
#[cfg(feature = "yaml_conf")]
|
||||
const EXTENSION: &str = "yml";
|
||||
|
||||
/// Load an application configuration from disk
|
||||
///
|
||||
/// A new configuration file is created with default values if none
|
||||
/// exists.
|
||||
///
|
||||
/// Errors that are returned from this function are I/O related,
|
||||
/// for example if the writing of the new configuration fails
|
||||
/// or `confy` encounters an operating system or environment
|
||||
/// that it does not support.
|
||||
///
|
||||
/// **Note:** The type of configuration needs to be declared in some way
|
||||
/// that is inferrable by the compiler. Also note that your
|
||||
/// configuration needs to implement `Default`.
|
||||
///
|
||||
/// ```rust,no_run
|
||||
/// # use confy::ConfyError;
|
||||
/// # use serde_derive::{Serialize, Deserialize};
|
||||
/// # fn main() -> Result<(), ConfyError> {
|
||||
/// #[derive(Default, Serialize, Deserialize)]
|
||||
/// struct MyConfig {}
|
||||
///
|
||||
/// let cfg: MyConfig = confy::load("my-app-name")?;
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// ```
|
||||
pub fn load<T: Serialize + DeserializeOwned + Default>(name: &str) -> Result<T, ConfyError> {
|
||||
let project = ProjectDirs::from("rs", "", name).ok_or(ConfyError::BadConfigDirectoryStr)?;
|
||||
|
||||
let config_dir_str = get_configuration_directory_str(&project)?;
|
||||
|
||||
let path: PathBuf = [config_dir_str, &format!("{}.{}", name, EXTENSION)].iter().collect();
|
||||
|
||||
load_path(path)
|
||||
}
|
||||
|
||||
/// Load an application configuration from a specified path.
|
||||
///
|
||||
/// This is an alternate version of [`load`] that allows the specification of
|
||||
/// an aritrary path instead of a system one. For more information on errors
|
||||
/// and behavior, see [`load`]'s documentation.
|
||||
///
|
||||
/// [`load`]: fn.load.html
|
||||
pub fn load_path<T: Serialize + DeserializeOwned + Default>(path: impl AsRef<Path>) -> Result<T, ConfyError> {
|
||||
match File::open(&path) {
|
||||
Ok(mut cfg) => {
|
||||
let cfg_string = cfg
|
||||
.get_string()
|
||||
.map_err(ConfyError::ReadConfigurationFileError)?;
|
||||
|
||||
#[cfg(feature = "toml_conf")] {
|
||||
let cfg_data = toml::from_str(&cfg_string);
|
||||
cfg_data.map_err(ConfyError::BadTomlData)
|
||||
}
|
||||
#[cfg(feature = "yaml_conf")] {
|
||||
let cfg_data = serde_yaml::from_str(&cfg_string);
|
||||
cfg_data.map_err(ConfyError::BadYamlData)
|
||||
}
|
||||
|
||||
}
|
||||
Err(ref e) if e.kind() == NotFound => {
|
||||
if let Some(parent) = path.as_ref().parent() {
|
||||
fs::create_dir_all(parent)
|
||||
.map_err(ConfyError::DirectoryCreationFailed)?;
|
||||
}
|
||||
store_path(path, T::default())?;
|
||||
Ok(T::default())
|
||||
}
|
||||
Err(e) => Err(ConfyError::GeneralLoadError(e)),
|
||||
}
|
||||
}
|
||||
|
||||
/// The errors the confy crate can encounter.
|
||||
#[derive(Debug)]
|
||||
pub enum ConfyError {
|
||||
#[cfg(feature = "toml_conf")]
|
||||
BadTomlData(toml::de::Error),
|
||||
|
||||
#[cfg(feature = "yaml_conf")]
|
||||
BadYamlData(serde_yaml::Error),
|
||||
|
||||
DirectoryCreationFailed(std::io::Error),
|
||||
GeneralLoadError(std::io::Error),
|
||||
BadConfigDirectoryStr,
|
||||
|
||||
#[cfg(feature = "toml_conf")]
|
||||
SerializeTomlError(toml::ser::Error),
|
||||
|
||||
#[cfg(feature = "yaml_conf")]
|
||||
SerializeYamlError(serde_yaml::Error),
|
||||
|
||||
WriteConfigurationFileError(std::io::Error),
|
||||
ReadConfigurationFileError(std::io::Error),
|
||||
OpenConfigurationFileError(std::io::Error),
|
||||
}
|
||||
|
||||
impl fmt::Display for ConfyError {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
match self {
|
||||
|
||||
#[cfg(feature = "toml_conf")]
|
||||
ConfyError::BadTomlData(e) => write!(f, "Bad TOML data: {}", e),
|
||||
#[cfg(feature = "toml_conf")]
|
||||
ConfyError::SerializeTomlError(_) => write!(f, "Failed to serialize configuration data into TOML."),
|
||||
|
||||
#[cfg(feature = "yaml_conf")]
|
||||
ConfyError::BadYamlData(e) => write!(f, "Bad YAML data: {}", e),
|
||||
#[cfg(feature = "yaml_conf")]
|
||||
ConfyError::SerializeYamlError(_) => write!(f, "Failed to serialize configuration data into YAML."),
|
||||
|
||||
ConfyError::DirectoryCreationFailed(e) => write!(f, "Failed to create directory: {}", e),
|
||||
ConfyError::GeneralLoadError(_) => write!(f, "Failed to load configuration file."),
|
||||
ConfyError::BadConfigDirectoryStr => write!(f, "Failed to convert directory name to str."),
|
||||
ConfyError::WriteConfigurationFileError(_) => write!(f, "Failed to write configuration file."),
|
||||
ConfyError::ReadConfigurationFileError(_) => write!(f, "Failed to read configuration file."),
|
||||
ConfyError::OpenConfigurationFileError(_) => write!(f, "Failed to open configuration file."),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Error for ConfyError {}
|
||||
|
||||
/// Save changes made to a configuration object
|
||||
///
|
||||
/// This function will update a configuration,
|
||||
/// with the provided values, and create a new one,
|
||||
/// if none exists.
|
||||
///
|
||||
/// You can also use this function to create a new configuration
|
||||
/// with different initial values than which are provided
|
||||
/// by your `Default` trait implementation, or if your
|
||||
/// configuration structure _can't_ implement `Default`.
|
||||
///
|
||||
/// ```rust,no_run
|
||||
/// # use serde_derive::{Serialize, Deserialize};
|
||||
/// # use confy::ConfyError;
|
||||
/// # fn main() -> Result<(), ConfyError> {
|
||||
/// #[derive(Serialize, Deserialize)]
|
||||
/// struct MyConf {}
|
||||
///
|
||||
/// let my_cfg = MyConf {};
|
||||
/// confy::store("my-app-name", my_cfg)?;
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// ```
|
||||
///
|
||||
/// Errors returned are I/O errors related to not being
|
||||
/// able to write the configuration file or if `confy`
|
||||
/// encounters an operating system or environment it does
|
||||
/// not support.
|
||||
pub fn store<T: Serialize>(name: &str, cfg: T) -> Result<(), ConfyError> {
|
||||
let project = ProjectDirs::from("rs", "", name).ok_or(ConfyError::BadConfigDirectoryStr)?;
|
||||
fs::create_dir_all(project.config_dir()).map_err(ConfyError::DirectoryCreationFailed)?;
|
||||
|
||||
let config_dir_str = get_configuration_directory_str(&project)?;
|
||||
|
||||
let path: PathBuf = [config_dir_str, &format!("{}.{}", name, EXTENSION)].iter().collect();
|
||||
|
||||
store_path(path, cfg)
|
||||
}
|
||||
|
||||
/// Save changes made to a configuration object at a specified path
|
||||
///
|
||||
/// This is an alternate version of [`store`] that allows the specification of
|
||||
/// an aritrary path instead of a system one. For more information on errors
|
||||
/// and behavior, see [`store`]'s documentation.
|
||||
///
|
||||
/// [`store`]: fn.store.html
|
||||
pub fn store_path<T: Serialize>(path: impl AsRef<Path>, cfg: T) -> Result<(), ConfyError> {
|
||||
let path = path.as_ref();
|
||||
let mut path_tmp = path.to_path_buf();
|
||||
use std::time::{SystemTime, UNIX_EPOCH};
|
||||
let mut i = 0;
|
||||
loop {
|
||||
i += 1;
|
||||
path_tmp.set_extension(SystemTime::now().duration_since(UNIX_EPOCH).map(|x| x.as_nanos()).unwrap_or(i).to_string());
|
||||
if !path_tmp.exists() {
|
||||
break;
|
||||
}
|
||||
}
|
||||
let mut f = OpenOptions::new()
|
||||
.write(true)
|
||||
.create(true)
|
||||
.truncate(true)
|
||||
.open(&path_tmp)
|
||||
.map_err(ConfyError::OpenConfigurationFileError)?;
|
||||
|
||||
let s;
|
||||
#[cfg(feature = "toml_conf")] {
|
||||
s = toml::to_string(&cfg).map_err(ConfyError::SerializeTomlError)?;
|
||||
}
|
||||
#[cfg(feature = "yaml_conf")] {
|
||||
s = serde_yaml::to_string(&cfg).map_err(ConfyError::SerializeYamlError)?;
|
||||
}
|
||||
|
||||
f.write_all(s.as_bytes())
|
||||
.map_err(ConfyError::WriteConfigurationFileError)?;
|
||||
std::fs::rename(path_tmp, path)
|
||||
.map_err(ConfyError::WriteConfigurationFileError)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn get_configuration_directory_str(project: &ProjectDirs) -> Result<&str, ConfyError> {
|
||||
let config_dir_option = project.config_dir().to_str();
|
||||
|
||||
match config_dir_option {
|
||||
Some(x) => Ok(x),
|
||||
None => Err(ConfyError::BadConfigDirectoryStr),
|
||||
}
|
||||
}
|
||||
16
libs/confy/src/utils.rs
Normal file
16
libs/confy/src/utils.rs
Normal file
@@ -0,0 +1,16 @@
|
||||
//! Some storage utilities
|
||||
|
||||
use std::fs::File;
|
||||
use std::io::{Error as IoError, Read};
|
||||
|
||||
pub trait CheckedStringRead {
|
||||
fn get_string(&mut self) -> Result<String, IoError>;
|
||||
}
|
||||
|
||||
impl CheckedStringRead for File {
|
||||
fn get_string(&mut self) -> Result<String, IoError> {
|
||||
let mut s = String::new();
|
||||
self.read_to_string(&mut s)?;
|
||||
Ok(s)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user