Skip to main content

use_env_key/
lib.rs

1#![forbid(unsafe_code)]
2#![doc = include_str!("../README.md")]
3
4use core::{fmt, str::FromStr};
5use std::error::Error;
6
7/// A validated environment variable key.
8#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
9pub struct EnvKey(String);
10
11impl EnvKey {
12    /// Creates an environment variable key.
13    ///
14    /// # Errors
15    ///
16    /// Returns [`EnvKeyError::Empty`] when the trimmed input is empty and
17    /// [`EnvKeyError::ContainsEquals`] when the key contains `=`.
18    pub fn new(value: impl AsRef<str>) -> Result<Self, EnvKeyError> {
19        let trimmed = value.as_ref().trim();
20
21        if trimmed.is_empty() {
22            return Err(EnvKeyError::Empty);
23        }
24
25        if trimmed.contains('=') {
26            return Err(EnvKeyError::ContainsEquals);
27        }
28
29        Ok(Self(trimmed.to_string()))
30    }
31
32    /// Returns the key text.
33    #[must_use]
34    pub fn as_str(&self) -> &str {
35        &self.0
36    }
37
38    /// Consumes the key and returns the owned string.
39    #[must_use]
40    pub fn into_string(self) -> String {
41        self.0
42    }
43}
44
45impl AsRef<str> for EnvKey {
46    fn as_ref(&self) -> &str {
47        self.as_str()
48    }
49}
50
51impl fmt::Display for EnvKey {
52    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
53        formatter.write_str(self.as_str())
54    }
55}
56
57impl FromStr for EnvKey {
58    type Err = EnvKeyError;
59
60    fn from_str(value: &str) -> Result<Self, Self::Err> {
61        Self::new(value)
62    }
63}
64
65/// Errors returned while constructing environment keys.
66#[derive(Clone, Copy, Debug, Eq, PartialEq)]
67pub enum EnvKeyError {
68    /// The key was empty after trimming whitespace.
69    Empty,
70    /// The key contained `=`.
71    ContainsEquals,
72}
73
74impl fmt::Display for EnvKeyError {
75    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
76        match self {
77            Self::Empty => formatter.write_str("environment key cannot be empty"),
78            Self::ContainsEquals => formatter.write_str("environment key cannot contain '='"),
79        }
80    }
81}
82
83impl Error for EnvKeyError {}
84
85#[cfg(test)]
86mod tests {
87    use super::{EnvKey, EnvKeyError};
88
89    #[test]
90    fn accepts_environment_keys_and_preserves_case() {
91        let key = EnvKey::new("Path").unwrap();
92
93        assert_eq!(key.as_str(), "Path");
94        assert_eq!(key.to_string(), "Path");
95    }
96
97    #[test]
98    fn rejects_empty_keys() {
99        assert_eq!(EnvKey::new("  "), Err(EnvKeyError::Empty));
100    }
101
102    #[test]
103    fn rejects_keys_containing_equals() {
104        assert_eq!(EnvKey::new("KEY=value"), Err(EnvKeyError::ContainsEquals));
105    }
106
107    #[test]
108    fn display_round_trips_through_parse() {
109        let key = EnvKey::new("RUST_LOG").unwrap();
110        let round_trip: EnvKey = key.to_string().parse().unwrap();
111
112        assert_eq!(round_trip, key);
113    }
114}