Skip to main content

nexus_common/config/file/
reader.rs

1use crate::types::DynError;
2use async_trait::async_trait;
3use serde::de::DeserializeOwned;
4use std::ffi::OsStr;
5use std::fmt::Debug;
6use std::path::{Component, Path, PathBuf};
7use tracing::error;
8
9use super::ConfigLoader;
10
11/// Path to default nexusd config file. Defaults to ~/.pubky-nexus
12pub const DEFAULT_HOME_DIR: &str = ".pubky-nexus";
13const DEFAULT_CONFIG_TOML: &str = include_str!("../../../default.config.toml");
14/// The sole configuration file name recognized by nexus
15pub const CONFIG_FILE_NAME: &str = "config.toml";
16
17/// Expands the data directory to the home directory if it starts with "~"
18/// Return the full path to the data directory
19pub fn expand_home_dir(path: PathBuf) -> PathBuf {
20    if let Some(first) = path.components().next() {
21        if first == Component::Normal(OsStr::new("~")) {
22            if let Some(home) = dirs::home_dir() {
23                // drop the "~" prefix and re-join
24                let without_tilde = path.iter().skip(1).collect::<PathBuf>();
25                return home.join(without_tilde);
26            }
27        }
28    }
29    path
30}
31
32#[async_trait]
33pub trait ConfigReader<T>: ConfigLoader<T>
34where
35    T: DeserializeOwned + Send + Sync + Debug,
36{
37    /// Returns the config file path in this directory
38    fn get_config_file_path(expanded_path: &Path) -> PathBuf {
39        expanded_path.join(CONFIG_FILE_NAME)
40    }
41
42    fn write_default_config_file(config_file_path: &PathBuf) -> std::io::Result<()> {
43        // Make sure before write the file, the directory path exists
44        if let Some(parent) = config_file_path.parent() {
45            println!(
46                "Validating existence of '{}' and creating it if missing before copying '{CONFIG_FILE_NAME}' file…",
47                parent.display()
48            );
49            std::fs::create_dir_all(parent)?;
50        }
51        // Create the file
52        std::fs::write(config_file_path, DEFAULT_CONFIG_TOML)?;
53        Ok(())
54    }
55
56    /// Given a directory path, ensures the directory exists, writes a default
57    /// `config.toml` if absent, then parses and returns the loaded config
58    async fn read_config_file(expanded_path: PathBuf) -> Result<T, DynError> {
59        let config_file_path = Self::get_config_file_path(&expanded_path);
60
61        if !config_file_path.exists() {
62            Self::write_default_config_file(&config_file_path)?;
63        }
64        println!(
65            "nexusd reading the '{CONFIG_FILE_NAME}' file from '{}'",
66            expanded_path.display()
67        );
68
69        let config = <Self as ConfigLoader<T>>::load(config_file_path)
70            .await
71            .map_err(|e| {
72                error!(
73                    "Failed to load config file {:?}: {}",
74                    Self::get_config_file_path(&expanded_path),
75                    e
76                );
77                e
78            })?;
79        Ok(config)
80    }
81}
82
83// ——————————————————————————————————————————————————————————————
84// Blanket impl so *any* `T` automatically gets a `ConfigReader<T>`
85// ——————————————————————————————————————————————————————————————
86#[async_trait]
87impl<T> ConfigReader<T> for T where T: ConfigLoader<T> + DeserializeOwned + Send + Sync + Debug {}