minaws 0.3.0

A synchronous subset of the AWS SDK.
Documentation
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, &param.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();
            }
        }
    }
}