Skip to main content

pimalaya_cli/
capability.rs

1//! # Capability
2//!
3//! The output of every binary's `account capability` command: one row per
4//! capability of the unified API, the backend serving it and how well. It
5//! prints as a [`Listing`] headed by the account.
6//!
7//! [`Listing`]: crate::listing::Listing
8
9use std::fmt;
10
11use schemars::JsonSchema;
12use serde::Serialize;
13
14use crate::{listing::Listing, sanitize};
15
16/// The `account capability` output.
17#[derive(Clone, Debug, Serialize, JsonSchema)]
18#[serde(rename_all = "camelCase")]
19pub struct AccountCapabilityOutput {
20    /// The account the capabilities are resolved for.
21    pub account: String,
22    /// One row per unified capability, in the order the commands reach
23    /// them.
24    pub capabilities: Vec<AccountCapability>,
25}
26
27/// How the account serves one unified capability.
28#[derive(Clone, Debug, Serialize, JsonSchema)]
29#[serde(rename_all = "camelCase")]
30pub struct AccountCapability {
31    /// The capability, `<noun>.<verb>` and its refinements
32    /// (`message.move`, `event.search.body`).
33    pub name: String,
34    /// The backend it resolves to, `null` when the account configures none
35    /// that `--backend` allows.
36    pub backend: Option<String>,
37    /// How well the backend serves it.
38    pub level: CapabilityLevel,
39    /// What the level leaves out, `null` for a full one.
40    pub detail: Option<String>,
41}
42
43/// How well a backend serves a capability.
44#[derive(Clone, Copy, Debug, Eq, PartialEq, Serialize, JsonSchema)]
45#[serde(rename_all = "kebab-case")]
46pub enum CapabilityLevel {
47    /// Served whole.
48    Full,
49    /// Served with the limits the detail names.
50    Partial,
51    /// Decided by the server once connected.
52    Server,
53    /// Not served: the command is refused before connecting.
54    None,
55}
56
57impl fmt::Display for CapabilityLevel {
58    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
59        f.write_str(match self {
60            Self::Full => "full",
61            Self::Partial => "partial",
62            Self::Server => "server",
63            Self::None => "none",
64        })
65    }
66}
67
68impl fmt::Display for AccountCapabilityOutput {
69    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
70        let mut listing = Listing::new(&self.account, None);
71
72        for capability in &self.capabilities {
73            let detail = capability.detail.as_deref().unwrap_or_default();
74
75            listing.items.push([
76                sanitize::text(&capability.name).into_owned(),
77                capability.backend.clone().unwrap_or_default(),
78                capability.level.to_string(),
79                sanitize::text(detail).into_owned(),
80            ]);
81        }
82
83        write!(f, "{listing}")
84    }
85}
86
87#[cfg(test)]
88mod tests {
89    use serde_json::json;
90
91    use crate::capability::*;
92
93    #[test]
94    fn capabilities_print_one_line_each_under_the_account() {
95        let output = AccountCapabilityOutput {
96            account: String::from("work"),
97            capabilities: vec![
98                AccountCapability {
99                    name: String::from("message.move"),
100                    backend: Some(String::from("imap")),
101                    level: CapabilityLevel::Full,
102                    detail: None,
103                },
104                AccountCapability {
105                    name: String::from("message.send"),
106                    backend: None,
107                    level: CapabilityLevel::None,
108                    detail: Some(String::from("No transport")),
109                },
110            ],
111        };
112
113        let expected = "\
114Account:  work
115
116message.move  imap  full  -
117message.send  -     none  No transport
118";
119        assert_eq!(output.to_string(), expected);
120
121        let json = serde_json::to_value(&output).unwrap();
122        let second = json!({
123            "name": "message.send",
124            "backend": null,
125            "level": "none",
126            "detail": "No transport",
127        });
128        assert_eq!(json["capabilities"][1], second);
129    }
130}