aws_smithy_runtime/client/orchestrator/
endpoints.rs1use aws_smithy_runtime_api::client::endpoint::{
7 error::ResolveEndpointError, EndpointFuture, EndpointResolverParams, ResolveEndpoint,
8};
9use aws_smithy_runtime_api::client::identity::Identity;
10use aws_smithy_runtime_api::client::interceptors::context::InterceptorContext;
11use aws_smithy_runtime_api::client::orchestrator::HttpRequest;
12use aws_smithy_runtime_api::client::runtime_components::RuntimeComponents;
13use aws_smithy_runtime_api::{box_error::BoxError, client::endpoint::EndpointPrefix};
14use aws_smithy_types::config_bag::ConfigBag;
15use aws_smithy_types::endpoint::Endpoint;
16use http_02x::header::HeaderName;
17use http_02x::uri::PathAndQuery;
18use http_02x::{HeaderValue, Uri};
19use std::borrow::Cow;
20use std::fmt::Debug;
21use std::str::FromStr;
22use tracing::trace;
23
24#[derive(Clone, Debug)]
26pub struct StaticUriEndpointResolver {
27 endpoint: String,
28}
29
30impl StaticUriEndpointResolver {
31 pub fn http_localhost(port: u16) -> Self {
33 Self {
34 endpoint: format!("http://localhost:{port}"),
35 }
36 }
37
38 pub fn uri(endpoint: impl Into<String>) -> Self {
40 Self {
41 endpoint: endpoint.into(),
42 }
43 }
44}
45
46impl ResolveEndpoint for StaticUriEndpointResolver {
47 fn resolve_endpoint<'a>(&'a self, _params: &'a EndpointResolverParams) -> EndpointFuture<'a> {
48 EndpointFuture::ready(Ok(Endpoint::builder()
49 .url(self.endpoint.to_string())
50 .build()))
51 }
52}
53
54#[derive(Debug, Default)]
56pub struct StaticUriEndpointResolverParams;
57
58impl StaticUriEndpointResolverParams {
59 pub fn new() -> Self {
61 Self
62 }
63}
64
65impl From<StaticUriEndpointResolverParams> for EndpointResolverParams {
66 fn from(params: StaticUriEndpointResolverParams) -> Self {
67 EndpointResolverParams::new(params)
68 }
69}
70
71pub(super) async fn orchestrate_endpoint(
72 identity: Identity,
73 ctx: &mut InterceptorContext,
74 runtime_components: &RuntimeComponents,
75 cfg: &mut ConfigBag,
76) -> Result<(), BoxError> {
77 trace!("orchestrating endpoint resolution");
78
79 let params = cfg
80 .get_mut_from_interceptor_state::<EndpointResolverParams>()
81 .expect("endpoint resolver params must be set in the interceptor state");
82 params.set_property(identity);
83
84 let endpoint_resolver = runtime_components.endpoint_resolver();
85
86 endpoint_resolver.finalize_params(params)?;
87
88 tracing::debug!(endpoint_params = ?params, "resolving endpoint");
89 let endpoint = endpoint_resolver.resolve_endpoint(params).await?;
90
91 apply_endpoint(&endpoint, ctx, cfg)?;
92
93 cfg.interceptor_state().store_put(endpoint);
95 Ok(())
96}
97
98pub(super) fn apply_endpoint(
99 endpoint: &Endpoint,
100 ctx: &mut InterceptorContext,
101 cfg: &ConfigBag,
102) -> Result<(), BoxError> {
103 let endpoint_prefix = cfg.load::<EndpointPrefix>();
104 tracing::debug!(endpoint_prefix = ?endpoint_prefix, "will apply endpoint {:?}", endpoint);
105 let request = ctx.request_mut().expect("set during serialization");
106
107 apply_endpoint_to_request(request, endpoint, endpoint_prefix)
108}
109
110fn apply_endpoint_to_request(
111 request: &mut HttpRequest,
112 endpoint: &Endpoint,
113 endpoint_prefix: Option<&EndpointPrefix>,
114) -> Result<(), BoxError> {
115 let endpoint_url = match endpoint_prefix {
116 None => Cow::Borrowed(endpoint.url()),
117 Some(prefix) => {
118 let parsed = endpoint.url().parse::<Uri>()?;
119 let scheme = parsed.scheme_str().unwrap_or_default();
120 let prefix = prefix.as_str();
121 let authority = parsed
122 .authority()
123 .map(|auth| auth.as_str())
124 .unwrap_or_default();
125 let path_and_query = parsed
126 .path_and_query()
127 .map(PathAndQuery::as_str)
128 .unwrap_or_default();
129 Cow::Owned(format!("{scheme}://{prefix}{authority}{path_and_query}"))
130 }
131 };
132
133 request
134 .uri_mut()
135 .set_endpoint(&endpoint_url)
136 .map_err(|err| {
137 ResolveEndpointError::message(format!(
138 "failed to apply endpoint `{}` to request `{:?}`",
139 endpoint_url, request,
140 ))
141 .with_source(Some(err.into()))
142 })?;
143
144 for (header_name, header_values) in endpoint.headers() {
145 request.headers_mut().remove(header_name);
146 for value in header_values {
147 request.headers_mut().append(
148 HeaderName::from_str(header_name).map_err(|err| {
149 ResolveEndpointError::message("invalid header name")
150 .with_source(Some(err.into()))
151 })?,
152 HeaderValue::from_str(value).map_err(|err| {
153 ResolveEndpointError::message("invalid header value")
154 .with_source(Some(err.into()))
155 })?,
156 );
157 }
158 }
159 Ok(())
160}
161
162#[cfg(test)]
163mod test {
164 use aws_smithy_runtime_api::client::endpoint::EndpointPrefix;
165 use aws_smithy_runtime_api::client::orchestrator::HttpRequest;
166 use aws_smithy_types::endpoint::Endpoint;
167
168 #[test]
169 fn test_apply_endpoint() {
170 let mut req = HttpRequest::empty();
171 req.set_uri("/foo?bar=1").unwrap();
172 let endpoint = Endpoint::builder().url("https://s3.amazon.com").build();
173 let prefix = EndpointPrefix::new("prefix.subdomain.").unwrap();
174 super::apply_endpoint_to_request(&mut req, &endpoint, Some(&prefix))
175 .expect("should succeed");
176 assert_eq!(
177 req.uri(),
178 "https://prefix.subdomain.s3.amazon.com/foo?bar=1"
179 );
180 }
181}