use std::fmt;
use time::OffsetDateTime;
use crate::{Error, Result};
use super::DynCredentialsProvider;
#[derive(Clone, Debug)]
pub struct CredentialsSnapshot {
credentials: Credentials,
expires_at: Option<OffsetDateTime>,
}
impl CredentialsSnapshot {
pub fn new(credentials: Credentials) -> Self {
Self {
credentials,
expires_at: None,
}
}
pub fn with_expires_at(mut self, expires_at: OffsetDateTime) -> Self {
self.expires_at = Some(expires_at);
self
}
pub fn credentials(&self) -> &Credentials {
&self.credentials
}
pub fn expires_at(&self) -> Option<OffsetDateTime> {
self.expires_at
}
}
#[derive(Clone, PartialEq, Eq, Hash)]
pub struct Region(String);
impl Region {
pub fn new(value: impl Into<String>) -> Result<Self> {
let value = value.into();
if value.is_empty() {
return Err(Error::invalid_config("region must not be empty"));
}
if value.trim() != value {
return Err(Error::invalid_config(
"region must not include leading or trailing whitespace",
));
}
if !value
.bytes()
.all(|b| b.is_ascii_lowercase() || b.is_ascii_digit() || b == b'-')
{
return Err(Error::invalid_config(
"region must contain only lowercase ASCII letters, digits, or '-'",
));
}
Ok(Self(value))
}
pub fn as_str(&self) -> &str {
&self.0
}
}
impl fmt::Debug for Region {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_tuple("Region").field(&self.0).finish()
}
}
impl fmt::Display for Region {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(&self.0)
}
}
impl TryFrom<&str> for Region {
type Error = Error;
fn try_from(value: &str) -> std::result::Result<Self, Self::Error> {
Self::new(value)
}
}
#[derive(Clone)]
pub struct Credentials {
access_key_id: String,
secret_access_key: String,
session_token: Option<String>,
}
impl Credentials {
pub fn new(
access_key_id: impl Into<String>,
secret_access_key: impl Into<String>,
) -> Result<Self> {
let access_key_id = access_key_id.into();
let secret_access_key = secret_access_key.into();
validate_credential_field("access_key_id", &access_key_id)?;
validate_credential_field("secret_access_key", &secret_access_key)?;
Ok(Self {
access_key_id,
secret_access_key,
session_token: None,
})
}
pub fn with_session_token(mut self, session_token: impl Into<String>) -> Result<Self> {
let session_token = session_token.into();
validate_credential_field("session_token", &session_token)?;
self.session_token = Some(session_token);
Ok(self)
}
pub fn access_key_id(&self) -> &str {
&self.access_key_id
}
pub fn secret_access_key(&self) -> &str {
&self.secret_access_key
}
pub fn session_token(&self) -> Option<&str> {
self.session_token.as_deref()
}
}
fn validate_credential_field(name: &'static str, value: &str) -> Result<()> {
if value.is_empty() {
return Err(Error::invalid_config(format!("{name} must not be empty")));
}
if value.trim() != value {
return Err(Error::invalid_config(format!(
"{name} must not include leading or trailing whitespace"
)));
}
if value
.bytes()
.any(|b| b.is_ascii_control() || b.is_ascii_whitespace())
{
return Err(Error::invalid_config(format!(
"{name} must not contain ASCII control or whitespace characters"
)));
}
if !value.is_ascii() {
return Err(Error::invalid_config(format!(
"{name} must contain only ASCII characters"
)));
}
Ok(())
}
impl fmt::Debug for Credentials {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Credentials")
.field(
"access_key_id",
&crate::util::redact::redact_value(&self.access_key_id),
)
.field("secret_access_key", &"<redacted>")
.field(
"session_token",
&self
.session_token
.as_ref()
.map(|v| crate::util::redact::redact_value(v)),
)
.finish()
}
}
#[non_exhaustive]
#[derive(Clone, Debug)]
pub enum Auth {
Anonymous,
Static(Credentials),
Provider(DynCredentialsProvider),
}
#[non_exhaustive]
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum AddressingStyle {
Auto,
Path,
VirtualHosted,
}