use serde::Deserialize;
fn default_true() -> bool {
true
}
#[derive(Debug, Clone, Deserialize)]
pub struct NotificationConfig {
pub discord_webhook_url: Option<String>,
pub telegram_bot_token: Option<String>,
pub telegram_chat_id: Option<String>,
#[serde(default = "default_environment")]
pub environment: String,
}
pub fn default_environment() -> String {
"development".to_string()
}
#[derive(Debug, Clone, Deserialize, Default)]
pub struct SsoConfig {
#[serde(default)]
pub enabled: bool,
}
impl Default for NotificationConfig {
fn default() -> Self {
Self {
discord_webhook_url: None,
telegram_bot_token: None,
telegram_chat_id: None,
environment: default_environment(),
}
}
}
impl NotificationConfig {
pub fn discord_enabled(&self) -> bool {
self.discord_webhook_url.is_some()
}
pub fn telegram_enabled(&self) -> bool {
self.telegram_bot_token.is_some() && self.telegram_chat_id.is_some()
}
pub fn any_enabled(&self) -> bool {
self.discord_enabled() || self.telegram_enabled()
}
}
#[derive(Debug, Clone, Deserialize)]
pub struct WebhookConfig {
#[serde(default)]
pub enabled: bool,
pub url: Option<String>,
pub secret: Option<String>,
#[serde(default = "default_webhook_timeout")]
pub timeout_secs: u64,
#[serde(default = "default_webhook_retries")]
pub retry_attempts: u32,
}
pub fn default_webhook_timeout() -> u64 {
10
}
pub fn default_webhook_retries() -> u32 {
2
}
impl Default for WebhookConfig {
fn default() -> Self {
Self {
enabled: false,
url: None,
secret: None,
timeout_secs: default_webhook_timeout(),
retry_attempts: default_webhook_retries(),
}
}
}
#[derive(Debug, Clone, Deserialize)]
pub struct RateLimitConfig {
#[serde(default = "default_true")]
pub enabled: bool,
#[serde(default = "default_rate_limit_store")]
pub store: String,
pub redis_url: Option<String>,
#[serde(default = "default_auth_limit")]
pub auth_limit: u32,
#[serde(default = "default_general_limit")]
pub general_limit: u32,
#[serde(default = "default_credit_limit")]
pub credit_limit: u32,
#[serde(default = "default_window_secs")]
pub window_secs: u64,
}
pub fn default_auth_limit() -> u32 {
10 }
pub fn default_general_limit() -> u32 {
60 }
pub fn default_credit_limit() -> u32 {
30 }
pub fn default_window_secs() -> u64 {
60 }
pub fn default_rate_limit_store() -> String {
"memory".to_string()
}
impl Default for RateLimitConfig {
fn default() -> Self {
Self {
enabled: true,
store: default_rate_limit_store(),
redis_url: None,
auth_limit: default_auth_limit(),
general_limit: default_general_limit(),
credit_limit: default_credit_limit(),
window_secs: default_window_secs(),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Deserialize, Default)]
#[serde(rename_all = "snake_case")]
pub enum WalletRecoveryMode {
#[default]
ShareCOnly,
FullSeed,
None,
}
impl WalletRecoveryMode {
pub fn as_str(&self) -> &'static str {
match self {
WalletRecoveryMode::ShareCOnly => "share_c_only",
WalletRecoveryMode::FullSeed => "full_seed",
WalletRecoveryMode::None => "none",
}
}
pub fn has_recovery(&self) -> bool {
matches!(
self,
WalletRecoveryMode::ShareCOnly | WalletRecoveryMode::FullSeed
)
}
}
impl std::str::FromStr for WalletRecoveryMode {
type Err = String;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s.to_lowercase().as_str() {
"share_c_only" | "sharec" | "share_c" => Ok(WalletRecoveryMode::ShareCOnly),
"full_seed" | "fullseed" | "full" | "portable" => Ok(WalletRecoveryMode::FullSeed),
"none" | "no_recovery" | "norecovery" => Ok(WalletRecoveryMode::None),
_ => Err(format!("Invalid recovery mode: {}", s)),
}
}
}
#[derive(Debug, Clone, Deserialize)]
pub struct WalletConfig {
#[serde(default)]
pub enabled: bool,
#[serde(default)]
pub recovery_mode: WalletRecoveryMode,
#[serde(default = "default_wallet_unlock_ttl")]
pub unlock_ttl_secs: u64,
}
pub fn default_wallet_unlock_ttl() -> u64 {
15 * 60 }
impl Default for WalletConfig {
fn default() -> Self {
Self {
enabled: false,
recovery_mode: WalletRecoveryMode::default(),
unlock_ttl_secs: default_wallet_unlock_ttl(),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_webhook_config_defaults() {
let config = WebhookConfig::default();
assert!(!config.enabled);
assert!(config.url.is_none());
assert!(config.secret.is_none());
assert_eq!(config.timeout_secs, 10);
assert_eq!(config.retry_attempts, 2);
}
#[test]
fn test_rate_limit_config_defaults() {
let config = RateLimitConfig::default();
assert!(config.enabled);
assert_eq!(config.auth_limit, 10);
assert_eq!(config.general_limit, 60);
assert_eq!(config.window_secs, 60);
assert_eq!(config.store, "memory");
}
#[test]
fn test_notification_config_defaults() {
let config = NotificationConfig::default();
assert!(config.discord_webhook_url.is_none());
assert!(config.telegram_bot_token.is_none());
assert!(config.telegram_chat_id.is_none());
assert_eq!(config.environment, "development");
assert!(!config.discord_enabled());
assert!(!config.telegram_enabled());
assert!(!config.any_enabled());
}
#[test]
fn test_notification_config_discord_enabled() {
let config = NotificationConfig {
discord_webhook_url: Some("https://discord.com/webhook".to_string()),
..Default::default()
};
assert!(config.discord_enabled());
assert!(!config.telegram_enabled());
assert!(config.any_enabled());
}
#[test]
fn test_notification_config_telegram_enabled() {
let config = NotificationConfig {
telegram_bot_token: Some("bot123".to_string()),
telegram_chat_id: Some("chat456".to_string()),
..Default::default()
};
assert!(!config.discord_enabled());
assert!(config.telegram_enabled());
assert!(config.any_enabled());
}
#[test]
fn test_wallet_config_defaults() {
let config = WalletConfig::default();
assert!(!config.enabled);
assert_eq!(config.recovery_mode, WalletRecoveryMode::ShareCOnly);
assert_eq!(config.unlock_ttl_secs, 15 * 60);
}
#[test]
fn test_wallet_recovery_mode_from_str() {
assert_eq!(
"share_c_only".parse::<WalletRecoveryMode>().unwrap(),
WalletRecoveryMode::ShareCOnly
);
assert_eq!(
"full_seed".parse::<WalletRecoveryMode>().unwrap(),
WalletRecoveryMode::FullSeed
);
assert_eq!(
"portable".parse::<WalletRecoveryMode>().unwrap(),
WalletRecoveryMode::FullSeed
);
assert_eq!(
"none".parse::<WalletRecoveryMode>().unwrap(),
WalletRecoveryMode::None
);
assert_eq!(
"no_recovery".parse::<WalletRecoveryMode>().unwrap(),
WalletRecoveryMode::None
);
assert!("invalid".parse::<WalletRecoveryMode>().is_err());
}
#[test]
fn test_wallet_recovery_mode_as_str() {
assert_eq!(WalletRecoveryMode::ShareCOnly.as_str(), "share_c_only");
assert_eq!(WalletRecoveryMode::FullSeed.as_str(), "full_seed");
assert_eq!(WalletRecoveryMode::None.as_str(), "none");
}
#[test]
fn test_wallet_recovery_mode_has_recovery() {
assert!(WalletRecoveryMode::ShareCOnly.has_recovery());
assert!(WalletRecoveryMode::FullSeed.has_recovery());
assert!(!WalletRecoveryMode::None.has_recovery());
}
}