aws_smithy_runtime_api/client/
identity.rs1use crate::box_error::BoxError;
7use crate::client::runtime_components::sealed::ValidateConfig;
8use crate::client::runtime_components::{RuntimeComponents, RuntimeComponentsBuilder};
9use crate::impl_shared_conversions;
10use aws_smithy_types::config_bag::ConfigBag;
11use aws_smithy_types::type_erasure::TypeErasedBox;
12use std::any::{Any, TypeId};
13use std::collections::HashMap;
14use std::fmt;
15use std::fmt::Debug;
16use std::sync::atomic::{AtomicUsize, Ordering};
17use std::sync::Arc;
18use std::time::SystemTime;
19
20#[cfg(feature = "http-auth")]
21pub mod http;
22
23new_type_future! {
24 #[doc = "Future for [`IdentityResolver::resolve_identity`]."]
25 pub struct IdentityFuture<'a, Identity, BoxError>;
26}
27
28static NEXT_CACHE_PARTITION: AtomicUsize = AtomicUsize::new(0);
29
30#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash, Ord, PartialOrd)]
43pub struct IdentityCachePartition(usize);
44
45impl IdentityCachePartition {
46 pub fn new() -> Self {
48 Self(NEXT_CACHE_PARTITION.fetch_add(1, Ordering::Relaxed))
49 }
50
51 #[cfg(feature = "test-util")]
53 pub fn new_for_tests(value: usize) -> IdentityCachePartition {
54 Self(value)
55 }
56}
57
58pub trait ResolveCachedIdentity: fmt::Debug + Send + Sync {
60 fn resolve_cached_identity<'a>(
62 &'a self,
63 resolver: SharedIdentityResolver,
64 runtime_components: &'a RuntimeComponents,
65 config_bag: &'a ConfigBag,
66 ) -> IdentityFuture<'a>;
67
68 #[doc = include_str!("../../rustdoc/validate_base_client_config.md")]
69 fn validate_base_client_config(
70 &self,
71 runtime_components: &RuntimeComponentsBuilder,
72 cfg: &ConfigBag,
73 ) -> Result<(), BoxError> {
74 let _ = (runtime_components, cfg);
75 Ok(())
76 }
77
78 #[doc = include_str!("../../rustdoc/validate_final_config.md")]
79 fn validate_final_config(
80 &self,
81 runtime_components: &RuntimeComponents,
82 cfg: &ConfigBag,
83 ) -> Result<(), BoxError> {
84 let _ = (runtime_components, cfg);
85 Ok(())
86 }
87}
88
89#[derive(Clone, Debug)]
91pub struct SharedIdentityCache(Arc<dyn ResolveCachedIdentity>);
92
93impl SharedIdentityCache {
94 pub fn new(cache: impl ResolveCachedIdentity + 'static) -> Self {
96 Self(Arc::new(cache))
97 }
98}
99
100impl ResolveCachedIdentity for SharedIdentityCache {
101 fn resolve_cached_identity<'a>(
102 &'a self,
103 resolver: SharedIdentityResolver,
104 runtime_components: &'a RuntimeComponents,
105 config_bag: &'a ConfigBag,
106 ) -> IdentityFuture<'a> {
107 self.0
108 .resolve_cached_identity(resolver, runtime_components, config_bag)
109 }
110}
111
112impl ValidateConfig for SharedIdentityResolver {}
113
114impl ValidateConfig for SharedIdentityCache {
115 fn validate_base_client_config(
116 &self,
117 runtime_components: &RuntimeComponentsBuilder,
118 cfg: &ConfigBag,
119 ) -> Result<(), BoxError> {
120 self.0.validate_base_client_config(runtime_components, cfg)
121 }
122
123 fn validate_final_config(
124 &self,
125 runtime_components: &RuntimeComponents,
126 cfg: &ConfigBag,
127 ) -> Result<(), BoxError> {
128 self.0.validate_final_config(runtime_components, cfg)
129 }
130}
131
132impl_shared_conversions!(convert SharedIdentityCache from ResolveCachedIdentity using SharedIdentityCache::new);
133
134pub trait ResolveIdentity: Send + Sync + Debug {
146 fn resolve_identity<'a>(
148 &'a self,
149 runtime_components: &'a RuntimeComponents,
150 config_bag: &'a ConfigBag,
151 ) -> IdentityFuture<'a>;
152
153 fn fallback_on_interrupt(&self) -> Option<Identity> {
162 None
163 }
164
165 fn cache_location(&self) -> IdentityCacheLocation {
171 IdentityCacheLocation::RuntimeComponents
172 }
173
174 fn cache_partition(&self) -> Option<IdentityCachePartition> {
178 None
179 }
180}
181
182#[non_exhaustive]
189#[derive(Copy, Clone, Debug, Eq, PartialEq)]
190pub enum IdentityCacheLocation {
191 RuntimeComponents,
193 IdentityResolver,
195}
196
197#[derive(Clone, Debug)]
199pub struct SharedIdentityResolver {
200 inner: Arc<dyn ResolveIdentity>,
201 cache_partition: IdentityCachePartition,
202}
203
204impl SharedIdentityResolver {
205 pub fn new(resolver: impl ResolveIdentity + 'static) -> Self {
207 let partition = match resolver.cache_partition() {
210 Some(p) => p,
211 None => IdentityCachePartition::new(),
212 };
213
214 Self {
215 inner: Arc::new(resolver),
216 cache_partition: partition,
217 }
218 }
219
220 pub fn cache_partition(&self) -> IdentityCachePartition {
225 self.cache_partition
226 }
227}
228
229impl ResolveIdentity for SharedIdentityResolver {
230 fn resolve_identity<'a>(
231 &'a self,
232 runtime_components: &'a RuntimeComponents,
233 config_bag: &'a ConfigBag,
234 ) -> IdentityFuture<'a> {
235 self.inner.resolve_identity(runtime_components, config_bag)
236 }
237
238 fn cache_location(&self) -> IdentityCacheLocation {
239 self.inner.cache_location()
240 }
241
242 fn cache_partition(&self) -> Option<IdentityCachePartition> {
243 Some(self.cache_partition())
244 }
245}
246
247impl_shared_conversions!(convert SharedIdentityResolver from ResolveIdentity using SharedIdentityResolver::new);
248
249type DataDebug = Arc<dyn (Fn(&Arc<dyn Any + Send + Sync>) -> &dyn Debug) + Send + Sync>;
250
251#[derive(Clone)]
263pub struct Identity {
264 data: Arc<dyn Any + Send + Sync>,
265 data_debug: DataDebug,
266 expiration: Option<SystemTime>,
267 properties: HashMap<TypeId, Arc<TypeErasedBox>>,
268}
269
270impl Identity {
271 pub fn new<T>(data: T, expiration: Option<SystemTime>) -> Self
273 where
274 T: Any + Debug + Send + Sync,
275 {
276 Self {
277 data: Arc::new(data),
278 data_debug: Arc::new(|d| d.downcast_ref::<T>().expect("type-checked") as _),
279 expiration,
280 properties: HashMap::default(),
281 }
282 }
283
284 pub fn builder() -> Builder {
286 Builder::default()
287 }
288
289 pub fn data<T: Any + Debug + Send + Sync + 'static>(&self) -> Option<&T> {
291 self.data.downcast_ref()
292 }
293
294 pub fn expiration(&self) -> Option<SystemTime> {
296 self.expiration
297 }
298
299 pub fn property<T: Any + Debug + Send + Sync + 'static>(&self) -> Option<&T> {
301 self.properties
302 .get(&TypeId::of::<T>())
303 .and_then(|b| b.downcast_ref())
304 }
305}
306
307impl Debug for Identity {
308 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
309 let mut debug_struct = f.debug_struct("Identity");
310 debug_struct
311 .field("data", (self.data_debug)(&self.data))
312 .field("expiration", &self.expiration);
313 for (i, prop) in self.properties.values().enumerate() {
314 debug_struct.field(&format!("property_{i}"), prop);
315 }
316 debug_struct.finish()
317 }
318}
319
320#[derive(Debug)]
321enum ErrorKind {
322 MissingRequiredField(&'static str),
324}
325
326#[derive(Debug)]
328pub struct BuildError {
329 kind: ErrorKind,
330}
331
332impl BuildError {
333 fn missing_required_field(field_name: &'static str) -> Self {
334 BuildError {
335 kind: ErrorKind::MissingRequiredField(field_name),
336 }
337 }
338}
339
340impl fmt::Display for BuildError {
341 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> std::fmt::Result {
342 use ErrorKind::*;
343 match self.kind {
344 MissingRequiredField(field_name) => write!(f, "missing required field: `{field_name}`"),
345 }
346 }
347}
348
349impl std::error::Error for BuildError {}
350
351#[derive(Default)]
353pub struct Builder {
354 data: Option<Arc<dyn Any + Send + Sync>>,
355 data_debug: Option<DataDebug>,
356 expiration: Option<SystemTime>,
357 properties: HashMap<TypeId, Arc<TypeErasedBox>>,
358}
359
360impl Builder {
361 pub fn data<T: Any + Debug + Send + Sync + 'static>(mut self, data: T) -> Self {
363 self.set_data(data);
364 self
365 }
366
367 pub fn set_data<T: Any + Debug + Send + Sync + 'static>(&mut self, data: T) {
369 self.data = Some(Arc::new(data));
370 self.data_debug = Some(Arc::new(|d| {
371 d.downcast_ref::<T>().expect("type-checked") as _
372 }));
373 }
374
375 pub fn expiration(mut self, expiration: SystemTime) -> Self {
377 self.set_expiration(Some(expiration));
378 self
379 }
380
381 pub fn set_expiration(&mut self, expiration: Option<SystemTime>) {
383 self.expiration = expiration;
384 }
385
386 pub fn property<T: Any + Debug + Send + Sync + 'static>(mut self, prop: T) -> Self {
388 self.set_property(prop);
389 self
390 }
391
392 pub fn set_property<T: Any + Debug + Send + Sync + 'static>(&mut self, prop: T) {
394 self.properties
395 .insert(TypeId::of::<T>(), Arc::new(TypeErasedBox::new(prop)));
396 }
397
398 pub fn build(self) -> Result<Identity, BuildError> {
400 Ok(Identity {
401 data: self
402 .data
403 .ok_or_else(|| BuildError::missing_required_field("data"))?,
404 data_debug: self
405 .data_debug
406 .expect("should always be set when `data` is set"),
407 expiration: self.expiration,
408 properties: self.properties,
409 })
410 }
411}
412
413#[cfg(test)]
414mod tests {
415 use super::*;
416 use aws_smithy_async::time::{SystemTimeSource, TimeSource};
417
418 #[test]
419 fn check_send_sync() {
420 fn is_send_sync<T: Send + Sync>(_: T) {}
421 is_send_sync(Identity::new("foo", None));
422 }
423
424 #[test]
425 fn create_retrieve_identity() {
426 #[derive(Debug)]
427 struct MyIdentityData {
428 first: String,
429 last: String,
430 }
431
432 let ts = SystemTimeSource::new();
433 let expiration = ts.now();
434 let identity = Identity::new(
435 MyIdentityData {
436 first: "foo".into(),
437 last: "bar".into(),
438 },
439 Some(expiration),
440 );
441
442 assert_eq!("foo", identity.data::<MyIdentityData>().unwrap().first);
443 assert_eq!("bar", identity.data::<MyIdentityData>().unwrap().last);
444 assert_eq!(Some(expiration), identity.expiration());
445 }
446
447 #[test]
448 fn insert_get_identity_properties() {
449 #[derive(Debug)]
450 struct MyIdentityData {
451 first: String,
452 last: String,
453 }
454 #[derive(Debug)]
455 struct PropertyAlpha;
456 #[derive(Debug)]
457 struct PropertyBeta;
458
459 let ts = SystemTimeSource::new();
460 let expiration = ts.now();
461 let identity = Identity::builder()
462 .data(MyIdentityData {
463 first: "foo".into(),
464 last: "bar".into(),
465 })
466 .expiration(expiration)
467 .property(PropertyAlpha)
468 .property(PropertyBeta)
469 .build()
470 .unwrap();
471
472 assert_eq!("foo", identity.data::<MyIdentityData>().unwrap().first);
473 assert_eq!("bar", identity.data::<MyIdentityData>().unwrap().last);
474 assert_eq!(Some(expiration), identity.expiration());
475 assert!(identity.property::<PropertyAlpha>().is_some());
476 assert!(identity.property::<PropertyBeta>().is_some());
477 }
478}