82 lines
2.6 KiB
Rust
82 lines
2.6 KiB
Rust
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<T>(file: impl AsRef<Path>, formats: &[(&str, ParseFn<T>)]) -> Result<T, ConfigError>
|
|
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<T: for<'de> Deserialize<'de>>(s: &str) -> Result<T, ConfigError> {
|
|
ron::from_str(s).map_err(ConfigError::Ron)
|
|
}
|
|
|
|
fn from_toml<T: for<'de> Deserialize<'de>>(s: &str) -> Result<T, ConfigError> {
|
|
toml::from_str(s).map_err(ConfigError::Toml)
|
|
}
|
|
|
|
fn from_json<T: for<'de> Deserialize<'de>>(s: &str) -> Result<T, ConfigError> {
|
|
serde_json::from_str(s).map_err(ConfigError::Json)
|
|
}
|
|
}
|
|
|
|
//
|
|
|
|
pub type ParseFn<T> = fn(&str) -> Result<T, ConfigError>;
|
|
|
|
#[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),
|
|
}
|