heddle_cli_render/cli/render/
query.rs1use anyhow::{Context, Result};
3use chrono::{TimeZone, Utc};
4use verbs::QueryReport;
5
6use crate::cli::render::write_stdout;
7
8pub fn query_json(report: &QueryReport) -> Result<()> {
9 let mut text = serde_json::to_string(report).context("serialize query output")?;
10 text.push('\n');
11 write_stdout(&text)
12}
13
14pub fn query_text(report: &QueryReport) -> Result<()> {
15 write_stdout(&format_query_text(report))
16}
17
18fn format_query_text(report: &QueryReport) -> String {
19 let mut text = String::new();
20 if report.hits.is_empty() {
21 text.push_str("(no matches)\n");
22 } else {
23 for hit in &report.hits {
24 let ts = Utc
25 .timestamp_opt(hit.timestamp_secs, 0)
26 .single()
27 .map(|d| d.to_rfc3339())
28 .unwrap_or_else(|| hit.timestamp_secs.to_string());
29 text.push_str(&format!(
30 "#{} {} {} <{}>",
31 hit.seq, ts, hit.verb, hit.actor_email
32 ));
33 if let Some(thread) = &hit.thread {
34 text.push_str(&format!(" thread={thread}"));
35 }
36 if let Some(state_id) = &hit.state_id {
37 text.push_str(&format!(" -> {state_id}"));
38 }
39 text.push('\n');
40 }
41 }
42 text
43}
44
45#[cfg(test)]
46mod tests {
47 use verbs::QueryHit;
48
49 use super::*;
50
51 #[test]
52 fn text_renderer_consumes_the_typed_query_report() {
53 let report = QueryReport {
54 output_kind: "query",
55 hits: vec![QueryHit {
56 seq: 7,
57 timestamp_secs: 0,
58 verb: "snapshot".to_string(),
59 actor_email: "agent@example.com".to_string(),
60 operation_id: None,
61 thread: Some("agent/facade".to_string()),
62 symbols: Vec::new(),
63 signal_kinds: Vec::new(),
64 state_id: Some("hs-123".to_string()),
65 }],
66 };
67
68 assert_eq!(
69 format_query_text(&report),
70 "#7 1970-01-01T00:00:00+00:00 snapshot <agent@example.com> thread=agent/facade -> hs-123\n"
71 );
72 }
73
74 #[test]
75 fn text_renderer_has_a_stable_empty_report() {
76 let report = QueryReport {
77 output_kind: "query",
78 hits: Vec::new(),
79 };
80 assert_eq!(format_query_text(&report), "(no matches)\n");
81 }
82}