Skip to main content

ic_query/cloud_engine/
text.rs

1//! Module: cloud_engine::text
2//!
3//! Responsibility: render compact human-facing CloudEngine reports.
4//! Does not own: report construction, JSON output, live calls, or process output.
5//! Boundary: formats cycle amounts only for text while JSON retains raw decimal fields.
6
7use super::{CloudEngineOperatorReport, CloudEnginePricesReport, CloudEngineReportContext};
8use crate::{
9    human_quantity::decimal_cycle_count_text,
10    table::{ColumnAlign, render_table},
11    text_value::{optional_text, sanitize_text, yes_no},
12};
13
14/// Render one CloudEngine Subnet-to-operator report.
15#[must_use]
16pub fn cloud_engine_operator_report_text(report: &CloudEngineOperatorReport) -> String {
17    let mut lines = context_lines(&report.context);
18    lines.extend([
19        format!("subnet_id: {}", report.subnet_id),
20        format!(
21            "operator_binding_present: {}",
22            yes_no(report.operator_binding_present)
23        ),
24        format!(
25            "operator_canister_id: {}",
26            optional_text(report.operator_canister_id.as_ref())
27        ),
28        format!(
29            "engine_owner: {}",
30            optional_text(report.engine_owner.as_ref())
31        ),
32        format!(
33            "platform_admin: {}",
34            optional_text(report.platform_admin.as_ref())
35        ),
36        format!(
37            "caffeine_enabled: {}",
38            report
39                .caffeine_enabled
40                .map_or_else(|| "-".to_string(), |enabled| yes_no(enabled).to_string())
41        ),
42        format!(
43            "claimed_domain_count: {}",
44            report
45                .claimed_domain_count
46                .map_or_else(|| "-".to_string(), |count| count.to_string())
47        ),
48    ]);
49
50    if let Some(domains) = report.claimed_domains.as_ref()
51        && !domains.is_empty()
52    {
53        lines.push(String::new());
54        lines.push("Claimed domains".to_string());
55        lines.extend(
56            domains
57                .iter()
58                .map(|domain| format!("  {}", sanitize_text(domain))),
59        );
60    }
61    lines.join("\n")
62}
63
64/// Render one bounded CloudEngine marketplace price report.
65#[must_use]
66pub fn cloud_engine_prices_report_text(report: &CloudEnginePricesReport) -> String {
67    let mut lines = context_lines(&report.context);
68    lines.extend([
69        format!("network_fee: {}", report.network_fee),
70        format!("price_count: {}", report.price_count),
71    ]);
72
73    if !report.prices.is_empty() {
74        let headers = [
75            "Key",
76            "Node type",
77            "Data center",
78            "Provider",
79            "Net/month",
80            "Gross/month",
81            "Updated (ns)",
82        ];
83        let alignments = [
84            ColumnAlign::Left,
85            ColumnAlign::Left,
86            ColumnAlign::Left,
87            ColumnAlign::Left,
88            ColumnAlign::Right,
89            ColumnAlign::Right,
90            ColumnAlign::Right,
91        ];
92        let rows = report
93            .prices
94            .iter()
95            .map(|row| {
96                [
97                    row.key.clone(),
98                    row.node_type.to_string(),
99                    row.data_center_id
100                        .as_deref()
101                        .map_or_else(|| "-".to_string(), sanitize_text),
102                    row.provider_id.clone().unwrap_or_else(|| "-".to_string()),
103                    decimal_cycle_count_text(&row.net_cycles_per_month),
104                    decimal_cycle_count_text(&row.gross_cycles_per_month),
105                    row.updated_at_unix_nanos.to_string(),
106                ]
107            })
108            .collect::<Vec<_>>();
109        lines.push(String::new());
110        lines.push("Marketplace prices".to_string());
111        lines.push(render_table(&headers, &rows, &alignments));
112    }
113    lines.join("\n")
114}
115
116fn context_lines(context: &CloudEngineReportContext) -> Vec<String> {
117    vec![
118        format!("network: {}", sanitize_text(&context.network)),
119        format!("authority: {}", sanitize_text(&context.authority)),
120        format!("engine_canister_id: {}", context.engine_canister_id),
121        format!("fetched_at: {}", sanitize_text(&context.fetched_at)),
122        format!(
123            "source_endpoint: {}",
124            sanitize_text(&context.source_endpoint)
125        ),
126        format!("fetched_by: {}", sanitize_text(&context.fetched_by)),
127        format!("certified: {}", yes_no(context.certified)),
128        format!(
129            "point_in_time_guaranteed: {}",
130            yes_no(context.point_in_time_guaranteed)
131        ),
132        format!("query_call_count: {}", context.query_call_count),
133    ]
134}