aws_sdk_s3/
presigning.rs

1// Code generated by software.amazon.smithy.rust.codegen.smithy-rs. DO NOT EDIT.
2/*
3 * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
4 * SPDX-License-Identifier: Apache-2.0
5 */
6
7// TODO(https://github.com/smithy-lang/smithy-rs/issues/2902): Code generate this documentation so that service-specific examples can be added.
8//! Presigned request types and configuration.
9//!
10//! The [`Client`](crate::Client) is used to create presigned requests. They are made
11//! by calling `.presigned()` instead of `.send()` on an operation, and require a
12//! [`PresigningConfig`](crate::presigning::PresigningConfig) to provide an expiration time.
13//!
14//! Only operations that support presigning have the `presigned()` method on them.
15
16use aws_smithy_runtime_api::box_error::BoxError;
17use aws_smithy_runtime_api::client::orchestrator::HttpRequest;
18use aws_smithy_types::body::SdkBody;
19use std::fmt;
20use std::time::{Duration, SystemTime};
21
22const ONE_WEEK: Duration = Duration::from_secs(604800);
23
24/// Presigning config values required for creating a presigned request.
25#[non_exhaustive]
26#[derive(Debug, Clone)]
27pub struct PresigningConfig {
28    start_time: SystemTime,
29    expires_in: Duration,
30}
31
32impl PresigningConfig {
33    /// Creates a `PresigningConfig` with the given `expires_in` duration.
34    ///
35    /// The `expires_in` duration is the total amount of time the presigned request should
36    /// be valid for. Other config values are defaulted.
37    ///
38    /// Credential expiration time takes priority over the `expires_in` value.
39    /// If the credentials used to sign the request expire before the presigned request is
40    /// set to expire, then the presigned request will become invalid.
41    pub fn expires_in(expires_in: Duration) -> Result<PresigningConfig, PresigningConfigError> {
42        Self::builder().expires_in(expires_in).build()
43    }
44
45    /// Creates a new builder for creating a `PresigningConfig`.
46    pub fn builder() -> PresigningConfigBuilder {
47        PresigningConfigBuilder::default()
48    }
49
50    /// Returns the amount of time the presigned request should be valid for.
51    pub fn expires(&self) -> Duration {
52        self.expires_in
53    }
54
55    /// Returns the start time. The presigned request will be valid between this and the end
56    /// time produced by adding the `expires()` value to it.
57    pub fn start_time(&self) -> SystemTime {
58        self.start_time
59    }
60}
61
62#[derive(Debug)]
63enum ErrorKind {
64    /// Presigned requests cannot be valid for longer than one week.
65    ExpiresInDurationTooLong,
66
67    /// The `PresigningConfig` builder requires a value for `expires_in`.
68    ExpiresInRequired,
69}
70
71/// `PresigningConfig` build errors.
72#[derive(Debug)]
73pub struct PresigningConfigError {
74    kind: ErrorKind,
75}
76
77impl std::error::Error for PresigningConfigError {}
78
79impl fmt::Display for PresigningConfigError {
80    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
81        match self.kind {
82            ErrorKind::ExpiresInDurationTooLong => {
83                write!(f, "`expires_in` must be no longer than one week")
84            }
85            ErrorKind::ExpiresInRequired => write!(f, "`expires_in` is required"),
86        }
87    }
88}
89
90impl From<ErrorKind> for PresigningConfigError {
91    fn from(kind: ErrorKind) -> Self {
92        Self { kind }
93    }
94}
95
96/// Builder used to create `PresigningConfig`.
97#[non_exhaustive]
98#[derive(Default, Debug)]
99pub struct PresigningConfigBuilder {
100    start_time: Option<SystemTime>,
101    expires_in: Option<Duration>,
102}
103
104impl PresigningConfigBuilder {
105    /// Sets the start time for the presigned request.
106    ///
107    /// The request will start to be valid at this time, and will cease to be valid after
108    /// the end time, which can be determined by adding the `expires_in` duration to this
109    /// start time. If not specified, this will default to the current time.
110    ///
111    /// Optional.
112    pub fn start_time(mut self, start_time: SystemTime) -> Self {
113        self.set_start_time(Some(start_time));
114        self
115    }
116
117    /// Sets the start time for the presigned request.
118    ///
119    /// The request will start to be valid at this time, and will cease to be valid after
120    /// the end time, which can be determined by adding the `expires_in` duration to this
121    /// start time. If not specified, this will default to the current time.
122    ///
123    /// Optional.
124    pub fn set_start_time(&mut self, start_time: Option<SystemTime>) {
125        self.start_time = start_time;
126    }
127
128    /// Sets how long the request should be valid after the `start_time` (which defaults
129    /// to the current time).
130    ///
131    /// Credential expiration time takes priority over the `expires_in` value.
132    /// If the credentials used to sign the request expire before the presigned request is
133    /// set to expire, then the presigned request will become invalid.
134    ///
135    /// Required.
136    pub fn expires_in(mut self, expires_in: Duration) -> Self {
137        self.set_expires_in(Some(expires_in));
138        self
139    }
140
141    /// Sets how long the request should be valid after the `start_time` (which defaults
142    /// to the current time).
143    ///
144    /// Credential expiration time takes priority over the `expires_in` value.
145    /// If the credentials used to sign the request expire before the presigned request is
146    /// set to expire, then the presigned request will become invalid.
147    ///
148    /// Required.
149    pub fn set_expires_in(&mut self, expires_in: Option<Duration>) {
150        self.expires_in = expires_in;
151    }
152
153    /// Builds the `PresigningConfig`. This will error if `expires_in` is not
154    /// given, or if it's longer than one week.
155    pub fn build(self) -> Result<PresigningConfig, PresigningConfigError> {
156        let expires_in = self.expires_in.ok_or(ErrorKind::ExpiresInRequired)?;
157        if expires_in > ONE_WEEK {
158            return Err(ErrorKind::ExpiresInDurationTooLong.into());
159        }
160        Ok(PresigningConfig {
161            start_time: self.start_time.unwrap_or_else(
162                // This usage is OK—customers can easily override this.
163                #[allow(clippy::disallowed_methods)]
164                SystemTime::now,
165            ),
166            expires_in,
167        })
168    }
169}
170
171/// Represents a presigned request. This only includes the HTTP request method, URI, and headers.
172///
173/// **This struct has conversion convenience functions:**
174///
175/// - [`PresignedRequest::make_http_02x_request<B>`][Self::make_http_02x_request] returns an [`http::Request<B>`](https://docs.rs/http/0.2.6/http/request/struct.Request.html)
176/// - [`PresignedRequest::into`](#impl-From<PresignedRequest>) returns an [`http::request::Builder`](https://docs.rs/http/0.2.6/http/request/struct.Builder.html)
177#[non_exhaustive]
178pub struct PresignedRequest {
179    http_request: HttpRequest,
180}
181
182impl Clone for PresignedRequest {
183    fn clone(&self) -> Self {
184        Self {
185            http_request: match self.http_request.try_clone() {
186                Some(body) => body,
187                None => {
188                    unreachable!("during construction, we replaced the body with `SdkBody::empty()`")
189                }
190            },
191        }
192    }
193}
194
195impl PresignedRequest {
196    #[allow(dead_code)]
197    pub(crate) fn new(inner: HttpRequest) -> Result<Self, BoxError> {
198        // throw out the body so we're sure it's cloneable
199        let http_request = inner.map(|_body| SdkBody::empty());
200        // this should never fail, a presigned request should always be convertible, but better to
201        // protect against this potential panic
202        let _ = http_request.try_clone().expect("must be cloneable, body is empty").try_into_http02x()?;
203        Ok(Self { http_request })
204    }
205
206    /// Returns the HTTP request method.
207    pub fn method(&self) -> &str {
208        self.http_request.method()
209    }
210
211    /// Returns the HTTP request URI.
212    pub fn uri(&self) -> &str {
213        self.http_request.uri()
214    }
215
216    /// Returns any HTTP headers that need to go along with the request, except for `Host`,
217    /// which should be sent based on the endpoint in the URI by the HTTP client rather than
218    /// added directly.
219    pub fn headers(&self) -> impl Iterator<Item = (&str, &str)> {
220        self.http_request.headers().iter()
221    }
222
223    /// Given a body, produce an `http::Request` from this `PresignedRequest`
224    pub fn make_http_02x_request<B>(&self, body: B) -> http::Request<B> {
225        self.clone().into_http_02x_request(body)
226    }
227
228    /// Converts this `PresignedRequest` directly into an `http` request.
229    pub fn into_http_02x_request<B>(self, body: B) -> http::Request<B> {
230        self.http_request
231            .try_into_http02x()
232            .expect("constructor validated convertibility")
233            .map(|_req| body)
234    }
235
236    #[cfg(feature = "http-1x")]
237    /// Given a body, produce an `http_1x::Request` from this `PresignedRequest`
238    pub fn make_http_1x_request<B>(&self, body: B) -> http_1x::Request<B> {
239        self.clone().into_http_1x_request(body)
240    }
241
242    #[cfg(feature = "http-1x")]
243    /// Converts this `PresignedRequest` directly into an `http_1x` request.
244    pub fn into_http_1x_request<B>(self, body: B) -> http_1x::Request<B> {
245        self.http_request
246            .try_into_http1x()
247            .expect("constructor validated convertibility")
248            .map(|_req| body)
249    }
250}
251
252impl fmt::Debug for PresignedRequest {
253    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
254        f.debug_struct("PresignedRequest")
255            .field("method", &self.method())
256            .field("uri", &self.uri())
257            .field("headers", self.http_request.headers())
258            .finish()
259    }
260}