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::fmt;
17use std::str::FromStr;
18use std::sync::Arc;
19
20new_type_future! {
21    #[doc = "Future for [`EndpointResolver::resolve_endpoint`]."]
22    pub struct EndpointFuture<'a, Endpoint, BoxError>;
23}
24
25/// Parameters originating from the Smithy endpoint ruleset required for endpoint resolution.
26///
27/// The actual endpoint parameters are code generated from the Smithy model, and thus,
28/// are not known to the runtime crates. Hence, this struct is really a new-type around
29/// a [`TypeErasedBox`] that holds the actual concrete parameters in it.
30#[derive(Debug)]
31pub struct EndpointResolverParams(TypeErasedBox);
32
33impl EndpointResolverParams {
34    /// Creates a new [`EndpointResolverParams`] from a concrete parameters instance.
35    pub fn new<T: fmt::Debug + Send + Sync + 'static>(params: T) -> Self {
36        Self(TypeErasedBox::new(params))
37    }
38
39    /// Attempts to downcast the underlying concrete parameters to `T` and return it as a reference.
40    pub fn get<T: fmt::Debug + Send + Sync + 'static>(&self) -> Option<&T> {
41        self.0.downcast_ref()
42    }
43}
44
45impl Storable for EndpointResolverParams {
46    type Storer = StoreReplace<Self>;
47}
48
49/// Configurable endpoint resolver implementation.
50pub trait ResolveEndpoint: Send + Sync + fmt::Debug {
51    /// Asynchronously resolves an endpoint to use from the given endpoint parameters.
52    fn resolve_endpoint<'a>(&'a self, params: &'a EndpointResolverParams) -> EndpointFuture<'a>;
53}
54
55/// Shared endpoint resolver.
56///
57/// This is a simple shared ownership wrapper type for the [`ResolveEndpoint`] trait.
58#[derive(Clone, Debug)]
59pub struct SharedEndpointResolver(Arc<dyn ResolveEndpoint>);
60
61impl SharedEndpointResolver {
62    /// Creates a new [`SharedEndpointResolver`].
63    pub fn new(endpoint_resolver: impl ResolveEndpoint + 'static) -> Self {
64        Self(Arc::new(endpoint_resolver))
65    }
66}
67
68impl ResolveEndpoint for SharedEndpointResolver {
69    fn resolve_endpoint<'a>(&'a self, params: &'a EndpointResolverParams) -> EndpointFuture<'a> {
70        self.0.resolve_endpoint(params)
71    }
72}
73
74impl ValidateConfig for SharedEndpointResolver {}
75
76impl_shared_conversions!(convert SharedEndpointResolver from ResolveEndpoint using SharedEndpointResolver::new);
77
78/// A special type that adds support for services that have special URL-prefixing rules.
79#[derive(Clone, Debug, Eq, PartialEq)]
80pub struct EndpointPrefix(String);
81impl EndpointPrefix {
82    /// Create a new endpoint prefix from an `impl Into<String>`. If the prefix argument is invalid,
83    /// a [`InvalidEndpointError`] will be returned.
84    pub fn new(prefix: impl Into<String>) -> Result<Self, InvalidEndpointError> {
85        let prefix = prefix.into();
86        match Authority::from_str(&prefix) {
87            Ok(_) => Ok(EndpointPrefix(prefix)),
88            Err(err) => Err(InvalidEndpointError::failed_to_construct_authority(
89                prefix, err,
90            )),
91        }
92    }
93
94    /// Get the `str` representation of this `EndpointPrefix`.
95    pub fn as_str(&self) -> &str {
96        &self.0
97    }
98}
99
100impl Storable for EndpointPrefix {
101    type Storer = StoreReplace<Self>;
102}
103
104/// Errors related to endpoint resolution and validation
105pub mod error {
106    use crate::box_error::BoxError;
107    use std::error::Error as StdError;
108    use std::fmt;
109
110    /// Endpoint resolution failed
111    #[derive(Debug)]
112    pub struct ResolveEndpointError {
113        message: String,
114        source: Option<BoxError>,
115    }
116
117    impl ResolveEndpointError {
118        /// Create an [`ResolveEndpointError`] with a message
119        pub fn message(message: impl Into<String>) -> Self {
120            Self {
121                message: message.into(),
122                source: None,
123            }
124        }
125
126        /// Add a source to the error
127        pub fn with_source(self, source: Option<BoxError>) -> Self {
128            Self { source, ..self }
129        }
130
131        /// Create a [`ResolveEndpointError`] from a message and a source
132        pub fn from_source(message: impl Into<String>, source: impl Into<BoxError>) -> Self {
133            Self::message(message).with_source(Some(source.into()))
134        }
135    }
136
137    impl fmt::Display for ResolveEndpointError {
138        fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
139            write!(f, "{}", self.message)
140        }
141    }
142
143    impl StdError for ResolveEndpointError {
144        fn source(&self) -> Option<&(dyn StdError + 'static)> {
145            self.source.as_ref().map(|err| err.as_ref() as _)
146        }
147    }
148
149    #[derive(Debug)]
150    pub(super) enum InvalidEndpointErrorKind {
151        EndpointMustHaveScheme,
152        FailedToConstructAuthority { authority: String, source: BoxError },
153        FailedToConstructUri { source: BoxError },
154    }
155
156    /// An error that occurs when an endpoint is found to be invalid. This usually occurs due to an
157    /// incomplete URI.
158    #[derive(Debug)]
159    pub struct InvalidEndpointError {
160        pub(super) kind: InvalidEndpointErrorKind,
161    }
162
163    impl InvalidEndpointError {
164        /// Construct a build error for a missing scheme
165        pub fn endpoint_must_have_scheme() -> Self {
166            Self {
167                kind: InvalidEndpointErrorKind::EndpointMustHaveScheme,
168            }
169        }
170
171        /// Construct a build error for an invalid authority
172        pub fn failed_to_construct_authority(
173            authority: impl Into<String>,
174            source: impl Into<Box<dyn StdError + Send + Sync + 'static>>,
175        ) -> Self {
176            Self {
177                kind: InvalidEndpointErrorKind::FailedToConstructAuthority {
178                    authority: authority.into(),
179                    source: source.into(),
180                },
181            }
182        }
183
184        /// Construct a build error for an invalid URI
185        pub fn failed_to_construct_uri(
186            source: impl Into<Box<dyn StdError + Send + Sync + 'static>>,
187        ) -> Self {
188            Self {
189                kind: InvalidEndpointErrorKind::FailedToConstructUri {
190                    source: source.into(),
191                },
192            }
193        }
194    }
195
196    impl From<InvalidEndpointErrorKind> for InvalidEndpointError {
197        fn from(kind: InvalidEndpointErrorKind) -> Self {
198            Self { kind }
199        }
200    }
201
202    impl fmt::Display for InvalidEndpointError {
203        fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
204            use InvalidEndpointErrorKind as ErrorKind;
205            match &self.kind {
206            ErrorKind::EndpointMustHaveScheme => write!(f, "endpoint must contain a valid scheme"),
207            ErrorKind::FailedToConstructAuthority { authority, source: _ } => write!(
208                f,
209                "endpoint must contain a valid authority when combined with endpoint prefix: {authority}"
210            ),
211            ErrorKind::FailedToConstructUri { .. } => write!(f, "failed to construct URI"),
212        }
213        }
214    }
215
216    impl StdError for InvalidEndpointError {
217        fn source(&self) -> Option<&(dyn StdError + 'static)> {
218            use InvalidEndpointErrorKind as ErrorKind;
219            match &self.kind {
220                ErrorKind::FailedToConstructUri { source } => Some(source.as_ref()),
221                ErrorKind::FailedToConstructAuthority {
222                    authority: _,
223                    source,
224                } => Some(source.as_ref()),
225                ErrorKind::EndpointMustHaveScheme => None,
226            }
227        }
228    }
229}