|
| 1 | +//! Structured diagnostics gathered during planning and optionally exposed to operators. |
| 2 | +//! |
| 3 | +//! Planner anomalies (unparsable XML, empty metadata, orphan child laws) flow into |
| 4 | +//! [`Diagnostics`], which feeds both the `--validate` JSON report and the post-build |
| 5 | +//! `--manifest` payload. |
| 6 | +
|
| 7 | +use std::path::Path; |
| 8 | + |
| 9 | +use anyhow::{Context, Result}; |
| 10 | +use serde::Serialize; |
| 11 | + |
| 12 | +use rustc_hash::FxHashMap as HashMap; |
| 13 | + |
| 14 | +/// Record for one cached XML file that failed the planning pass. |
| 15 | +#[derive(Serialize, Debug, Clone)] |
| 16 | +pub struct UnparsableRecord { |
| 17 | + /// Source file name (basename) of the offending XML document. |
| 18 | + pub file: String, |
| 19 | + /// Human-readable parser error message. |
| 20 | + pub error: String, |
| 21 | +} |
| 22 | + |
| 23 | +/// Record for one planned child entry whose parent 법률 is missing from the cache. |
| 24 | +#[derive(Serialize, Debug, Clone)] |
| 25 | +pub struct OrphanRecord { |
| 26 | + /// Original law name reported by the child entry. |
| 27 | + pub law_name: String, |
| 28 | + /// Law-type label of the orphan child (e.g. 대통령령). |
| 29 | + pub law_type: String, |
| 30 | + /// Normalized parent group name that was expected but not found. |
| 31 | + pub parent_group: String, |
| 32 | +} |
| 33 | + |
| 34 | +/// Accumulated planning diagnostics shared by validate and build paths. |
| 35 | +#[derive(Serialize, Debug, Default)] |
| 36 | +pub struct Diagnostics { |
| 37 | + /// Cache files whose XML failed to parse. |
| 38 | + pub unparsable: Vec<UnparsableRecord>, |
| 39 | + /// Cache file names whose basic metadata block was empty. |
| 40 | + pub empty_metadata: Vec<String>, |
| 41 | + /// Planned child entries whose parent 법률 could not be located. |
| 42 | + pub orphan_children: Vec<OrphanRecord>, |
| 43 | + /// Count of planned entries keyed by law_type. |
| 44 | + pub by_type: HashMap<String, usize>, |
| 45 | + /// Total XML files observed in the detail cache directory. |
| 46 | + pub total_xml: usize, |
| 47 | +} |
| 48 | + |
| 49 | +impl Diagnostics { |
| 50 | + /// Returns true when no anomalies were collected. |
| 51 | + pub fn is_clean(&self) -> bool { |
| 52 | + self.unparsable.is_empty() |
| 53 | + && self.empty_metadata.is_empty() |
| 54 | + && self.orphan_children.is_empty() |
| 55 | + } |
| 56 | +} |
| 57 | + |
| 58 | +/// Pre-flight validation report emitted via stdout when `--validate` is set. |
| 59 | +#[derive(Serialize, Debug)] |
| 60 | +pub struct ValidationReport<'a> { |
| 61 | + /// Total XML files observed in the detail cache directory. |
| 62 | + pub total_xml: usize, |
| 63 | + /// Cache files whose XML failed to parse. |
| 64 | + pub unparsable: &'a [UnparsableRecord], |
| 65 | + /// Cache file names whose basic metadata block was empty. |
| 66 | + pub empty_metadata: &'a [String], |
| 67 | + /// Planned child entries whose parent 법률 could not be located. |
| 68 | + pub orphan_children: &'a [OrphanRecord], |
| 69 | + /// Planned entry counts keyed by law_type. |
| 70 | + pub by_type: &'a HashMap<String, usize>, |
| 71 | + /// Operator-supplied expected law total (via `--expect-laws`), if any. |
| 72 | + pub expected_laws: Option<usize>, |
| 73 | + /// Actual planned entry count after pass 1. |
| 74 | + pub actual_laws: usize, |
| 75 | +} |
| 76 | + |
| 77 | +/// Build manifest JSON written to disk after a successful build. |
| 78 | +#[derive(Serialize, Debug)] |
| 79 | +pub struct BuildManifest<'a> { |
| 80 | + /// Manifest schema version. |
| 81 | + pub schema_version: u32, |
| 82 | + /// HEAD commit SHA of the finished bare repository (lowercase hex, 40 chars). |
| 83 | + pub head_commit_sha: String, |
| 84 | + /// Total number of planned entries committed into the output repository. |
| 85 | + pub entries_total: usize, |
| 86 | + /// Cache files whose XML failed to parse. |
| 87 | + pub unparsable: &'a [UnparsableRecord], |
| 88 | + /// Cache file names whose basic metadata block was empty. |
| 89 | + pub empty_metadata: &'a [String], |
| 90 | + /// Planned child entries whose parent 법률 could not be located. |
| 91 | + pub orphan_children: &'a [OrphanRecord], |
| 92 | + /// Planned entry counts keyed by law_type. |
| 93 | + pub by_type: &'a HashMap<String, usize>, |
| 94 | +} |
| 95 | + |
| 96 | +/// Writes a pretty-printed manifest JSON payload to the given path. |
| 97 | +pub fn write_manifest(path: &Path, manifest: &BuildManifest) -> Result<()> { |
| 98 | + let json = serde_json::to_string_pretty(manifest) |
| 99 | + .context("failed to serialize build manifest as JSON")?; |
| 100 | + std::fs::write(path, json) |
| 101 | + .with_context(|| format!("failed to write manifest to {}", path.display()))?; |
| 102 | + Ok(()) |
| 103 | +} |
0 commit comments