use crate::paths::Paths;
use anyhow::{Context, Result};
use serde::Deserialize;
#[derive(Debug, Default, Clone, Deserialize)]
pub struct Config {
#[serde(default)]
pub server: ServerConfig,
#[serde(default)]
pub auth: AuthConfig,
#[serde(default)]
pub auto_update: AutoUpdateConfig,
#[serde(default)]
pub rate_limit: RateLimitConfig,
}
#[derive(Debug, Clone, Deserialize)]
pub struct ServerConfig {
#[serde(default = "default_bind")]
pub bind: String,
#[serde(default)]
pub cors_origins: Vec<String>,
}
impl Default for ServerConfig {
fn default() -> Self {
Self {
bind: default_bind(),
cors_origins: Vec::new(),
}
}
}
fn default_bind() -> String {
"127.0.0.1:8000".to_string()
}
#[derive(Debug, Default, Clone, Deserialize)]
pub struct AuthConfig {
#[serde(default)]
pub tokens: Vec<String>,
#[serde(default)]
pub admin_tokens: Vec<String>,
}
#[derive(Debug, Clone, Deserialize)]
pub struct AutoUpdateConfig {
#[serde(default)]
pub enabled: bool,
#[serde(default = "default_interval")]
pub interval: String,
#[serde(default)]
pub pin: String,
}
impl Default for AutoUpdateConfig {
fn default() -> Self {
Self {
enabled: false,
interval: default_interval(),
pin: String::new(),
}
}
}
fn default_interval() -> String {
"24h".to_string()
}
#[derive(Debug, Clone, Deserialize)]
pub struct RateLimitConfig {
#[serde(default = "default_per_second")]
pub per_second: u64,
#[serde(default = "default_burst_size")]
pub burst_size: u32,
}
impl Default for RateLimitConfig {
fn default() -> Self {
Self {
per_second: default_per_second(),
burst_size: default_burst_size(),
}
}
}
fn default_per_second() -> u64 {
1
}
fn default_burst_size() -> u32 {
5
}
impl Config {
pub fn load(paths: &Paths) -> Result<Self> {
if !paths.config_file.exists() {
return Ok(Self::default());
}
let content = std::fs::read_to_string(&paths.config_file).with_context(|| {
format!("設定ファイル読み込み失敗: {}", paths.config_file.display())
})?;
let cfg: Self = toml::from_str(&content)
.with_context(|| format!("設定ファイルパース失敗: {}", paths.config_file.display()))?;
Ok(cfg)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn default_rate_limit_is_1_per_sec_burst_5() {
let cfg = Config::default();
assert_eq!(cfg.rate_limit.per_second, 1);
assert_eq!(cfg.rate_limit.burst_size, 5);
}
#[test]
fn rate_limit_section_overrides_defaults() {
let cfg: Config = toml::from_str(
r#"
[rate_limit]
per_second = 20
burst_size = 100
"#,
)
.unwrap();
assert_eq!(cfg.rate_limit.per_second, 20);
assert_eq!(cfg.rate_limit.burst_size, 100);
}
#[test]
fn missing_rate_limit_section_falls_back_to_defaults() {
let cfg: Config = toml::from_str(
r#"
[server]
bind = "0.0.0.0:9000"
"#,
)
.unwrap();
assert_eq!(cfg.rate_limit.per_second, 1);
assert_eq!(cfg.rate_limit.burst_size, 5);
}
#[test]
fn partial_rate_limit_keeps_other_default() {
let cfg: Config = toml::from_str(
r#"
[rate_limit]
per_second = 50
"#,
)
.unwrap();
assert_eq!(cfg.rate_limit.per_second, 50);
assert_eq!(cfg.rate_limit.burst_size, 5);
}
}