use std::path::Path;
use std::path::PathBuf;
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct ServerConfig {
pub server: ServerSection,
pub data: DataSection,
#[serde(default)]
pub authentication: AuthSection,
}
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct ServerSection {
pub host: String,
pub port: u16,
}
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct DataSection {
pub files: Vec<PathBuf>,
}
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct AuthSection {
#[serde(default = "default_auth_method")]
pub method: String,
#[serde(default)]
pub keys: Vec<String>,
}
impl Default for AuthSection {
fn default() -> Self {
Self {
method: default_auth_method(),
keys: Vec::new(),
}
}
}
fn default_auth_method() -> String {
"none".into()
}
impl AuthSection {
pub fn validate(&self) -> Result<(), String> {
match self.method.as_str() {
"none" => Ok(()),
"api_key" => {
if self.keys.is_empty() {
Err(
"authentication.method = \"api_key\" requires at least one entry in keys"
.into(),
)
} else {
Ok(())
}
}
other => Err(format!("unsupported authentication.method: {other}")),
}
}
}
impl ServerConfig {
pub fn load(path: &Path) -> Result<Self, ConfigError> {
let s = std::fs::read_to_string(path).map_err(ConfigError::Io)?;
toml::from_str(&s).map_err(ConfigError::Parse)
}
}
#[derive(Debug, thiserror::Error)]
pub enum ConfigError {
#[error("io error: {0}")]
Io(#[from] std::io::Error),
#[error("parse error: {0}")]
Parse(#[from] toml::de::Error),
}
#[cfg(test)]
mod tests {
use super::*;
const BASE: &str = r#"
[server]
host = "127.0.0.1"
port = 50051
[data]
files = ["store.nc"]
"#;
#[test]
fn omitted_authentication_section_defaults_to_none() {
let cfg: ServerConfig = toml::from_str(BASE).unwrap();
assert_eq!(cfg.authentication.method, "none");
cfg.authentication.validate().unwrap();
}
#[test]
fn authentication_section_without_method_defaults_to_none() {
let cfg: ServerConfig = toml::from_str(&format!("{BASE}\n[authentication]\n")).unwrap();
assert_eq!(cfg.authentication.method, "none");
cfg.authentication.validate().unwrap();
}
#[test]
fn api_key_without_keys_is_rejected() {
let cfg: ServerConfig =
toml::from_str(&format!("{BASE}\n[authentication]\nmethod = \"api_key\"\n")).unwrap();
assert!(cfg.authentication.validate().is_err());
}
}