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}