From 40425233714ee9ff3e1a4884518b400f113c16d2 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E7=8E=8B=E7=BF=BC=E7=BF=94?= <836250617@qq.com>
Date: Mon, 18 Dec 2023 12:54:26 +0800
Subject: [PATCH] alpha1 (#10)
* Update issue templates (#1)
* Create test.yml (#2)
* Create release.yml (#3)
* Update test.yml (#4)
* test
* error handling (#6)
* 7 action cache (#8)
* action (#9)
* test
* test
* test
* test
* test
* test
* test
* test
* modified: README.md description
---
.github/ISSUE_TEMPLATE/bug_report.md | 27 +++++
.github/ISSUE_TEMPLATE/feature_request.md | 20 ++++
.github/workflows/release.yml | 45 +++++++
.github/workflows/test.yml | 52 ++++++++
Cargo.toml | 2 +-
README.md | 10 +-
tests/tests.rs => examples/example.rs | 23 ++--
src/config.rs | 50 ++++----
src/encrypt_utils.rs | 29 +++--
src/error.rs | 26 +++-
src/lib.rs | 137 +++++++++++++++++-----
tests/.gitkeep | 1 +
12 files changed, 346 insertions(+), 76 deletions(-)
create mode 100644 .github/ISSUE_TEMPLATE/bug_report.md
create mode 100644 .github/ISSUE_TEMPLATE/feature_request.md
create mode 100644 .github/workflows/release.yml
create mode 100644 .github/workflows/test.yml
rename tests/tests.rs => examples/example.rs (84%)
create mode 100644 tests/.gitkeep
diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md
new file mode 100644
index 0000000..8ad4866
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/bug_report.md
@@ -0,0 +1,27 @@
+---
+name: Bug report
+about: Create a report to help us improve
+title: "[Bug]"
+labels: ''
+assignees: ''
+
+---
+
+**Describe the bug**
+A clear and concise description of what the bug is.
+
+**To Reproduce**
+Steps to reproduce the behavior, or a Minimal Reproducible Code Snippet.
+
+**Expected behavior**
+A clear and concise description of what you expected to happen.
+
+**Screenshots**
+If applicable, add screenshots to help explain your problem.
+
+**Information (please complete the following information):**
+OS: [e.g. macOS]
+Output of `rustc -V`
+
+**Additional context**
+Add any other context about the problem here.
diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md
new file mode 100644
index 0000000..1ce1302
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/feature_request.md
@@ -0,0 +1,20 @@
+---
+name: Feature request
+about: Suggest an idea for this project
+title: "[New Feature]"
+labels: ''
+assignees: ''
+
+---
+
+**Is your feature request related to a problem? Please describe.**
+A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
+
+**Describe the solution you'd like**
+A clear and concise description of what you want to happen.
+
+**Describe alternatives you've considered**
+A clear and concise description of any alternative solutions or features you've considered.
+
+**Additional context**
+Add any other context or screenshots about the feature request here.
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
new file mode 100644
index 0000000..72431cc
--- /dev/null
+++ b/.github/workflows/release.yml
@@ -0,0 +1,45 @@
+name: Rust
+
+on:
+ pull_request:
+ branches: [release]
+ types: [closed]
+
+env:
+ CARGO_TERM_COLOR: always
+
+jobs:
+ publish:
+ runs-on: ubuntu-latest
+
+ steps:
+ - uses: actions/checkout@v3
+
+ - name: Extract version
+ id: extract-version
+ run: echo "version=$(grep -oP '^version = "\K[^"]+' Cargo.toml | awk '{$1=$1;print}')" >> $GITHUB_OUTPUT
+
+ - name: Cache restore
+ uses: actions/cache/restore@v3
+ id: cache-cargo-restore
+ with:
+ path: |
+ ~/.cargo/bin/
+ ~/.cargo/registry/index/
+ ~/.cargo/registry/cache/
+ ~/.cargo/git/db/
+ target/
+ key: ${{ runner.os }}-cargo-${{ steps.extract-version.outputs.version }}
+
+ - name: Run clippy
+ run: cargo clippy --all-features --all-targets -- -D warnings
+ - name: Build
+ run: cargo build
+ - name: Run tests
+ run: cargo test
+
+ - name: publish crates
+ uses: katyo/publish-crates@v2
+ with:
+ # crates.io registry token
+ registry-token: ${{ secrets.crates_io }}
diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
new file mode 100644
index 0000000..eae471e
--- /dev/null
+++ b/.github/workflows/test.yml
@@ -0,0 +1,52 @@
+name: Rust
+
+on:
+ pull_request:
+ branches: [dev]
+ types: [opened]
+
+env:
+ CARGO_TERM_COLOR: always
+
+jobs:
+ test:
+ runs-on: ubuntu-latest
+
+ steps:
+ - uses: actions/checkout@v3
+
+ - name: Extract version
+ id: extract-version
+ run: echo "version=$(grep -oP '^version = "\K[^"]+' Cargo.toml | awk '{$1=$1;print}')" >> $GITHUB_OUTPUT
+
+ - name: Cache restore
+ uses: actions/cache/restore@v3
+ id: cache-cargo-restore
+ with:
+ path: |
+ ~/.cargo/bin/
+ ~/.cargo/registry/index/
+ ~/.cargo/registry/cache/
+ ~/.cargo/git/db/
+ target/
+ key: ${{ runner.os }}-cargo-${{ steps.extract-version.outputs.version }}
+
+ - name: Run clippy
+ run: cargo clippy --all-features --all-targets -- -D warnings
+ - name: Build
+ run: cargo build
+ - name: Run tests
+ run: cargo test
+
+ - name: Cache save
+ if: steps.cache-cargo-restore.outputs.cache-hit != 'true'
+ uses: actions/cache/save@v3
+ id: cache-cargo-save
+ with:
+ path: |
+ ~/.cargo/bin/
+ ~/.cargo/registry/index/
+ ~/.cargo/registry/cache/
+ ~/.cargo/git/db/
+ target/
+ key: ${{ steps.cache-cargo-restore.outputs.cache-primary-key }}
diff --git a/Cargo.toml b/Cargo.toml
index adc4ca2..b9c7062 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -2,7 +2,7 @@
name = "encrypt_config"
version = "0.0.1-alpha1"
authors = ["Louis <836250617@qq.com>"]
-description = "A config manager, supporting encryption."
+description = "A rust crate to manage, persist and encrypt your configurations."
license = "MIT"
edition = "2021"
repository = "https://github.com/kingwingfly/encrypt-config"
diff --git a/README.md b/README.md
index b368f20..cba206c 100644
--- a/README.md
+++ b/README.md
@@ -87,7 +87,7 @@ A rust crate to manage, persist, encrypt configurations.
## Getting Started
-Details here: [Example](tests/tests.rs)
+Details here: [Example](example/eamples.rs)
(back to top)
@@ -95,8 +95,8 @@ Details here: [Example](tests/tests.rs)
## Usage
-```rust
-use encrypt_config::{Config, ConfigKey, ConfigResult, SecretSource};
+```rust 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/tests/tests.rs b/examples/example.rs
similarity index 84%
rename from tests/tests.rs
rename to examples/example.rs
index 63fc0c5..fcb484d 100644
--- a/tests/tests.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()
}
@@ -50,8 +50,7 @@ impl SecretSource for SecretSourceImpl {
}
}
-#[test]
-fn source_test() {
+fn config_tests() {
let mut config = Config::new("test");
config.add_source(NormalSource).unwrap();
config.add_persist_source(PersistSourceImpl).unwrap();
@@ -62,20 +61,22 @@ fn source_test() {
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()));
std::fs::remove_file("tests/secret_test").unwrap();
std::fs::remove_file("tests/test").unwrap();
}
+
+fn main() {
+ config_tests();
+}
diff --git a/src/config.rs b/src/config.rs
index 7bc9602..fe2b7e2 100644
--- a/src/config.rs
+++ b/src/config.rs
@@ -1,16 +1,20 @@
//! # 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.
/// # Example
-/// ```
+/// ```no_run
/// use encrypt_config::{Config, Source, ConfigResult};
///
/// let mut config = Config::new("test");
@@ -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(())
}
@@ -92,8 +99,8 @@ impl Config {
/// You can get a `ConfigPatch` by calling [`PersistSource::upgrade`], and apply it by calling [`ConfigPatch::apply`] to a config.
/// No change will happen until you call [`ConfigPatch::apply`].
/// # Example
-/// ```rust
-/// use encrypt_config::{Config, ConfigKey, PersistSource, ConfigResult};
+/// ```no_run
+/// 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,
@@ -144,12 +150,14 @@ impl ConfigPatch {
}
}
+type Func = Box ConfigResult>;
+
/// A patch that can be used to modify the config.
/// You can get a `SecretConfigPatch` by calling [`SecretSource::upgrade`], and apply it by calling [`SecretConfigPatch::apply`] to a config.
/// No change will happen until you call [`SecretConfigPatch::apply`].
/// # Example
-/// ```rust
-/// use encrypt_config::{Config, ConfigKey, SecretSource, ConfigResult};
+/// ```no_run
+/// use encrypt_config::{Config, SecretSource, ConfigResult};
///
/// let mut config = Config::new("test");
///
@@ -160,7 +168,7 @@ impl ConfigPatch {
/// impl SecretSource for SecretSourceImpl {
/// type Value = Foo;
///
-/// fn source_name(&self) -> ConfigKey {
+/// fn source_name(&self) -> String {
/// "secret_test".to_owned()
/// }
///
@@ -176,22 +184,18 @@ impl ConfigPatch {
/// 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,
- func: Box ConfigResult>,
+ func: Func,
}
impl SecretConfigPatch {
- pub(crate) fn new(
- key: ConfigKey,
- func: Box ConfigResult>>,
- ) -> Self {
+ pub(crate) fn new(key: ConfigKey, func: Func) -> Self {
Self { key, func }
}
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 8bebe28..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,42 +73,125 @@ 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)
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ struct NormalSource;
+ impl Source for NormalSource {
+ type Value = String;
+ type Map = Vec<(String, Self::Value)>;
+
+ fn collect(&self) -> Result> {
+ Ok(vec![("key".to_owned(), "value".to_owned())])
+ }
+ }
+
+ #[derive(serde::Serialize, serde::Deserialize, Debug, PartialEq)]
+ struct Foo(String);
+
+ struct PersistSourceImpl;
+ impl PersistSource for PersistSourceImpl {
+ type Value = Foo;
+
+ fn source_name(&self) -> String {
+ "test".to_owned()
+ }
+
+ fn default(&self) -> Self::Value {
+ Foo("hello".to_owned())
+ }
+
+ fn path(&self) -> std::path::PathBuf {
+ std::path::PathBuf::from("tests").join(self.source_name())
+ }
+ }
+
+ #[derive(serde::Serialize, serde::Deserialize, Debug, PartialEq)]
+ struct Bar(String);
+
+ struct SecretSourceImpl;
+ impl SecretSource for SecretSourceImpl {
+ type Value = Bar;
+
+ fn source_name(&self) -> String {
+ "secret_test".to_owned()
+ }
+
+ fn default(&self) -> Self::Value {
+ Bar("world".to_owned())
+ }
+
+ fn path(&self) -> std::path::PathBuf {
+ std::path::PathBuf::from("tests").join(self.source_name())
+ }
+ }
+
+ #[test]
+ fn config_tests() {
+ let mut config = Config::new("test");
+ config.add_source(NormalSource).unwrap();
+ config.add_persist_source(PersistSourceImpl).unwrap();
+ config.add_secret_source(SecretSourceImpl).unwrap();
+ let v: String = config.get("key").unwrap();
+ assert_eq!(v, "value");
+ let v: Foo = config.get("test").unwrap();
+ 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());
+ 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()));
+ 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()));
+ patch.apply(&mut config).unwrap();
+ let v: Bar = config.get("secret_test").unwrap();
+ assert_eq!(v, Bar("Louis".to_owned()));
+ std::fs::remove_file("tests/secret_test").unwrap();
+ std::fs::remove_file("tests/test").unwrap();
}
}
diff --git a/tests/.gitkeep b/tests/.gitkeep
new file mode 100644
index 0000000..8b13789
--- /dev/null
+++ b/tests/.gitkeep
@@ -0,0 +1 @@
+