Skip to main content

features_cli/
features_toml_parser.rs

1//! Module for parsing FEATURES.toml files
2//!
3//! This module provides functionality to read and parse FEATURES.toml files
4//! that contain feature metadata (name, owner, description, and custom meta fields).
5
6use 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
21/// Reads and parses a FEATURES.toml file
22pub 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
32/// Finds a FEATURES.toml file in a directory
33pub 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}