commit 5650a6ce4818a2477282d9c82670308f37967911 Author: darwincereska Date: Mon Jun 8 01:12:46 2026 -0400 feat: initial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..7791d9f --- /dev/null +++ b/.gitignore @@ -0,0 +1,10 @@ +/target +idea/ + + +# Added by cargo +# +# already existing elements were commented out + +#/target +/.idea/ diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..6180afb --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,7 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "envkit" +version = "0.1.0" diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..9515cfd --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,4 @@ +[package] +name = "envkit" +version = "0.1.0" +edition = "2024" diff --git a/src/error.rs b/src/error.rs new file mode 100644 index 0000000..de0e6fd --- /dev/null +++ b/src/error.rs @@ -0,0 +1,25 @@ +#[derive(Debug, PartialEq, Eq)] +pub enum EnvError { + Invalid { key: String, value: String, message: String }, + MissingVar(String), +} + +impl EnvError { + pub fn invalid(key: K, value: V, message: M) -> Self + where + K: Into, + V: Into, + M: Into, + { + Self::Invalid { + key: key.into(), + value: value.into(), + message: message.into(), + } + } + + pub fn missing(key: T) -> Self + where T: Into { + Self::MissingVar(key.into()) + } +} diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..9df1989 --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,90 @@ +use crate::error::EnvError; +use crate::parser::Number; + +pub mod error; +pub mod parser; + +pub trait FromEnv: Sized { + fn from_env(value: &str) -> Result; +} + +impl FromEnv for bool { + fn from_env(value: &str) -> Result { + match parser::parse_bool(value) { + Ok(value) => Ok(value), + Err(e) => Err(e) + } + } +} + +impl FromEnv for T { + fn from_env(value: &str) -> Result { + match parser::parse_number(value) { + Ok(value) => Ok(value), + Err(e) => Err(e) + } + } +} + +impl FromEnv for String { + fn from_env(value: &str) -> Result { + Ok(value.to_string()) + } +} + +pub struct EnvLoader; + +impl EnvLoader { + pub fn with_prefix>(&self, prefix: P) -> PrefixedEnvLoader { + PrefixedEnvLoader { + prefix: prefix.into(), + } + } + + pub fn get(&self, key: &str) -> Result { + let raw = std::env::var(key).map_err(|_| EnvError::missing(key))?; + FromEnv::from_env(&raw) + } + + pub fn get_or(&self, key: &str, default: T) -> T { + match self.get(key) { + Ok(value) => value, + Err(_) => default + } + } + + pub fn get_opt(&self, key: &str) -> Option { + match self.get(key) { + Ok(value) => Some(value), + Err(_) => None + } + } +} + +pub struct PrefixedEnvLoader { + prefix: String, +} + +impl PrefixedEnvLoader { + fn key(&self, key: &str) -> String { + format!("{}{}", self.prefix, key) + } + + pub fn get(&self, key: &str) -> Result { + EnvLoader.get(&self.key(key)) + } + + pub fn get_or(&self, key: &str, default: T) -> T { + match self.get(key) { + Ok(value) => value, + Err(_) => default + } + } + + pub fn get_opt(&self, key: &str) -> Option { + match self.get(key) { + Ok(value) => Some(value), + Err(_) => None + } + } +} diff --git a/src/parser.rs b/src/parser.rs new file mode 100644 index 0000000..a83656d --- /dev/null +++ b/src/parser.rs @@ -0,0 +1,40 @@ +use std::fmt::Display; +use std::any::type_name; +use crate::error::EnvError; +use std::str::FromStr; + +pub trait Number: FromStr {} + +impl Number for i8 {} +impl Number for i16 {} +impl Number for i32 {} +impl Number for i64 {} +impl Number for i128 {} +impl Number for isize {} +impl Number for u8 {} +impl Number for u16 {} +impl Number for u32 {} +impl Number for u64 {} +impl Number for u128 {} +impl Number for usize {} +impl Number for f32 {} +impl Number for f64 {} + +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) } + + match value.parse::() { + Ok(value) => Ok(value), + Err(e) => Err(EnvError::invalid("bool", value, &e.to_string())) + } +} + +pub fn parse_number(value: &str) -> Result { + match value.parse::() { + Ok(value) => Ok(value), + Err(e) => Err(EnvError::invalid(type_name::(), value, &e.to_string())) + } +} diff --git a/tests/test_errors.rs b/tests/test_errors.rs new file mode 100644 index 0000000..1d752a2 --- /dev/null +++ b/tests/test_errors.rs @@ -0,0 +1,18 @@ +#[cfg(test)] +mod test_errors { + use envkit::error::EnvError; + + #[test] + fn test_missing_var() { + assert_eq!(EnvError::missing("missing test"), EnvError::MissingVar("missing test".to_string())); + } + + #[test] + fn test_invalid() { + assert_eq!(EnvError::invalid("key", "value", "message"), EnvError::Invalid { + key: "key".to_string(), + value: "value".to_string(), + message: "message".to_string(), + }); + } +} \ No newline at end of file diff --git a/tests/test_parser.rs b/tests/test_parser.rs new file mode 100644 index 0000000..eab7621 --- /dev/null +++ b/tests/test_parser.rs @@ -0,0 +1,33 @@ +#[cfg(test)] +mod test_parser { + use envkit::parser::parse_bool; + use envkit::parser::parse_number; + + #[test] + fn test_parse_bool() { + assert_eq!(parse_bool("true"), Ok(true)); + assert_eq!(parse_bool("false"), Ok(false)); + assert_eq!(parse_bool("1"), Ok(true)); + assert_eq!(parse_bool("0"), Ok(false)); + assert_eq!(parse_bool("t"), Ok(true)); + assert_eq!(parse_bool("f"), Ok(false)); + } + + #[test] + fn test_parse_number() { + assert_eq!(parse_number::("42"), Ok(42_i8)); + assert_eq!(parse_number::("42"), Ok(42_i16)); + assert_eq!(parse_number::("42"), Ok(42_i32)); + assert_eq!(parse_number::("42"), Ok(42_i64)); + assert_eq!(parse_number::("42"), Ok(42_i128)); + assert_eq!(parse_number::("42"), Ok(42_isize)); + assert_eq!(parse_number::("42"), Ok(42_u8)); + assert_eq!(parse_number::("42"), Ok(42_u16)); + assert_eq!(parse_number::("42"), Ok(42_u32)); + assert_eq!(parse_number::("42"), Ok(42_u64)); + assert_eq!(parse_number::("42"), Ok(42_u128)); + assert_eq!(parse_number::("42"), Ok(42_usize)); + assert_eq!(parse_number::("42"), Ok(42_f32)); + assert_eq!(parse_number::("42"), Ok(42_f64)); + } +}