use std::{
fmt::Display,
time::{Duration, Instant, SystemTime},
};
use aws_sigv4::{
http_request::{
sign, PayloadChecksumKind, SignableBody, SignableRequest, SigningError,
SigningInstructions, SigningSettings,
},
sign::v4::SigningParams,
};
use aws_smithy_runtime_api::client::identity::Identity;
use crossbeam::utils::Backoff;
use ureq::{Request, Response};
type Result<T> = std::result::Result<T, Error>;
#[derive(Debug)]
pub enum Error {
Api(u16, Box<Response>),
SigningError(SigningError),
Transport(Box<ureq::Error>),
}
impl Display for Error {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Api(status, response) => {
write!(f, "api request error status {}: {:?}", status, response)
}
Self::SigningError(e) => write!(f, "signing error: {}", e),
Self::Transport(e) => write!(f, "http transport error: {}", e),
}
}
}
impl From<SigningError> for Error {
fn from(e: SigningError) -> Self {
Self::SigningError(e)
}
}
impl From<ureq::Error> for Error {
fn from(err: ureq::Error) -> Self {
match err {
ureq::Error::Status(status, response) => Error::Api(status, Box::new(response)),
ureq::Error::Transport(_) => Error::Transport(Box::new(err)),
}
}
}
pub fn sign_request(
request: Request,
body: &[u8],
identity: &Identity,
region: &str,
service: &str,
) -> Result<Request> {
let mut signing_settings = SigningSettings::default();
if service == "s3" {
signing_settings.payload_checksum_kind = PayloadChecksumKind::XAmzSha256;
}
let signing_params = SigningParams::builder()
.identity(identity)
.region(region)
.name(service)
.time(SystemTime::now())
.settings(signing_settings)
.build()
.unwrap()
.into();
let header_names = &request.header_names();
let headers = header_names.iter().map(|name| {
let value = request.header(name).unwrap_or("");
(name.as_ref(), value)
});
let signable_request = SignableRequest::new(
request.method(),
request.url(),
headers,
SignableBody::Bytes(body),
)?;
let signing_output = sign(signable_request, &signing_params)?;
let (signing_instructions, _) = signing_output.into_parts();
Ok(update_request(request, signing_instructions))
}
fn update_request(mut request: Request, instructions: SigningInstructions) -> Request {
let (headers, params) = instructions.into_parts();
for header in headers {
request = request.set(header.name(), header.value());
}
for param in params {
request = request.query(param.0, ¶m.1);
}
request
}
pub(crate) fn with_retry<F>(mut f: F, timeout_secs: u64) -> Result<Response>
where
F: FnMut() -> std::result::Result<Response, ureq::Error>,
{
let start = Instant::now();
let backoff = Backoff::new();
loop {
match f() {
Ok(response) => return Ok(response),
Err(e) => {
if start.elapsed() > Duration::from_secs(timeout_secs) {
return Err(e.into());
}
backoff.snooze();
}
}
}
}