use crate::format::StoreFormat;
use serde::{Deserialize, Serialize};
use thiserror::Error;
#[derive(Error, Debug)]
pub enum ConfigError {
#[error("io error: {0}")]
Io(#[from] std::io::Error),
#[error("toml parse error: {0}")]
TomlParse(#[from] toml::de::Error),
#[error("yaml parse error: {0}")]
YamlLoad(#[from] serde_yaml::Error),
#[error("yaml save error: {0}")]
YamlSave(String),
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct RegistryConfig {
pub name: String,
pub url: String,
#[serde(default = "default_priority")]
pub priority: i32,
#[serde(default = "default_ttl_secs")]
pub ttl_secs: u64,
#[serde(default)]
pub trusted: bool,
}
fn default_priority() -> i32 {
50
}
fn default_ttl_secs() -> u64 {
3600
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct StoreConfig {
#[serde(default)]
pub store: StoreSection,
#[serde(default)]
pub registries: Vec<RegistryConfig>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub cid_gateway: Option<String>,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct StoreSection {
#[serde(default = "default_format")]
pub format: String,
}
fn default_format() -> String {
"yaml".to_string()
}
impl Default for StoreConfig {
fn default() -> Self {
StoreConfig {
store: StoreSection {
format: "yaml".to_string(),
},
registries: Vec::new(),
cid_gateway: None,
}
}
}
impl StoreConfig {
pub fn load() -> Result<Self, ConfigError> {
if let Some(citum_dir) = crate::platform_config_dir() {
let yaml_path = citum_dir.join("config.yaml");
if yaml_path.exists() {
let content = std::fs::read_to_string(&yaml_path)?;
return serde_yaml::from_str(&content).map_err(ConfigError::YamlLoad);
}
let toml_path = citum_dir.join("config.toml");
if toml_path.exists() {
let content = std::fs::read_to_string(&toml_path)?;
return Ok(toml::from_str(&content)?);
}
}
Ok(StoreConfig::default())
}
pub fn save(&self) -> Result<(), ConfigError> {
if let Some(citum_dir) = crate::platform_config_dir() {
std::fs::create_dir_all(&citum_dir)?;
let config_path = citum_dir.join("config.yaml");
let content =
serde_yaml::to_string(self).map_err(|e| ConfigError::YamlSave(e.to_string()))?;
std::fs::write(&config_path, content)?;
Ok(())
} else {
Err(ConfigError::Io(std::io::Error::new(
std::io::ErrorKind::NotFound,
"config directory not found",
)))
}
}
#[must_use]
pub fn store_format(&self) -> StoreFormat {
self.store.format.parse().unwrap_or(StoreFormat::Yaml)
}
}
#[cfg(test)]
#[allow(
clippy::unwrap_used,
clippy::expect_used,
clippy::panic,
clippy::indexing_slicing,
clippy::todo,
clippy::unimplemented,
clippy::unreachable,
clippy::get_unwrap,
reason = "Panicking is acceptable and often desired in tests."
)]
mod tests {
use super::*;
#[test]
fn test_default_config() {
let cfg = StoreConfig::default();
assert_eq!(cfg.store.format, "yaml");
assert_eq!(cfg.store_format(), StoreFormat::Yaml);
assert!(cfg.registries.is_empty());
}
#[test]
fn test_parse_toml_config() {
let toml_str = r#"
[store]
format = "json"
"#;
let cfg: StoreConfig = toml::from_str(toml_str).unwrap();
assert_eq!(cfg.store.format, "json");
assert_eq!(cfg.store_format(), StoreFormat::Json);
}
#[test]
fn test_parse_yaml_config() {
let yaml_str = r#"
store:
format: json
registries:
- name: example
url: https://example.org/registry.yaml
priority: 100
ttl_secs: 3600
trusted: true
"#;
let cfg: StoreConfig = serde_yaml::from_str(yaml_str).unwrap();
assert_eq!(cfg.store.format, "json");
assert_eq!(cfg.registries.len(), 1);
assert_eq!(cfg.registries[0].name, "example");
assert_eq!(cfg.registries[0].priority, 100);
}
}