Skip to main content

via/
capabilities.rs

1use serde::Serialize;
2
3use crate::config::{CapabilityMode, Config};
4use crate::error::ViaError;
5
6#[derive(Serialize)]
7struct Capabilities<'a> {
8    services: Vec<ServiceCapabilities<'a>>,
9}
10
11#[derive(Serialize)]
12struct ServiceCapabilities<'a> {
13    name: &'a str,
14    description: Option<&'a str>,
15    #[serde(skip_serializing_if = "Option::is_none")]
16    hint: Option<&'a str>,
17    capabilities: Vec<CapabilitySummary<'a>>,
18}
19
20#[derive(Serialize)]
21struct CapabilitySummary<'a> {
22    name: &'a str,
23    description: Option<&'a str>,
24    mode: CapabilityMode,
25}
26
27pub fn print(config: &Config, json: bool) -> Result<(), ViaError> {
28    print!("{}", render(config, json)?);
29    Ok(())
30}
31
32pub fn render(config: &Config, json: bool) -> Result<String, ViaError> {
33    if json {
34        let capabilities = Capabilities {
35            services: config
36                .services
37                .iter()
38                .map(|(name, service)| ServiceCapabilities {
39                    name,
40                    description: service.description.as_deref(),
41                    hint: service.hint.as_deref(),
42                    capabilities: service
43                        .commands
44                        .iter()
45                        .map(|(command_name, command)| CapabilitySummary {
46                            name: command_name,
47                            description: command.description().map(String::as_str),
48                            mode: command.mode(),
49                        })
50                        .collect(),
51                })
52                .collect(),
53        };
54        return Ok(format!(
55            "{}\n",
56            serde_json::to_string_pretty(&capabilities)?
57        ));
58    }
59
60    let mut output = String::new();
61    for (service_name, service) in &config.services {
62        match &service.description {
63            Some(description) => output.push_str(&format!("{service_name}: {description}\n")),
64            None => output.push_str(&format!("{service_name}\n")),
65        }
66        if let Some(hint) = &service.hint {
67            output.push_str(&format!("  hint: {hint}\n"));
68        }
69
70        for (command_name, command) in &service.commands {
71            match command.description() {
72                Some(description) => output.push_str(&format!(
73                    "  {command_name} ({:?}): {description}\n",
74                    command.mode()
75                )),
76                None => output.push_str(&format!("  {command_name} ({:?})\n", command.mode())),
77            }
78        }
79    }
80
81    Ok(output)
82}
83
84#[cfg(test)]
85mod tests {
86    use super::*;
87
88    fn config() -> Config {
89        Config::from_toml_str(
90            r#"
91version = 1
92
93[providers.onepassword]
94type = "1password"
95
96[services.github]
97description = "GitHub access"
98hint = "via github api /user"
99provider = "onepassword"
100
101[services.github.secrets]
102token = "op://Private/GitHub/token"
103
104[services.github.commands.api]
105description = "REST access"
106mode = "rest"
107base_url = "https://api.github.com"
108
109[services.github.commands.api.auth]
110type = "bearer"
111secret = "token"
112"#,
113        )
114        .unwrap()
115    }
116
117    #[test]
118    fn renders_human_capabilities() {
119        let output = render(&config(), false).unwrap();
120
121        assert!(output.contains("github: GitHub access"));
122        assert!(output.contains("hint: via github api /user"));
123        assert!(output.contains("api (Rest): REST access"));
124    }
125
126    #[test]
127    fn renders_json_capabilities_without_secret_refs() {
128        let output = render(&config(), true).unwrap();
129
130        assert!(output.contains("\"name\": \"github\""));
131        assert!(output.contains("\"hint\": \"via github api /user\""));
132        assert!(output.contains("\"mode\": \"rest\""));
133        assert!(!output.contains("op://"));
134    }
135}