aws_smithy_types/
endpoint.rs

1/*
2 *  Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3 *  SPDX-License-Identifier: Apache-2.0
4 */
5//! Smithy Endpoint Types
6
7use crate::config_bag::{Storable, StoreReplace};
8use crate::Document;
9use std::borrow::Cow;
10use std::collections::HashMap;
11
12type MaybeStatic = Cow<'static, str>;
13
14/// An authentication scheme configuration for an endpoint.
15///
16/// This is a lightweight alternative to storing auth schemes as
17/// `Document::Object(HashMap<String, Document>)` in endpoint properties.
18/// Properties are stored in a flat `Vec` and looked up via linear scan,
19/// which is faster than HashMap for the typical 3-4 entries.
20#[derive(Debug, Clone, PartialEq)]
21pub struct EndpointAuthScheme {
22    name: MaybeStatic,
23    properties: Vec<(MaybeStatic, Document)>,
24}
25
26impl EndpointAuthScheme {
27    /// Creates a new `EndpointAuthScheme` with pre-allocated capacity for properties.
28    pub fn with_capacity(name: impl Into<MaybeStatic>, capacity: usize) -> Self {
29        Self {
30            name: name.into(),
31            properties: Vec::with_capacity(capacity),
32        }
33    }
34
35    /// Returns the auth scheme name (e.g., "sigv4", "sigv4a").
36    pub fn name(&self) -> &str {
37        &self.name
38    }
39
40    /// Adds a property to this auth scheme. Chainable.
41    pub fn put(mut self, key: impl Into<MaybeStatic>, value: impl Into<Document>) -> Self {
42        self.properties.push((key.into(), value.into()));
43        self
44    }
45
46    /// Gets a property value by name (linear scan).
47    pub fn get(&self, key: &str) -> Option<&Document> {
48        self.properties
49            .iter()
50            .find(|(k, _)| k.as_ref() == key)
51            .map(|(_, v)| v)
52    }
53
54    /// Converts this auth scheme into a `Document` for backward compatibility.
55    pub fn as_document(&self) -> Document {
56        let mut map = HashMap::with_capacity(self.properties.len() + 1);
57        map.insert("name".to_string(), Document::String(self.name.to_string()));
58        for (k, v) in &self.properties {
59            map.insert(k.to_string(), v.clone());
60        }
61        Document::Object(map)
62    }
63}
64
65/* ANCHOR: endpoint */
66/// Smithy Endpoint Type
67///
68/// Generally, this type should not be used from user code
69#[derive(Debug, Clone, PartialEq)]
70pub struct Endpoint {
71    url: MaybeStatic,
72    headers: HashMap<MaybeStatic, Vec<MaybeStatic>>,
73    properties: HashMap<MaybeStatic, Document>,
74    auth_schemes: Vec<EndpointAuthScheme>,
75}
76
77/* ANCHOR_END: endpoint */
78
79#[allow(unused)]
80impl Endpoint {
81    /// Returns the URL of this endpoint
82    pub fn url(&self) -> &str {
83        &self.url
84    }
85
86    /// Returns the headers associated with this endpoint
87    pub fn headers(&self) -> impl Iterator<Item = (&str, impl Iterator<Item = &str>)> {
88        self.headers
89            .iter()
90            .map(|(k, v)| (k.as_ref(), v.iter().map(|v| v.as_ref())))
91    }
92
93    /// Returns the properties associated with this endpoint
94    pub fn properties(&self) -> &HashMap<Cow<'static, str>, Document> {
95        &self.properties
96    }
97
98    /// Returns the typed auth schemes associated with this endpoint
99    pub fn auth_schemes(&self) -> &[EndpointAuthScheme] {
100        &self.auth_schemes
101    }
102
103    /// Converts this endpoint back into a [`Builder`]
104    pub fn into_builder(self) -> Builder {
105        Builder { endpoint: self }
106    }
107
108    /// A builder for [`Endpoint`]
109    pub fn builder() -> Builder {
110        Builder::new()
111    }
112}
113
114impl Storable for Endpoint {
115    type Storer = StoreReplace<Self>;
116}
117
118#[derive(Debug, Clone)]
119/// Builder for [`Endpoint`]
120pub struct Builder {
121    endpoint: Endpoint,
122}
123
124#[allow(unused)]
125impl Builder {
126    pub(crate) fn new() -> Self {
127        Self {
128            endpoint: Endpoint {
129                url: Default::default(),
130                headers: HashMap::new(),
131                properties: HashMap::new(),
132                auth_schemes: Vec::new(),
133            },
134        }
135    }
136
137    /// Set the URL of the Endpoint
138    ///
139    /// # Examples
140    /// ```rust
141    /// use aws_smithy_types::endpoint::Endpoint;
142    /// let endpoint = Endpoint::builder().url("https://www.example.com").build();
143    /// ```
144    pub fn url(mut self, url: impl Into<MaybeStatic>) -> Self {
145        self.endpoint.url = url.into();
146        self
147    }
148
149    /// Adds a header to the endpoint
150    ///
151    /// If there is already a header for this key, this header will be appended to that key
152    ///
153    /// # Examples
154    /// ```rust
155    /// use aws_smithy_types::endpoint::Endpoint;
156    /// let endpoint = Endpoint::builder().url("https://www.example.com").header("x-my-header", "hello").build();
157    /// ```
158    pub fn header(mut self, name: impl Into<MaybeStatic>, value: impl Into<MaybeStatic>) -> Self {
159        self.endpoint
160            .headers
161            .entry(name.into())
162            .or_default()
163            .push(value.into());
164        self
165    }
166
167    /// Adds a property to the endpoint
168    ///
169    /// If there is already a property for this key, the existing property will be overwritten
170    ///
171    /// # Examples
172    /// ```rust
173    /// use aws_smithy_types::endpoint::Endpoint;
174    /// let endpoint = Endpoint::builder()
175    ///   .url("https://www.example.com")
176    ///   .property("x-my-header", true)
177    ///   .build();
178    /// ```
179    pub fn property(mut self, key: impl Into<MaybeStatic>, value: impl Into<Document>) -> Self {
180        self.endpoint.properties.insert(key.into(), value.into());
181        self
182    }
183
184    /// Adds a typed auth scheme to the endpoint
185    pub fn auth_scheme(mut self, auth_scheme: EndpointAuthScheme) -> Self {
186        self.endpoint.auth_schemes.push(auth_scheme);
187        self
188    }
189
190    /// Constructs an [`Endpoint`] from this builder
191    ///
192    /// # Panics
193    /// Panics if URL is unset or empty
194    pub fn build(self) -> Endpoint {
195        assert_ne!(self.endpoint.url(), "", "URL was unset");
196        self.endpoint
197    }
198}
199
200#[cfg(test)]
201mod test {
202    use crate::endpoint::Endpoint;
203    use crate::Document;
204    use std::borrow::Cow;
205    use std::collections::HashMap;
206
207    #[test]
208    fn endpoint_builder() {
209        let endpoint = Endpoint::builder()
210            .url("https://www.amazon.com")
211            .header("x-amz-test", "header-value")
212            .property("custom", Document::Bool(true))
213            .build();
214        assert_eq!(endpoint.url, Cow::Borrowed("https://www.amazon.com"));
215        assert_eq!(
216            endpoint.headers,
217            HashMap::from([(
218                Cow::Borrowed("x-amz-test"),
219                vec![Cow::Borrowed("header-value")]
220            )])
221        );
222        assert_eq!(
223            endpoint.properties,
224            HashMap::from([(Cow::Borrowed("custom"), Document::Bool(true))])
225        );
226
227        assert_eq!(endpoint.url(), "https://www.amazon.com");
228        assert_eq!(
229            endpoint
230                .headers()
231                .map(|(k, v)| (k, v.collect::<Vec<_>>()))
232                .collect::<Vec<_>>(),
233            vec![("x-amz-test", vec!["header-value"])]
234        );
235    }
236
237    #[test]
238    fn borrowed_values() {
239        fn foo(a: &str) {
240            // borrowed values without a static lifetime need to be converted into owned values
241            let endpoint = Endpoint::builder().url(a.to_string()).build();
242            assert_eq!(endpoint.url(), a);
243        }
244
245        foo("asdf");
246    }
247}