vespertide_config/
lib.rs

1use std::path::{Path, PathBuf};
2
3use serde::{Deserialize, Serialize};
4use clap::ValueEnum;
5
6/// Supported naming cases.
7#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
8#[serde(rename_all = "snake_case")]
9pub enum NameCase {
10    Snake,
11    Camel,
12    Pascal,
13}
14
15impl NameCase {
16    /// Returns true when snake case.
17    pub fn is_snake(self) -> bool {
18        matches!(self, NameCase::Snake)
19    }
20
21    /// Returns true when camel case.
22    pub fn is_camel(self) -> bool {
23        matches!(self, NameCase::Camel)
24    }
25
26    /// Returns true when pascal case.
27    pub fn is_pascal(self) -> bool {
28        matches!(self, NameCase::Pascal)
29    }
30}
31
32/// Supported file formats for generated artifacts.
33#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, ValueEnum)]
34#[serde(rename_all = "lowercase")]
35pub enum FileFormat {
36    Json,
37    Yaml,
38    Yml,
39}
40
41impl Default for FileFormat {
42    fn default() -> Self {
43        FileFormat::Json
44    }
45}
46
47/// Default migration filename pattern: zero-padded version + sanitized comment.
48pub fn default_migration_filename_pattern() -> String {
49    "%04v_%m".to_string()
50}
51
52/// Top-level vespertide configuration.
53#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
54#[serde(rename_all = "camelCase")]
55pub struct VespertideConfig {
56    pub models_dir: PathBuf,
57    pub migrations_dir: PathBuf,
58    pub table_naming_case: NameCase,
59    pub column_naming_case: NameCase,
60    #[serde(default)]
61    pub model_format: FileFormat,
62    #[serde(default)]
63    pub migration_format: FileFormat,
64    #[serde(default = "default_migration_filename_pattern")]
65    pub migration_filename_pattern: String,
66}
67
68impl Default for VespertideConfig {
69    fn default() -> Self {
70        Self {
71            models_dir: PathBuf::from("models"),
72            migrations_dir: PathBuf::from("migrations"),
73            table_naming_case: NameCase::Snake,
74            column_naming_case: NameCase::Snake,
75            model_format: FileFormat::Json,
76            migration_format: FileFormat::Json,
77            migration_filename_pattern: default_migration_filename_pattern(),
78        }
79    }
80}
81
82impl VespertideConfig {
83    pub fn new() -> Self {
84        Self::default()
85    }
86
87    /// Path where model definitions are stored.
88    pub fn models_dir(&self) -> &Path {
89        &self.models_dir
90    }
91
92    /// Path where migrations are stored.
93    pub fn migrations_dir(&self) -> &Path {
94        &self.migrations_dir
95    }
96
97    /// Naming case for table names (flattened).
98    pub fn table_case(&self) -> NameCase {
99        self.table_naming_case
100    }
101
102    /// Naming case for column names (flattened).
103    pub fn column_case(&self) -> NameCase {
104        self.column_naming_case
105    }
106
107    /// Preferred file format for models.
108    pub fn model_format(&self) -> FileFormat {
109        self.model_format
110    }
111
112    /// Preferred file format for migrations.
113    pub fn migration_format(&self) -> FileFormat {
114        self.migration_format
115    }
116
117    /// Pattern for migration filenames (supports %v and %m placeholders).
118    pub fn migration_filename_pattern(&self) -> &str {
119        &self.migration_filename_pattern
120    }
121}
122
123#[cfg(test)]
124mod tests {
125    use super::*;
126
127    #[test]
128    fn default_values_are_snake_and_standard_paths() {
129        let cfg = VespertideConfig::default();
130        assert_eq!(cfg.models_dir, PathBuf::from("models"));
131        assert_eq!(cfg.migrations_dir, PathBuf::from("migrations"));
132        assert!(cfg.table_case().is_snake());
133        assert!(cfg.column_case().is_snake());
134    }
135
136    #[test]
137    fn overrides_work_via_struct_update() {
138        let cfg = VespertideConfig {
139            models_dir: PathBuf::from("custom_models"),
140            migrations_dir: PathBuf::from("custom_migrations"),
141            table_naming_case: NameCase::Camel,
142            column_naming_case: NameCase::Pascal,
143            ..Default::default()
144        };
145
146        assert_eq!(cfg.models_dir(), Path::new("custom_models"));
147        assert_eq!(cfg.migrations_dir(), Path::new("custom_migrations"));
148        assert!(cfg.table_case().is_camel());
149        assert!(cfg.column_case().is_pascal());
150    }
151}