1use crate::migration::MigrationConfig;
2use anyhow::{Context, Result};
3use serde::{Deserialize, Serialize};
4use std::fs;
5use std::path::Path;
6
7#[derive(Debug, Default, Clone, Serialize, Deserialize, PartialEq)]
31pub struct Config {
32 pub migration: MigrationConfig,
34}
35
36impl Config {
37 pub fn new() -> Self {
39 Self::default()
40 }
41
42 pub fn load() -> Result<Self> {
44 Self::load_from(Path::new("Toasty.toml"))
45 }
46
47 pub fn load_from(path: &Path) -> Result<Self> {
49 let contents = fs::read_to_string(path).with_context(|| {
50 format!(
51 "failed to read Toasty config file at `{}` — check that the file exists \
52 at this path relative to the working directory (a common cause in Docker \
53 multi-stage builds is forgetting to copy it into the final image)",
54 path.display()
55 )
56 })?;
57 let config: Config = toml::from_str(&contents).with_context(|| {
58 format!("failed to parse Toasty config file at `{}`", path.display())
59 })?;
60 Ok(config)
61 }
62
63 pub fn load_or_default(project_root: &Path) -> Result<Self> {
66 let path = project_root.join("Toasty.toml");
67 if path.exists() {
68 Self::load_from(&path)
69 } else {
70 let config = Self::default();
71 let toml = toml::to_string_pretty(&config)?;
72 fs::write(&path, toml)?;
73 Ok(config)
74 }
75 }
76
77 pub fn migration(mut self, migration: MigrationConfig) -> Self {
79 self.migration = migration;
80 self
81 }
82}
83
84#[cfg(test)]
85mod tests {
86 use super::*;
87 use tempfile::tempdir;
88
89 #[test]
90 fn load_or_default_creates_toasty_toml_when_missing() {
91 let dir = tempdir().unwrap();
92 let path = dir.path().join("Toasty.toml");
93 assert!(!path.exists());
94
95 Config::load_or_default(dir.path()).unwrap();
96
97 assert!(path.exists(), "Toasty.toml should be created on first load");
98 let contents = fs::read_to_string(&path).unwrap();
99 let reparsed: Config = toml::from_str(&contents).unwrap();
100 let default = Config::default();
101
102 assert_eq!(reparsed, default);
103 }
104
105 #[test]
106 fn load_from_missing_file_reports_path() {
107 let dir = tempdir().unwrap();
108 let path = dir.path().join("Toasty.toml");
109
110 let err = Config::load_from(&path).unwrap_err();
111
112 let message = format!("{err:#}");
113 assert!(
114 message.contains(&path.display().to_string()),
115 "error message should mention the missing path: {message}"
116 );
117 }
118}