features_cli/
features_toml_parser.rs1use anyhow::{Context, Result};
7use serde::Deserialize;
8use std::collections::HashMap;
9use std::fs;
10use std::path::Path;
11
12#[derive(Debug, Deserialize, Clone)]
13pub struct FeaturesToml {
14 pub name: Option<String>,
15 pub owner: Option<String>,
16 pub description: Option<String>,
17 #[serde(flatten)]
18 pub meta: HashMap<String, serde_json::Value>,
19}
20
21pub fn read_features_toml(path: &Path) -> Result<FeaturesToml> {
23 let content = fs::read_to_string(path)
24 .with_context(|| format!("could not read FEATURES.toml at `{}`", path.display()))?;
25
26 let parsed: FeaturesToml = toml::from_str(&content)
27 .with_context(|| format!("could not parse FEATURES.toml at `{}`", path.display()))?;
28
29 Ok(parsed)
30}
31
32pub fn find_features_toml(dir_path: &Path) -> Option<std::path::PathBuf> {
34 let features_toml_path = dir_path.join("FEATURES.toml");
35 if features_toml_path.exists() {
36 Some(features_toml_path)
37 } else {
38 None
39 }
40}
41
42#[cfg(test)]
43mod tests {
44 use super::*;
45 use std::io::Write;
46 use tempfile::TempDir;
47
48 #[test]
49 fn test_parse_basic_features_toml() {
50 let temp_dir = TempDir::new().unwrap();
51 let toml_path = temp_dir.path().join("FEATURES.toml");
52 let mut file = fs::File::create(&toml_path).unwrap();
53 writeln!(
54 file,
55 r#"
56name = "Test Feature"
57owner = "test-team"
58description = "A test feature"
59"#
60 )
61 .unwrap();
62
63 let result = read_features_toml(&toml_path).unwrap();
64 assert_eq!(result.name, Some("Test Feature".to_string()));
65 assert_eq!(result.owner, Some("test-team".to_string()));
66 assert_eq!(result.description, Some("A test feature".to_string()));
67 }
68
69 #[test]
70 fn test_parse_features_toml_with_meta() {
71 let temp_dir = TempDir::new().unwrap();
72 let toml_path = temp_dir.path().join("FEATURES.toml");
73 let mut file = fs::File::create(&toml_path).unwrap();
74 writeln!(
75 file,
76 r#"
77name = "Feature with Meta"
78owner = "team-a"
79description = "Feature with extra metadata"
80status = "active"
81priority = "high"
82"#
83 )
84 .unwrap();
85
86 let result = read_features_toml(&toml_path).unwrap();
87 assert_eq!(result.name, Some("Feature with Meta".to_string()));
88 assert_eq!(result.owner, Some("team-a".to_string()));
89 assert!(result.meta.contains_key("status"));
90 assert!(result.meta.contains_key("priority"));
91 }
92
93 #[test]
94 fn test_find_features_toml() {
95 let temp_dir = TempDir::new().unwrap();
96 let toml_path = temp_dir.path().join("FEATURES.toml");
97 fs::File::create(&toml_path).unwrap();
98
99 let found = find_features_toml(temp_dir.path());
100 assert!(found.is_some());
101 assert_eq!(found.unwrap(), toml_path);
102 }
103
104 #[test]
105 fn test_find_features_toml_not_found() {
106 let temp_dir = TempDir::new().unwrap();
107 let found = find_features_toml(temp_dir.path());
108 assert!(found.is_none());
109 }
110}