1#![forbid(unsafe_code)]
2#![doc = include_str!("../README.md")]
3
4use core::{fmt, str::FromStr};
5use std::error::Error;
6
7#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
9pub struct EnvKey(String);
10
11impl EnvKey {
12 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 #[must_use]
34 pub fn as_str(&self) -> &str {
35 &self.0
36 }
37
38 #[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#[derive(Clone, Copy, Debug, Eq, PartialEq)]
67pub enum EnvKeyError {
68 Empty,
70 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}