figment/profile.rs
1use serde::{de, ser};
2use uncased::{Uncased, UncasedStr};
3
4use crate::value::{Dict, Map};
5
6/// A configuration profile: effectively a case-insensitive string.
7///
8/// See [the top-level docs](crate#extracting-and-profiles) for details.
9#[derive(Debug, PartialEq, Eq, Hash, Clone, PartialOrd, Ord)]
10pub struct Profile(Uncased<'static>);
11
12impl Default for Profile {
13 fn default() -> Self {
14 Profile::Default
15 }
16}
17
18/// 2-bit tags used by the top bits of `Tag`.
19#[repr(u8)]
20#[derive(Debug, Copy, Clone, PartialEq)]
21pub(crate) enum ProfileTag {
22 Default = 0b00,
23 Global = 0b01,
24 Custom = 0b11,
25}
26
27impl From<u8> for ProfileTag {
28 fn from(bits: u8) -> ProfileTag {
29 if bits == ProfileTag::Default as u8 {
30 ProfileTag::Default
31 } else if bits == ProfileTag::Global as u8 {
32 ProfileTag::Global
33 } else {
34 ProfileTag::Custom
35 }
36 }
37}
38
39impl From<ProfileTag> for Option<Profile> {
40 fn from(tag: ProfileTag) -> Self {
41 match tag {
42 ProfileTag::Default => Some(Profile::Default),
43 ProfileTag::Global => Some(Profile::Global),
44 ProfileTag::Custom => None,
45 }
46 }
47}
48
49impl From<&Profile> for ProfileTag {
50 fn from(profile: &Profile) -> Self {
51 match profile {
52 p if p == Profile::Default => ProfileTag::Default,
53 p if p == Profile::Global => ProfileTag::Global,
54 _ => ProfileTag::Custom
55 }
56 }
57}
58
59impl Profile {
60 /// The default profile: `"default"`.
61 #[allow(non_upper_case_globals)]
62 pub const Default: Profile = Profile::const_new("default");
63
64 /// The global profile: `"global"`.
65 #[allow(non_upper_case_globals)]
66 pub const Global: Profile = Profile::const_new("global");
67
68 /// Constructs a profile with the name `name`.
69 ///
70 /// # Example
71 ///
72 /// ```rust
73 /// use figment::Profile;
74 ///
75 /// let profile = Profile::new("staging");
76 /// assert_eq!(profile, "staging");
77 /// assert_eq!(profile, "STAGING");
78 /// ```
79 pub fn new(name: &str) -> Profile {
80 Profile(name.to_string().into())
81 }
82
83 /// A `const` to construct a profile with the name `name`.
84 ///
85 /// # Example
86 ///
87 /// ```rust
88 /// use figment::Profile;
89 ///
90 /// const STAGING: Profile = Profile::const_new("staging");
91 ///
92 /// assert_eq!(STAGING, "staging");
93 /// assert_eq!(STAGING, "STAGING");
94 /// ```
95 pub const fn const_new(name: &'static str) -> Profile {
96 Profile(Uncased::from_borrowed(name))
97 }
98
99 /// Constructs a profile from the value of the environment variable with
100 /// name `key`, if one is present. The search for `key` is case-insensitive.
101 ///
102 /// # Example
103 ///
104 /// ```rust
105 /// use figment::{Profile, Jail};
106 ///
107 /// Jail::expect_with(|jail| {
108 /// jail.set_env("MY_PROFILE", "secret");
109 ///
110 /// assert_eq!(Profile::from_env("MY_PROFILE"), Some("secret".into()));
111 /// assert_eq!(Profile::from_env("MY_PROFILE"), Some("secret".into()));
112 /// assert_eq!(Profile::from_env("MY_profile"), Some("secret".into()));
113 /// assert_eq!(Profile::from_env("other_profile"), None);
114 /// Ok(())
115 /// });
116 /// ```
117 pub fn from_env(key: &str) -> Option<Self> {
118 for (env_key, val) in std::env::vars_os() {
119 let env_key = env_key.to_string_lossy();
120 if uncased::eq(env_key.trim(), key) {
121 return Some(Profile::new(&val.to_string_lossy()));
122 }
123 }
124
125 None
126 }
127
128 /// Constructs a profile from the value of the environment variable with
129 /// name `var`, if one is present, or `default` if one is not. The search
130 /// for `var` is case-insensitive.
131 ///
132 /// # Example
133 ///
134 /// ```rust
135 /// use figment::{Profile, Jail};
136 ///
137 /// Jail::expect_with(|jail| {
138 /// jail.set_env("MY_PROFILE", "secret");
139 ///
140 /// assert_eq!(Profile::from_env_or("MY_PROFILE", "default"), "secret");
141 /// assert_eq!(Profile::from_env_or("MY_profile", "default"), "secret");
142 /// assert_eq!(Profile::from_env_or("other_prof", "default"), "default");
143 /// Ok(())
144 /// });
145 /// ```
146 pub fn from_env_or<P: Into<Profile>>(var: &str, default: P) -> Self {
147 Profile::from_env(var).unwrap_or_else(|| default.into())
148 }
149
150 /// Converts `self` into an `&UncasedStr`.
151 ///
152 /// # Example
153 ///
154 /// ```rust
155 /// use figment::Profile;
156 ///
157 /// let profile = Profile::new("static");
158 /// let string = profile.as_str();
159 /// ```
160 pub fn as_str(&self) -> &UncasedStr {
161 &self.0
162 }
163
164 /// Returns `true` iff `self` case-insensitively starts with `prefix`.
165 ///
166 /// # Example
167 ///
168 /// ```rust
169 /// use figment::Profile;
170 ///
171 /// let profile = Profile::new("static");
172 /// assert!(profile.starts_with("STAT"));
173 /// assert!(profile.starts_with("stat"));
174 /// assert!(profile.starts_with("static"));
175 /// ```
176 pub fn starts_with(&self, prefix: &str) -> bool {
177 self.as_str().starts_with(prefix)
178 }
179
180 /// Returns `true` iff `self` is neither "default" nor "global".
181 ///
182 /// # Example
183 ///
184 /// ```rust
185 /// use figment::Profile;
186 ///
187 /// let profile = Profile::new("static");
188 /// assert!(profile.is_custom());
189 ///
190 /// assert!(!Profile::Default.is_custom());
191 /// assert!(!Profile::Global.is_custom());
192 /// ```
193 pub fn is_custom(&self) -> bool {
194 self != Profile::Default && self != Profile::Global
195 }
196
197 /// Creates a new map with a single key of `*self` and a value of `dict`.
198 ///
199 /// # Example
200 ///
201 /// ```rust
202 /// use figment::{Profile, util::map};
203 ///
204 /// let profile = Profile::new("static");
205 /// let map = profile.collect(map!["hi".into() => 123.into()]);
206 /// ```
207 pub fn collect(&self, dict: Dict) -> Map<Profile, Dict> {
208 let mut map = Map::new();
209 map.insert(self.clone(), dict);
210 map
211 }
212}
213
214impl<T: AsRef<str>> From<T> for Profile {
215 fn from(string: T) -> Profile {
216 Profile::new(string.as_ref())
217 }
218}
219
220impl From<Profile> for String {
221 fn from(profile: Profile) -> String {
222 profile.0.to_string()
223 }
224}
225
226impl std::ops::Deref for Profile {
227 type Target = UncasedStr;
228
229 fn deref(&self) -> &UncasedStr {
230 self.as_str()
231 }
232}
233
234impl std::fmt::Display for Profile {
235 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
236 self.as_str().fmt(f)
237 }
238}
239
240impl PartialEq<str> for Profile {
241 fn eq(&self, other: &str) -> bool {
242 self.as_str() == other
243 }
244}
245
246impl PartialEq<&str> for Profile {
247 fn eq(&self, other: &&str) -> bool {
248 self.as_str() == other
249 }
250}
251
252impl PartialEq<Profile> for str {
253 fn eq(&self, other: &Profile) -> bool {
254 self == other.as_str()
255 }
256}
257
258impl PartialEq<Profile> for &str {
259 fn eq(&self, other: &Profile) -> bool {
260 self == other.as_str()
261 }
262}
263
264impl PartialEq<Profile> for &Profile {
265 fn eq(&self, other: &Profile) -> bool {
266 self.as_str() == other.as_str()
267 }
268}
269
270impl PartialEq<&Profile> for Profile {
271 fn eq(&self, other: &&Profile) -> bool {
272 self.as_str() == other.as_str()
273 }
274}
275
276impl<'de> de::Deserialize<'de> for Profile {
277 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
278 where D: serde::Deserializer<'de>
279 {
280 struct Visitor;
281
282 impl<'de> de::Visitor<'de> for Visitor {
283 type Value = Profile;
284
285 fn expecting(&self, formatter: &mut core::fmt::Formatter) -> core::fmt::Result {
286 formatter.write_str("a string")
287 }
288
289 fn visit_str<E: de::Error>(self, v: &str) -> Result<Self::Value, E> {
290 Ok(Profile::from(v))
291 }
292 }
293
294 deserializer.deserialize_str(Visitor)
295 }
296}
297
298impl ser::Serialize for Profile {
299 fn serialize<S: ser::Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {
300 s.serialize_str(self.as_str().as_str())
301 }
302}