1use tutti_types::Project;
2
3mod adapter;
4mod raw;
5
6#[derive(Debug, thiserror::Error)]
8pub enum ConfigError {
9 #[cfg(feature = "toml")]
10 #[error("toml parse error: {0}")]
11 Toml(#[from] toml::de::Error),
12
13 #[error("io error: {0}")]
14 Io(#[from] std::io::Error),
15
16 #[error("validation error(s): {0}")]
17 Validation(String),
18}
19
20pub fn load_from_path(path: &std::path::Path) -> Result<Project, ConfigError> {
26 let text = std::fs::read_to_string(path)?;
27 parse_auto(&text, path)
28}
29
30pub fn parse_auto(text: &str, path: &std::path::Path) -> Result<Project, ConfigError> {
36 let ext = path.extension().and_then(|s| s.to_str()).unwrap_or("");
37 match ext {
38 #[cfg(feature = "toml")]
39 "toml" => parse_toml(text, path),
40 _ => Err(ConfigError::Validation("unknown config extension".into())),
41 }
42}
43
44#[cfg(feature = "toml")]
50pub fn parse_toml(config: &str, path: &std::path::Path) -> Result<Project, ConfigError> {
51 let raw_project = toml::from_str::<raw::RawProject>(config)?;
52 raw_project.to_project(path)
53}
54
55#[cfg(test)]
56mod tests {
57 use std::{collections::HashMap, path::PathBuf};
58
59 use tutti_types::Restart;
60
61 use super::*;
62
63 #[test]
64 fn parse_toml_ok() {
65 let txt = r#"
66 version = 100500
67
68 [services.api]
69 cmd = ["cargo","run","--bin","api"]
70 cwd = "/home/user"
71 env = { RUST_LOG = "info" }
72 deps = ["db"]
73 restart = "always"
74
75 [services.db]
76 cmd = ["postgres","-D",".pg"]
77 restart = "never"
78 "#;
79 let p = parse_toml(txt, std::path::Path::new("config.toml")).unwrap();
80 assert!(p.services.contains_key("api"));
81 assert_eq!(p.version, 100500);
82 assert_eq!(p.services["api"].cmd, vec!["cargo", "run", "--bin", "api"]);
83 assert_eq!(p.services["api"].cwd, Some(PathBuf::from("/home/user")));
84 assert_eq!(
85 p.services["api"].env,
86 Some(HashMap::from_iter(vec![(
87 "RUST_LOG".to_string(),
88 "info".to_string()
89 )]))
90 );
91 assert_eq!(p.services["api"].deps, vec!["db"]);
92 assert_eq!(p.services["api"].restart, Restart::Always);
93 assert_eq!(p.services["db"].cmd, vec!["postgres", "-D", ".pg"]);
94 assert_eq!(p.services["db"].cwd, None);
95 assert_eq!(p.services["db"].env, None);
96 assert!(p.services["db"].deps.is_empty());
97 assert_eq!(p.services["db"].restart, Restart::Never);
98 }
99
100 #[test]
101 fn parse_auto_ok() {
102 let txt = r#"
103 [services.api]
104 cmd = ["cargo","run","--bin","api"]
105
106 [services.db]
107 cmd = ["postgres","-D",".pg"]
108 "#;
109 let p = parse_auto(txt, std::path::Path::new("config.toml")).unwrap();
110 assert!(p.services.contains_key("api"));
111 assert_eq!(p.services["api"].cmd, vec!["cargo", "run", "--bin", "api"]);
112 assert_eq!(p.version, 1);
113 }
114
115 #[test]
116 fn parse_auto_unknown_format() {
117 let txt = r#"
118 UnknownFormat
119 "#;
120 let result = parse_auto(txt, std::path::Path::new("config.unknown"));
121 assert!(result.is_err());
122 }
123
124 #[test]
125 fn load_from_path_ok() {
126 let path = PathBuf::from("../../tests/assets/correct_config.toml");
127 let p = load_from_path(&path).unwrap();
128 assert!(p.services.contains_key("service"));
129 }
130
131 #[test]
132 fn load_from_path_unknown_format() {
133 let path = PathBuf::from("../../tests/assets/unknown_format.toml");
134 let result = load_from_path(&path);
135 assert!(result.is_err());
136 }
137}