capability_generation_inputs/
grower_inputs.rs1crate::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#[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 #[builder(default)]
41 #[structopt(long, help = "Global environment descriptor (required, non-empty)")]
42 global_environment_descriptor: Option<String>,
43
44 #[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 #[structopt(long, help = "Repeat for each neighbor context", use_delimiter = false)]
54 neighbors: Vec<String>,
55
56 #[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 #[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 info!("GrowerInputs validation succeeded");
104 Ok(())
105 }
106
107 #[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 #[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 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 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 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 assert!(input.validate().is_ok());
167 }
168
169 #[traced_test]
170 fn test_builder_empty_fields_fail() {
171 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 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 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 inputs.to_toml_file(&file_path).expect("Writing to file must succeed");
222
223 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 assert!(loaded.validate().is_ok());
236 }
237}