homestar_runtime/runner/
response.rs

1//! Responses for display/return to the user for
2//! client requests.
3
4use crate::{
5    cli::show::{self, ApplyStyle},
6    runner::WorkflowReceiptInfo,
7    workflow::{self, IndexedResources},
8};
9use chrono::NaiveDateTime;
10use faststr::FastStr;
11use libipld::Cid;
12use serde::{Deserialize, Serialize};
13use std::{fmt, net::SocketAddr, sync::Arc};
14use tabled::{
15    builder::Builder,
16    col,
17    settings::{object::Rows, Format, Modify},
18    Table, Tabled,
19};
20
21use super::{DynamicNodeInfo, StaticNodeInfo};
22
23/// Workflow information specified for response / display upon
24/// acknowledgement of running a workflow.
25#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Tabled)]
26pub struct AckWorkflow {
27    pub(crate) cid: Cid,
28    pub(crate) name: FastStr,
29    pub(crate) num_tasks: u32,
30    pub(crate) progress_count: u32,
31    #[tabled(skip)]
32    pub(crate) resources: IndexedResources,
33    #[tabled(skip)]
34    pub(crate) replayed_receipt_info: Vec<WorkflowReceiptInfo>,
35    pub(crate) timestamp: String,
36}
37
38impl fmt::Display for AckWorkflow {
39    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
40        write!(
41            f,
42            "cid: {}, progress: {}/{}, timestamp: {}",
43            self.cid, self.progress_count, self.num_tasks, self.timestamp
44        )
45    }
46}
47
48impl AckWorkflow {
49    /// Workflow information for response / display.
50    pub(crate) fn new(
51        workflow_info: Arc<workflow::Info>,
52        replayed_receipt_info: Vec<WorkflowReceiptInfo>,
53        name: FastStr,
54        timestamp: NaiveDateTime,
55    ) -> Self {
56        Self {
57            cid: workflow_info.cid,
58            name,
59            num_tasks: workflow_info.num_tasks,
60            progress_count: workflow_info.progress_count,
61            resources: workflow_info.resources.clone(),
62            replayed_receipt_info,
63            timestamp: timestamp.format("%Y-%m-%d %H:%M:%S").to_string(),
64        }
65    }
66}
67
68impl show::ConsoleTable for AckWorkflow {
69    fn table(&self) -> show::Output {
70        show::Output::new(Table::new(vec![self]).to_string())
71    }
72
73    fn echo_table(&self) -> Result<(), std::io::Error> {
74        let table = self.table();
75
76        let mut resource_table = Table::new(
77            self.resources
78                .iter()
79                .map(|v| v.to_string())
80                .collect::<Vec<String>>(),
81        );
82
83        resource_table
84            .with(Modify::new(Rows::first()).with(Format::content(|_s| "Resources".to_string())));
85
86        let mut receipt_table_builder = Builder::default();
87        receipt_table_builder.push_record([
88            "Replayed Receipt".to_string(),
89            "Invocation Ran".to_string(),
90            "Instruction".to_string(),
91        ]);
92
93        for (cid, info) in &self.replayed_receipt_info {
94            if let Some((ran, instruction)) = info {
95                receipt_table_builder.push_record([
96                    cid.to_string(),
97                    ran.to_string(),
98                    instruction.to_string(),
99                ]);
100            }
101        }
102
103        // If there are no replayed receipts, add a placeholder row.
104        if receipt_table_builder.count_records() == 1 {
105            receipt_table_builder.push_record([
106                "<none>".to_string(),
107                "".to_string(),
108                "".to_string(),
109            ]);
110        };
111
112        let receipt_table = receipt_table_builder.build();
113
114        let tbl = col![table, resource_table, receipt_table].default_with_title("run");
115
116        tbl.echo()
117    }
118}
119
120/// Ping response for display.
121#[derive(Debug, Tabled)]
122pub(crate) struct Ping {
123    address: SocketAddr,
124    response: String,
125}
126
127impl Ping {
128    /// Create a new [Ping] response.
129    pub(crate) fn new(address: SocketAddr, response: String) -> Self {
130        Self { address, response }
131    }
132}
133
134impl show::ConsoleTable for Ping {
135    fn table(&self) -> show::Output {
136        Table::new(vec![&self]).default_with_title("ping/pong")
137    }
138
139    fn echo_table(&self) -> Result<(), std::io::Error> {
140        self.table().echo()
141    }
142}
143
144/// Node identity response for display.
145#[derive(Debug, Clone, Serialize, Deserialize, Tabled)]
146pub struct AckNodeInfo {
147    /// Static node information.
148    static_info: StaticNodeInfo,
149    /// Dynamic node information.
150    dyn_info: DynamicNodeInfo,
151}
152
153impl fmt::Display for AckNodeInfo {
154    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
155        write!(f, "")
156    }
157}
158
159impl AckNodeInfo {
160    /// Create a new [AckNodeInfo] response.
161    pub(crate) fn new(static_info: StaticNodeInfo, dyn_info: DynamicNodeInfo) -> Self {
162        Self {
163            static_info,
164            dyn_info,
165        }
166    }
167}
168
169impl show::ConsoleTable for AckNodeInfo {
170    fn table(&self) -> show::Output {
171        show::Output::new(Table::new(vec![self]).to_string())
172    }
173
174    fn echo_table(&self) -> Result<(), std::io::Error> {
175        let static_info_table = Table::new(vec![&self.static_info]);
176
177        let mut listeners_table = Table::new(
178            self.dyn_info
179                .listeners
180                .iter()
181                .map(|v| v.to_string())
182                .collect::<Vec<String>>(),
183        );
184
185        let conns = self
186            .dyn_info
187            .connections
188            .iter()
189            .map(|(k, v)| vec![k.to_string(), v.to_string()])
190            .collect::<Vec<Vec<String>>>();
191
192        let mut conns_table_builder = tabled::builder::Builder::from_iter(conns);
193
194        // If there are no connections, add a placeholder row.
195        if conns_table_builder.count_records() == 0 {
196            conns_table_builder.push_record([
197                "Connections".to_string(),
198                "".to_string(),
199                "".to_string(),
200            ]);
201            conns_table_builder.push_record(["<none>".to_string(), "".to_string(), "".to_string()]);
202        } else {
203            conns_table_builder.insert_record(
204                0,
205                ["Connections".to_string(), "".to_string(), "".to_string()],
206            );
207        }
208
209        listeners_table.with(
210            Modify::new(Rows::first()).with(Format::content(|_s| "Listen Addresses".to_string())),
211        );
212        let conns_table = conns_table_builder.build();
213
214        let tbl = col![static_info_table, listeners_table, conns_table].default_with_title("node");
215
216        tbl.echo()
217    }
218}
219
220/// Info response for display.
221#[derive(Debug, Tabled)]
222pub struct Info {
223    version: String,
224    git_sha: String,
225    timestamp: String,
226    features: String,
227}
228
229impl Default for Info {
230    fn default() -> Self {
231        Self::new()
232    }
233}
234
235impl Info {
236    /// Create a new [Info] response.
237    pub(crate) fn new() -> Self {
238        Self {
239            version: env!("CARGO_PKG_VERSION").to_string(),
240            git_sha: option_env!("VERGEN_GIT_SHA")
241                .unwrap_or("unknown")
242                .to_string(),
243            timestamp: option_env!("VERGEN_GIT_COMMIT_TIMESTAMP")
244                .unwrap_or("unknown")
245                .to_string(),
246            features: env!("VERGEN_CARGO_FEATURES").to_string(),
247        }
248    }
249}
250
251impl show::ConsoleTable for Info {
252    fn table(&self) -> show::Output {
253        Table::new(vec![&self]).default_with_title("info")
254    }
255
256    fn echo_table(&self) -> Result<(), std::io::Error> {
257        self.table().echo()
258    }
259}