use core::fmt;
use std::sync::Arc;
use async_trait::async_trait;
use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
#[derive(Clone, Copy, Debug, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "snake_case")]
pub enum S3ChecksumAlgorithm {
Crc32,
Crc32c,
Crc64Nvme,
Sha1,
Sha256,
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
#[non_exhaustive]
pub struct S3Options {
pub region: Option<String>,
pub endpoint_url: Option<String>,
pub anonymous: bool,
pub allow_http: bool,
#[serde(default = "default_force_path_style")]
pub force_path_style: bool,
pub network_stream_timeout_seconds: Option<u32>,
#[serde(default)]
pub requester_pays: bool,
#[serde(default)]
pub checksum_algorithm: Option<S3ChecksumAlgorithm>,
}
fn default_force_path_style() -> bool {
false
}
impl Default for S3Options {
fn default() -> Self {
Self {
region: None,
endpoint_url: None,
anonymous: false,
allow_http: false,
force_path_style: default_force_path_style(),
network_stream_timeout_seconds: None,
requester_pays: false,
checksum_algorithm: None,
}
}
}
impl S3Options {
pub fn with_region(mut self, region: impl Into<String>) -> Self {
self.region = Some(region.into());
self
}
pub fn with_endpoint_url(mut self, endpoint_url: impl Into<String>) -> Self {
self.endpoint_url = Some(endpoint_url.into());
self
}
pub fn with_anonymous(mut self, anonymous: bool) -> Self {
self.anonymous = anonymous;
self
}
pub fn with_allow_http(mut self, allow_http: bool) -> Self {
self.allow_http = allow_http;
self
}
pub fn with_force_path_style(mut self, force_path_style: bool) -> Self {
self.force_path_style = force_path_style;
self
}
pub fn with_network_stream_timeout_seconds(mut self, seconds: u32) -> Self {
self.network_stream_timeout_seconds = Some(seconds);
self
}
pub fn with_requester_pays(mut self, requester_pays: bool) -> Self {
self.requester_pays = requester_pays;
self
}
pub fn with_checksum_algorithm(mut self, algorithm: S3ChecksumAlgorithm) -> Self {
self.checksum_algorithm = Some(algorithm);
self
}
pub fn info_fields(&self) -> Vec<(&'static str, String)> {
let mut fields = Vec::new();
if let Some(region) = &self.region {
fields.push(("region", region.clone()));
}
if let Some(endpoint) = &self.endpoint_url {
fields.push(("endpoint_url", endpoint.clone()));
}
if self.anonymous {
fields.push(("anonymous", "True".to_string()));
}
if self.allow_http {
fields.push(("allow_http", "True".to_string()));
}
if self.force_path_style {
fields.push(("force_path_style", "True".to_string()));
}
if self.requester_pays {
fields.push(("requester_pays", "True".to_string()));
}
if let Some(algo) = self.checksum_algorithm {
fields.push(("checksum_algorithm", format!("{algo:?}")));
}
fields
}
}
impl fmt::Display for S3ChecksumAlgorithm {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let s = match self {
S3ChecksumAlgorithm::Crc32 => "crc32",
S3ChecksumAlgorithm::Crc32c => "crc32c",
S3ChecksumAlgorithm::Crc64Nvme => "crc64nvme",
S3ChecksumAlgorithm::Sha1 => "sha1",
S3ChecksumAlgorithm::Sha256 => "sha256",
};
f.write_str(s)
}
}
impl fmt::Display for S3Options {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"S3Options(region={}, endpoint_url={}, anonymous={}, allow_http={}, force_path_style={}, network_stream_timeout_seconds={}, requester_pays={}, checksum_algorithm={})",
self.region.as_deref().unwrap_or("None"),
self.endpoint_url.as_deref().unwrap_or("None"),
self.anonymous,
self.allow_http,
self.force_path_style,
self.network_stream_timeout_seconds
.map(|n| n.to_string())
.unwrap_or("None".to_string()),
self.requester_pays,
self.checksum_algorithm.map(|a| a.to_string()).unwrap_or("None".to_string()),
)
}
}
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)]
pub struct S3StaticCredentials {
pub access_key_id: String,
pub secret_access_key: String,
pub session_token: Option<String>,
pub expires_after: Option<DateTime<Utc>>,
}
#[async_trait]
#[typetag::serde(tag = "s3_credentials_fetcher_type")]
pub trait S3CredentialsFetcher: fmt::Debug + Sync + Send {
async fn get(&self) -> Result<S3StaticCredentials, String>;
}
#[derive(Clone, Debug, Deserialize, Serialize, Default)]
#[serde(tag = "s3_credential_type")]
#[serde(rename_all = "snake_case")]
pub enum S3Credentials {
#[default]
FromEnv,
Anonymous,
Static(S3StaticCredentials),
Refreshable(Arc<dyn S3CredentialsFetcher>),
}