capability_generation_inputs/
grower_inputs.rs

1// ---------------- [ File: capability-generation-inputs/src/grower_inputs.rs ]
2crate::ix!();
3
4error_tree!{
5    pub enum GrowerInputError {
6        EmptyEnvironmentDescription,
7        EmptyCrateName,
8        TomlSerError(toml::ser::Error),
9        TomlDeError(toml::de::Error),
10        Io(std::io::Error),
11    }
12}
13
14/// Represents the primary user- or AI-supplied environment
15/// description and other essential context. These inputs are
16/// typically consumed by later crates to shape tree parameters,
17/// regenerate models, etc.
18///
19/// We rely exclusively on `derive_builder` for constructing instances,
20/// rather than providing a separate `new` method.
21#[derive(
22    Debug,
23    Clone,
24    PartialEq,
25    Eq,
26    Hash,
27    Getters,
28    Builder,
29    Default,
30    Serialize,
31    Deserialize,
32    StructOpt
33)]
34#[builder(pattern = "owned", setter(into))]
35#[getset(get = "pub")]
36#[structopt(name = "grower-inputs", about = "Specifies the main domain and environment context")]
37pub struct GrowerInputs {
38
39    /// A textual description of the overall environment.
40    #[builder(default)]
41    #[structopt(long, help = "Global environment descriptor (required, non-empty)")]
42    global_environment_descriptor: Option<String>,
43
44    /// A plain string name for the crate that we will ultimately generate.
45    #[structopt(
46        long,
47        required_unless("toml-inputs-file"),
48        help = "Target domain name (required unless --toml-inputs-file is provided)"
49    )]
50    target: String,
51
52    /// A list of relevant neighbor crate names or contexts.
53    #[structopt(long, help = "Repeat for each neighbor context", use_delimiter = false)]
54    neighbors: Vec<String>,
55
56    /// A list of sub-environments relevant to this crate.
57    #[structopt(long, help = "Repeat for each sub-environment", use_delimiter = false)]
58    sub_environments: Vec<String>,
59}
60
61impl GrowerInputs {
62
63    pub fn global_environment_display(&self) -> String {
64        self.global_environment_descriptor.clone().unwrap_or(YOU_ARE_HERE.to_string())
65    }
66
67    pub fn sub_environment_display(&self) -> String {
68        format!("{:#?}",self.sub_environments)
69    }
70
71    pub fn neighbor_display(&self) -> String {
72        format!("{:#?}",self.neighbors)
73    }
74
75    pub fn output_file_name(&self) -> String {
76        format!("{}.json", self.target)
77    }
78
79    /// Validates essential fields. Returns an Err if any major constraint is violated.
80    #[instrument(level = "trace", skip(self))]
81    pub fn validate(&self) -> Result<(), GrowerInputError> {
82
83        trace!("Validating GrowerInputs structure");
84
85        if let Some(env) = self.global_environment_descriptor() {
86
87            if env.trim().is_empty() {
88                error!("Environment description is empty");
89                return Err(GrowerInputError::EmptyEnvironmentDescription);
90            }
91
92        } else {
93            error!("Environment description is empty");
94            return Err(GrowerInputError::EmptyEnvironmentDescription);
95        }
96
97        if self.target.trim().is_empty() {
98            error!("Crate name is empty");
99            return Err(GrowerInputError::EmptyCrateName);
100        }
101
102        // Additional checks can be added here.
103        info!("GrowerInputs validation succeeded");
104        Ok(())
105    }
106
107    /// Parses a `GrowerInputs` from a TOML string.
108    #[instrument(level = "trace")]
109    pub fn from_toml_str(toml_str: &str) -> Result<Self, GrowerInputError> {
110        trace!("Parsing GrowerInputs from TOML string");
111        let inputs: GrowerInputs = toml::from_str(toml_str)?;
112        info!("Parsed GrowerInputs from TOML successfully");
113        Ok(inputs)
114    }
115
116    /// Converts this `GrowerInputs` into a TOML string.
117    #[instrument(level = "trace", skip(self))]
118    pub fn to_toml_string(&self) -> Result<String, GrowerInputError> {
119        trace!("Serializing GrowerInputs to TOML string");
120        let toml_str = toml::to_string_pretty(self)?;
121        info!("Serialized GrowerInputs to TOML successfully");
122        Ok(toml_str)
123    }
124
125    /// Loads a `GrowerInputs` from a TOML file on disk.
126    pub fn from_toml_file<P: Debug + AsRef<Path>>(path: P) -> Result<Self, GrowerInputError> {
127        trace!("Reading GrowerInputs from TOML file {:?}", path.as_ref());
128        let contents = std::fs::read_to_string(path)?;
129        let parsed = Self::from_toml_str(&contents)?;
130        Ok(parsed)
131    }
132
133    /// Writes this `GrowerInputs` out to a TOML file on disk.
134    pub fn to_toml_file<P: Debug + AsRef<Path>>(&self, path: P) -> Result<(), GrowerInputError> {
135        trace!("Writing GrowerInputs to TOML file {:?}", path.as_ref());
136        let toml_str = self.to_toml_string()?;
137        std::fs::write(path, toml_str)?;
138        Ok(())
139    }
140}
141
142#[cfg(test)]
143mod tests {
144    use super::*;
145
146    #[traced_test]
147    fn test_builder_construction_and_validation() {
148        // Construct via the builder pattern
149        let input = GrowerInputsBuilder::default()
150            .global_environment_descriptor(Some("A scenic environment with mountains and rivers".into()))
151            .target("torch-and-lantern-craft")
152            .neighbors(vec!["fire-staff-spinning".into(), "forest-environment".into()])
153            .sub_environments(vec!["cave".into(), "fire".into()])
154            .build()
155            .expect("Builder must succeed");
156
157        assert_eq!(
158            *input.global_environment_descriptor(),
159            Some("A scenic environment with mountains and rivers".to_string())
160        );
161        assert_eq!(input.target(), "torch-and-lantern-craft");
162        assert_eq!(input.neighbors(), &["fire-staff-spinning", "forest-environment"]);
163        assert_eq!(input.sub_environments(), &["cave", "fire"]);
164
165        // validation should succeed
166        assert!(input.validate().is_ok());
167    }
168
169    #[traced_test]
170    fn test_builder_empty_fields_fail() {
171        // Omit global_environment_descriptor for minimal test
172        let input = GrowerInputsBuilder::default()
173            .target("test-crate")
174            .neighbors(vec![])
175            .sub_environments(vec![])
176            .build()
177            .unwrap();
178
179        let result = input.validate();
180        assert!(result.is_err(), "Validation must fail for empty fields");
181    }
182
183    #[traced_test]
184    fn test_from_toml_str_round_trip() {
185        let source_toml = r#"
186            global_environment_descriptor = "Mythic planet with scenic vistas"
187            target = "torch-and-lantern-craft"
188            neighbors = ["fire-staff-spinning", "forest-environment"]
189            sub_environments = ["cave", "fire", "swamp"]
190        "#;
191
192        let parsed = GrowerInputs::from_toml_str(source_toml).expect("Parsing from TOML must succeed");
193        parsed.validate().unwrap();
194
195        assert_eq!(*parsed.global_environment_descriptor(), Some("Mythic planet with scenic vistas".to_string()));
196        assert_eq!(parsed.target(), "torch-and-lantern-craft");
197        assert_eq!(parsed.neighbors(), &["fire-staff-spinning", "forest-environment"]);
198        assert_eq!(parsed.sub_environments(), &["cave", "fire", "swamp"]);
199
200        // Now round-trip back to TOML and parse again
201        let toml_str_out = parsed.to_toml_string().expect("Serialization must succeed");
202        let parsed_again = GrowerInputs::from_toml_str(&toml_str_out).expect("Parsing again must succeed");
203        assert_eq!(parsed_again, parsed, "Round-tripped GrowerInputs must be identical");
204    }
205
206    #[traced_test]
207    fn test_toml_file_io() {
208        let dir = tempdir().unwrap();
209        let file_path = dir.path().join("grower_inputs.toml");
210
211        // Construct a sample via the builder
212        let inputs = GrowerInputsBuilder::default()
213            .global_environment_descriptor(Some("Snowy environment, mountain peaks, evergreen forests".to_string()))
214            .target("frosty-peak-camp")
215            .neighbors(vec!["river-environment".into(), "old-town-environment".into()])
216            .sub_environments(vec!["tundra".into(), "forest".into()])
217            .build()
218            .unwrap();
219
220        // Write it out to a file
221        inputs.to_toml_file(&file_path).expect("Writing to file must succeed");
222
223        // Now read it back
224        let loaded = GrowerInputs::from_toml_file(&file_path).expect("Reading from file must succeed");
225
226        assert_eq!(
227            *loaded.global_environment_descriptor(),
228            Some("Snowy environment, mountain peaks, evergreen forests".to_string())
229        );
230        assert_eq!(loaded.target(), "frosty-peak-camp");
231        assert_eq!(loaded.neighbors(), &["river-environment", "old-town-environment"]);
232        assert_eq!(loaded.sub_environments(), &["tundra", "forest"]);
233
234        // Validate
235        assert!(loaded.validate().is_ok());
236    }
237}