aws_smithy_runtime_api/client/
endpoint.rs

1/*
2 * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3 * SPDX-License-Identifier: Apache-2.0
4 */
5
6//! APIs needed to configure endpoint resolution for clients.
7
8use crate::box_error::BoxError;
9use crate::client::runtime_components::sealed::ValidateConfig;
10use crate::impl_shared_conversions;
11use aws_smithy_types::config_bag::{Storable, StoreReplace};
12use aws_smithy_types::endpoint::Endpoint;
13use aws_smithy_types::type_erasure::TypeErasedBox;
14use error::InvalidEndpointError;
15use http_02x::uri::Authority;
16use std::any::TypeId;
17use std::collections::HashMap;
18use std::fmt;
19use std::str::FromStr;
20use std::sync::Arc;
21
22new_type_future! {
23    #[doc = "Future for [`ResolveEndpoint::resolve_endpoint`]."]
24    pub struct EndpointFuture<'a, Endpoint, BoxError>;
25}
26
27/// Parameters originating from the Smithy endpoint ruleset required for endpoint resolution.
28///
29/// The actual endpoint parameters are code generated from the Smithy model, and thus,
30/// are not known to the runtime crates. Hence, this struct is really a new-type around
31/// a [`TypeErasedBox`] that holds the actual concrete parameters in it.
32///
33/// This struct allows the caller to store and retrieve properties of arbitrary types.
34/// These arbitrary properties are intended to be incorporated into the concrete parameters
35/// by [`ResolveEndpoint::finalize_params`].
36#[derive(Debug)]
37pub struct EndpointResolverParams {
38    inner: TypeErasedBox,
39    property: HashMap<TypeId, TypeErasedBox>,
40}
41
42impl EndpointResolverParams {
43    /// Creates a new [`EndpointResolverParams`] from a concrete parameters instance.
44    pub fn new<T: fmt::Debug + Send + Sync + 'static>(params: T) -> Self {
45        Self {
46            inner: TypeErasedBox::new(params),
47            property: HashMap::new(),
48        }
49    }
50
51    /// Attempts to downcast the underlying concrete parameters to `T` and return it as a reference.
52    pub fn get<T: fmt::Debug + Send + Sync + 'static>(&self) -> Option<&T> {
53        self.inner.downcast_ref()
54    }
55
56    /// Attempts to downcast the underlying concrete parameters to `T` and return it as a mutable reference.
57    pub fn get_mut<T: fmt::Debug + Send + Sync + 'static>(&mut self) -> Option<&mut T> {
58        self.inner.downcast_mut()
59    }
60
61    /// Sets property of an arbitrary type `T` for the endpoint resolver params.
62    pub fn set_property<T: fmt::Debug + Send + Sync + 'static>(&mut self, t: T) {
63        self.property
64            .insert(TypeId::of::<T>(), TypeErasedBox::new(t));
65    }
66
67    /// Attempts to retrieve a reference to property of a given type `T`.
68    pub fn get_property<T: fmt::Debug + Send + Sync + 'static>(&self) -> Option<&T> {
69        self.property
70            .get(&TypeId::of::<T>())
71            .and_then(|b| b.downcast_ref())
72    }
73
74    /// Attempts to retrieve a mutable reference to property of a given type `T`.
75    pub fn get_property_mut<T: fmt::Debug + Send + Sync + 'static>(&mut self) -> Option<&mut T> {
76        self.property
77            .get_mut(&TypeId::of::<T>())
78            .and_then(|b| b.downcast_mut())
79    }
80}
81
82impl Storable for EndpointResolverParams {
83    type Storer = StoreReplace<Self>;
84}
85
86/// Configurable endpoint resolver implementation.
87pub trait ResolveEndpoint: Send + Sync + fmt::Debug {
88    /// Asynchronously resolves an endpoint to use from the given endpoint parameters.
89    fn resolve_endpoint<'a>(&'a self, params: &'a EndpointResolverParams) -> EndpointFuture<'a>;
90
91    /// Finalize the service-specific concrete parameters in `_params`.
92    ///
93    /// The `EndpointResolverParams` may need to include additional data at a later point,
94    /// after its creation in the `read_before_execution` method of an endpoint parameters interceptor.
95    /// Modifying it directly within the [`ResolveEndpoint::resolve_endpoint`] method is not feasible,
96    /// as `params` is passed by reference. This means that incorporating extra data would require
97    /// cloning `params` within the method. However, the return type `EndpointFuture` has a lifetime
98    /// tied to the input argument, making it impossible to return the cloned `params`, as its lifetime
99    /// is scoped to the method.
100    fn finalize_params<'a>(
101        &'a self,
102        _params: &'a mut EndpointResolverParams,
103    ) -> Result<(), BoxError> {
104        Ok(())
105    }
106}
107
108/// Shared endpoint resolver.
109///
110/// This is a simple shared ownership wrapper type for the [`ResolveEndpoint`] trait.
111#[derive(Clone, Debug)]
112pub struct SharedEndpointResolver(Arc<dyn ResolveEndpoint>);
113
114impl SharedEndpointResolver {
115    /// Creates a new [`SharedEndpointResolver`].
116    pub fn new(endpoint_resolver: impl ResolveEndpoint + 'static) -> Self {
117        Self(Arc::new(endpoint_resolver))
118    }
119}
120
121impl ResolveEndpoint for SharedEndpointResolver {
122    fn resolve_endpoint<'a>(&'a self, params: &'a EndpointResolverParams) -> EndpointFuture<'a> {
123        self.0.resolve_endpoint(params)
124    }
125
126    fn finalize_params<'a>(
127        &'a self,
128        params: &'a mut EndpointResolverParams,
129    ) -> Result<(), BoxError> {
130        self.0.finalize_params(params)
131    }
132}
133
134impl ValidateConfig for SharedEndpointResolver {}
135
136impl_shared_conversions!(convert SharedEndpointResolver from ResolveEndpoint using SharedEndpointResolver::new);
137
138/// A special type that adds support for services that have special URL-prefixing rules.
139#[derive(Clone, Debug, Eq, PartialEq)]
140pub struct EndpointPrefix(String);
141impl EndpointPrefix {
142    /// Create a new endpoint prefix from an `impl Into<String>`. If the prefix argument is invalid,
143    /// a [`InvalidEndpointError`] will be returned.
144    pub fn new(prefix: impl Into<String>) -> Result<Self, InvalidEndpointError> {
145        let prefix = prefix.into();
146        match Authority::from_str(&prefix) {
147            Ok(_) => Ok(EndpointPrefix(prefix)),
148            Err(err) => Err(InvalidEndpointError::failed_to_construct_authority(
149                prefix, err,
150            )),
151        }
152    }
153
154    /// Get the `str` representation of this `EndpointPrefix`.
155    pub fn as_str(&self) -> &str {
156        &self.0
157    }
158}
159
160impl Storable for EndpointPrefix {
161    type Storer = StoreReplace<Self>;
162}
163
164/// Errors related to endpoint resolution and validation
165pub mod error {
166    use crate::box_error::BoxError;
167    use std::error::Error as StdError;
168    use std::fmt;
169
170    /// Endpoint resolution failed
171    #[derive(Debug)]
172    pub struct ResolveEndpointError {
173        message: String,
174        source: Option<BoxError>,
175    }
176
177    impl ResolveEndpointError {
178        /// Create an [`ResolveEndpointError`] with a message
179        pub fn message(message: impl Into<String>) -> Self {
180            Self {
181                message: message.into(),
182                source: None,
183            }
184        }
185
186        /// Add a source to the error
187        pub fn with_source(self, source: Option<BoxError>) -> Self {
188            Self { source, ..self }
189        }
190
191        /// Create a [`ResolveEndpointError`] from a message and a source
192        pub fn from_source(message: impl Into<String>, source: impl Into<BoxError>) -> Self {
193            Self::message(message).with_source(Some(source.into()))
194        }
195    }
196
197    impl fmt::Display for ResolveEndpointError {
198        fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
199            write!(f, "{}", self.message)
200        }
201    }
202
203    impl StdError for ResolveEndpointError {
204        fn source(&self) -> Option<&(dyn StdError + 'static)> {
205            self.source.as_ref().map(|err| err.as_ref() as _)
206        }
207    }
208
209    #[derive(Debug)]
210    pub(super) enum InvalidEndpointErrorKind {
211        EndpointMustHaveScheme,
212        FailedToConstructAuthority { authority: String, source: BoxError },
213        FailedToConstructUri { source: BoxError },
214    }
215
216    /// An error that occurs when an endpoint is found to be invalid. This usually occurs due to an
217    /// incomplete URI.
218    #[derive(Debug)]
219    pub struct InvalidEndpointError {
220        pub(super) kind: InvalidEndpointErrorKind,
221    }
222
223    impl InvalidEndpointError {
224        /// Construct a build error for a missing scheme
225        pub fn endpoint_must_have_scheme() -> Self {
226            Self {
227                kind: InvalidEndpointErrorKind::EndpointMustHaveScheme,
228            }
229        }
230
231        /// Construct a build error for an invalid authority
232        pub fn failed_to_construct_authority(
233            authority: impl Into<String>,
234            source: impl Into<Box<dyn StdError + Send + Sync + 'static>>,
235        ) -> Self {
236            Self {
237                kind: InvalidEndpointErrorKind::FailedToConstructAuthority {
238                    authority: authority.into(),
239                    source: source.into(),
240                },
241            }
242        }
243
244        /// Construct a build error for an invalid URI
245        pub fn failed_to_construct_uri(
246            source: impl Into<Box<dyn StdError + Send + Sync + 'static>>,
247        ) -> Self {
248            Self {
249                kind: InvalidEndpointErrorKind::FailedToConstructUri {
250                    source: source.into(),
251                },
252            }
253        }
254    }
255
256    impl From<InvalidEndpointErrorKind> for InvalidEndpointError {
257        fn from(kind: InvalidEndpointErrorKind) -> Self {
258            Self { kind }
259        }
260    }
261
262    impl fmt::Display for InvalidEndpointError {
263        fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
264            use InvalidEndpointErrorKind as ErrorKind;
265            match &self.kind {
266            ErrorKind::EndpointMustHaveScheme => write!(f, "endpoint must contain a valid scheme"),
267            ErrorKind::FailedToConstructAuthority { authority, source: _ } => write!(
268                f,
269                "endpoint must contain a valid authority when combined with endpoint prefix: {authority}"
270            ),
271            ErrorKind::FailedToConstructUri { .. } => write!(f, "failed to construct URI"),
272        }
273        }
274    }
275
276    impl StdError for InvalidEndpointError {
277        fn source(&self) -> Option<&(dyn StdError + 'static)> {
278            use InvalidEndpointErrorKind as ErrorKind;
279            match &self.kind {
280                ErrorKind::FailedToConstructUri { source } => Some(source.as_ref()),
281                ErrorKind::FailedToConstructAuthority {
282                    authority: _,
283                    source,
284                } => Some(source.as_ref()),
285                ErrorKind::EndpointMustHaveScheme => None,
286            }
287        }
288    }
289}