use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize, Deserialize)]
#[non_exhaustive]
pub struct DynamoConfig {
#[serde(default = "default_region")]
pub region: String,
#[serde(default)]
pub endpoint_url: Option<String>,
#[serde(default)]
pub table_prefix: Option<String>,
}
fn default_region() -> String {
std::env::var("AWS_REGION")
.or_else(|_| std::env::var("AWS_DEFAULT_REGION"))
.unwrap_or_else(|_| "us-east-1".to_string())
}
impl Default for DynamoConfig {
fn default() -> Self {
Self {
region: default_region(),
endpoint_url: None,
table_prefix: None,
}
}
}
impl DynamoConfig {
#[must_use]
pub fn from_env() -> Self {
Self {
region: default_region(),
endpoint_url: std::env::var("DYNAMODB_ENDPOINT_URL").ok(),
table_prefix: std::env::var("DYNAMODB_TABLE_PREFIX").ok(),
}
}
pub async fn build_aws_config(&self) -> aws_config::SdkConfig {
let mut builder = aws_config::defaults(aws_config::BehaviorVersion::latest())
.region(aws_config::Region::new(self.region.clone()));
if let Some(ref endpoint) = self.endpoint_url {
builder = builder.endpoint_url(endpoint);
}
builder.load().await
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn default_config_has_region() {
let cfg = DynamoConfig::default();
assert!(!cfg.region.is_empty());
assert!(cfg.endpoint_url.is_none());
assert!(cfg.table_prefix.is_none());
}
#[test]
fn config_serialization_roundtrip() {
let cfg = DynamoConfig {
region: "eu-west-1".to_string(),
endpoint_url: Some("http://localhost:4566".to_string()),
table_prefix: Some("myapp_".to_string()),
};
let json = serde_json::to_string(&cfg).expect("serialize");
let parsed: DynamoConfig = serde_json::from_str(&json).expect("deserialize");
assert_eq!(parsed.region, "eu-west-1");
assert_eq!(
parsed.endpoint_url.as_deref(),
Some("http://localhost:4566")
);
assert_eq!(parsed.table_prefix.as_deref(), Some("myapp_"));
}
#[test]
fn from_env_reads_defaults() {
let cfg = DynamoConfig::from_env();
assert!(!cfg.region.is_empty());
}
}