Skip to main content

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![
112                "Status".to_string(),
113                self.status
114                    .map(|status| format!("{status:?}"))
115                    .unwrap_or_else(|| "-".to_string()),
116            ],
117            vec![
118                "Scheduled".to_string(),
119                self.scheduled_at
120                    .as_ref()
121                    .map(|value| value.0.clone())
122                    .unwrap_or_else(|| "-".to_string()),
123            ],
124            vec![
125                "Started".to_string(),
126                self.started_at
127                    .as_ref()
128                    .map(|value| value.0.clone())
129                    .unwrap_or_else(|| "-".to_string()),
130            ],
131            vec![
132                "Finished".to_string(),
133                self.finished_at
134                    .as_ref()
135                    .map(|value| value.0.clone())
136                    .unwrap_or_else(|| "-".to_string()),
137            ],
138            vec![
139                "Duration ms".to_string(),
140                self.duration_ms
141                    .map(|value| value.to_string())
142                    .unwrap_or_else(|| "-".to_string()),
143            ],
144            vec![
145                "Result".to_string(),
146                cron_invocation_result_summary(&self.result),
147            ],
148            vec![
149                "Error".to_string(),
150                self.error_summary
151                    .clone()
152                    .unwrap_or_else(|| "-".to_string()),
153            ],
154        ]);
155        table.to_string()
156    }
157
158    fn render_list_table(items: &[Self]) -> String {
159        let mut table = Table::new();
160        table.set_header(vec![
161            "Id".to_string(),
162            "Status".to_string(),
163            "Scheduled".to_string(),
164            "Started".to_string(),
165            "Duration ms".to_string(),
166            "Result".to_string(),
167        ]);
168        table.add_rows(items.iter().map(|invocation| {
169            vec![
170                invocation.id.inner().to_string(),
171                invocation
172                    .status
173                    .map(|status| format!("{status:?}"))
174                    .unwrap_or_else(|| "-".to_string()),
175                invocation
176                    .scheduled_at
177                    .as_ref()
178                    .map(|value| value.0.clone())
179                    .unwrap_or_else(|| "-".to_string()),
180                invocation
181                    .started_at
182                    .as_ref()
183                    .map(|value| value.0.clone())
184                    .unwrap_or_else(|| "-".to_string()),
185                invocation
186                    .duration_ms
187                    .map(|value| value.to_string())
188                    .unwrap_or_else(|| "-".to_string()),
189                cron_invocation_result_summary(&invocation.result),
190            ]
191        }));
192        table.to_string()
193    }
194}
195
196impl CliRender for CronJobLog {
197    fn render_item_table(&self) -> String {
198        let mut table = Table::new();
199        table.add_rows([
200            vec!["Timestamp".to_string(), self.datetime.0.clone()],
201            vec!["Message".to_string(), self.message.clone()],
202        ]);
203        table.to_string()
204    }
205
206    fn render_list_table(items: &[Self]) -> String {
207        let mut table = Table::new();
208        table.load_style(comfy_table::presets::NOTHING);
209        table.set_content_arrangement(comfy_table::ContentArrangement::Dynamic);
210
211        for item in items {
212            let message = match item.stream {
213                Some(LogStream::Stderr) => item.message.clone().yellow(),
214                Some(LogStream::Runtime) => item.message.clone().cyan(),
215                Some(LogStream::Stdout) | None => item.message.clone().bold(),
216            };
217            table.add_row([
218                comfy_table::Cell::new(format!("[{}]", item.datetime.0))
219                    .set_alignment(comfy_table::CellAlignment::Right),
220                comfy_table::Cell::new(message),
221            ]);
222        }
223        table.to_string()
224    }
225}
226
227impl CliRender for SearchPackageVersion {
228    fn render_item_table(&self) -> String {
229        let mut table = Table::new();
230        table.add_rows([
231            vec!["Package".to_string(), package_full_name(self)],
232            vec!["Version".to_string(), self.version.clone()],
233            vec!["Created".to_string(), self.created_at.0.clone()],
234        ]);
235        table.to_string()
236    }
237
238    fn render_list_table(items: &[Self]) -> String {
239        let mut table = Table::new();
240        table.set_header(vec![
241            "Package".to_string(),
242            "Version".to_string(),
243            "Created".to_string(),
244        ]);
245        table.add_rows(items.iter().map(|pv| {
246            vec![
247                package_full_name(pv),
248                pv.version.clone(),
249                pv.created_at.0.clone(),
250            ]
251        }));
252        table.to_string()
253    }
254}
255
256impl CliRender for DnsDomain {
257    fn render_item_table(&self) -> String {
258        let mut table = Table::new();
259        table.add_rows([vec!["Domain".to_string(), self.name.clone()]]);
260        table.to_string()
261    }
262
263    fn render_list_table(items: &[Self]) -> String {
264        if items.is_empty() {
265            return String::new();
266        }
267        let mut table = Table::new();
268        table.set_header(vec!["Domain".to_string(), "Owner".to_string()]);
269        table.add_rows(
270            items
271                .iter()
272                .map(|ns| vec![ns.name.clone(), ns.owner.global_name.clone()]),
273        );
274        table.to_string()
275    }
276}
277
278impl CliRender for DnsDomainWithRecords {
279    fn render_item_table(&self) -> String {
280        let mut output = String::new();
281        let mut table = Table::new();
282        table
283            .load_style(comfy_table::presets::UTF8_FULL_CONDENSED)
284            .set_header(vec![
285                "Type".to_string(),
286                "Name".to_string(),
287                "TTL".to_string(),
288                "Value".to_string(),
289            ]);
290        let mut rows: Vec<Vec<String>> = vec![];
291        if let Some(ref records) = self.records {
292            records.iter().flatten().for_each(|record| {
293                rows.push(vec![
294                    record.record_type().to_string(),
295                    record.name().unwrap_or("<no name>").to_string(),
296                    record
297                        .ttl()
298                        .expect("expected a TTL value for record")
299                        .to_string(),
300                    record.text().to_string(),
301                ]);
302            });
303        }
304
305        table.add_rows(rows);
306        output += &table.to_string();
307        output
308    }
309
310    fn render_list_table(items: &[Self]) -> String {
311        if items.is_empty() {
312            return String::new();
313        }
314        let mut table = Table::new();
315        table.set_header(vec!["Domain".to_string()]);
316        table.add_rows(items.iter().map(|ns| vec![ns.name.clone()]));
317        table.to_string()
318    }
319}
320
321impl CliRender for Namespace {
322    fn render_item_table(&self) -> String {
323        let mut table = Table::new();
324        table.add_rows([
325            vec!["Namespace".to_string(), self.name.clone()],
326            vec!["Id".to_string(), self.id.inner().to_string()],
327        ]);
328        table.to_string()
329    }
330
331    fn render_list_table(items: &[Self]) -> String {
332        let mut table = Table::new();
333        table.set_header(vec!["Namespace".to_string(), "Id".to_string()]);
334        table.add_rows(
335            items
336                .iter()
337                .map(|ns| vec![ns.name.clone(), ns.id.inner().to_string()]),
338        );
339        table.to_string()
340    }
341}
342
343impl CliRender for DeployApp {
344    fn render_item_table(&self) -> String {
345        let mut table = Table::new();
346        table.add_rows([
347            vec![
348                "App".to_string(),
349                format!("{}/{}", self.owner.global_name, self.name),
350            ],
351            vec![
352                "Version".to_string(),
353                self.active_version
354                    .as_ref()
355                    .map_or_else(|| "n/a".to_string(), |v| v.version.clone()),
356            ],
357            vec!["Created".to_string(), self.created_at.0.clone()],
358            vec!["Updated".to_string(), self.updated_at.0.clone()],
359            vec!["Id".to_string(), self.id.inner().to_string()],
360        ]);
361        table.to_string()
362    }
363
364    fn render_list_table(items: &[Self]) -> String {
365        let mut table = Table::new();
366        table.set_header(vec![
367            "App".to_string(),
368            "Version".to_string(),
369            "Created".to_string(),
370            "Updated".to_string(),
371            "Id".to_string(),
372        ]);
373        table.add_rows(items.iter().map(|app| {
374            vec![
375                format!("{}/{}", app.owner.global_name, app.name),
376                app.active_version
377                    .as_ref()
378                    .map_or_else(|| "n/a".to_string(), |v| v.version.clone()),
379                app.created_at.0.clone(),
380                app.updated_at.0.clone(),
381                app.id.inner().to_string(),
382            ]
383        }));
384        table.to_string()
385    }
386}
387
388impl CliRender for DeployAppVersion {
389    fn render_item_table(&self) -> String {
390        let mut table = Table::new();
391        table.add_rows([
392            vec!["Version name".to_string(), self.version.clone()],
393            vec!["Created".to_string(), self.created_at.0.clone()],
394            vec!["Id".to_string(), self.id.inner().to_string()],
395        ]);
396        table.to_string()
397    }
398
399    fn render_list_table(items: &[Self]) -> String {
400        let mut table = Table::new();
401        table.set_header(vec![
402            "Version name".to_string(),
403            "Created".to_string(),
404            "Id".to_string(),
405        ]);
406        table.add_rows(items.iter().map(|ver| {
407            vec![
408                ver.version.clone(),
409                ver.created_at.0.clone(),
410                ver.id.inner().to_string(),
411            ]
412        }));
413        table.to_string()
414    }
415}
416
417impl CliRender for wasmer_backend_api::types::AppDatabase {
418    fn render_item_table(&self) -> String {
419        let mut table = Table::new();
420        table.add_rows([
421            vec!["Name".to_string(), self.name.clone()],
422            vec!["Host".to_string(), self.host.clone()],
423            vec!["Port".to_string(), self.port.clone()],
424            vec!["Username".to_string(), self.username.clone()],
425            vec![
426                "Password".to_string(),
427                self.password.clone().unwrap_or_else(|| "n/a".to_string()),
428            ],
429            vec![
430                "UI".to_string(),
431                self.db_explorer_url
432                    .clone()
433                    .unwrap_or_else(|| "n/a".to_string()),
434            ],
435        ]);
436        table.to_string()
437    }
438
439    fn render_list_table(items: &[Self]) -> String {
440        let mut table = Table::new();
441        table.set_header(vec![
442            "Name".to_string(),
443            "Host".to_string(),
444            "Port".to_string(),
445            "UI".to_string(),
446            "Password".to_string(),
447        ]);
448        table.add_rows(items.iter().map(|vol| {
449            vec![
450                vol.name.clone(),
451                vol.host.clone(),
452                vol.port.clone(),
453                vol.db_explorer_url
454                    .clone()
455                    .unwrap_or_else(|| "n/a".to_string()),
456                vol.password.clone().unwrap_or_else(|| "n/a".to_string()),
457            ]
458        }));
459        table.to_string()
460    }
461}
462
463pub(crate) fn format_disk_size_opt(value: Option<wasmer_backend_api::types::BigInt>) -> String {
464    let value = value.and_then(|x| {
465        let y: Option<u64> = x.0.try_into().ok();
466        y
467    });
468
469    if let Some(v) = value {
470        let s = bytesize::ByteSize(v);
471        s.to_string()
472    } else {
473        "n/a".to_string()
474    }
475}
476
477impl CliRender for Deployment {
478    fn render_item_table(&self) -> String {
479        match self {
480            Deployment::NakedDeployment(naked) => naked.render_item_table(),
481            Deployment::AutobuildRepository(build) => build.render_item_table(),
482            Deployment::Other => "unknown deployment type".to_string(),
483        }
484    }
485
486    fn render_list_table(items: &[Self]) -> String {
487        let mut table = Table::new();
488        table.set_header(vec![
489            "Id".to_string(),
490            "Type".to_string(),
491            "Created at".to_string(),
492            "Status".to_string(),
493            "App version".to_string(),
494        ]);
495
496        let rows = items
497            .iter()
498            .map(|item| match item {
499                Deployment::NakedDeployment(naked) => {
500                    vec![
501                        naked.id.inner().to_string(),
502                        "Manual".to_string(),
503                        naked.created_at.0.clone(),
504                        String::new(),
505                        naked
506                            .app_version
507                            .as_ref()
508                            .map_or_else(|| "n/a".to_string(), |x| x.version.clone()),
509                    ]
510                }
511                Deployment::AutobuildRepository(build) => {
512                    vec![
513                        build.id.inner().to_string(),
514                        "Autobuild".to_string(),
515                        build.status.as_str().to_string(),
516                        build.created_at.0.clone(),
517                    ]
518                }
519                Deployment::Other => vec![
520                    String::new(),
521                    "Unknown".to_string(),
522                    String::new(),
523                    String::new(),
524                ],
525            })
526            .collect::<Vec<_>>();
527        table.add_rows(rows);
528
529        table.to_string()
530    }
531}
532
533impl CliRender for wasmer_backend_api::types::NakedDeployment {
534    fn render_item_table(&self) -> String {
535        let mut table = Table::new();
536        table.add_rows([
537            vec!["Id".to_string(), self.id.clone().into_inner()],
538            vec!["Created at".to_string(), self.created_at.0.clone()],
539            vec![
540                "App version".to_string(),
541                self.app_version
542                    .as_ref()
543                    .map_or_else(|| "n/a".to_string(), |x| x.version.clone()),
544            ],
545        ]);
546        table.to_string()
547    }
548
549    fn render_list_table(items: &[Self]) -> String {
550        let mut table = Table::new();
551        table.set_header(vec![
552            "Id".to_string(),
553            "Created at".to_string(),
554            "App version".to_string(),
555        ]);
556        table.add_rows(items.iter().map(|item| {
557            vec![
558                item.id.clone().into_inner(),
559                item.created_at.0.clone(),
560                item.app_version
561                    .as_ref()
562                    .map_or_else(|| "n/a".to_string(), |x| x.version.clone()),
563            ]
564        }));
565        table.to_string()
566    }
567}
568
569impl CliRender for wasmer_backend_api::types::AutobuildRepository {
570    fn render_item_table(&self) -> String {
571        let mut table = Table::new();
572        table.add_rows([
573            vec!["Id".to_string(), self.id.clone().into_inner()],
574            vec!["Status".to_string(), self.status.as_str().to_string()],
575            vec!["Created at".to_string(), self.created_at.0.clone()],
576        ]);
577        table.to_string()
578    }
579
580    fn render_list_table(items: &[Self]) -> String {
581        let mut table = Table::new();
582        table.set_header(vec![
583            "Id".to_string(),
584            "Status".to_string(),
585            "Created at".to_string(),
586        ]);
587        table.add_rows(items.iter().map(|item| {
588            vec![
589                item.id.clone().into_inner(),
590                item.status.as_str().to_string(),
591                item.created_at.0.clone(),
592            ]
593        }));
594        table.to_string()
595    }
596}