use std::time::Duration; use serde::{Deserialize, Serialize}; #[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(default)] pub struct S3Config { pub bucket: Option, pub region: Option, pub endpoint: Option, /// Endpoint used when signing URLs handed to clients (browser downloads, /// avatar links, LFS transfers). Presigned URLs embed the endpoint host in /// their signature, so signing with an in-cluster address produces links /// no client can resolve. Defaults to `endpoint` when unset. pub public_endpoint: Option, pub access_key_id: Option, pub secret_access_key: Option, pub path_style: Option, pub request_timeout_secs: Option, } impl S3Config { pub fn new( bucket: impl Into, region: impl Into, endpoint: Option, ) -> Self { Self { bucket: Some(bucket.into()), region: Some(region.into()), endpoint, ..Self::default() } } pub fn bucket_name(&self) -> &str { self.bucket.as_deref().unwrap_or("") } pub fn region(&self) -> &str { self.region.as_deref().unwrap_or("us-east-1") } pub fn use_path_style(&self) -> bool { self.path_style.unwrap_or(false) } pub fn request_timeout(&self) -> Option { self.request_timeout_secs.map(Duration::from_secs) } pub fn validate(&self) -> Result<(&str, &str), String> { let bucket = self .bucket .as_deref() .filter(|s| !s.is_empty()) .ok_or_else(|| "s3.bucket must be set".to_string())?; let region = self .region .as_deref() .filter(|s| !s.is_empty()) .ok_or_else(|| "s3.region must be set".to_string())?; Ok((bucket, region)) } }