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 .property("is_custom_endpoint", true)
51 .build()))
52 }
53}
54
55#[derive(Debug, Default)]
57pub struct StaticUriEndpointResolverParams;
58
59impl StaticUriEndpointResolverParams {
60 pub fn new() -> Self {
62 Self
63 }
64}
65
66impl From<StaticUriEndpointResolverParams> for EndpointResolverParams {
67 fn from(params: StaticUriEndpointResolverParams) -> Self {
68 EndpointResolverParams::new(params)
69 }
70}
71
72pub(super) async fn orchestrate_endpoint(
73 identity: Identity,
74 ctx: &mut InterceptorContext,
75 runtime_components: &RuntimeComponents,
76 cfg: &mut ConfigBag,
77) -> Result<(), BoxError> {
78 trace!("orchestrating endpoint resolution");
79
80 let params = cfg
81 .get_mut_from_interceptor_state::<EndpointResolverParams>()
82 .expect("endpoint resolver params must be set in the interceptor state");
83 params.set_property(identity);
84
85 let endpoint_resolver = runtime_components.endpoint_resolver();
86
87 endpoint_resolver.finalize_params(params)?;
88
89 tracing::debug!(endpoint_params = ?params, "resolving endpoint");
90 let endpoint = endpoint_resolver.resolve_endpoint(params).await?;
91
92 apply_endpoint(&endpoint, ctx, cfg)?;
93
94 cfg.interceptor_state().store_put(endpoint);
96 Ok(())
97}
98
99pub(super) fn apply_endpoint(
100 endpoint: &Endpoint,
101 ctx: &mut InterceptorContext,
102 cfg: &ConfigBag,
103) -> Result<(), BoxError> {
104 let endpoint_prefix = cfg.load::<EndpointPrefix>();
105 tracing::debug!(endpoint_prefix = ?endpoint_prefix, "will apply endpoint {:?}", endpoint);
106 let request = ctx.request_mut().expect("set during serialization");
107
108 apply_endpoint_to_request(request, endpoint, endpoint_prefix)
109}
110
111fn apply_endpoint_to_request(
112 request: &mut HttpRequest,
113 endpoint: &Endpoint,
114 endpoint_prefix: Option<&EndpointPrefix>,
115) -> Result<(), BoxError> {
116 let endpoint_url = match endpoint_prefix {
117 None => Cow::Borrowed(endpoint.url()),
118 Some(prefix) => {
119 let parsed = endpoint.url().parse::<Uri>()?;
120 let scheme = parsed.scheme_str().unwrap_or_default();
121 let prefix = prefix.as_str();
122 let authority = parsed
123 .authority()
124 .map(|auth| auth.as_str())
125 .unwrap_or_default();
126 let path_and_query = parsed
127 .path_and_query()
128 .map(PathAndQuery::as_str)
129 .unwrap_or_default();
130 Cow::Owned(format!("{scheme}://{prefix}{authority}{path_and_query}"))
131 }
132 };
133
134 request
135 .uri_mut()
136 .set_endpoint(&endpoint_url)
137 .map_err(|err| {
138 ResolveEndpointError::message(format!(
139 "failed to apply endpoint `{endpoint_url}` to request `{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}