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)
| 679 | /// |
| 680 | /// Returns `None` if the detail is empty or has no interesting fields. |
| 681 | fn format_node_detail(detail: &str) -> Option<String> { |
| 682 | let v: serde_json::Value = serde_json::from_str(detail).ok()?; |
| 683 | let obj = v.as_object()?; |
| 684 | |
| 685 | // Command (bash/execution nodes) |
| 686 | if let Some(cmd) = obj.get("command").and_then(|v| v.as_str()) { |
| 687 | let truncated = if cmd.len() > 120 { |
| 688 | format!("{}...", &cmd[..117]) |
| 689 | } else { |
| 690 | cmd.to_string() |
| 691 | }; |
| 692 | return Some(format!("$ {}", truncated)); |
| 693 | } |
| 694 | |
| 695 | // File path (read/write/edit nodes) |
| 696 | if let Some(file) = obj |
| 697 | .get("file") |
| 698 | .or_else(|| obj.get("file_path")) |
| 699 | .or_else(|| obj.get("target")) |
| 700 | .and_then(|v| v.as_str()) |
| 701 | { |
| 702 | let op = obj.get("operation").and_then(|v| v.as_str()).unwrap_or(""); |
| 703 | if op.is_empty() { |
| 704 | return Some(file.to_string()); |
| 705 | } |
| 706 | return Some(format!("{}: {}", op, file)); |
| 707 | } |
| 708 | |
| 709 | // Output summary (fallback for nodes with only output) |
| 710 | if let Some(summary) = obj.get("output_summary").and_then(|v| v.as_str()) { |
| 711 | let truncated = if summary.len() > 120 { |
| 712 | format!("{}...", &summary[..117]) |
| 713 | } else { |
| 714 | summary.to_string() |
| 715 | }; |
| 716 | return Some(truncated); |
| 717 | } |
| 718 | |
| 719 | None |
| 720 | } |
| 721 | |
| 722 | impl Default for ChangeCmd { |
| 723 | fn default() -> Self { |
no test coverage detected