From 85e77832e92a832b9cf6f107a888fa7542847871 Mon Sep 17 00:00:00 2001 From: Charles Hall Date: Tue, 11 Jun 2024 20:41:05 -0700 Subject: [PATCH] follow xdg base dirs spec by default --- Cargo.lock | 7 +++++++ Cargo.toml | 1 + src/args.rs | 22 +++++++++++++++++++--- src/config.rs | 28 +++++++++++++++++++++++++--- src/error.rs | 16 ++++++++++++++++ src/main.rs | 2 +- 6 files changed, 69 insertions(+), 7 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index ba8c564c..0ec85d7c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -876,6 +876,7 @@ dependencies = [ "tracing-opentelemetry", "tracing-subscriber", "trust-dns-resolver", + "xdg", ] [[package]] @@ -3660,6 +3661,12 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "xdg" +version = "2.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "213b7324336b53d2414b2db8537e56544d981803139155afa84f76eeebb7a546" + [[package]] name = "yap" version = "0.12.0" diff --git a/Cargo.toml b/Cargo.toml index ed55a338..c2da8680 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -141,6 +141,7 @@ tracing-flame = "0.2.0" tracing-opentelemetry = "0.24.0" tracing-subscriber = { version = "0.3.18", features = ["env-filter"] } trust-dns-resolver = "0.23.2" +xdg = "2.5.2" [target.'cfg(unix)'.dependencies] nix = { version = "0.29", features = ["resource"] } diff --git a/src/args.rs b/src/args.rs index a54589b3..33862de2 100644 --- a/src/args.rs +++ b/src/args.rs @@ -2,7 +2,7 @@ use std::path::PathBuf; -use clap::Parser; +use clap::{CommandFactory as _, FromArgMatches as _, Parser}; /// Command line arguments #[derive(Parser)] @@ -10,10 +10,26 @@ use clap::Parser; pub(crate) struct Args { /// Path to the configuration file #[clap(long, short)] - pub(crate) config: PathBuf, + pub(crate) config: Option, } /// Parse command line arguments into structured data pub(crate) fn parse() -> Args { - Args::parse() + let mut command = Args::command().mut_arg("config", |x| { + let help = "Set the path to the configuration file"; + x.help(help).long_help(format!( + "{}\n\nIf this option is specified, the provided value is used \ + as-is.\n\nIf this option is not specified, then the XDG Base \ + Directory Specification is followed, searching for the path `{}` \ + in the configuration directories. + ", + help, + crate::config::DEFAULT_PATH.display(), + )) + }); + + match Args::from_arg_matches(&command.get_matches_mut()) { + Ok(x) => x, + Err(e) => e.format(&mut command).exit(), + } } diff --git a/src/config.rs b/src/config.rs index c1ffde91..88399a46 100644 --- a/src/config.rs +++ b/src/config.rs @@ -1,8 +1,10 @@ use std::{ + borrow::Cow, net::{IpAddr, Ipv4Addr}, - path::Path, + path::{Path, PathBuf}, }; +use once_cell::sync::Lazy; use ruma::{OwnedServerName, RoomVersionId}; use serde::Deserialize; @@ -14,6 +16,10 @@ mod proxy; use env_filter_clone::EnvFilterClone; use proxy::ProxyConfig; +/// The default configuration file path +pub(crate) static DEFAULT_PATH: Lazy = + Lazy::new(|| [env!("CARGO_PKG_NAME"), "config.toml"].iter().collect()); + #[allow(clippy::struct_excessive_bools)] #[derive(Debug, Deserialize)] pub(crate) struct Config { @@ -163,13 +169,29 @@ pub(crate) fn default_default_room_version() -> RoomVersionId { RoomVersionId::V10 } -/// Load the configuration from the given path -pub(crate) async fn load

(path: P) -> Result +/// Search default locations for a configuration file +/// +/// If one isn't found, the list of tried paths is returned. +fn search() -> Result { + use error::ConfigSearch as Error; + + xdg::BaseDirectories::new()? + .find_config_file(&*DEFAULT_PATH) + .ok_or(Error::NotFound) +} + +/// Load the configuration from the given path or XDG Base Directories +pub(crate) async fn load

(path: Option

) -> Result where P: AsRef, { use error::Config as Error; + let path = match path.as_ref().map(AsRef::as_ref) { + Some(x) => Cow::Borrowed(x), + None => Cow::Owned(search()?), + }; + let path = path.as_ref(); toml::from_str( diff --git a/src/error.rs b/src/error.rs index c8e9d8e2..2c57b021 100644 --- a/src/error.rs +++ b/src/error.rs @@ -75,9 +75,25 @@ pub(crate) enum Observability { #[allow(missing_docs)] #[derive(Error, Debug)] pub(crate) enum Config { + #[error("failed to find configuration file")] + Search(#[from] ConfigSearch), + #[error("failed to read configuration file {1:?}")] Read(#[source] std::io::Error, PathBuf), #[error("failed to parse configuration file {1:?}")] Parse(#[source] toml::de::Error, PathBuf), } + +/// Errors that can occur while searching for a config file +// Missing docs are allowed here since that kind of information should be +// encoded in the error messages themselves anyway. +#[allow(missing_docs)] +#[derive(Error, Debug)] +pub(crate) enum ConfigSearch { + #[error("XDG Base Directory error")] + Xdg(#[from] xdg::BaseDirectoriesError), + + #[error("no relevant configuration files found in XDG Base Directories")] + NotFound, +} diff --git a/src/main.rs b/src/main.rs index ba1f9e29..bc318479 100644 --- a/src/main.rs +++ b/src/main.rs @@ -106,7 +106,7 @@ async fn try_main() -> Result<(), error::Main> { let args = args::parse(); - let config = config::load(&args.config).await?; + let config = config::load(args.config.as_ref()).await?; let _guard = observability::init(&config);