Skip to main content

runtime_cli/commands/
org.rs

1//! `runtime org` — list / members / teams.
2
3use std::io::Write;
4
5use anyhow::{anyhow, Result};
6use clap::{Args, Subcommand};
7use serde_json::{json, Value};
8
9use crate::client;
10use crate::output::{render_table, FormatChoice};
11
12#[derive(Debug, Subcommand)]
13pub enum OrgCmd {
14    /// List organizations the viewer is a member of.
15    List(FormatArgs),
16    /// List members of an organization.
17    Members(LoginArgs),
18    /// List teams in an organization.
19    Teams(LoginArgs),
20}
21
22#[derive(Debug, Args)]
23pub struct FormatArgs {
24    #[arg(long, default_value_t = false)]
25    pub json: bool,
26    #[arg(long, default_value_t = false)]
27    pub tsv: bool,
28}
29
30#[derive(Debug, Args)]
31pub struct LoginArgs {
32    pub login: String,
33    #[arg(long, default_value_t = false)]
34    pub json: bool,
35    #[arg(long, default_value_t = false)]
36    pub tsv: bool,
37}
38
39pub fn run(cmd: OrgCmd) -> Result<()> {
40    match cmd {
41        OrgCmd::List(a) => list(a),
42        OrgCmd::Members(a) => members(a),
43        OrgCmd::Teams(a) => teams(a),
44    }
45}
46
47fn list(args: FormatArgs) -> Result<()> {
48    let (cli, _cfg) = client::authenticated()?;
49    let data = cli.execute(
50        "{ viewer { organizations { login displayName description } } }",
51        json!({}),
52    )?;
53    let orgs = data["viewer"]["organizations"]
54        .as_array()
55        .cloned()
56        .unwrap_or_default();
57    let rows = org_rows(&orgs);
58    let fmt = FormatChoice {
59        json: args.json,
60        tsv: args.tsv,
61    }
62    .resolve();
63    std::io::stdout()
64        .lock()
65        .write_all(render_table(&["login", "name", "description"], &rows, fmt).as_bytes())?;
66    Ok(())
67}
68
69fn members(args: LoginArgs) -> Result<()> {
70    let (cli, _cfg) = client::authenticated()?;
71    let data = cli.execute(
72        "query($login: String!) { organization(login: $login) { members { role account { username displayName } } } }",
73        json!({ "login": args.login }),
74    )?;
75    let org = &data["organization"];
76    if org.is_null() {
77        return Err(anyhow!("no organization `{}`", args.login));
78    }
79    let members = org["members"].as_array().cloned().unwrap_or_default();
80    let rows = member_rows(&members);
81    let fmt = FormatChoice {
82        json: args.json,
83        tsv: args.tsv,
84    }
85    .resolve();
86    std::io::stdout()
87        .lock()
88        .write_all(render_table(&["user", "name", "role"], &rows, fmt).as_bytes())?;
89    Ok(())
90}
91
92fn teams(args: LoginArgs) -> Result<()> {
93    let (cli, _cfg) = client::authenticated()?;
94    let data = cli.execute(
95        "query($login: String!) { organization(login: $login) { teams { slug name description } } }",
96        json!({ "login": args.login }),
97    )?;
98    let org = &data["organization"];
99    if org.is_null() {
100        return Err(anyhow!("no organization `{}`", args.login));
101    }
102    let teams = org["teams"].as_array().cloned().unwrap_or_default();
103    let rows = team_rows(&teams);
104    let fmt = FormatChoice {
105        json: args.json,
106        tsv: args.tsv,
107    }
108    .resolve();
109    std::io::stdout()
110        .lock()
111        .write_all(render_table(&["slug", "name", "description"], &rows, fmt).as_bytes())?;
112    Ok(())
113}
114
115fn org_rows(orgs: &[Value]) -> Vec<Vec<String>> {
116    orgs.iter()
117        .map(|o| {
118            vec![
119                o["login"].as_str().unwrap_or("").to_string(),
120                o["displayName"].as_str().unwrap_or("").to_string(),
121                o["description"].as_str().unwrap_or("").to_string(),
122            ]
123        })
124        .collect()
125}
126
127fn member_rows(members: &[Value]) -> Vec<Vec<String>> {
128    members
129        .iter()
130        .map(|m| {
131            vec![
132                m["account"]["username"].as_str().unwrap_or("").to_string(),
133                m["account"]["displayName"]
134                    .as_str()
135                    .unwrap_or("")
136                    .to_string(),
137                m["role"].as_str().unwrap_or("").to_string(),
138            ]
139        })
140        .collect()
141}
142
143fn team_rows(teams: &[Value]) -> Vec<Vec<String>> {
144    teams
145        .iter()
146        .map(|t| {
147            vec![
148                t["slug"].as_str().unwrap_or("").to_string(),
149                t["name"].as_str().unwrap_or("").to_string(),
150                t["description"].as_str().unwrap_or("").to_string(),
151            ]
152        })
153        .collect()
154}
155
156#[cfg(test)]
157mod tests {
158    use super::*;
159
160    #[test]
161    fn org_rows_format_org_member_and_team_tables_with_defaults() {
162        let orgs = vec![
163            json!({
164                "login": "runtime-labs",
165                "displayName": "Runtime Labs",
166                "description": "Builds Runtime"
167            }),
168            json!({}),
169        ];
170        assert_eq!(
171            org_rows(&orgs),
172            vec![
173                vec!["runtime-labs", "Runtime Labs", "Builds Runtime"],
174                vec!["", "", ""],
175            ]
176        );
177
178        let members = vec![
179            json!({
180                "role": "ADMIN",
181                "account": { "username": "bri", "displayName": "Bri" }
182            }),
183            json!({}),
184        ];
185        assert_eq!(
186            member_rows(&members),
187            vec![vec!["bri", "Bri", "ADMIN"], vec!["", "", ""]]
188        );
189
190        let teams = vec![
191            json!({ "slug": "ops", "name": "Ops", "description": "Operators" }),
192            json!({}),
193        ];
194        assert_eq!(
195            team_rows(&teams),
196            vec![vec!["ops", "Ops", "Operators"], vec!["", "", ""]]
197        );
198    }
199}