From a3f4301f16a4576a5e400bb713ba25d5d1452eba Mon Sep 17 00:00:00 2001 From: Louis <836250617@qq.com> Date: Mon, 18 Dec 2023 10:51:51 +0800 Subject: [PATCH] error handling --- README.md | 8 +++--- examples/example.rs | 16 +++++------ src/config.rs | 35 +++++++++++++---------- src/encrypt_utils.rs | 29 ++++++++++++++----- src/error.rs | 26 ++++++++++++++++- src/lib.rs | 68 +++++++++++++++++++++----------------------- 6 files changed, 110 insertions(+), 72 deletions(-) diff --git a/README.md b/README.md index 2fffa25..128792d 100644 --- a/README.md +++ b/README.md @@ -95,8 +95,8 @@ Details here: [Example](example/eamples.rs) ## Usage -```rust -use encrypt_config::{Config, ConfigKey, ConfigResult, SecretSource}; +```no_run +use encrypt_config::{Config, ConfigResult, SecretSource}; #[derive(serde::Serialize, serde::Deserialize, Debug, PartialEq)] struct Bar(String); @@ -108,7 +108,7 @@ impl SecretSource for SecretSourceImpl { type Value = Bar; // The key to query from `Config` - fn source_name(&self) -> ConfigKey { + fn source_name(&self) -> String { "secret_test".to_owned() } @@ -132,7 +132,7 @@ let v: Bar = config.get("secret_test").unwrap(); assert_eq!(v, Bar("world".to_owned())); // `upgrade` will return a `Patch` -let patch = SecretSourceImpl.upgrade(&Bar("Louis".to_owned())).unwrap(); +let patch = SecretSourceImpl.upgrade(&Bar("Louis".to_owned())); // No change will happen until the `Patch` is applied patch.apply(&mut config).unwrap(); let v: Bar = config.get("secret_test").unwrap(); diff --git a/examples/example.rs b/examples/example.rs index 29d7507..fcb484d 100644 --- a/examples/example.rs +++ b/examples/example.rs @@ -1,11 +1,11 @@ -use encrypt_config::{Config, ConfigKey, ConfigResult, PersistSource, SecretSource, Source}; +use encrypt_config::{Config, PersistSource, SecretSource, Source}; struct NormalSource; impl Source for NormalSource { type Value = String; type Map = Vec<(String, Self::Value)>; - fn collect(&self) -> ConfigResult { + fn collect(&self) -> Result> { Ok(vec![("key".to_owned(), "value".to_owned())]) } } @@ -17,7 +17,7 @@ struct PersistSourceImpl; impl PersistSource for PersistSourceImpl { type Value = Foo; - fn source_name(&self) -> ConfigKey { + fn source_name(&self) -> String { "test".to_owned() } @@ -37,7 +37,7 @@ struct SecretSourceImpl; impl SecretSource for SecretSourceImpl { type Value = Bar; - fn source_name(&self) -> ConfigKey { + fn source_name(&self) -> String { "secret_test".to_owned() } @@ -61,17 +61,15 @@ fn config_tests() { assert_eq!(v, Foo("hello".to_owned())); let v: Bar = config.get("secret_test").unwrap(); assert_eq!(v, Bar("world".to_owned())); - let patch = NormalSource - .upgrade("key", &"new_value".to_owned()) - .unwrap(); + let patch = NormalSource.upgrade("key", &"new_value".to_owned()); patch.apply(&mut config).unwrap(); let v: String = config.get("key").unwrap(); assert_eq!(v, "new_value"); - let patch = PersistSourceImpl.upgrade(&Foo("hi".to_owned())).unwrap(); + let patch = PersistSourceImpl.upgrade(&Foo("hi".to_owned())); patch.apply(&mut config).unwrap(); let v: Foo = config.get("test").unwrap(); assert_eq!(v, Foo("hi".to_owned())); - let patch = SecretSourceImpl.upgrade(&Bar("Louis".to_owned())).unwrap(); + let patch = SecretSourceImpl.upgrade(&Bar("Louis".to_owned())); patch.apply(&mut config).unwrap(); let v: Bar = config.get("secret_test").unwrap(); assert_eq!(v, Bar("Louis".to_owned())); diff --git a/src/config.rs b/src/config.rs index 02387ec..fe2b7e2 100644 --- a/src/config.rs +++ b/src/config.rs @@ -1,11 +1,15 @@ //! # Config //! This module provides a `Config` struct that can be used to store configuration values. +use snafu::OptionExt; use std::collections::HashMap; -use crate::{encrypt_utils::Encrypter, ConfigResult, PersistSource, SecretSource, Source}; +use crate::{ + encrypt_utils::Encrypter, CollectFailed, ConfigNotFound, ConfigResult, PersistSource, + SecretSource, Source, +}; -pub type ConfigKey = String; +pub(crate) type ConfigKey = String; pub(crate) type ConfigValue = Vec; /// A struct that can be used to store configuration values. @@ -20,7 +24,7 @@ pub(crate) type ConfigValue = Vec; /// type Value = String; /// type Map = Vec<(String, Self::Value)>; /// -/// fn collect(&self) -> ConfigResult { +/// fn collect(&self) -> Result> { /// Ok(vec![("key".to_owned(), "value".to_owned())]) /// } /// } @@ -56,7 +60,9 @@ impl Config { K: AsRef, R: serde::de::DeserializeOwned, { - let serded = self.inner.get(key.as_ref()).unwrap(); + let serded = self.inner.get(key.as_ref()).context(ConfigNotFound { + key: key.as_ref().to_owned(), + })?; Ok(serde_json::from_slice(serded).unwrap()) } @@ -64,7 +70,8 @@ impl Config { /// The source must implement [`Source`] trait, which is for normal config that does not need to be encrypted or persisted. pub fn add_source(&mut self, source: impl Source) -> ConfigResult<()> { let map = source - .collect()? + .collect() + .map_err(|_| CollectFailed.build())? .into_iter() .map(|(k, v)| (k, serde_json::to_vec(&v).unwrap())); self.inner.extend(map); @@ -74,7 +81,7 @@ impl Config { /// Add a persist source to the config. /// The source must implement [`PersistSource`] trait, which is for config that needs to be persisted. pub fn add_persist_source(&mut self, source: impl PersistSource) -> ConfigResult<()> { - let patch = source.collect()?; + let patch = source.collect(); patch.apply(self)?; Ok(()) } @@ -82,7 +89,7 @@ impl Config { /// Add a secret source to the config. /// The source must implement [`SecretSource`] trait, which is for config that needs to be encrypted and persisted. pub fn add_secret_source(&mut self, source: impl SecretSource) -> ConfigResult<()> { - let patch = source.collect()?; + let patch = source.collect(); patch.apply(self)?; Ok(()) } @@ -93,7 +100,7 @@ impl Config { /// No change will happen until you call [`ConfigPatch::apply`]. /// # Example /// ```no_run -/// use encrypt_config::{Config, ConfigKey, PersistSource, ConfigResult}; +/// use encrypt_config::{Config, PersistSource, ConfigResult}; /// /// let mut config = Config::new("test"); /// @@ -104,7 +111,7 @@ impl Config { /// impl PersistSource for PersistSourceImpl { /// type Value = Foo; /// -/// fn source_name(&self) -> ConfigKey { +/// fn source_name(&self) -> String { /// "test".to_owned() /// } /// @@ -120,11 +127,10 @@ impl Config { /// config.add_persist_source(PersistSourceImpl).unwrap(); /// let v: Foo = config.get("test").unwrap(); /// assert_eq!(v, Foo("hello".to_owned())); -/// let patch = PersistSourceImpl.upgrade(&Foo("hi".to_owned())).unwrap(); +/// let patch = PersistSourceImpl.upgrade(&Foo("hi".to_owned())); /// patch.apply(&mut config).unwrap(); /// let v: Foo = config.get("test").unwrap(); /// assert_eq!(v, Foo("hi".to_owned())); -/// # std::fs::remove_file("tests/test").unwrap(); /// ``` pub struct ConfigPatch { key: ConfigKey, @@ -151,7 +157,7 @@ type Func = Box ConfigResult>; /// No change will happen until you call [`SecretConfigPatch::apply`]. /// # Example /// ```no_run -/// use encrypt_config::{Config, ConfigKey, SecretSource, ConfigResult}; +/// use encrypt_config::{Config, SecretSource, ConfigResult}; /// /// let mut config = Config::new("test"); /// @@ -162,7 +168,7 @@ type Func = Box ConfigResult>; /// impl SecretSource for SecretSourceImpl { /// type Value = Foo; /// -/// fn source_name(&self) -> ConfigKey { +/// fn source_name(&self) -> String { /// "secret_test".to_owned() /// } /// @@ -178,11 +184,10 @@ type Func = Box ConfigResult>; /// config.add_secret_source(SecretSourceImpl).unwrap(); /// let v: Foo = config.get("secret_test").unwrap(); /// assert_eq!(v, Foo("hello".to_owned())); -/// let patch = SecretSourceImpl.upgrade(&Foo("hi".to_owned())).unwrap(); +/// let patch = SecretSourceImpl.upgrade(&Foo("hi".to_owned())); /// patch.apply(&mut config).unwrap(); /// let v: Foo = config.get("secret_test").unwrap(); /// assert_eq!(v, Foo("hi".to_owned())); -/// # std::fs::remove_file("tests/secret_test").unwrap(); /// ``` pub struct SecretConfigPatch { key: ConfigKey, diff --git a/src/encrypt_utils.rs b/src/encrypt_utils.rs index 3883f91..706af80 100644 --- a/src/encrypt_utils.rs +++ b/src/encrypt_utils.rs @@ -16,14 +16,13 @@ impl Encrypter { pub(crate) fn new(config_name: impl AsRef) -> ConfigResult { let entry = keyring_entry(config_name); match entry.get_password() { - Ok(serded_enc) => Ok(serde_json::from_str(&serded_enc).unwrap()), - Err(_) => { + Ok(serded_enc) => Ok(serde_json::from_str(&serded_enc)?), + Err(keyring::Error::NoEntry) => { let new_enc = Encrypter::build(); - entry - .set_password(&serde_json::to_string(&new_enc).unwrap()) - .unwrap(); + entry.set_password(&serde_json::to_string(&new_enc).unwrap())?; Ok(new_enc) } + Err(e) => Err(e)?, } } @@ -43,6 +42,22 @@ impl Encrypter { // self.encrypt_serded(&origin) // } + /// This is used to encrypt seriliazed Value. + /// # Arguments + /// * origin - The returning of `serde_json::to_vec` + /// # Example + /// ```ignore + /// let foo = Foo::new(); + /// let serded = serde_json::to_vec(&foo)?; + /// let encrypter = Encrypter::new("test")?; + /// let encrypted = encrypter.encrypt_serded(serded)?; + /// ``` + /// # Question + /// Q: Why not use `Foo` as origin more conviniently? + /// + /// A: The user passes `&Foo` to [`SecretSource::upgrade`] to upgrade the config, which returns a [`SecretConfigPatch`], + /// containing a [`Func`] as its field. `Func`, which is a boxed closure, should take the ownership of `Foo` if directly use + /// it. To avoid this, and due to we need seriliaze it anyway, we just move its serded `Vec` into the closure. pub(crate) fn encrypt_serded(&self, origin: &[u8]) -> ConfigResult { let mut rng = rand::thread_rng(); let chunk_size = if cfg!(not(target_os = "windows")) { @@ -53,7 +68,7 @@ impl Encrypter { let pub_key = RsaPublicKey::from(&self.priv_key); let mut encrypted = vec![]; for c in origin.chunks(chunk_size) { - encrypted.extend(pub_key.encrypt(&mut rng, Pkcs1v15Encrypt, c).unwrap()); + encrypted.extend(pub_key.encrypt(&mut rng, Pkcs1v15Encrypt, c)?); } Ok(encrypted) } @@ -66,7 +81,7 @@ impl Encrypter { 128 }; for c in encrypted.chunks(chunk_size) { - decrypted.extend(self.priv_key.decrypt(Pkcs1v15Encrypt, c).unwrap()); + decrypted.extend(self.priv_key.decrypt(Pkcs1v15Encrypt, c)?); } Ok(decrypted) } diff --git a/src/error.rs b/src/error.rs index 1b58a49..e4bff2e 100644 --- a/src/error.rs +++ b/src/error.rs @@ -2,6 +2,30 @@ use snafu::Snafu; #[derive(Snafu, Debug)] #[snafu(visibility(pub(crate)), context(suffix(false)))] -pub enum ConfigError {} +pub enum ConfigError { + #[snafu(display("The key `{}` not found in Config", key))] + ConfigNotFound { key: String }, + #[snafu( + display("Failed to deseriliaze encrypter from keyring."), + context(false) + )] + LoadEncrypterFailed { source: serde_json::Error }, + #[snafu( + display( + "Keyring Error.\nThis error may caused by OS' secret manager, the rsa private key cannot be saved or read." + ), + context(false) + )] + KeyringError { source: keyring::Error }, + #[snafu( + display("Encryption Error. Cannot encrypt or decrypt.\nIf it's a decrypt error, maybe it's the private key stored in keyring being incorrect, modified or recreated."), + context(false) + )] + EncryptionError { source: rsa::Error }, + #[snafu(display("IO error. Cannot operate the file."), context(false))] + IoError { source: std::io::Error }, + #[snafu(display("Cannot collect from Source"))] + CollectFailed, +} pub type ConfigResult = Result; diff --git a/src/lib.rs b/src/lib.rs index 681587f..0e7f709 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,11 +1,10 @@ -//! # encrypt-config -//! A crate helping managing, persisting, encrypting configs. +#![doc = include_str!("../README.md")] mod config; mod encrypt_utils; mod error; -pub use config::{Config, ConfigKey, ConfigPatch, SecretConfigPatch}; +pub use config::{Config, ConfigPatch, SecretConfigPatch}; pub use error::*; use encrypt_utils::Encrypter; @@ -17,13 +16,12 @@ pub trait Source { type Value: serde::Serialize; type Map: IntoIterator; - fn collect(&self) -> ConfigResult; + fn collect(&self) -> Result>; - fn upgrade(&self, key: impl AsRef, new_value: &Self::Value) -> ConfigResult { + fn upgrade(&self, key: impl AsRef, new_value: &Self::Value) -> ConfigPatch { let serded = serde_json::to_vec(&new_value).unwrap(); let func = Box::new(move || Ok(serded)); - let patch = ConfigPatch::new(key.as_ref().to_owned(), func); - Ok(patch) + ConfigPatch::new(key.as_ref().to_owned(), func) } } @@ -33,39 +31,39 @@ pub trait Source { pub trait PersistSource { type Value: serde::Serialize + serde::de::DeserializeOwned; - fn source_name(&self) -> ConfigKey; + fn source_name(&self) -> String; /// This will be used to initialize the source if not existing. fn default(&self) -> Self::Value; #[cfg(feature = "default_config_dir")] fn path(&self) -> std::path::PathBuf { - dirs_next::config_dir().unwrap().join(self.source_name()) + dirs_next::config_dir() + .expect("Default config dir unknown, turn off feature `default_config_dir`") + .join(self.source_name()) } #[cfg(not(feature = "default_config_dir"))] fn path(&self) -> std::path::PathBuf; - fn collect(&self) -> ConfigResult { + fn collect(&self) -> ConfigPatch { match std::fs::read(self.path()) { Ok(serded) => { let func = Box::new(move || Ok(serded)); - let patch = ConfigPatch::new(self.source_name(), func); - Ok(patch) + ConfigPatch::new(self.source_name(), func) } - Err(_) => Ok(self.upgrade(&self.default()).unwrap()), + Err(_) => self.upgrade(&self.default()), } } - fn upgrade(&self, new_value: &Self::Value) -> ConfigResult { + fn upgrade(&self, new_value: &Self::Value) -> ConfigPatch { let path = self.path(); let serded = serde_json::to_vec(new_value).unwrap(); let func = Box::new(move || { - std::fs::write(path, &serded).unwrap(); + std::fs::write(path, &serded)?; Ok(serded) }); - let patch = ConfigPatch::new(self.source_name(), func); - Ok(patch) + ConfigPatch::new(self.source_name(), func) } } @@ -75,43 +73,43 @@ pub trait PersistSource { pub trait SecretSource { type Value: serde::Serialize + serde::de::DeserializeOwned; - fn source_name(&self) -> ConfigKey; + fn source_name(&self) -> String; /// This will be used to initialize the source if not existing. fn default(&self) -> Self::Value; #[cfg(feature = "default_config_dir")] fn path(&self) -> std::path::PathBuf { - dirs_next::config_dir().unwrap().join(self.source_name()) + dirs_next::config_dir() + .expect("Default config dir unknown, turn off feature `default_config_dir`") + .join(self.source_name()) } #[cfg(not(feature = "default_config_dir"))] fn path(&self) -> std::path::PathBuf; - fn collect(&self) -> ConfigResult { + fn collect(&self) -> SecretConfigPatch { match std::fs::read(self.path()) { Ok(encrypted) => { let func = Box::new(move |encrypter: &Encrypter| { let serded = encrypter.decrypt(&encrypted).unwrap(); Ok(serded) }); - let patch = SecretConfigPatch::new(self.source_name(), func); - Ok(patch) + SecretConfigPatch::new(self.source_name(), func) } - Err(_) => Ok(self.upgrade(&self.default()).unwrap()), + Err(_) => self.upgrade(&self.default()), } } - fn upgrade(&self, new_value: &Self::Value) -> ConfigResult { + fn upgrade(&self, new_value: &Self::Value) -> SecretConfigPatch { let path = self.path(); let new_value = serde_json::to_vec(new_value).unwrap(); let func = Box::new(move |encrypter: &Encrypter| { - let encrypted = encrypter.encrypt_serded(&new_value).unwrap(); - std::fs::write(path, encrypted).unwrap(); + let encrypted = encrypter.encrypt_serded(&new_value)?; + std::fs::write(path, encrypted)?; Ok(new_value) }); - let patch = SecretConfigPatch::new(self.source_name(), func); - Ok(patch) + SecretConfigPatch::new(self.source_name(), func) } } @@ -124,7 +122,7 @@ mod tests { type Value = String; type Map = Vec<(String, Self::Value)>; - fn collect(&self) -> ConfigResult { + fn collect(&self) -> Result> { Ok(vec![("key".to_owned(), "value".to_owned())]) } } @@ -136,7 +134,7 @@ mod tests { impl PersistSource for PersistSourceImpl { type Value = Foo; - fn source_name(&self) -> ConfigKey { + fn source_name(&self) -> String { "test".to_owned() } @@ -156,7 +154,7 @@ mod tests { impl SecretSource for SecretSourceImpl { type Value = Bar; - fn source_name(&self) -> ConfigKey { + fn source_name(&self) -> String { "secret_test".to_owned() } @@ -181,17 +179,15 @@ mod tests { assert_eq!(v, Foo("hello".to_owned())); let v: Bar = config.get("secret_test").unwrap(); assert_eq!(v, Bar("world".to_owned())); - let patch = NormalSource - .upgrade("key", &"new_value".to_owned()) - .unwrap(); + let patch = NormalSource.upgrade("key", &"new_value".to_owned()); patch.apply(&mut config).unwrap(); let v: String = config.get("key").unwrap(); assert_eq!(v, "new_value"); - let patch = PersistSourceImpl.upgrade(&Foo("hi".to_owned())).unwrap(); + let patch = PersistSourceImpl.upgrade(&Foo("hi".to_owned())); patch.apply(&mut config).unwrap(); let v: Foo = config.get("test").unwrap(); assert_eq!(v, Foo("hi".to_owned())); - let patch = SecretSourceImpl.upgrade(&Bar("Louis".to_owned())).unwrap(); + let patch = SecretSourceImpl.upgrade(&Bar("Louis".to_owned())); patch.apply(&mut config).unwrap(); let v: Bar = config.get("secret_test").unwrap(); assert_eq!(v, Bar("Louis".to_owned()));