Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

error handling #6

Merged
merged 1 commit into from
Dec 18, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -95,8 +95,8 @@ Details here: [Example](example/eamples.rs)

<!-- USAGE EXAMPLES -->
## 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);
Expand All @@ -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()
}

Expand All @@ -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();
Expand Down
16 changes: 7 additions & 9 deletions examples/example.rs
Original file line number Diff line number Diff line change
@@ -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<Self::Map> {
fn collect(&self) -> Result<Self::Map, Box<dyn std::error::Error>> {
Ok(vec![("key".to_owned(), "value".to_owned())])
}
}
Expand All @@ -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()
}

Expand All @@ -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()
}

Expand All @@ -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()));
Expand Down
35 changes: 20 additions & 15 deletions src/config.rs
Original file line number Diff line number Diff line change
@@ -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<u8>;

/// A struct that can be used to store configuration values.
Expand All @@ -20,7 +24,7 @@ pub(crate) type ConfigValue = Vec<u8>;
/// type Value = String;
/// type Map = Vec<(String, Self::Value)>;
///
/// fn collect(&self) -> ConfigResult<Self::Map> {
/// fn collect(&self) -> Result<Self::Map, Box<dyn std::error::Error>> {
/// Ok(vec![("key".to_owned(), "value".to_owned())])
/// }
/// }
Expand Down Expand Up @@ -56,15 +60,18 @@ impl Config {
K: AsRef<str>,
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())
}

/// Add a source to the 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);
Expand All @@ -74,15 +81,15 @@ 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(())
}

/// 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(())
}
Expand All @@ -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");
///
Expand All @@ -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()
/// }
///
Expand All @@ -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,
Expand All @@ -151,7 +157,7 @@ type Func = Box<dyn FnOnce(&Encrypter) -> ConfigResult<ConfigValue>>;
/// 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");
///
Expand All @@ -162,7 +168,7 @@ type Func = Box<dyn FnOnce(&Encrypter) -> ConfigResult<ConfigValue>>;
/// impl SecretSource for SecretSourceImpl {
/// type Value = Foo;
///
/// fn source_name(&self) -> ConfigKey {
/// fn source_name(&self) -> String {
/// "secret_test".to_owned()
/// }
///
Expand All @@ -178,11 +184,10 @@ type Func = Box<dyn FnOnce(&Encrypter) -> ConfigResult<ConfigValue>>;
/// 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,
Expand Down
29 changes: 22 additions & 7 deletions src/encrypt_utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,13 @@ impl Encrypter {
pub(crate) fn new(config_name: impl AsRef<str>) -> ConfigResult<Self> {
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)?,
}
}

Expand All @@ -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<u8>` into the closure.
pub(crate) fn encrypt_serded(&self, origin: &[u8]) -> ConfigResult<Encrypted> {
let mut rng = rand::thread_rng();
let chunk_size = if cfg!(not(target_os = "windows")) {
Expand All @@ -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)
}
Expand All @@ -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)
}
Expand Down
26 changes: 25 additions & 1 deletion src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<T> = Result<T, ConfigError>;
Loading
Loading