use std::path::Path; use serde::Deserialize; use thiserror::Error; /// A trait for types that can be loaded from a configuration file. /// /// Implementors must define [`load`](FromConfig::load), which is responsible for /// resolving the config path and calling [`deserialize_config`](FromConfig::deserialize_config). /// All parsing methods have default implementations supporting RON, TOML, and JSON, /// and can be overridden if custom parsing behaviour is needed. pub trait FromConfig { /// Loads and deserializes the config into `Self`. /// /// Implementations should resolve the config file path and call /// [`deserialize_config`](FromConfig::deserialize_config) with the desired formats. fn load() -> Self; /// Deserializes a config file into a rust struct. /// /// Example: /// ``` /// let cfg: Config = Config::deserialize_config( /// "path/to/config", /// &[ /// ("ron", Config::from_ron), /// ("toml", Config::from_toml), /// ("json", Config::from_json), /// ], /// )?; /// ``` fn deserialize_config(file: impl AsRef, formats: &[(&str, ParseFn)]) -> Result where T: for<'de> Deserialize<'de>, { let base = file.as_ref(); for (ext, parse) in formats { let path = base.with_extension(ext); if path.exists() { let raw = std::fs::read_to_string(&path) .map_err(ConfigError::Io)?; return parse(&raw); } } Err(ConfigError::NotFound { path: file.as_ref().to_string_lossy().into() }) } fn from_ron Deserialize<'de>>(s: &str) -> Result { ron::from_str(s).map_err(ConfigError::Ron) } fn from_toml Deserialize<'de>>(s: &str) -> Result { toml::from_str(s).map_err(ConfigError::Toml) } fn from_json Deserialize<'de>>(s: &str) -> Result { serde_json::from_str(s).map_err(ConfigError::Json) } } // pub type ParseFn = fn(&str) -> Result; #[derive(Debug, Error)] pub enum ConfigError { #[error("No config file found at '{path}.(ron|toml|json)'.")] NotFound { path: String }, #[error("IO error: {0}")] Io(#[from] std::io::Error), #[error("Ron parse error: {0}")] Ron(#[from] ron::error::SpannedError), #[error("TOML parse error: {0}")] Toml(#[from] toml::de::Error), #[error("JSON parse error: {0}")] Json(#[from] serde_json::Error), }