capability_example/
partially_grown_model.rs

1// ---------------- [ File: capability-example/src/partially_grown_model.rs ]
2crate::ix!();
3
4#[derive(SaveLoad,Builder,Getters,Setters,Clone,Debug,Serialize,Deserialize)]
5#[builder(setter(into))]
6#[getset(get = "pub", set = "pub")]
7#[serde(deny_unknown_fields)]  // <--- Force parse error on unknown fields
8pub struct PartiallyGrownModel {
9
10    #[builder(default)]
11    grower_inputs:                                       Option<GrowerInputs>,
12
13    #[builder(default)]
14    maybe_ungrown_justified_grower_tree_configuration:   Option<JustifiedGrowerTreeConfiguration>,
15
16    #[builder(default)]
17    maybe_ungrown_justified_string_skeleton:             Option<JustifiedStringSkeleton>,
18
19    #[builder(default)]
20    maybe_ungrown_stripped_string_skeleton:              Option<StrippedStringSkeleton>,
21
22    #[builder(default)]
23    maybe_ungrown_core_string_skeleton:                  Option<CoreStringSkeleton>,
24
25    #[builder(default)]
26    maybe_ungrown_annotated_leaf_holder_expansions:      Option<AnnotatedLeafHolderExpansions>,
27}
28
29impl From<GrowerInputs> for PartiallyGrownModel {
30
31    fn from(grower_inputs: GrowerInputs) -> Self {
32        Self {
33            grower_inputs:                                       Some(grower_inputs),
34            maybe_ungrown_justified_grower_tree_configuration:   None,
35            maybe_ungrown_justified_string_skeleton:             None,
36            maybe_ungrown_stripped_string_skeleton:              None,
37            maybe_ungrown_core_string_skeleton:                  None,
38            maybe_ungrown_annotated_leaf_holder_expansions:      None,
39        }
40    }
41}
42
43impl PartiallyGrownModel {
44
45    pub fn empty() -> Self {
46        Self {
47            grower_inputs:                                       None,
48            maybe_ungrown_justified_grower_tree_configuration:   None,
49            maybe_ungrown_justified_string_skeleton:             None,
50            maybe_ungrown_stripped_string_skeleton:              None,
51            maybe_ungrown_core_string_skeleton:                  None,
52            maybe_ungrown_annotated_leaf_holder_expansions:      None,
53        }
54    }
55
56    pub fn essentially_empty(&self) -> bool {
57        self.grower_inputs.is_some() &&
58            self.maybe_ungrown_justified_grower_tree_configuration.is_none() &&
59            self.maybe_ungrown_justified_string_skeleton.is_none() &&
60            self.maybe_ungrown_stripped_string_skeleton.is_none() &&
61            self.maybe_ungrown_core_string_skeleton.is_none() &&
62            self.maybe_ungrown_annotated_leaf_holder_expansions.is_none()
63    }
64
65    pub fn validate(&self) -> Result<(), GrowerModelGenerationInvalidPartial> {
66
67        trace!("Starting validate for PartiallyGrownModel: {:?}", self);
68
69        // Ensure no downstream component is present when an upstream step is missing
70        if self.maybe_ungrown_justified_grower_tree_configuration.is_none() {
71
72            if self.maybe_ungrown_justified_string_skeleton.is_some() {
73                error!("JustifiedStringSkeleton present without JustifiedGrowerTreeConfiguration");
74            }
75
76            if self.maybe_ungrown_stripped_string_skeleton.is_some() {
77                error!("StrippedStringSkeleton present without JustifiedGrowerTreeConfiguration");
78            }
79
80            if self.maybe_ungrown_core_string_skeleton.is_some() {
81                error!("CoreStringSkeleton present without JustifiedGrowerTreeConfiguration");
82            }
83            if self.maybe_ungrown_annotated_leaf_holder_expansions.is_some() {
84                error!("AnnotatedLeafHolderExpansions present without JustifiedGrowerTreeConfiguration");
85            }
86
87            return Err(GrowerModelGenerationInvalidPartial::MissingJustifiedGrowerTreeConfiguration);
88        }
89
90        if self.maybe_ungrown_justified_string_skeleton.is_none() {
91
92            if self.maybe_ungrown_stripped_string_skeleton.is_some() {
93                error!("StrippedStringSkeleton present without JustifiedStringSkeleton");
94            }
95
96            if self.maybe_ungrown_core_string_skeleton.is_some() {
97                error!("CoreStringSkeleton present without JustifiedStringSkeleton");
98            }
99
100            if self.maybe_ungrown_annotated_leaf_holder_expansions.is_some() {
101                error!("AnnotatedLeafHolderExpansions present without JustifiedStringSkeleton");
102            }
103
104            return Err(GrowerModelGenerationInvalidPartial::MissingJustifiedStringSkeleton);
105        }
106
107        if self.maybe_ungrown_stripped_string_skeleton.is_none() {
108
109            if self.maybe_ungrown_core_string_skeleton.is_some() {
110                error!("CoreStringSkeleton present without StrippedStringSkeleton");
111            }
112
113            if self.maybe_ungrown_annotated_leaf_holder_expansions.is_some() {
114                error!("AnnotatedLeafHolderExpansions present without StrippedStringSkeleton");
115            }
116
117            return Err(GrowerModelGenerationInvalidPartial::MissingStrippedStringSkeleton);
118        }
119
120        if self.maybe_ungrown_core_string_skeleton.is_none() {
121
122            if self.maybe_ungrown_annotated_leaf_holder_expansions.is_some() {
123                error!("AnnotatedLeafHolderExpansions present without CoreStringSkeleton");
124            }
125            return Err(GrowerModelGenerationInvalidPartial::MissingCoreStringSkeleton);
126        }
127
128        if self.maybe_ungrown_annotated_leaf_holder_expansions.is_none() {
129            return Err(GrowerModelGenerationInvalidPartial::MissingAnnotatedLeafHolderExpansions);
130        }
131
132        info!("PartiallyGrownModel validation passed");
133        Ok(())
134    }
135}
136
137impl PartiallyGrownModel {
138    #[tracing::instrument(level = "trace", skip_all)]
139    pub async fn load_from_file_fuzzy<P: AsRef<std::path::Path> + Send + Sync>(
140        path: P,
141    ) -> Result<Self, FuzzyLoadPartiallyGrownModelError> {
142        use std::fs;
143
144        let raw_contents = fs::read_to_string(&path)?;
145        debug!(
146            "Read {} bytes from '{:?}' => attempting fuzzy parse of PartiallyGrownModel.",
147            raw_contents.len(),
148            path.as_ref()
149        );
150
151        // Step 1) Convert entire file to a serde_json::Value
152        let mut root: serde_json::Value = serde_json::from_str(&raw_contents)?;
153
154        // Flatten "fields" throughout the entire JSON
155        recursively_flatten_fields(&mut root);
156
157        // Step 2) We'll try to parse the entire object as a standard PartiallyGrownModel
158        match try_deserialize_with_path::<PartiallyGrownModel>(&root) {
159            Ok(mut pg) => {
160                trace!("Direct parse of PartiallyGrownModel succeeded => returning without further fuzz.");
161                let did_fill = pg.try_filling_next_none_field_from_clipboard();
162                if did_fill {
163                    pg.save_to_file(&path).await?;
164                }
165                return Ok(pg);
166            }
167            Err(e) => {
168                warn!("Direct parse of PartiallyGrownModel failed => will attempt subfield fuzzy logic. Error string: {}", e);
169            }
170        }
171
172        // Step 3) We'll treat `root` as an object, manually parse each subfield fuzzily or precisely.
173        let mut root_obj = match root.as_object_mut() {
174            Some(obj) => obj,
175            None => {
176                return Err(FuzzyLoadPartiallyGrownModelError::RootOfJSONIsNotAnObjectForPartiallyGrownModel);
177            }
178        };
179
180
181        // --- (A) parse "grower_inputs" precisely from JSON or from clipboard (no fuzzy needed) ---
182        let grower_inputs_val = root_obj
183            .remove("grower_inputs")
184            .unwrap_or(serde_json::Value::Null);
185
186        let grower_inputs: GrowerInputs = if grower_inputs_val.is_null() {
187            // Attempt parse from clipboard as precise JSON
188            match (|| {
189                let mut ctx = ClipboardContext::new().map_err(|e| {
190                    std::io::Error::new(
191                        std::io::ErrorKind::Other,
192                        format!("Clipboard context creation error: {e}"),
193                    )
194                })?;
195
196                let contents = ctx.get_contents().map_err(|e| {
197                    std::io::Error::new(
198                        std::io::ErrorKind::Other,
199                        format!("Clipboard get_contents error: {e}"),
200                    )
201                })?;
202
203                debug!("Clipboard contents retrieved: {}", contents);
204
205                let json_val: serde_json::Value = serde_json::from_str(&contents).map_err(|e| {
206                    std::io::Error::new(
207                        std::io::ErrorKind::InvalidData,
208                        format!("Clipboard JSON parsing error: {e}"),
209                    )
210                })?;
211
212                debug!("Parsed JSON value from clipboard for GrowerInputs: {:?}", json_val);
213
214                // Use the standard path-aware approach to parse GrowerInputs exactly
215                let gi = try_deserialize_with_path::<GrowerInputs>(&json_val).map_err(|e| {
216                    std::io::Error::new(
217                        std::io::ErrorKind::InvalidData,
218                        format!("Clipboard to GrowerInputs parse error: {e}"),
219                    )
220                })?;
221
222                // Explicit result type for E0282 fix:
223                Ok::<GrowerInputs, std::io::Error>(gi)
224            })() {
225                Ok(v) => {
226                    trace!("Clipboard parse for 'grower_inputs' succeeded => using that value.");
227                    v
228                }
229                Err(e) => {
230                    error!("No JSON for 'grower_inputs' in file and clipboard parse failed => cannot continue. Error: {e}");
231                    return Err(FuzzyLoadPartiallyGrownModelError::NoJsonForGrowerInputsAndClipboardParseFailed);
232                }
233            }
234        } else {
235            trace!("Attempting standard parse from JSON");
236            match try_deserialize_with_path(&grower_inputs_val) {
237                Ok(g) => g,
238                Err(e) => {
239                    error!("Could not parse 'grower_inputs': {e}");
240                    return Err(FuzzyLoadPartiallyGrownModelError::CouldNotParseGrowerInputs);
241                }
242            }
243        };
244
245        // --- (B) parse "maybe_ungrown_justified_grower_tree_configuration" fuzzily, fallback clipboard (OPTIONAL) ---
246        let mgc_val = root_obj
247            .remove("maybe_ungrown_justified_grower_tree_configuration")
248            .unwrap_or(serde_json::Value::Null);
249
250        let maybe_ungrown_justified_grower_tree_configuration = if mgc_val.is_null() {
251            match fuzzy_parse_clipboard_contents::<JustifiedGrowerTreeConfiguration>(false) {
252                Ok(obj) => {
253                    trace!("Clipboard parse of JustifiedGrowerTreeConfiguration succeeded => using that value.");
254                    Some(obj)
255                }
256                Err(e) => {
257                    warn!("Clipboard parse of JustifiedGrowerTreeConfiguration failed => returning None. Error: {:?}", e);
258                    None
259                }
260            }
261        } else {
262            match JustifiedGrowerTreeConfiguration::fuzzy_from_json_value(&mgc_val) {
263                Ok(obj) => Some(obj),
264                Err(e) => {
265                    warn!("Fuzzy parse of JustifiedGrowerTreeConfiguration failed => returning None. Error: {:?}", e);
266                    None
267                }
268            }
269        };
270
271        // --- (C) parse "maybe_ungrown_justified_string_skeleton" fuzzily, fallback clipboard (OPTIONAL) ---
272        let msk_val = root_obj
273            .remove("maybe_ungrown_justified_string_skeleton")
274            .unwrap_or(serde_json::Value::Null);
275
276        let maybe_ungrown_justified_string_skeleton = if msk_val.is_null() {
277            match fuzzy_parse_clipboard_contents::<JustifiedStringSkeleton>(false) {
278                Ok(obj) => {
279                    trace!("Clipboard parse of JustifiedStringSkeleton succeeded => using that value.");
280                    Some(obj)
281                }
282                Err(e) => {
283                    warn!("Clipboard parse of JustifiedStringSkeleton failed => returning None. Error: {:?}", e);
284                    None
285                }
286            }
287        } else {
288            match JustifiedStringSkeleton::fuzzy_from_json_value(&msk_val) {
289                Ok(obj) => Some(obj),
290                Err(e) => {
291                    warn!("Fuzzy parse of JustifiedStringSkeleton failed => returning None. Error: {:?}", e);
292                    None
293                }
294            }
295        };
296
297        // --- (D) parse "maybe_ungrown_stripped_string_skeleton" normally, fallback clipboard (OPTIONAL) ---
298        let stripped_val = root_obj
299            .remove("maybe_ungrown_stripped_string_skeleton")
300            .unwrap_or(serde_json::Value::Null);
301
302        let maybe_ungrown_stripped_string_skeleton = if stripped_val.is_null() {
303            match (|| {
304                let mut ctx = ClipboardContext::new().map_err(|e| {
305                    std::io::Error::new(
306                        std::io::ErrorKind::Other,
307                        format!("Clipboard context creation error: {e}"),
308                    )
309                })?;
310                let contents = ctx.get_contents().map_err(|e| {
311                    std::io::Error::new(
312                        std::io::ErrorKind::Other,
313                        format!("Clipboard get_contents error: {e}"),
314                    )
315                })?;
316                let json_val: serde_json::Value = serde_json::from_str(&contents).map_err(|e| {
317                    std::io::Error::new(
318                        std::io::ErrorKind::InvalidData,
319                        format!("Clipboard JSON parsing error: {e}"),
320                    )
321                })?;
322                try_deserialize_with_path::<StrippedStringSkeleton>(&json_val).map_err(|e| {
323                    std::io::Error::new(
324                        std::io::ErrorKind::InvalidData,
325                        format!("Clipboard parse error for StrippedStringSkeleton: {e}"),
326                    )
327                })
328            })() {
329                Ok(s) => {
330                    trace!("Clipboard parse of StrippedStringSkeleton succeeded => using that value.");
331                    Some(s)
332                }
333                Err(e) => {
334                    warn!("Clipboard parse of StrippedStringSkeleton failed => returning None. Error: {:?}", e);
335                    None
336                }
337            }
338        } else {
339            match try_deserialize_with_path(&stripped_val) {
340                Ok(s) => Some(s),
341                Err(e) => {
342                    warn!("StrippedStringSkeleton parse error => returning None: {}", e);
343                    None
344                }
345            }
346        };
347
348        // --- (E) parse "maybe_ungrown_core_string_skeleton" fuzzily, fallback clipboard (OPTIONAL) ---
349        let core_val = root_obj
350            .remove("maybe_ungrown_core_string_skeleton")
351            .unwrap_or(serde_json::Value::Null);
352
353        let maybe_ungrown_core_string_skeleton = if core_val.is_null() {
354            match fuzzy_parse_clipboard_contents::<CoreStringSkeleton>(false) {
355                Ok(cs) => {
356                    trace!("Clipboard parse of CoreStringSkeleton succeeded => using that value.");
357                    Some(cs)
358                }
359                Err(e) => {
360                    warn!("Clipboard parse of CoreStringSkeleton failed => returning None. Error: {:?}", e);
361                    None
362                }
363            }
364        } else {
365            match CoreStringSkeleton::fuzzy_from_json_value(&core_val) {
366                Ok(cs) => Some(cs),
367                Err(e) => {
368                    warn!("Fuzzy parse of CoreStringSkeleton failed => returning None. Error: {:?}", e);
369                    None
370                }
371            }
372        };
373
374        // --- (F) parse "maybe_ungrown_annotated_leaf_holder_expansions" fuzzily, fallback clipboard (OPTIONAL) ---
375        let ann_val = root_obj
376            .remove("maybe_ungrown_annotated_leaf_holder_expansions")
377            .unwrap_or(serde_json::Value::Null);
378
379        let maybe_ungrown_annotated_leaf_holder_expansions = if ann_val.is_null() {
380            match fuzzy_parse_clipboard_contents::<AnnotatedLeafHolderExpansions>(false) {
381                Ok(ann) => {
382                    trace!("Clipboard parse of AnnotatedLeafHolderExpansions succeeded => using that value.");
383                    Some(ann)
384                }
385                Err(e) => {
386                    warn!("Clipboard parse of AnnotatedLeafHolderExpansions failed => returning None. Error: {:?}", e);
387                    None
388                }
389            }
390        } else {
391            match AnnotatedLeafHolderExpansions::fuzzy_from_json_value(&ann_val) {
392                Ok(ann) => Some(ann),
393                Err(e) => {
394                    warn!("Fuzzy parse of AnnotatedLeafHolderExpansions failed => returning None. Error: {:?}", e);
395                    None
396                }
397            }
398        };
399
400        // --- (G) Build the partial model ---
401        let partial = PartiallyGrownModel {
402            grower_inputs: Some(grower_inputs),
403            maybe_ungrown_justified_grower_tree_configuration,
404            maybe_ungrown_justified_string_skeleton,
405            maybe_ungrown_stripped_string_skeleton,
406            maybe_ungrown_core_string_skeleton,
407            maybe_ungrown_annotated_leaf_holder_expansions,
408        };
409
410        Ok(partial)
411    }
412}
413
414/// Attempt to deserialize `val` into `T`, returning an error string on failure
415/// that includes the JSON path (e.g. `.capstone.probability`).
416pub fn try_deserialize_with_path<T: DeserializeOwned>(val: &serde_json::Value)
417    -> Result<T, serde_json::Error>
418{
419    match from_value_pathaware::<T>(&val) {
420        Ok(parsed) => Ok(parsed),
421        Err(path_err) => {
422            eprintln!(
423                "Deserialization failed at path {path_err}",
424            );
425            Err(path_err)
426        }
427    }
428}