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 fn invalidate(&self, rejected: &Identity) {
72 let _ = rejected;
73 }
74
75 #[doc = include_str!("../../rustdoc/validate_base_client_config.md")]
76 fn validate_base_client_config(
77 &self,
78 runtime_components: &RuntimeComponentsBuilder,
79 cfg: &ConfigBag,
80 ) -> Result<(), BoxError> {
81 let _ = (runtime_components, cfg);
82 Ok(())
83 }
84
85 #[doc = include_str!("../../rustdoc/validate_final_config.md")]
86 fn validate_final_config(
87 &self,
88 runtime_components: &RuntimeComponents,
89 cfg: &ConfigBag,
90 ) -> Result<(), BoxError> {
91 let _ = (runtime_components, cfg);
92 Ok(())
93 }
94}
95
96#[derive(Clone, Debug)]
98pub struct SharedIdentityCache(Arc<dyn ResolveCachedIdentity>);
99
100impl SharedIdentityCache {
101 pub fn new(cache: impl ResolveCachedIdentity + 'static) -> Self {
103 Self(Arc::new(cache))
104 }
105}
106
107impl ResolveCachedIdentity for SharedIdentityCache {
108 fn resolve_cached_identity<'a>(
109 &'a self,
110 resolver: SharedIdentityResolver,
111 runtime_components: &'a RuntimeComponents,
112 config_bag: &'a ConfigBag,
113 ) -> IdentityFuture<'a> {
114 self.0
115 .resolve_cached_identity(resolver, runtime_components, config_bag)
116 }
117
118 fn invalidate(&self, rejected: &Identity) {
119 self.0.invalidate(rejected)
120 }
121}
122
123impl ValidateConfig for SharedIdentityResolver {}
124
125impl ValidateConfig for SharedIdentityCache {
126 fn validate_base_client_config(
127 &self,
128 runtime_components: &RuntimeComponentsBuilder,
129 cfg: &ConfigBag,
130 ) -> Result<(), BoxError> {
131 self.0.validate_base_client_config(runtime_components, cfg)
132 }
133
134 fn validate_final_config(
135 &self,
136 runtime_components: &RuntimeComponents,
137 cfg: &ConfigBag,
138 ) -> Result<(), BoxError> {
139 self.0.validate_final_config(runtime_components, cfg)
140 }
141}
142
143impl_shared_conversions!(convert SharedIdentityCache from ResolveCachedIdentity using SharedIdentityCache::new);
144
145pub trait ResolveIdentity: Send + Sync + Debug {
157 fn resolve_identity<'a>(
159 &'a self,
160 runtime_components: &'a RuntimeComponents,
161 config_bag: &'a ConfigBag,
162 ) -> IdentityFuture<'a>;
163
164 fn fallback_on_interrupt(&self) -> Option<Identity> {
173 None
174 }
175
176 fn cache_location(&self) -> IdentityCacheLocation {
182 IdentityCacheLocation::RuntimeComponents
183 }
184
185 fn cache_partition(&self) -> Option<IdentityCachePartition> {
189 None
190 }
191}
192
193#[non_exhaustive]
200#[derive(Copy, Clone, Debug, Eq, PartialEq)]
201pub enum IdentityCacheLocation {
202 RuntimeComponents,
204 IdentityResolver,
206}
207
208#[derive(Clone, Debug)]
210pub struct SharedIdentityResolver {
211 inner: Arc<dyn ResolveIdentity>,
212 cache_partition: IdentityCachePartition,
213}
214
215impl SharedIdentityResolver {
216 pub fn new(resolver: impl ResolveIdentity + 'static) -> Self {
218 let partition = match resolver.cache_partition() {
221 Some(p) => p,
222 None => IdentityCachePartition::new(),
223 };
224
225 Self {
226 inner: Arc::new(resolver),
227 cache_partition: partition,
228 }
229 }
230
231 pub fn cache_partition(&self) -> IdentityCachePartition {
236 self.cache_partition
237 }
238}
239
240impl ResolveIdentity for SharedIdentityResolver {
241 fn resolve_identity<'a>(
242 &'a self,
243 runtime_components: &'a RuntimeComponents,
244 config_bag: &'a ConfigBag,
245 ) -> IdentityFuture<'a> {
246 self.inner.resolve_identity(runtime_components, config_bag)
247 }
248
249 fn cache_location(&self) -> IdentityCacheLocation {
250 self.inner.cache_location()
251 }
252
253 fn cache_partition(&self) -> Option<IdentityCachePartition> {
254 Some(self.cache_partition())
255 }
256}
257
258impl_shared_conversions!(convert SharedIdentityResolver from ResolveIdentity using SharedIdentityResolver::new);
259
260type DataDebug = Arc<dyn (Fn(&Arc<dyn Any + Send + Sync>) -> &dyn Debug) + Send + Sync>;
261
262#[derive(Clone)]
274pub struct Identity {
275 data: Arc<dyn Any + Send + Sync>,
276 data_debug: DataDebug,
277 expiration: Option<SystemTime>,
278 properties: HashMap<TypeId, Arc<TypeErasedBox>>,
279}
280
281impl Identity {
282 pub fn new<T>(data: T, expiration: Option<SystemTime>) -> Self
284 where
285 T: Any + Debug + Send + Sync,
286 {
287 Self {
288 data: Arc::new(data),
289 data_debug: Arc::new(|d| d.downcast_ref::<T>().expect("type-checked") as _),
290 expiration,
291 properties: HashMap::default(),
292 }
293 }
294
295 pub fn builder() -> Builder {
297 Builder::default()
298 }
299
300 pub fn data<T: Any + Debug + Send + Sync + 'static>(&self) -> Option<&T> {
302 self.data.downcast_ref()
303 }
304
305 pub fn expiration(&self) -> Option<SystemTime> {
307 self.expiration
308 }
309
310 pub fn property<T: Any + Debug + Send + Sync + 'static>(&self) -> Option<&T> {
312 self.properties
313 .get(&TypeId::of::<T>())
314 .and_then(|b| b.downcast_ref())
315 }
316
317 pub fn ptr_eq(&self, other: &Identity) -> bool {
323 Arc::ptr_eq(&self.data, &other.data)
324 }
325}
326
327impl Debug for Identity {
328 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
329 let mut debug_struct = f.debug_struct("Identity");
330 debug_struct
331 .field("data", (self.data_debug)(&self.data))
332 .field("expiration", &self.expiration);
333 for (i, prop) in self.properties.values().enumerate() {
334 debug_struct.field(&format!("property_{i}"), prop);
335 }
336 debug_struct.finish()
337 }
338}
339
340impl ResolveIdentity for Identity {
341 fn resolve_identity<'a>(
342 &'a self,
343 _runtime_components: &'a RuntimeComponents,
344 _config_bag: &'a ConfigBag,
345 ) -> IdentityFuture<'a> {
346 IdentityFuture::ready(Ok(self.clone()))
347 }
348}
349
350#[derive(Debug)]
351enum ErrorKind {
352 MissingRequiredField(&'static str),
354}
355
356#[derive(Debug)]
358pub struct BuildError {
359 kind: ErrorKind,
360}
361
362impl BuildError {
363 fn missing_required_field(field_name: &'static str) -> Self {
364 BuildError {
365 kind: ErrorKind::MissingRequiredField(field_name),
366 }
367 }
368}
369
370impl fmt::Display for BuildError {
371 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> std::fmt::Result {
372 use ErrorKind::*;
373 match self.kind {
374 MissingRequiredField(field_name) => write!(f, "missing required field: `{field_name}`"),
375 }
376 }
377}
378
379impl std::error::Error for BuildError {}
380
381#[derive(Default)]
383pub struct Builder {
384 data: Option<Arc<dyn Any + Send + Sync>>,
385 data_debug: Option<DataDebug>,
386 expiration: Option<SystemTime>,
387 properties: HashMap<TypeId, Arc<TypeErasedBox>>,
388}
389
390impl Builder {
391 pub fn data<T: Any + Debug + Send + Sync + 'static>(mut self, data: T) -> Self {
393 self.set_data(data);
394 self
395 }
396
397 pub fn set_data<T: Any + Debug + Send + Sync + 'static>(&mut self, data: T) {
399 self.data = Some(Arc::new(data));
400 self.data_debug = Some(Arc::new(|d| {
401 d.downcast_ref::<T>().expect("type-checked") as _
402 }));
403 }
404
405 pub fn expiration(mut self, expiration: SystemTime) -> Self {
407 self.set_expiration(Some(expiration));
408 self
409 }
410
411 pub fn set_expiration(&mut self, expiration: Option<SystemTime>) {
413 self.expiration = expiration;
414 }
415
416 pub fn property<T: Any + Debug + Send + Sync + 'static>(mut self, prop: T) -> Self {
418 self.set_property(prop);
419 self
420 }
421
422 pub fn set_property<T: Any + Debug + Send + Sync + 'static>(&mut self, prop: T) {
424 self.properties
425 .insert(TypeId::of::<T>(), Arc::new(TypeErasedBox::new(prop)));
426 }
427
428 pub fn build(self) -> Result<Identity, BuildError> {
430 Ok(Identity {
431 data: self
432 .data
433 .ok_or_else(|| BuildError::missing_required_field("data"))?,
434 data_debug: self
435 .data_debug
436 .expect("should always be set when `data` is set"),
437 expiration: self.expiration,
438 properties: self.properties,
439 })
440 }
441}
442
443#[cfg(test)]
444mod tests {
445 use super::*;
446 use aws_smithy_async::time::{SystemTimeSource, TimeSource};
447
448 #[test]
449 fn check_send_sync() {
450 fn is_send_sync<T: Send + Sync>(_: T) {}
451 is_send_sync(Identity::new("foo", None));
452 }
453
454 #[test]
455 fn create_retrieve_identity() {
456 #[derive(Debug)]
457 struct MyIdentityData {
458 first: String,
459 last: String,
460 }
461
462 let ts = SystemTimeSource::new();
463 let expiration = ts.now();
464 let identity = Identity::new(
465 MyIdentityData {
466 first: "foo".into(),
467 last: "bar".into(),
468 },
469 Some(expiration),
470 );
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 }
476
477 #[test]
478 fn insert_get_identity_properties() {
479 #[derive(Debug)]
480 struct MyIdentityData {
481 first: String,
482 last: String,
483 }
484 #[derive(Debug)]
485 struct PropertyAlpha;
486 #[derive(Debug)]
487 struct PropertyBeta;
488
489 let ts = SystemTimeSource::new();
490 let expiration = ts.now();
491 let identity = Identity::builder()
492 .data(MyIdentityData {
493 first: "foo".into(),
494 last: "bar".into(),
495 })
496 .expiration(expiration)
497 .property(PropertyAlpha)
498 .property(PropertyBeta)
499 .build()
500 .unwrap();
501
502 assert_eq!("foo", identity.data::<MyIdentityData>().unwrap().first);
503 assert_eq!("bar", identity.data::<MyIdentityData>().unwrap().last);
504 assert_eq!(Some(expiration), identity.expiration());
505 assert!(identity.property::<PropertyAlpha>().is_some());
506 assert!(identity.property::<PropertyBeta>().is_some());
507 }
508
509 #[test]
510 fn ptr_eq_true_for_clone() {
511 let identity = Identity::new("some-identity-data", None);
513 let clone = identity.clone();
514 assert!(identity.ptr_eq(&clone));
515 assert!(clone.ptr_eq(&identity)); assert!(identity.ptr_eq(&identity)); }
518
519 #[test]
520 fn ptr_eq_false_for_independently_built_identities() {
521 let a = Identity::new("same-data", None);
524 let b = Identity::new("same-data", None);
525 assert!(!a.ptr_eq(&b));
526 }
527}