feat: initial commit

This commit is contained in:
darwincereska
2026-06-08 01:12:46 -04:00
commit 5650a6ce48
8 changed files with 227 additions and 0 deletions
+25
View File
@@ -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<K, V, M>(key: K, value: V, message: M) -> Self
where
K: Into<String>,
V: Into<String>,
M: Into<String>,
{
Self::Invalid {
key: key.into(),
value: value.into(),
message: message.into(),
}
}
pub fn missing<T>(key: T) -> Self
where T: Into<String> {
Self::MissingVar(key.into())
}
}
+90
View File
@@ -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<Self, EnvError>;
}
impl FromEnv for bool {
fn from_env(value: &str) -> Result<bool, EnvError> {
match parser::parse_bool(value) {
Ok(value) => Ok(value),
Err(e) => Err(e)
}
}
}
impl<T: Number> FromEnv for T {
fn from_env(value: &str) -> Result<Self, EnvError> {
match parser::parse_number(value) {
Ok(value) => Ok(value),
Err(e) => Err(e)
}
}
}
impl FromEnv for String {
fn from_env(value: &str) -> Result<String, EnvError> {
Ok(value.to_string())
}
}
pub struct EnvLoader;
impl EnvLoader {
pub fn with_prefix<P: Into<String>>(&self, prefix: P) -> PrefixedEnvLoader {
PrefixedEnvLoader {
prefix: prefix.into(),
}
}
pub fn get<T: FromEnv>(&self, key: &str) -> Result<T, EnvError> {
let raw = std::env::var(key).map_err(|_| EnvError::missing(key))?;
FromEnv::from_env(&raw)
}
pub fn get_or<T: FromEnv>(&self, key: &str, default: T) -> T {
match self.get(key) {
Ok(value) => value,
Err(_) => default
}
}
pub fn get_opt<T: FromEnv>(&self, key: &str) -> Option<T> {
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<T: FromEnv>(&self, key: &str) -> Result<T, EnvError> {
EnvLoader.get(&self.key(key))
}
pub fn get_or<T: FromEnv>(&self, key: &str, default: T) -> T {
match self.get(key) {
Ok(value) => value,
Err(_) => default
}
}
pub fn get_opt<T: FromEnv>(&self, key: &str) -> Option<T> {
match self.get(key) {
Ok(value) => Some(value),
Err(_) => None
}
}
}
+40
View File
@@ -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<Err: Display> {}
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<bool, EnvError> {
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::<bool>() {
Ok(value) => Ok(value),
Err(e) => Err(EnvError::invalid("bool", value, &e.to_string()))
}
}
pub fn parse_number<T: Number>(value: &str) -> Result<T, EnvError> {
match value.parse::<T>() {
Ok(value) => Ok(value),
Err(e) => Err(EnvError::invalid(type_name::<T>(), value, &e.to_string()))
}
}