aws_types/
origin.rs

1/*
2 * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3 * SPDX-License-Identifier: Apache-2.0
4 */
5
6//! Types for tracking the origin of config values.
7
8use std::cmp::Ordering;
9use std::fmt;
10
11/// A type for tracking the origin of config values.
12#[derive(Debug, Clone, Copy, PartialEq, PartialOrd)]
13#[non_exhaustive]
14pub struct Origin {
15    inner: Inner,
16}
17
18impl fmt::Display for Origin {
19    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
20        use Inner::*;
21
22        match self.inner {
23            Imds => write!(f, "IMDS"),
24            ProfileFile(Kind::Shared) => write!(f, "shared profile file"),
25            ProfileFile(Kind::Service) => write!(f, "service profile file"),
26            EnvironmentVariable(Kind::Shared) => write!(f, "shared environment variable"),
27            EnvironmentVariable(Kind::Service) => write!(f, "service environment variable"),
28            Programmatic(Kind::Shared) => write!(f, "shared client"),
29            Programmatic(Kind::Service) => write!(f, "service client"),
30            Unknown => write!(f, "unknown"),
31        }
32    }
33}
34
35impl Origin {
36    /// The origin is unknown.
37    pub fn unknown() -> Self {
38        Self {
39            inner: Inner::Unknown,
40        }
41    }
42
43    /// Set with IMDS.
44    pub fn imds() -> Self {
45        Self { inner: Inner::Imds }
46    }
47
48    /// Set on a shared config struct.
49    pub fn shared_config() -> Self {
50        Self {
51            inner: Inner::Programmatic(Kind::Shared),
52        }
53    }
54
55    /// Set on a service config struct.
56    pub fn service_config() -> Self {
57        Self {
58            inner: Inner::Programmatic(Kind::Service),
59        }
60    }
61
62    /// Set by an environment variable.
63    pub fn shared_environment_variable() -> Self {
64        Self {
65            inner: Inner::EnvironmentVariable(Kind::Shared),
66        }
67    }
68
69    /// Set by a service-specific environment variable.
70    pub fn service_environment_variable() -> Self {
71        Self {
72            inner: Inner::EnvironmentVariable(Kind::Service),
73        }
74    }
75
76    /// Set in a profile file.
77    pub fn shared_profile_file() -> Self {
78        Self {
79            inner: Inner::ProfileFile(Kind::Shared),
80        }
81    }
82
83    /// Service-specific, set in a profile file.
84    pub fn service_profile_file() -> Self {
85        Self {
86            inner: Inner::ProfileFile(Kind::Service),
87        }
88    }
89
90    /// Return true if the origin was set programmatically i.e. on an `SdkConfig` or service `Config`.
91    pub fn is_client_config(&self) -> bool {
92        matches!(
93            self,
94            Origin {
95                inner: Inner::Programmatic(..),
96                ..
97            }
98        )
99    }
100}
101
102impl Default for Origin {
103    fn default() -> Self {
104        Self::unknown()
105    }
106}
107
108#[derive(Debug, Clone, Copy)]
109enum Inner {
110    Imds,
111    ProfileFile(Kind),
112    EnvironmentVariable(Kind),
113    Programmatic(Kind),
114    Unknown,
115}
116
117impl Inner {
118    pub(self) fn is_unknown(&self) -> bool {
119        matches!(self, Inner::Unknown)
120    }
121}
122
123// Unknown is like NaN. It's not equal to anything, not even itself.
124impl PartialEq for Inner {
125    fn eq(&self, other: &Self) -> bool {
126        use Inner::*;
127
128        match (self, other) {
129            (Imds, Imds) => true,
130            (Programmatic(a), Programmatic(b)) => a == b,
131            (EnvironmentVariable(a), EnvironmentVariable(b)) => a == b,
132            (ProfileFile(a), ProfileFile(b)) => a == b,
133            _ => false,
134        }
135    }
136}
137
138impl PartialOrd for Inner {
139    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
140        use Inner::*;
141
142        if self.is_unknown() || other.is_unknown() {
143            return None;
144        }
145
146        match self {
147            // IMDS is the lowest priority
148            Imds => Some(Ordering::Less),
149            // ProfileFile is the second-lowest priority
150            ProfileFile(kind) => match other {
151                Imds => Some(Ordering::Greater),
152                ProfileFile(other_kind) => kind.partial_cmp(other_kind),
153                _ => Some(Ordering::Less),
154            },
155            // EnvironmentVariable is the second-highest priority
156            EnvironmentVariable(kind) => match other {
157                Imds | ProfileFile(_) => Some(Ordering::Greater),
158                EnvironmentVariable(other_kind) => kind.partial_cmp(other_kind),
159                _ => Some(Ordering::Less),
160            },
161            // Programmatic is the highest priority
162            Programmatic(kind) => match other {
163                Imds | EnvironmentVariable(_) | ProfileFile(_) => Some(Ordering::Greater),
164                Programmatic(other_kind) => kind.partial_cmp(other_kind),
165                _ => unreachable!(
166                    "When we have something higher than programmatic we can update this case."
167                ),
168            },
169            _ => unreachable!(),
170        }
171    }
172}
173
174#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd)]
175enum Kind {
176    Shared,
177    Service,
178}
179
180#[cfg(test)]
181mod test {
182    use super::Origin;
183
184    #[test]
185    fn test_precedence_low_to_high() {
186        // Lowest to highest precedence
187        let list = [
188            Origin::imds(),
189            Origin::shared_profile_file(),
190            Origin::service_profile_file(),
191            Origin::shared_environment_variable(),
192            Origin::service_environment_variable(),
193            Origin::shared_config(),
194            Origin::service_config(),
195        ];
196
197        for window in list.windows(2) {
198            let &[a, b] = window else { unreachable!() };
199            assert!(a < b);
200        }
201    }
202
203    #[test]
204    fn test_precedence_high_to_low() {
205        // Highest to lowest precedence
206        let list = [
207            Origin::service_config(),
208            Origin::shared_config(),
209            Origin::service_environment_variable(),
210            Origin::shared_environment_variable(),
211            Origin::service_profile_file(),
212            Origin::shared_profile_file(),
213            Origin::imds(),
214        ];
215
216        for window in list.windows(2) {
217            let &[a, b] = window else { unreachable!() };
218            assert!(a > b);
219        }
220    }
221
222    #[test]
223    fn test_unknown_is_not_equal() {
224        assert_ne!(Origin::unknown(), Origin::imds());
225        assert_ne!(Origin::unknown(), Origin::shared_config());
226        assert_ne!(Origin::unknown(), Origin::service_config());
227        assert_ne!(Origin::unknown(), Origin::shared_environment_variable());
228        assert_ne!(Origin::unknown(), Origin::service_environment_variable());
229        assert_ne!(Origin::unknown(), Origin::shared_profile_file());
230        assert_ne!(Origin::unknown(), Origin::service_profile_file());
231        assert_ne!(Origin::unknown(), Origin::unknown());
232    }
233
234    #[test]
235    fn test_self_equality() {
236        assert_eq!(Origin::imds(), Origin::imds());
237        assert_eq!(Origin::shared_config(), Origin::shared_config());
238        assert_eq!(Origin::service_config(), Origin::service_config());
239        assert_eq!(
240            Origin::shared_environment_variable(),
241            Origin::shared_environment_variable()
242        );
243        assert_eq!(
244            Origin::service_environment_variable(),
245            Origin::service_environment_variable()
246        );
247        assert_eq!(Origin::shared_profile_file(), Origin::shared_profile_file());
248        assert_eq!(
249            Origin::service_profile_file(),
250            Origin::service_profile_file()
251        );
252    }
253}