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