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