//! Non-secret local folder inventory used for bulk import. use std::{ fs, path::{Path, PathBuf}, }; use serde::{Deserialize, Serialize}; use crate::{domain::FolderMode, storage::FolderConfig}; /// Version of the local folder inventory format. pub const CONFIG_VERSION: u16 = 1; pub const DEFAULT_CONFIG_PATH: &str = "appa.toml"; #[derive(Clone, Debug, Deserialize, Serialize)] #[serde(deny_unknown_fields)] pub struct AppaConfig { #[serde(default)] pub(crate) version: u16, #[serde(default)] pub(crate) folders: Vec, } #[derive(Clone, Debug, Deserialize, Serialize)] #[serde(deny_unknown_fields)] pub struct ConfiguredFolder { pub(crate) path: PathBuf, pub(crate) name: Option, #[serde(default)] pub(crate) mode: FolderMode, } impl AppaConfig { pub fn read(path: &Path) -> anyhow::Result { let contents = fs::read_to_string(path) .map_err(|error| anyhow::anyhow!("could not read {}: {error}", path.display()))?; let config: Self = toml::from_str(&contents) .map_err(|error| anyhow::anyhow!("could not parse {}: {error}", path.display()))?; config.validate()?; Ok(config) } pub fn write_template(path: &Path, folders: &[FolderConfig]) -> anyhow::Result<()> { if path.exists() { anyhow::bail!("{} already exists", path.display()); } let config = Self { version: CONFIG_VERSION, folders: folders .iter() .map(ConfiguredFolder::from_storage_config) .collect(), }; fs::write(path, toml::to_string_pretty(&config)?)?; Ok(()) } fn validate(&self) -> anyhow::Result<()> { if self.version != CONFIG_VERSION { anyhow::bail!("unsupported Appa config version {}", self.version); } Ok(()) } } impl ConfiguredFolder { fn from_storage_config(folder: &FolderConfig) -> Self { Self { path: folder.path.clone(), name: Some(folder.name.clone()), mode: folder.mode, } } } #[cfg(test)] mod tests { use std::fs; use tempfile::TempDir; use super::{AppaConfig, CONFIG_VERSION}; #[test] fn rejects_shared_folder_fields() { let directory = TempDir::new().expect("temporary directory"); let config_path = directory.path().join("appa.toml"); fs::write(&config_path, format!("version = {CONFIG_VERSION}\n[[folders]]\npath = \"notes\"\nfolder_id = \"00000000-0000-0000-0000-000000000000\"\n")).expect("write config"); assert!(AppaConfig::read(&config_path).is_err()); } #[test] fn rejects_unknown_fields_and_reports_missing_versions() { let directory = TempDir::new().expect("temporary directory"); let config_path = directory.path().join("appa.toml"); fs::write(&config_path, "unknown = true\n").expect("write config"); assert!(AppaConfig::read(&config_path).is_err()); fs::write(&config_path, "[[folders]]\npath = \"notes\"\n").expect("write config"); let error = AppaConfig::read(&config_path).expect_err("missing version must fail"); assert!( error .to_string() .contains("unsupported Appa config version 0") ); } }