V0.1.0 initial
This commit is contained in:
2
Cargo.lock
generated
2
Cargo.lock
generated
@@ -3,5 +3,5 @@
|
||||
version = 4
|
||||
|
||||
[[package]]
|
||||
name = "no-man-sky"
|
||||
name = "merlin_env_helper"
|
||||
version = "0.1.0"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "no-man-sky"
|
||||
name = "merlin_env_helper"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
edition = "2024"
|
||||
|
||||
[dependencies]
|
||||
|
||||
1
resources/test/test_secret.txt
Normal file
1
resources/test/test_secret.txt
Normal file
@@ -0,0 +1 @@
|
||||
my_secret
|
||||
253
src/config/env.rs
Normal file
253
src/config/env.rs
Normal file
@@ -0,0 +1,253 @@
|
||||
//! Module for reading configuration values from environment variables or secure files.
|
||||
//!
|
||||
//! This module provides utilities to retrieve configuration values from environment variables,
|
||||
//! with support for fallback values and secure file-based secrets. It defines error types for
|
||||
//! configuration loading and exposes a unified `Result` type for error handling.
|
||||
//!
|
||||
//! # Types
|
||||
//!
|
||||
//! - [`ConfigError`]: Represents errors that can occur when reading configuration values.
|
||||
//! - [`ConfigErrorKind`]: Enum describing the kind of configuration error (I/O, file not found, or not found).
|
||||
//! - [`ConfigErrorMessage`]: Simple error message wrapper.
|
||||
//!
|
||||
//! # Functions
|
||||
//!
|
||||
//! - [`get_env_value`]: Retrieves a configuration value from the environment or a secure file, with optional fallback.
|
||||
//!
|
||||
//! # Usage
|
||||
//!
|
||||
//! Use [`get_env_value`] with an [`EnvKey`] to retrieve configuration values, handling errors as needed.
|
||||
//!
|
||||
//! # Examples
|
||||
//!
|
||||
//! ```rust
|
||||
//! use no_man_sky::config::EnvKey;
|
||||
//! use no_man_sky::config::get_env_value;
|
||||
//! let env_key = EnvKey::key("MY_CONFIG_KEY");
|
||||
//! match get_env_value(&env_key) {
|
||||
//! Ok(value) => println!("Config value: {}", value),
|
||||
//! Err(e) => eprintln!("Error: {}", e),
|
||||
//! }
|
||||
//! ```
|
||||
use core::fmt;
|
||||
use std::{error::Error, fmt::{Display, Formatter}, io::{self, Read}, path::Path};
|
||||
|
||||
use crate::config::types::EnvKey;
|
||||
|
||||
pub type Result<T, E = ConfigError> = std::result::Result<T, E>;
|
||||
|
||||
|
||||
#[derive(Debug)]
|
||||
#[non_exhaustive]
|
||||
pub struct ConfigError {
|
||||
pub key: String,
|
||||
pub kind: ConfigErrorKind,
|
||||
}
|
||||
|
||||
impl Display for ConfigError {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "error reading configuration key `{}`", self.key)
|
||||
}
|
||||
}
|
||||
|
||||
impl Error for ConfigError {
|
||||
fn source(&self) -> Option<&(dyn Error + 'static)> {
|
||||
match &self.kind {
|
||||
ConfigErrorKind::IO(e) => Some(e),
|
||||
ConfigErrorKind::FileNotFound(e) => Some(e),
|
||||
ConfigErrorKind::NotFound => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// The kind of error that can occur when reading a configuration key.
|
||||
#[derive(Debug)]
|
||||
pub enum ConfigErrorKind {
|
||||
IO(io::Error),
|
||||
FileNotFound(ConfigErrorMessage),
|
||||
NotFound,
|
||||
}
|
||||
|
||||
/// A simple message that can be used to describe an error.
|
||||
#[derive(Debug)]
|
||||
#[non_exhaustive]
|
||||
pub struct ConfigErrorMessage {
|
||||
pub msg: String,
|
||||
}
|
||||
|
||||
impl Display for ConfigErrorMessage {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "{}", self.msg)
|
||||
}
|
||||
}
|
||||
|
||||
impl Error for ConfigErrorMessage {
|
||||
}
|
||||
|
||||
|
||||
|
||||
pub fn get_env_value(env_key : &EnvKey) -> Result<String> {
|
||||
if let Some(sec_key) = &env_key.sec_key {
|
||||
let key = std::env::var(&sec_key);
|
||||
|
||||
if let Ok(path_file) = key {
|
||||
return read_secure_value(&env_key.key, &path_file);
|
||||
}
|
||||
}
|
||||
|
||||
read_key_value(&env_key.key, &env_key.fallback)
|
||||
}
|
||||
|
||||
fn read_key_value(key: &str, fallback: &Option<String>) -> Result<String> {
|
||||
let env_value = std::env::var(key);
|
||||
|
||||
println!("env_value: {:?}", env_value);
|
||||
if let Ok(value) = env_value {
|
||||
return Ok(value);
|
||||
}
|
||||
|
||||
if let Some(fallback) = fallback {
|
||||
return Ok(fallback.to_string());
|
||||
}
|
||||
|
||||
Err(
|
||||
ConfigError {
|
||||
key: key.to_string(),
|
||||
kind: ConfigErrorKind::NotFound,
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
fn read_secure_value(key: &str, path_file: &str) -> Result<String> {
|
||||
|
||||
let path = Path::new(path_file);
|
||||
|
||||
let exists = path.try_exists().map_err(|e| -> ConfigError {
|
||||
println!("path.try_exists(): {:?}", e);
|
||||
ConfigError {
|
||||
key: format!("{}: {} File not found.", key.to_string(), path_file),
|
||||
kind: ConfigErrorKind::IO(e),
|
||||
}
|
||||
})?;
|
||||
|
||||
if !exists {
|
||||
return Err(
|
||||
ConfigError {
|
||||
key: format!("{}: {} File not found.", key.to_string(), path_file),
|
||||
kind: ConfigErrorKind::FileNotFound(ConfigErrorMessage {
|
||||
msg: format!("File not found: {}", path_file),
|
||||
}),
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
let file = std::fs::File::open(path).map_err(|e| -> ConfigError {
|
||||
println!("std::fs::File::open(path_file): {:?}", e);
|
||||
ConfigError {
|
||||
key: key.to_string(),
|
||||
kind: ConfigErrorKind::IO(e),
|
||||
}
|
||||
})?;
|
||||
|
||||
let mut reader = std::io::BufReader::new(file);
|
||||
let mut content = String::new();
|
||||
|
||||
reader.read_to_string(&mut content).map_err(|e| -> ConfigError {
|
||||
ConfigError {
|
||||
key: key.to_string(),
|
||||
kind: ConfigErrorKind::IO(e),
|
||||
}
|
||||
})?;
|
||||
|
||||
Ok(content)
|
||||
}
|
||||
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use std::{panic, path::PathBuf};
|
||||
|
||||
fn clean_up(reset_value: &Option<String>) {
|
||||
if let Some(value) = reset_value {
|
||||
unsafe {std::env::set_var("TEST_KEY", value)};
|
||||
} else {
|
||||
unsafe {std::env::remove_var("TEST_KEY")};
|
||||
}
|
||||
}
|
||||
|
||||
fn run_test<T, U>(env_key : &str, test: T, clean_up: U) -> ()
|
||||
where T: FnOnce() -> () + std::panic::UnwindSafe
|
||||
, U: FnOnce(&Option<String>) -> ()
|
||||
{
|
||||
let old_value = std::env::var(env_key);
|
||||
let mut reset_value= None;
|
||||
|
||||
if let Ok(value) = old_value {
|
||||
reset_value = Some(value.clone());
|
||||
}
|
||||
|
||||
let result = panic::catch_unwind( || {
|
||||
test();
|
||||
});
|
||||
|
||||
clean_up(&reset_value);
|
||||
|
||||
assert!(!result.is_err(), "Test failed");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_get_env_value() {
|
||||
|
||||
run_test("TEST_KEY", || {
|
||||
let key = "TEST_KEY";
|
||||
let value = "test_value";
|
||||
unsafe {std::env::set_var(key, value)};
|
||||
let env_key = EnvKey::key(key);
|
||||
let result = get_env_value(&env_key);
|
||||
assert_eq!(result.is_ok(), true);
|
||||
assert_eq!(result.unwrap(), value);
|
||||
|
||||
},
|
||||
clean_up
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_get_env_value_with_fallback() {
|
||||
run_test("TEST_KEY", || {
|
||||
let key = "TEST_KEY";
|
||||
let fallback_value = "fallback_value";
|
||||
|
||||
let env_key = EnvKey::key_with_fallback(key, fallback_value);
|
||||
let result = get_env_value(&env_key);
|
||||
assert_eq!(result.is_ok(), true);
|
||||
assert_eq!(result.unwrap(), fallback_value);
|
||||
},
|
||||
clean_up);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_get_env_value_with_secure_key() {
|
||||
run_test("TEST_KEY", || {
|
||||
let key = "TEST_KEY";
|
||||
let value = "test_value";
|
||||
let secure_key = "TEST_KEY_FILE";
|
||||
let secure_value = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("resources/test/test_secret.txt");
|
||||
|
||||
unsafe {std::env::set_var(key, value)};
|
||||
unsafe {std::env::set_var(secure_key, &secure_value)};
|
||||
|
||||
assert_eq!(secure_value.try_exists().unwrap(), true);
|
||||
|
||||
let env_key = EnvKey::secure_key(key, secure_key);
|
||||
let result = get_env_value(&env_key);
|
||||
assert_eq!(result.is_ok(), true);
|
||||
assert_eq!(result.unwrap(), "my_secret");
|
||||
},
|
||||
clean_up);
|
||||
}
|
||||
}
|
||||
5
src/config/mod.rs
Normal file
5
src/config/mod.rs
Normal file
@@ -0,0 +1,5 @@
|
||||
pub mod env;
|
||||
pub mod types;
|
||||
|
||||
pub use types::*;
|
||||
pub use env::*;
|
||||
40
src/config/types.rs
Normal file
40
src/config/types.rs
Normal file
@@ -0,0 +1,40 @@
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
pub struct EnvKey {
|
||||
pub key: String,
|
||||
pub sec_key: Option<String>,
|
||||
pub fallback: Option<String>,
|
||||
}
|
||||
|
||||
impl EnvKey {
|
||||
pub fn key(key: &str) -> Self {
|
||||
Self {
|
||||
key: key.to_string(),
|
||||
sec_key: None,
|
||||
fallback: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn key_with_fallback(key: &str, fallback: &str) -> Self {
|
||||
Self {
|
||||
key: key.to_string(),
|
||||
sec_key: None,
|
||||
fallback: Some(fallback.to_string()),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn secure_key(key: &str, sec_key: &str) -> Self {
|
||||
Self {
|
||||
key: key.to_string(),
|
||||
sec_key: Some(sec_key.to_string()),
|
||||
fallback: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn secure_with_fallback(key: &str, sec_key: &str, fallback: &str) -> Self {
|
||||
Self {
|
||||
key: key.to_string(),
|
||||
sec_key: Some(sec_key.to_string()),
|
||||
fallback: Some(fallback.to_string()),
|
||||
}
|
||||
}
|
||||
}
|
||||
1
src/lib.rs
Normal file
1
src/lib.rs
Normal file
@@ -0,0 +1 @@
|
||||
pub mod config;
|
||||
@@ -1,3 +0,0 @@
|
||||
fn main() {
|
||||
println!("Hello, world!");
|
||||
}
|
||||
Reference in New Issue
Block a user