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_1x::header::HeaderName;
17use http_1x::uri::PathAndQuery;
18use http_1x::{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 if let Some(protocol) = cfg.load::<aws_smithy_schema::protocol::SharedClientProtocol>() {
109 protocol
110 .update_endpoint(request, endpoint, cfg)
111 .map_err(BoxError::from)?;
112 } else {
113 apply_endpoint_to_request(request, endpoint, endpoint_prefix)?;
114 }
115
116 Ok(())
117}
118
119fn apply_endpoint_to_request(
120 request: &mut HttpRequest,
121 endpoint: &Endpoint,
122 endpoint_prefix: Option<&EndpointPrefix>,
123) -> Result<(), BoxError> {
124 let endpoint_url = match endpoint_prefix {
125 None => Cow::Borrowed(endpoint.url()),
126 Some(prefix) => {
127 let parsed = endpoint.url().parse::<Uri>()?;
128 let scheme = parsed.scheme_str().unwrap_or_default();
129 let prefix = prefix.as_str();
130 let authority = parsed
131 .authority()
132 .map(|auth| auth.as_str())
133 .unwrap_or_default();
134 let path_and_query = parsed
135 .path_and_query()
136 .map(PathAndQuery::as_str)
137 .unwrap_or_default();
138 Cow::Owned(format!("{scheme}://{prefix}{authority}{path_and_query}"))
139 }
140 };
141
142 request
143 .uri_mut()
144 .set_endpoint(&endpoint_url)
145 .map_err(|err| {
146 ResolveEndpointError::message(format!(
147 "failed to apply endpoint `{endpoint_url}` to request `{request:?}`",
148 ))
149 .with_source(Some(err.into()))
150 })?;
151
152 for (header_name, header_values) in endpoint.headers() {
153 request.headers_mut().remove(header_name);
154 for value in header_values {
155 request.headers_mut().append(
156 HeaderName::from_str(header_name).map_err(|err| {
157 ResolveEndpointError::message("invalid header name")
158 .with_source(Some(err.into()))
159 })?,
160 HeaderValue::from_str(value).map_err(|err| {
161 ResolveEndpointError::message("invalid header value")
162 .with_source(Some(err.into()))
163 })?,
164 );
165 }
166 }
167 Ok(())
168}
169
170#[cfg(test)]
171mod test {
172 use aws_smithy_runtime_api::client::endpoint::EndpointPrefix;
173 use aws_smithy_runtime_api::client::orchestrator::HttpRequest;
174 use aws_smithy_types::endpoint::Endpoint;
175
176 #[test]
177 fn test_apply_endpoint() {
178 let mut req = HttpRequest::empty();
179 req.set_uri("/foo?bar=1").unwrap();
180 let endpoint = Endpoint::builder().url("https://s3.amazon.com").build();
181 let prefix = EndpointPrefix::new("prefix.subdomain.").unwrap();
182 super::apply_endpoint_to_request(&mut req, &endpoint, Some(&prefix))
183 .expect("should succeed");
184 assert_eq!(
185 req.uri(),
186 "https://prefix.subdomain.s3.amazon.com/foo?bar=1"
187 );
188 }
189}