From 9c087b7baaa8c167aa030a0554790a3aa0c95e57 Mon Sep 17 00:00:00 2001 From: darwincereska Date: Mon, 8 Jun 2026 03:04:17 -0400 Subject: [PATCH] feat: updated bool parsing and added tests --- Cargo.lock | 2 +- Cargo.toml | 3 ++- src/error.rs | 6 ++++++ src/lib.rs | 24 +++++++++++++++++------- src/parser.rs | 5 +++++ tests/test_errors.rs | 2 ++ tests/test_main.rs | 43 +++++++++++++++++++++++++++++++++++++++++++ tests/test_parser.rs | 4 ++++ 8 files changed, 80 insertions(+), 9 deletions(-) create mode 100644 tests/test_main.rs diff --git a/Cargo.lock b/Cargo.lock index 6180afb..d04ff0b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4,4 +4,4 @@ version = 4 [[package]] name = "envkit" -version = "0.1.0" +version = "0.1.1" diff --git a/Cargo.toml b/Cargo.toml index 4d5673a..17bebd6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,9 +1,10 @@ [package] name = "envkit" -version = "0.1.0" +version = "0.1.1" edition = "2024" authors = ["Darwin Cereska "] description = "A dead-simple env loader" readme = "README.md" license = "MIT" repository = "https://github.com/darwincereska/envkit" +categories = ["environment", "configuration", "loader"] \ No newline at end of file diff --git a/src/error.rs b/src/error.rs index de0e6fd..eba25ac 100644 --- a/src/error.rs +++ b/src/error.rs @@ -1,10 +1,15 @@ #[derive(Debug, PartialEq, Eq)] +/// Errors that can occur when parsing environment variables. pub enum EnvError { + /// An environment variable was found, but its value was invalid. Invalid { key: String, value: String, message: String }, + + /// An environment variable was expected, but it was not found. MissingVar(String), } impl EnvError { + /// Creates a new `EnvError::Invalid` with the given key, value, and message. pub fn invalid(key: K, value: V, message: M) -> Self where K: Into, @@ -18,6 +23,7 @@ impl EnvError { } } + /// Creates a new `EnvError::MissingVar` with the given key. pub fn missing(key: T) -> Self where T: Into { Self::MissingVar(key.into()) diff --git a/src/lib.rs b/src/lib.rs index 9df1989..5558d22 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -8,68 +8,78 @@ pub trait FromEnv: Sized { fn from_env(value: &str) -> Result; } +/// Adds support for loading booleans from environment variables. impl FromEnv for bool { fn from_env(value: &str) -> Result { match parser::parse_bool(value) { Ok(value) => Ok(value), - Err(e) => Err(e) + Err(e) => Err(e), } } } +/// Adds support for loading numbers from environment variables. impl FromEnv for T { fn from_env(value: &str) -> Result { match parser::parse_number(value) { Ok(value) => Ok(value), - Err(e) => Err(e) + Err(e) => Err(e), } } } +/// Adds support for loading strings from environment variables. impl FromEnv for String { fn from_env(value: &str) -> Result { Ok(value.to_string()) } } +/// A loader for environment variables. pub struct EnvLoader; impl EnvLoader { + /// Creates a new `EnvLoader` with the given prefix. pub fn with_prefix>(&self, prefix: P) -> PrefixedEnvLoader { PrefixedEnvLoader { prefix: prefix.into(), } } + /// Gets the value of the environment variable with the given key. pub fn get(&self, key: &str) -> Result { let raw = std::env::var(key).map_err(|_| EnvError::missing(key))?; FromEnv::from_env(&raw) } + /// Gets the value of the environment variable with the given key, or returns the default value if the variable is not set. pub fn get_or(&self, key: &str, default: T) -> T { match self.get(key) { Ok(value) => value, - Err(_) => default + Err(_) => default, } } + /// Gets the value of the environment variable with the given key, or returns `None` if the variable is not set. pub fn get_opt(&self, key: &str) -> Option { match self.get(key) { Ok(value) => Some(value), - Err(_) => None + Err(_) => None, } } } +/// A loader for environment variables with a prefix. pub struct PrefixedEnvLoader { prefix: String, } impl PrefixedEnvLoader { + /// Returns the key with the prefix prepended. fn key(&self, key: &str) -> String { format!("{}{}", self.prefix, key) } - + pub fn get(&self, key: &str) -> Result { EnvLoader.get(&self.key(key)) } @@ -77,14 +87,14 @@ impl PrefixedEnvLoader { pub fn get_or(&self, key: &str, default: T) -> T { match self.get(key) { Ok(value) => value, - Err(_) => default + Err(_) => default, } } pub fn get_opt(&self, key: &str) -> Option { match self.get(key) { Ok(value) => Some(value), - Err(_) => None + Err(_) => None, } } } diff --git a/src/parser.rs b/src/parser.rs index a83656d..ca40b8d 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -3,6 +3,7 @@ use std::any::type_name; use crate::error::EnvError; use std::str::FromStr; +/// A trait for types that can be parsed from a string as a number. pub trait Number: FromStr {} impl Number for i8 {} @@ -20,11 +21,14 @@ impl Number for usize {} impl Number for f32 {} impl Number for f64 {} +/// Parses a string as a boolean value. pub fn parse_bool(value: &str) -> Result { if value.to_lowercase() == "t" { return Ok(true) } if value.to_lowercase() == "f" { return Ok(false) } if value == "1" { return Ok(true) } if value == "0" { return Ok(false) } + if value == "yes" { return Ok(true) } + if value == "no" { return Ok(false) } match value.parse::() { Ok(value) => Ok(value), @@ -32,6 +36,7 @@ pub fn parse_bool(value: &str) -> Result { } } +/// Parses a string as a number of the specified type. pub fn parse_number(value: &str) -> Result { match value.parse::() { Ok(value) => Ok(value), diff --git a/tests/test_errors.rs b/tests/test_errors.rs index 1d752a2..9c2d9bf 100644 --- a/tests/test_errors.rs +++ b/tests/test_errors.rs @@ -3,11 +3,13 @@ mod test_errors { use envkit::error::EnvError; #[test] + /// Tests for the `missing` function. fn test_missing_var() { assert_eq!(EnvError::missing("missing test"), EnvError::MissingVar("missing test".to_string())); } #[test] + /// Tests for the `invalid` function. fn test_invalid() { assert_eq!(EnvError::invalid("key", "value", "message"), EnvError::Invalid { key: "key".to_string(), diff --git a/tests/test_main.rs b/tests/test_main.rs new file mode 100644 index 0000000..8eef4bb --- /dev/null +++ b/tests/test_main.rs @@ -0,0 +1,43 @@ +#[cfg(test)] +mod test_main { + use envkit::*; + + #[test] + /// Tests that `get` successfully retrieves an existing environment variable. + fn test_get() { + let env = EnvLoader; + + unsafe { + std::env::set_var("TEST_VAR", "test_value"); + } + + assert_eq!(env.get::("TEST_VAR"), Ok("test_value".to_string())); + } + + #[test] + /// Tests that `get_or` returns the default value when the environment variable is missing. + fn test_get_or() { + let env = EnvLoader; + + unsafe { + std::env::remove_var("MISSING_VAR"); + std::env::set_var("TEST_VAR", "test_value"); + } + + assert_eq!(env.get_or::("MISSING_VAR", "default".to_string()), "default".to_string()); + assert_eq!(env.get_or::("TEST_VAR", "default".to_string()), "test_value".to_string()); + } + + #[test] + /// Tests that `get_opt` returns `Some` when the environment variable exists. + fn test_get_opt() { + let env = EnvLoader; + + unsafe { + std::env::set_var("OPTIONAL_VAR", "optional_value"); + } + + assert_eq!(env.get_opt::("OPTIONAL_VAR"), Some("optional_value".to_string())); + assert_eq!(env.get_opt::("DEV"), None); + } +} \ No newline at end of file diff --git a/tests/test_parser.rs b/tests/test_parser.rs index eab7621..427c3e7 100644 --- a/tests/test_parser.rs +++ b/tests/test_parser.rs @@ -4,6 +4,7 @@ mod test_parser { use envkit::parser::parse_number; #[test] + /// Tests for the `parse_bool` function. fn test_parse_bool() { assert_eq!(parse_bool("true"), Ok(true)); assert_eq!(parse_bool("false"), Ok(false)); @@ -11,9 +12,12 @@ mod test_parser { assert_eq!(parse_bool("0"), Ok(false)); assert_eq!(parse_bool("t"), Ok(true)); assert_eq!(parse_bool("f"), Ok(false)); + assert_eq!(parse_bool("yes"), Ok(true)); + assert_eq!(parse_bool("no"), Ok(false)); } #[test] + /// Tests for the `parse_number` function. fn test_parse_number() { assert_eq!(parse_number::("42"), Ok(42_i8)); assert_eq!(parse_number::("42"), Ok(42_i16));