Skip to main content

toasty_cli/
config.rs

1use crate::migration::MigrationConfig;
2use anyhow::Result;
3use serde::{Deserialize, Serialize};
4use std::fs;
5use std::path::Path;
6
7/// Configuration for Toasty CLI operations.
8///
9/// Holds all settings that control how the CLI behaves. Currently this is
10/// limited to [`MigrationConfig`]. A `Config` can be built programmatically
11/// with the builder methods or loaded from a `Toasty.toml` file via
12/// [`Config::load`].
13///
14/// # Examples
15///
16/// ```
17/// use toasty_cli::{Config, MigrationConfig, MigrationPrefixStyle};
18///
19/// let config = Config::new()
20///     .migration(
21///         MigrationConfig::new()
22///             .path("db")
23///             .prefix_style(MigrationPrefixStyle::Timestamp),
24///     );
25/// assert_eq!(
26///     config.migration.get_migrations_dir(),
27///     std::path::PathBuf::from("db/migrations"),
28/// );
29/// ```
30#[derive(Debug, Default, Clone, Serialize, Deserialize, PartialEq)]
31pub struct Config {
32    /// Migration-related configuration
33    pub migration: MigrationConfig,
34}
35
36impl Config {
37    /// Create a new Config with default values
38    pub fn new() -> Self {
39        Self::default()
40    }
41
42    /// Load configuration from Toasty.toml in the project root
43    pub fn load() -> Result<Self> {
44        Self::load_from(Path::new("Toasty.toml"))
45    }
46
47    /// Load configuration from a specific path.
48    pub fn load_from(path: &Path) -> Result<Self> {
49        let contents = fs::read_to_string(path)?;
50        let config: Config = toml::from_str(&contents)?;
51        Ok(config)
52    }
53
54    /// Load configuration from `<project_root>/Toasty.toml`, creating it with
55    /// default contents if the file does not exist.
56    pub fn load_or_default(project_root: &Path) -> Result<Self> {
57        let path = project_root.join("Toasty.toml");
58        if path.exists() {
59            Self::load_from(&path)
60        } else {
61            let config = Self::default();
62            let toml = toml::to_string_pretty(&config)?;
63            fs::write(&path, toml)?;
64            Ok(config)
65        }
66    }
67
68    /// Set the migration configuration
69    pub fn migration(mut self, migration: MigrationConfig) -> Self {
70        self.migration = migration;
71        self
72    }
73}
74
75#[cfg(test)]
76mod tests {
77    use super::*;
78    use tempfile::tempdir;
79
80    #[test]
81    fn load_or_default_creates_toasty_toml_when_missing() {
82        let dir = tempdir().unwrap();
83        let path = dir.path().join("Toasty.toml");
84        assert!(!path.exists());
85
86        Config::load_or_default(dir.path()).unwrap();
87
88        assert!(path.exists(), "Toasty.toml should be created on first load");
89        let contents = fs::read_to_string(&path).unwrap();
90        let reparsed: Config = toml::from_str(&contents).unwrap();
91        let default = Config::default();
92
93        assert_eq!(reparsed, default);
94    }
95}