Extract a human-readable one-liner from a provenance node's detail JSON. Returns `None` if the detail is empty or has no interesting fields.
(detail: &str)
| 719 | /// |
| 720 | /// Returns `None` if the detail is empty or has no interesting fields. |
| 721 | fn format_node_detail(detail: &str) -> Option<String> { |
| 722 | let v: serde_json::Value = serde_json::from_str(detail).ok()?; |
| 723 | let obj = v.as_object()?; |
| 724 | |
| 725 | // Command (bash/execution nodes) |
| 726 | if let Some(cmd) = obj.get("command").and_then(|v| v.as_str()) { |
| 727 | let truncated = if cmd.len() > 120 { |
| 728 | format!("{}...", &cmd[..117]) |
| 729 | } else { |
| 730 | cmd.to_string() |
| 731 | }; |
| 732 | return Some(format!("$ {}", truncated)); |
| 733 | } |
| 734 | |
| 735 | // File path (read/write/edit nodes) |
| 736 | if let Some(file) = obj |
| 737 | .get("file") |
| 738 | .or_else(|| obj.get("file_path")) |
| 739 | .or_else(|| obj.get("target")) |
| 740 | .and_then(|v| v.as_str()) |
| 741 | { |
| 742 | let op = obj.get("operation").and_then(|v| v.as_str()).unwrap_or(""); |
| 743 | if op.is_empty() { |
| 744 | return Some(file.to_string()); |
| 745 | } |
| 746 | return Some(format!("{}: {}", op, file)); |
| 747 | } |
| 748 | |
| 749 | // Output summary (fallback for nodes with only output) |
| 750 | if let Some(summary) = obj.get("output_summary").and_then(|v| v.as_str()) { |
| 751 | let truncated = if summary.len() > 120 { |
| 752 | format!("{}...", &summary[..117]) |
| 753 | } else { |
| 754 | summary.to_string() |
| 755 | }; |
| 756 | return Some(truncated); |
| 757 | } |
| 758 | |
| 759 | None |
| 760 | } |
| 761 | |
| 762 | impl Default for ChangeCmd { |
| 763 | fn default() -> Self { |
no test coverage detected