figment/value/
tag.rs

1use std::fmt;
2use std::sync::atomic::Ordering;
3
4use serde::{de, ser};
5use crate::profile::{Profile, ProfileTag};
6/// An opaque, unique tag identifying a value's [`Metadata`](crate::Metadata)
7/// and profile.
8///
9/// A `Tag` is retrieved either via [`Tagged`] or [`Value::tag()`]. The
10/// corresponding metadata can be retrieved via [`Figment::get_metadata()`] and
11/// the profile vile [`Tag::profile()`].
12///
13/// [`Tagged`]: crate::value::magic::Tagged
14/// [`Value::tag()`]: crate::value::Value::tag()
15/// [`Figment::get_metadata()`]: crate::Figment::get_metadata()
16#[derive(Copy, Clone)]
17pub struct Tag(u64);
18
19#[cfg(any(target_pointer_width = "8", target_pointer_width = "16", target_pointer_width = "32"))]
20static COUNTER: atomic::Atomic<u64> = atomic::Atomic::new(1);
21
22#[cfg(not(any(target_pointer_width = "8", target_pointer_width = "16", target_pointer_width = "32")))]
23static COUNTER: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(1);
24
25
26impl Tag {
27    /// The default `Tag`. Such a tag will never have associated metadata and
28    /// is associated with a profile of `Default`.
29    // NOTE: `0` is special! We should never create a default tag via `next()`.
30    #[allow(non_upper_case_globals)]
31    pub const Default: Tag = Tag(0);
32
33    const PROFILE_TAG_SHIFT: u64 = 62;
34    const PROFILE_TAG_MASK: u64 = 0b11 << Self::PROFILE_TAG_SHIFT;
35
36    const METADATA_ID_SHIFT: u64 = 0;
37    const METADATA_ID_MASK: u64 = (!Self::PROFILE_TAG_MASK) << Self::METADATA_ID_SHIFT;
38
39    const fn new(metadata_id: u64, profile_tag: ProfileTag) -> Tag {
40        let bits = ((metadata_id << Self::METADATA_ID_SHIFT) & Self::METADATA_ID_MASK)
41            | ((profile_tag as u64) << Self::PROFILE_TAG_SHIFT) & Self::PROFILE_TAG_MASK;
42
43        Tag(bits)
44    }
45
46    // Returns a tag with a unique metadata id.
47    pub(crate) fn next() -> Tag {
48        let id = COUNTER.fetch_add(1, Ordering::AcqRel);
49        if id > Self::METADATA_ID_MASK {
50            panic!("figment: out of unique tag IDs");
51        }
52
53        Tag::new(id, ProfileTag::Default)
54    }
55
56    pub(crate) fn metadata_id(self) -> u64 {
57        (self.0 & Self::METADATA_ID_MASK) >> Self::METADATA_ID_SHIFT
58    }
59
60    pub(crate) fn profile_tag(self) -> ProfileTag {
61        let bits = (self.0 & Self::PROFILE_TAG_MASK) >> Self::PROFILE_TAG_SHIFT;
62        (bits as u8).into()
63    }
64
65    pub(crate) fn for_profile(self, profile: &crate::Profile) -> Self {
66        Tag::new(self.metadata_id(), profile.into())
67    }
68
69    /// Returns `true` if `self` is `Tag::Default`.
70    ///
71    /// # Example
72    ///
73    /// ```rust
74    /// use figment::value::Tag;
75    ///
76    /// assert!(Tag::Default.is_default());
77    /// ```
78    pub const fn is_default(self) -> bool {
79        self.0 == Tag::Default.0
80    }
81
82    /// Returns the profile `self` refers to if it is either `Profile::Default`
83    /// or `Profile::Custom`; otherwise returns `None`.
84    ///
85    /// # Example
86    ///
87    /// ```rust
88    /// use figment::Profile;
89    /// use figment::value::Tag;
90    ///
91    /// assert_eq!(Tag::Default.profile(), Some(Profile::Default));
92    /// ```
93    pub fn profile(self) -> Option<Profile> {
94        self.profile_tag().into()
95    }
96}
97
98impl Default for Tag {
99    fn default() -> Self {
100        Tag::Default
101    }
102}
103
104impl PartialEq for Tag {
105    fn eq(&self, other: &Self) -> bool {
106        self.metadata_id() == other.metadata_id()
107    }
108}
109
110impl Eq for Tag {  }
111
112impl PartialOrd for Tag {
113    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
114        self.metadata_id().partial_cmp(&other.metadata_id())
115    }
116}
117
118impl Ord for Tag {
119    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
120        self.metadata_id().cmp(&other.metadata_id())
121    }
122}
123
124impl std::hash::Hash for Tag {
125    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
126        state.write_u64(self.metadata_id())
127    }
128}
129
130impl From<Tag> for crate::value::Value {
131    fn from(tag: Tag) -> Self {
132        crate::value::Value::from(tag.0)
133    }
134}
135
136impl<'de> de::Deserialize<'de> for Tag {
137    fn deserialize<D>(deserializer: D) -> Result<Tag, D::Error>
138        where D: de::Deserializer<'de>
139    {
140        struct Visitor;
141
142        impl<'de> de::Visitor<'de> for Visitor {
143            type Value = Tag;
144
145            fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result {
146                f.write_str("a 64-bit metadata id integer")
147            }
148
149            fn visit_u64<E: de::Error>(self, v: u64) -> Result<Self::Value, E> {
150                Ok(Tag(v))
151            }
152        }
153
154        deserializer.deserialize_any(Visitor)
155    }
156}
157
158impl ser::Serialize for Tag {
159    fn serialize<S: ser::Serializer>(&self, ser: S) -> Result<S::Ok, S::Error> {
160        ser.serialize_u64(self.0)
161    }
162}
163
164impl fmt::Debug for Tag {
165    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
166        match self {
167            t if t.is_default() => write!(f, "Tag::Default"),
168            _ => write!(f, "Tag({:?}, {})", self.profile_tag(), self.metadata_id())
169        }
170    }
171}