Skip to main content

heddle_cli_render/cli/render/
query.rs

1// SPDX-License-Identifier: Apache-2.0
2use 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}