trust-registry 0.20.0

Trust Registry
use super::{Configs, loaders::environment::*};

const DEFAULT_TRUST_REGISTRY_FILE_PATH: &str = "trust_records.csv";
const DEFAULT_TRUST_REGISTRY_UPDATE_INTERVAL_SEC: u64 = 60;
const DEFAULT_REGION: &str = "ap-southeast-1";

#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum TrustStorageBackend {
    /// CSV file storage — the default, matching `load_storage_backend`'s fallback.
    #[default]
    Csv,
    DynamoDb,
    Redis,
    /// Embedded fjall LSM store (compiled with `--features storage-fjall`).
    Fjall,
}

const DEFAULT_FJALL_PATH: &str = "./trust_records.fjall";

#[derive(Debug, Clone, Default)]
pub struct FileStorageConfig {
    pub is_enabled: bool,
    pub path: String,
    pub update_interval_sec: u64,
}

#[derive(Debug, Clone, Default)]
pub struct DynamoDbStorageConfig {
    pub is_enabled: bool,
    pub table_name: String,
    pub region: Option<String>,
    pub profile: Option<String>,
    pub endpoint_url: Option<String>,
}

#[derive(Debug, Clone, Default)]
pub struct RedisStorageConfig {
    pub is_enabled: bool,
    pub redis_url: String,
}

#[derive(Debug, Clone, Default)]
pub struct FjallStorageConfig {
    pub is_enabled: bool,
    pub path: String,
}

#[derive(Debug, Clone, Default)]
pub struct StorageConfig {
    pub ddb_storage_config: DynamoDbStorageConfig,
    pub file_storage_config: FileStorageConfig,
    pub redis_storage_config: RedisStorageConfig,
    pub fjall_storage_config: FjallStorageConfig,
    pub storage_backend: TrustStorageBackend,
}

fn load_storage_backend() -> TrustStorageBackend {
    let storage_backend_str = env_or("TR_STORAGE_BACKEND", "csv").to_lowercase();
    match storage_backend_str.as_str() {
        "dynamodb" | "ddb" => TrustStorageBackend::DynamoDb,
        "redis" => TrustStorageBackend::Redis,
        "fjall" => TrustStorageBackend::Fjall,
        _ => TrustStorageBackend::Csv,
    }
}

#[async_trait::async_trait]
impl Configs for FileStorageConfig {
    async fn load() -> Result<Self, Box<dyn std::error::Error + Send + Sync>> {
        if load_storage_backend() == TrustStorageBackend::Csv {
            Ok(FileStorageConfig {
                is_enabled: true,
                path: env_or("FILE_STORAGE_PATH", DEFAULT_TRUST_REGISTRY_FILE_PATH),
                update_interval_sec: env_or(
                    "FILE_STORAGE_UPDATE_INTERVAL_SEC",
                    &DEFAULT_TRUST_REGISTRY_UPDATE_INTERVAL_SEC.to_string(),
                )
                .parse::<u64>()?,
            })
        } else {
            Ok(Default::default())
        }
    }
}

#[async_trait::async_trait]
impl Configs for DynamoDbStorageConfig {
    async fn load() -> Result<Self, Box<dyn std::error::Error + Send + Sync>> {
        if load_storage_backend() == TrustStorageBackend::DynamoDb {
            Ok(DynamoDbStorageConfig {
                is_enabled: true,
                table_name: required_env("DDB_TABLE_NAME")?,
                region: Some(env_or("AWS_REGION", DEFAULT_REGION)),
                profile: optional_env("AWS_PROFILE"),
                endpoint_url: optional_env("AWS_ENDPOINT")
                    .or_else(|| optional_env("DYNAMODB_ENDPOINT")),
            })
        } else {
            Ok(Default::default())
        }
    }
}

#[async_trait::async_trait]
impl Configs for RedisStorageConfig {
    async fn load() -> Result<Self, Box<dyn std::error::Error + Send + Sync>> {
        if load_storage_backend() == TrustStorageBackend::Redis {
            Ok(RedisStorageConfig {
                is_enabled: true,
                redis_url: required_env("REDIS_URL")?,
            })
        } else {
            Ok(Default::default())
        }
    }
}

#[async_trait::async_trait]
impl Configs for FjallStorageConfig {
    async fn load() -> Result<Self, Box<dyn std::error::Error + Send + Sync>> {
        if load_storage_backend() == TrustStorageBackend::Fjall {
            Ok(FjallStorageConfig {
                is_enabled: true,
                path: env_or("TR_FJALL_PATH", DEFAULT_FJALL_PATH),
            })
        } else {
            Ok(Default::default())
        }
    }
}

#[async_trait::async_trait]
impl Configs for StorageConfig {
    async fn load() -> Result<Self, Box<dyn std::error::Error + Send + Sync>> {
        let storage_backend = load_storage_backend();
        Ok(StorageConfig {
            ddb_storage_config: DynamoDbStorageConfig::load().await?,
            file_storage_config: FileStorageConfig::load().await?,
            redis_storage_config: RedisStorageConfig::load().await?,
            fjall_storage_config: FjallStorageConfig::load().await?,
            storage_backend,
        })
    }
}