Skip to main content

aws_smithy_schema/schema/
header_omit_settings.rs

1/*
2 * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3 * SPDX-License-Identifier: Apache-2.0
4 */
5
6//! Runtime suppression of protocol-default request headers.
7//!
8//! [`SharedHeaderOmitSettings`] is the [`Storable`] config-bag entry that the
9//! runtime (notably
10//! [`HttpBindingProtocol::serialize_request_with_body`](crate::http_protocol::HttpBindingProtocol::serialize_request_with_body))
11//! consults when deciding whether to insert protocol-default `Content-Type` and
12//! `Content-Length` headers on outgoing requests. The most common producer is
13//! the SigV4 presigning interceptor, which omits both so they don't end up in
14//! the signed-header set of presigned URLs.
15//!
16//! Customer-supplied `@httpHeader` values are unaffected by these settings;
17//! they always take priority over the runtime's defaults regardless of the
18//! omit flags.
19//!
20//! The trait/wrapper split mirrors `SharedClientProtocol` — the trait is the
21//! contract any caller can implement, the wrapper is the thing actually placed
22//! in the [`ConfigBag`](aws_smithy_types::config_bag::ConfigBag).
23
24// IMPLEMENTATION NOTE — why a trait + `Arc`'d wrapper rather than just
25// promoting the inlineable `HeaderSerializationSettings` to `pub`?
26//
27// `HeaderSerializationSettings` lives in `rust-runtime/inlineable/` (and the
28// AWS-side duplicate) where it's `pub(crate)` and gets inlined into each
29// generated SDK crate. The runtime here needs to read the omit flags from
30// the `ConfigBag`, and `ConfigBag::load::<T>()` is `TypeId`-keyed — so the
31// producer (presigning interceptor) and the consumer (runtime) must
32// reference the same Rust type.
33//
34// Promoting the inlineable type to `pub` would solve the lookup, but would
35// pin a concrete data shape into a published crate's public API. Instead,
36// this module publishes only the abstract surface (the trait + an `Arc`'d
37// wrapper for `Storable`). Inlineable implements the trait on its existing
38// `HeaderSerializationSettings`; new fields there stay a private inlineable
39// concern. Adding a new omit category requires a new trait method here,
40// which is forward-compatible thanks to default `false` bodies.
41
42use aws_smithy_types::config_bag::{Storable, StoreReplace};
43use std::sync::Arc;
44
45/// Configures whether the runtime should suppress protocol-default request
46/// headers during serialization.
47///
48/// All methods default to returning `false` so future trait additions do not
49/// break existing implementors.
50pub trait HeaderOmitSettings: Send + Sync + std::fmt::Debug {
51    /// Returns `true` if the runtime must not insert a default `Content-Type`
52    /// header. A customer-supplied `@httpHeader("Content-Type")` value still
53    /// takes priority and is unaffected by this flag.
54    fn should_omit_default_content_type(&self) -> bool {
55        false
56    }
57
58    /// Returns `true` if the runtime must not insert a default `Content-Length`
59    /// header.
60    fn should_omit_default_content_length(&self) -> bool {
61        false
62    }
63}
64
65/// A shared, type-erased [`HeaderOmitSettings`] suitable for storage in the
66/// [`ConfigBag`](aws_smithy_types::config_bag::ConfigBag).
67///
68/// Wraps `Arc<dyn HeaderOmitSettings>`. Cheaply [`Clone`]able via the inner
69/// `Arc`.
70#[derive(Debug, Clone)]
71pub struct SharedHeaderOmitSettings {
72    inner: Arc<dyn HeaderOmitSettings>,
73}
74
75impl SharedHeaderOmitSettings {
76    /// Wraps any [`HeaderOmitSettings`] implementation in a shared, type-erased
77    /// container.
78    pub fn new<T>(settings: T) -> Self
79    where
80        T: HeaderOmitSettings + 'static,
81    {
82        Self {
83            inner: Arc::new(settings),
84        }
85    }
86
87    /// Constructs from an existing `Arc<dyn HeaderOmitSettings>`. Useful when
88    /// the same settings instance must be shared across multiple config-bag
89    /// entries without re-allocating.
90    pub fn from_arc(inner: Arc<dyn HeaderOmitSettings>) -> Self {
91        Self { inner }
92    }
93}
94
95impl std::ops::Deref for SharedHeaderOmitSettings {
96    type Target = dyn HeaderOmitSettings;
97
98    fn deref(&self) -> &Self::Target {
99        &*self.inner
100    }
101}
102
103impl Storable for SharedHeaderOmitSettings {
104    type Storer = StoreReplace<Self>;
105}
106
107#[cfg(test)]
108mod tests {
109    use super::*;
110    use aws_smithy_types::config_bag::{ConfigBag, Layer};
111
112    #[derive(Debug, Default)]
113    struct OmitBoth;
114    impl HeaderOmitSettings for OmitBoth {
115        fn should_omit_default_content_type(&self) -> bool {
116            true
117        }
118        fn should_omit_default_content_length(&self) -> bool {
119            true
120        }
121    }
122
123    #[derive(Debug, Default)]
124    struct OmitNone;
125    impl HeaderOmitSettings for OmitNone {}
126
127    #[test]
128    fn default_trait_methods_return_false() {
129        let settings = OmitNone;
130        assert!(!settings.should_omit_default_content_type());
131        assert!(!settings.should_omit_default_content_length());
132    }
133
134    #[test]
135    fn shared_delegates_to_inner_via_deref() {
136        let shared = SharedHeaderOmitSettings::new(OmitBoth);
137        assert!(shared.should_omit_default_content_type());
138        assert!(shared.should_omit_default_content_length());
139    }
140
141    #[test]
142    fn shared_round_trips_through_config_bag() {
143        let mut layer = Layer::new("test");
144        layer.store_put(SharedHeaderOmitSettings::new(OmitBoth));
145        let cfg = ConfigBag::of_layers(vec![layer]);
146
147        let loaded = cfg
148            .load::<SharedHeaderOmitSettings>()
149            .expect("settings stored in ConfigBag");
150        assert!(loaded.should_omit_default_content_type());
151        assert!(loaded.should_omit_default_content_length());
152    }
153
154    #[test]
155    fn shared_absent_from_config_bag_means_no_omits() {
156        let cfg = ConfigBag::base();
157        assert!(cfg.load::<SharedHeaderOmitSettings>().is_none());
158    }
159
160    #[test]
161    fn from_arc_avoids_realloc_when_caller_already_holds_arc() {
162        let original: Arc<dyn HeaderOmitSettings> = Arc::new(OmitBoth);
163        let strong = Arc::strong_count(&original);
164        let shared = SharedHeaderOmitSettings::from_arc(Arc::clone(&original));
165        assert_eq!(Arc::strong_count(&original), strong + 1);
166        assert!(shared.should_omit_default_content_type());
167    }
168}