wasmer_cli/
types.rs

1use colored::Colorize;
2use comfy_table::Table;
3use wasmer_backend_api::types::{
4    CronJob, CronJobInvocation, CronJobInvocationResult, CronJobKind, CronJobLog, CronJobTarget,
5    DeployApp, DeployAppVersion, Deployment, DnsDomain, DnsDomainWithRecords, LogStream, Namespace,
6    SearchPackageVersion,
7};
8
9use crate::utils::render::CliRender;
10
11/// Render the full name (`namespace/name`) of a package version's package.
12fn package_full_name(pv: &SearchPackageVersion) -> String {
13    match &pv.package.namespace {
14        Some(ns) => format!("{ns}/{}", pv.package.package_name),
15        None => pv.package.package_name.clone(),
16    }
17}
18
19fn cron_job_target_summary(target: &CronJobTarget) -> String {
20    match target {
21        CronJobTarget::FetchCronJobTarget(target) => {
22            format!("{} {}", target.method, target.path)
23        }
24        CronJobTarget::ExecuteCronJobTarget(target) => {
25            let package = target.package_name.as_deref().unwrap_or("-");
26            let command = target.command.as_deref().unwrap_or("-");
27            format!("{package} {command}")
28        }
29        CronJobTarget::Unknown => "unknown".to_string(),
30    }
31}
32
33fn cron_job_kind(kind: CronJobKind) -> &'static str {
34    match kind {
35        CronJobKind::Fetch => "fetch",
36        CronJobKind::Execute => "execute",
37    }
38}
39
40fn cron_invocation_result_summary(result: &Option<CronJobInvocationResult>) -> String {
41    match result {
42        Some(CronJobInvocationResult::ExecuteCronJobInvocationResult(result)) => result
43            .exit_code
44            .map(|code| format!("exit_code={code}"))
45            .unwrap_or_else(|| "execute".to_string()),
46        Some(CronJobInvocationResult::FetchCronJobInvocationResult(result)) => result
47            .status_code
48            .map(|code| format!("status_code={code}"))
49            .unwrap_or_else(|| "fetch".to_string()),
50        Some(CronJobInvocationResult::Unknown) => "unknown".to_string(),
51        None => "-".to_string(),
52    }
53}
54
55impl CliRender for CronJob {
56    fn render_item_table(&self) -> String {
57        let mut table = Table::new();
58        table.add_rows([
59            vec!["Name".to_string(), self.name.clone()],
60            vec!["Id".to_string(), self.id.inner().to_string()],
61            vec!["Kind".to_string(), cron_job_kind(self.kind).to_string()],
62            vec!["Schedule".to_string(), self.schedule.clone()],
63            vec!["Enabled".to_string(), self.enabled.to_string()],
64            vec!["Managed".to_string(), self.is_managed.to_string()],
65            vec!["Target".to_string(), cron_job_target_summary(&self.target)],
66            vec![
67                "Max retries".to_string(),
68                self.max_retries
69                    .map(|value| value.to_string())
70                    .unwrap_or_else(|| "-".to_string()),
71            ],
72            vec![
73                "Timeout".to_string(),
74                self.timeout.clone().unwrap_or_else(|| "-".to_string()),
75            ],
76            vec!["Created".to_string(), self.created_at.0.clone()],
77            vec!["Updated".to_string(), self.updated_at.0.clone()],
78        ]);
79        table.to_string()
80    }
81
82    fn render_list_table(items: &[Self]) -> String {
83        let mut table = Table::new();
84        table.set_header(vec![
85            "Name".to_string(),
86            "Kind".to_string(),
87            "Schedule".to_string(),
88            "Enabled".to_string(),
89            "Target".to_string(),
90            "Id".to_string(),
91        ]);
92        table.add_rows(items.iter().map(|job| {
93            vec![
94                job.name.clone(),
95                cron_job_kind(job.kind).to_string(),
96                job.schedule.clone(),
97                job.enabled.to_string(),
98                cron_job_target_summary(&job.target),
99                job.id.inner().to_string(),
100            ]
101        }));
102        table.to_string()
103    }
104}
105
106impl CliRender for CronJobInvocation {
107    fn render_item_table(&self) -> String {
108        let mut table = Table::new();
109        table.add_rows([
110            vec!["Id".to_string(), self.id.inner().to_string()],
111            vec!["Edge job id".to_string(), self.edge_job_id.clone()],
112            vec![
113                "Status".to_string(),
114                self.status
115                    .map(|status| format!("{status:?}"))
116                    .unwrap_or_else(|| "-".to_string()),
117            ],
118            vec![
119                "Scheduled".to_string(),
120                self.scheduled_at
121                    .as_ref()
122                    .map(|value| value.0.clone())
123                    .unwrap_or_else(|| "-".to_string()),
124            ],
125            vec![
126                "Started".to_string(),
127                self.started_at
128                    .as_ref()
129                    .map(|value| value.0.clone())
130                    .unwrap_or_else(|| "-".to_string()),
131            ],
132            vec![
133                "Finished".to_string(),
134                self.finished_at
135                    .as_ref()
136                    .map(|value| value.0.clone())
137                    .unwrap_or_else(|| "-".to_string()),
138            ],
139            vec![
140                "Duration ms".to_string(),
141                self.duration_ms
142                    .map(|value| value.to_string())
143                    .unwrap_or_else(|| "-".to_string()),
144            ],
145            vec![
146                "Result".to_string(),
147                cron_invocation_result_summary(&self.result),
148            ],
149            vec![
150                "Error".to_string(),
151                self.error_summary
152                    .clone()
153                    .unwrap_or_else(|| "-".to_string()),
154            ],
155        ]);
156        table.to_string()
157    }
158
159    fn render_list_table(items: &[Self]) -> String {
160        let mut table = Table::new();
161        table.set_header(vec![
162            "Id".to_string(),
163            "Status".to_string(),
164            "Scheduled".to_string(),
165            "Started".to_string(),
166            "Duration ms".to_string(),
167            "Result".to_string(),
168        ]);
169        table.add_rows(items.iter().map(|invocation| {
170            vec![
171                invocation.id.inner().to_string(),
172                invocation
173                    .status
174                    .map(|status| format!("{status:?}"))
175                    .unwrap_or_else(|| "-".to_string()),
176                invocation
177                    .scheduled_at
178                    .as_ref()
179                    .map(|value| value.0.clone())
180                    .unwrap_or_else(|| "-".to_string()),
181                invocation
182                    .started_at
183                    .as_ref()
184                    .map(|value| value.0.clone())
185                    .unwrap_or_else(|| "-".to_string()),
186                invocation
187                    .duration_ms
188                    .map(|value| value.to_string())
189                    .unwrap_or_else(|| "-".to_string()),
190                cron_invocation_result_summary(&invocation.result),
191            ]
192        }));
193        table.to_string()
194    }
195}
196
197impl CliRender for CronJobLog {
198    fn render_item_table(&self) -> String {
199        let mut table = Table::new();
200        table.add_rows([
201            vec!["Timestamp".to_string(), self.datetime.0.clone()],
202            vec!["Message".to_string(), self.message.clone()],
203        ]);
204        table.to_string()
205    }
206
207    fn render_list_table(items: &[Self]) -> String {
208        let mut table = Table::new();
209        table.load_preset(comfy_table::presets::NOTHING);
210        table.set_content_arrangement(comfy_table::ContentArrangement::Dynamic);
211
212        for item in items {
213            let message = match item.stream {
214                Some(LogStream::Stderr) => item.message.clone().yellow(),
215                Some(LogStream::Runtime) => item.message.clone().cyan(),
216                Some(LogStream::Stdout) | None => item.message.clone().bold(),
217            };
218            table.add_row([
219                comfy_table::Cell::new(format!("[{}]", item.datetime.0))
220                    .set_alignment(comfy_table::CellAlignment::Right),
221                comfy_table::Cell::new(message),
222            ]);
223        }
224        table.to_string()
225    }
226}
227
228impl CliRender for SearchPackageVersion {
229    fn render_item_table(&self) -> String {
230        let mut table = Table::new();
231        table.add_rows([
232            vec!["Package".to_string(), package_full_name(self)],
233            vec!["Version".to_string(), self.version.clone()],
234            vec!["Created".to_string(), self.created_at.0.clone()],
235        ]);
236        table.to_string()
237    }
238
239    fn render_list_table(items: &[Self]) -> String {
240        let mut table = Table::new();
241        table.set_header(vec![
242            "Package".to_string(),
243            "Version".to_string(),
244            "Created".to_string(),
245        ]);
246        table.add_rows(items.iter().map(|pv| {
247            vec![
248                package_full_name(pv),
249                pv.version.clone(),
250                pv.created_at.0.clone(),
251            ]
252        }));
253        table.to_string()
254    }
255}
256
257impl CliRender for DnsDomain {
258    fn render_item_table(&self) -> String {
259        let mut table = Table::new();
260        table.add_rows([vec!["Domain".to_string(), self.name.clone()]]);
261        table.to_string()
262    }
263
264    fn render_list_table(items: &[Self]) -> String {
265        if items.is_empty() {
266            return String::new();
267        }
268        let mut table = Table::new();
269        table.set_header(vec!["Domain".to_string(), "Owner".to_string()]);
270        table.add_rows(
271            items
272                .iter()
273                .map(|ns| vec![ns.name.clone(), ns.owner.global_name.clone()]),
274        );
275        table.to_string()
276    }
277}
278
279impl CliRender for DnsDomainWithRecords {
280    fn render_item_table(&self) -> String {
281        let mut output = String::new();
282        let mut table = Table::new();
283        table
284            .load_preset(comfy_table::presets::UTF8_FULL_CONDENSED)
285            .set_header(vec![
286                "Type".to_string(),
287                "Name".to_string(),
288                "TTL".to_string(),
289                "Value".to_string(),
290            ]);
291        let mut rows: Vec<Vec<String>> = vec![];
292        if let Some(ref records) = self.records {
293            records.iter().flatten().for_each(|record| {
294                rows.push(vec![
295                    record.record_type().to_string(),
296                    record.name().unwrap_or("<no name>").to_string(),
297                    record
298                        .ttl()
299                        .expect("expected a TTL value for record")
300                        .to_string(),
301                    record.text().to_string(),
302                ]);
303            });
304        }
305
306        table.add_rows(rows);
307        output += &table.to_string();
308        output
309    }
310
311    fn render_list_table(items: &[Self]) -> String {
312        if items.is_empty() {
313            return String::new();
314        }
315        let mut table = Table::new();
316        table.set_header(vec!["Domain".to_string()]);
317        table.add_rows(items.iter().map(|ns| vec![ns.name.clone()]));
318        table.to_string()
319    }
320}
321
322impl CliRender for Namespace {
323    fn render_item_table(&self) -> String {
324        let mut table = Table::new();
325        table.add_rows([
326            vec!["Namespace".to_string(), self.name.clone()],
327            vec!["Id".to_string(), self.id.inner().to_string()],
328        ]);
329        table.to_string()
330    }
331
332    fn render_list_table(items: &[Self]) -> String {
333        let mut table = Table::new();
334        table.set_header(vec!["Namespace".to_string(), "Id".to_string()]);
335        table.add_rows(
336            items
337                .iter()
338                .map(|ns| vec![ns.name.clone(), ns.id.inner().to_string()]),
339        );
340        table.to_string()
341    }
342}
343
344impl CliRender for DeployApp {
345    fn render_item_table(&self) -> String {
346        let mut table = Table::new();
347        table.add_rows([
348            vec![
349                "App".to_string(),
350                format!("{}/{}", self.owner.global_name, self.name),
351            ],
352            vec![
353                "Version".to_string(),
354                self.active_version
355                    .as_ref()
356                    .map_or_else(|| "n/a".to_string(), |v| v.version.clone()),
357            ],
358            vec!["Created".to_string(), self.created_at.0.clone()],
359            vec!["Updated".to_string(), self.updated_at.0.clone()],
360            vec!["Id".to_string(), self.id.inner().to_string()],
361        ]);
362        table.to_string()
363    }
364
365    fn render_list_table(items: &[Self]) -> String {
366        let mut table = Table::new();
367        table.set_header(vec![
368            "App".to_string(),
369            "Version".to_string(),
370            "Created".to_string(),
371            "Updated".to_string(),
372            "Id".to_string(),
373        ]);
374        table.add_rows(items.iter().map(|app| {
375            vec![
376                format!("{}/{}", app.owner.global_name, app.name),
377                app.active_version
378                    .as_ref()
379                    .map_or_else(|| "n/a".to_string(), |v| v.version.clone()),
380                app.created_at.0.clone(),
381                app.updated_at.0.clone(),
382                app.id.inner().to_string(),
383            ]
384        }));
385        table.to_string()
386    }
387}
388
389impl CliRender for DeployAppVersion {
390    fn render_item_table(&self) -> String {
391        let mut table = Table::new();
392        table.add_rows([
393            vec!["Version name".to_string(), self.version.clone()],
394            vec!["Created".to_string(), self.created_at.0.clone()],
395            vec!["Id".to_string(), self.id.inner().to_string()],
396        ]);
397        table.to_string()
398    }
399
400    fn render_list_table(items: &[Self]) -> String {
401        let mut table = Table::new();
402        table.set_header(vec![
403            "Version name".to_string(),
404            "Created".to_string(),
405            "Id".to_string(),
406        ]);
407        table.add_rows(items.iter().map(|ver| {
408            vec![
409                ver.version.clone(),
410                ver.created_at.0.clone(),
411                ver.id.inner().to_string(),
412            ]
413        }));
414        table.to_string()
415    }
416}
417
418impl CliRender for wasmer_backend_api::types::AppDatabase {
419    fn render_item_table(&self) -> String {
420        let mut table = Table::new();
421        table.add_rows([
422            vec!["Name".to_string(), self.name.clone()],
423            vec!["Host".to_string(), self.host.clone()],
424            vec!["Port".to_string(), self.port.clone()],
425            vec!["Username".to_string(), self.username.clone()],
426            vec![
427                "Password".to_string(),
428                self.password.clone().unwrap_or_else(|| "n/a".to_string()),
429            ],
430            vec![
431                "UI".to_string(),
432                self.db_explorer_url
433                    .clone()
434                    .unwrap_or_else(|| "n/a".to_string()),
435            ],
436        ]);
437        table.to_string()
438    }
439
440    fn render_list_table(items: &[Self]) -> String {
441        let mut table = Table::new();
442        table.set_header(vec![
443            "Name".to_string(),
444            "Host".to_string(),
445            "Port".to_string(),
446            "UI".to_string(),
447            "Password".to_string(),
448        ]);
449        table.add_rows(items.iter().map(|vol| {
450            vec![
451                vol.name.clone(),
452                vol.host.clone(),
453                vol.port.clone(),
454                vol.db_explorer_url
455                    .clone()
456                    .unwrap_or_else(|| "n/a".to_string()),
457                vol.password.clone().unwrap_or_else(|| "n/a".to_string()),
458            ]
459        }));
460        table.to_string()
461    }
462}
463
464pub(crate) fn format_disk_size_opt(value: Option<wasmer_backend_api::types::BigInt>) -> String {
465    let value = value.and_then(|x| {
466        let y: Option<u64> = x.0.try_into().ok();
467        y
468    });
469
470    if let Some(v) = value {
471        let s = bytesize::ByteSize(v);
472        s.to_string()
473    } else {
474        "n/a".to_string()
475    }
476}
477
478impl CliRender for Deployment {
479    fn render_item_table(&self) -> String {
480        match self {
481            Deployment::NakedDeployment(naked) => naked.render_item_table(),
482            Deployment::AutobuildRepository(build) => build.render_item_table(),
483            Deployment::Other => "unknown deployment type".to_string(),
484        }
485    }
486
487    fn render_list_table(items: &[Self]) -> String {
488        let mut table = Table::new();
489        table.set_header(vec![
490            "Id".to_string(),
491            "Type".to_string(),
492            "Created at".to_string(),
493            "Status".to_string(),
494            "App version".to_string(),
495        ]);
496
497        let rows = items
498            .iter()
499            .map(|item| match item {
500                Deployment::NakedDeployment(naked) => {
501                    vec![
502                        naked.id.inner().to_string(),
503                        "Manual".to_string(),
504                        naked.created_at.0.clone(),
505                        String::new(),
506                        naked
507                            .app_version
508                            .as_ref()
509                            .map_or_else(|| "n/a".to_string(), |x| x.version.clone()),
510                    ]
511                }
512                Deployment::AutobuildRepository(build) => {
513                    vec![
514                        build.id.inner().to_string(),
515                        "Autobuild".to_string(),
516                        build.status.as_str().to_string(),
517                        build.created_at.0.clone(),
518                    ]
519                }
520                Deployment::Other => vec![
521                    String::new(),
522                    "Unknown".to_string(),
523                    String::new(),
524                    String::new(),
525                ],
526            })
527            .collect::<Vec<_>>();
528        table.add_rows(rows);
529
530        table.to_string()
531    }
532}
533
534impl CliRender for wasmer_backend_api::types::NakedDeployment {
535    fn render_item_table(&self) -> String {
536        let mut table = Table::new();
537        table.add_rows([
538            vec!["Id".to_string(), self.id.clone().into_inner()],
539            vec!["Created at".to_string(), self.created_at.0.clone()],
540            vec![
541                "App version".to_string(),
542                self.app_version
543                    .as_ref()
544                    .map_or_else(|| "n/a".to_string(), |x| x.version.clone()),
545            ],
546        ]);
547        table.to_string()
548    }
549
550    fn render_list_table(items: &[Self]) -> String {
551        let mut table = Table::new();
552        table.set_header(vec![
553            "Id".to_string(),
554            "Created at".to_string(),
555            "App version".to_string(),
556        ]);
557        table.add_rows(items.iter().map(|item| {
558            vec![
559                item.id.clone().into_inner(),
560                item.created_at.0.clone(),
561                item.app_version
562                    .as_ref()
563                    .map_or_else(|| "n/a".to_string(), |x| x.version.clone()),
564            ]
565        }));
566        table.to_string()
567    }
568}
569
570impl CliRender for wasmer_backend_api::types::AutobuildRepository {
571    fn render_item_table(&self) -> String {
572        let mut table = Table::new();
573        table.add_rows([
574            vec!["Id".to_string(), self.id.clone().into_inner()],
575            vec!["Status".to_string(), self.status.as_str().to_string()],
576            vec!["Created at".to_string(), self.created_at.0.clone()],
577        ]);
578        table.to_string()
579    }
580
581    fn render_list_table(items: &[Self]) -> String {
582        let mut table = Table::new();
583        table.set_header(vec![
584            "Id".to_string(),
585            "Status".to_string(),
586            "Created at".to_string(),
587        ]);
588        table.add_rows(items.iter().map(|item| {
589            vec![
590                item.id.clone().into_inner(),
591                item.status.as_str().to_string(),
592                item.created_at.0.clone(),
593            ]
594        }));
595        table.to_string()
596    }
597}