Skip to main content

derive_defs/
parser.rs

1//! TOML configuration parser.
2//!
3//! This module handles parsing of the `derive_defs.toml` configuration file.
4//!
5//! # Configuration Format
6//!
7//! The configuration file uses TOML format with the following structure:
8//!
9//! ```toml
10//! # Optional: Include definitions from other files
11//! [includes]
12//! common = "shared/common_defs.toml"
13//!
14//! # Define derive presets
15//! [defs.serialization]
16//! traits = ["Clone", "Serialize", "Deserialize"]
17//! attrs = ['#[serde(rename_all = "camelCase")]']
18//!
19//! [defs.model]
20//! extends = "serialization"  # Inherit from another def
21//! traits = ["Debug", "PartialEq"]
22//! ```
23
24use serde::Deserialize;
25use std::collections::HashMap;
26use std::path::Path;
27
28use crate::{Error, Result};
29
30/// Root configuration structure.
31#[derive(Debug, Clone, Deserialize)]
32pub struct Config {
33    /// Include files for cross-file definitions.
34    #[serde(default)]
35    pub includes: HashMap<String, String>,
36
37    /// Definition bundles.
38    #[serde(default)]
39    pub defs: HashMap<String, Def>,
40}
41
42/// A single definition bundle.
43#[derive(Debug, Clone, Deserialize)]
44pub struct Def {
45    /// Parent definition to extend.
46    #[serde(default)]
47    pub extends: Option<String>,
48
49    /// List of derive traits.
50    #[serde(default)]
51    pub traits: Vec<String>,
52
53    /// Additional attributes.
54    #[serde(default)]
55    pub attrs: Vec<String>,
56}
57
58/// Parse a TOML configuration file.
59///
60/// # Errors
61///
62/// Returns an error if the file cannot be read or the TOML is invalid.
63///
64/// # Example
65///
66/// ```no_run
67/// use derive_defs::parser;
68///
69/// let config = parser::parse_file("derive_defs.toml").unwrap();
70/// ```
71pub fn parse_file<P: AsRef<Path>>(path: P) -> Result<Config> {
72    let content = std::fs::read_to_string(path.as_ref()).map_err(Error::ConfigRead)?;
73    parse(&content)
74}
75
76/// Parse TOML content from a string.
77///
78/// # Errors
79///
80/// Returns an error if the TOML is invalid.
81///
82/// # Example
83///
84/// ```
85/// use derive_defs::parser;
86///
87/// let toml = r#"
88/// [defs.model]
89/// traits = ["Debug", "Clone"]
90/// "#;
91///
92/// let config = parser::parse(toml).unwrap();
93/// assert!(config.defs.contains_key("model"));
94/// ```
95pub fn parse(content: &str) -> Result<Config> {
96    toml::from_str(content).map_err(Error::TomlParse)
97}
98
99#[cfg(test)]
100mod tests {
101    use super::*;
102
103    #[test]
104    fn test_parse_basic() {
105        let toml = r#"
106[defs.serialization]
107traits = ["Clone", "Serialize", "Deserialize"]
108attrs = ['#[serde(rename_all = "camelCase")]']
109
110[defs.model]
111traits = ["Debug", "Clone", "PartialEq"]
112"#;
113
114        let config = parse(toml).unwrap();
115        assert_eq!(config.defs.len(), 2);
116        assert!(config.defs.contains_key("serialization"));
117        assert!(config.defs.contains_key("model"));
118
119        let serialization = &config.defs["serialization"];
120        assert_eq!(serialization.traits.len(), 3);
121        assert_eq!(serialization.attrs.len(), 1);
122        assert!(serialization.extends.is_none());
123
124        let model = &config.defs["model"];
125        assert_eq!(model.traits.len(), 3);
126        assert!(model.attrs.is_empty());
127    }
128
129    #[test]
130    fn test_parse_with_extends() {
131        let toml = r#"
132[defs.base]
133traits = ["Debug", "Clone"]
134
135[defs.extended]
136extends = "base"
137traits = ["PartialEq"]
138"#;
139
140        let config = parse(toml).unwrap();
141        assert_eq!(config.defs.len(), 2);
142
143        let extended = &config.defs["extended"];
144        assert_eq!(extended.extends.as_deref(), Some("base"));
145        assert_eq!(extended.traits.len(), 1);
146    }
147
148    #[test]
149    fn test_parse_with_includes() {
150        let toml = r#"
151[includes]
152common = "shared/common.toml"
153
154[defs.test]
155traits = ["Debug"]
156"#;
157
158        let config = parse(toml).unwrap();
159        assert_eq!(config.includes.len(), 1);
160        assert_eq!(
161            config.includes.get("common"),
162            Some(&"shared/common.toml".to_string())
163        );
164    }
165
166    #[test]
167    fn test_parse_empty() {
168        let config = parse("").unwrap();
169        assert!(config.defs.is_empty());
170        assert!(config.includes.is_empty());
171    }
172
173    #[test]
174    fn test_parse_invalid_toml() {
175        let result = parse("invalid toml [[");
176        assert!(result.is_err());
177    }
178}