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