cloudsctl/
format.rs

1use anyhow::{Context, Result};
2use indexmap::IndexMap;
3use std::collections::HashMap;
4use structopt::clap::arg_enum;
5use term_table::{row::Row, table_cell::TableCell, Table};
6
7type ListerResult = Result<fn(&str, Vec<&str>) -> Result<String>>;
8type ShowOneResult = Result<fn(IndexMap<String, String>, Vec<String>) -> Result<String>>;
9
10arg_enum! {
11    #[derive(Debug, PartialEq)]
12    #[allow(non_camel_case_types)]
13    pub enum Format {
14        table,
15        value,
16        yaml,
17        json,
18    }
19}
20
21fn value_lister(_: &str, values: Vec<&str>) -> Result<String> {
22    Ok(values.iter().fold(String::new(), |acc, s| acc + s + "\n"))
23}
24
25fn yaml_lister(header: &str, values: Vec<&str>) -> Result<String> {
26    let mut output: Vec<HashMap<&str, &str>> = vec![];
27    for value in values.iter() {
28        let mut temp: HashMap<&str, &str> = HashMap::new();
29        let _ = temp.insert(header, value);
30        output.push(temp);
31    }
32    serde_yaml::to_string(&output).context("Failed to output list as yaml.")
33}
34
35fn json_lister(header: &str, values: Vec<&str>) -> Result<String> {
36    let mut output: Vec<HashMap<&str, &str>> = vec![];
37    for value in values.iter() {
38        let mut temp: HashMap<&str, &str> = HashMap::new();
39        let _ = temp.insert(header, value);
40        output.push(temp);
41    }
42    serde_json::to_string_pretty(&output).context("Failed to output list as json.")
43}
44
45fn table_lister(header: &str, values: Vec<&str>) -> Result<String> {
46    let mut table = Table::new();
47    table.style = term_table::TableStyle::simple();
48
49    let header = Row::new(vec![TableCell::new(header)]);
50    table.add_row(header);
51
52    values.iter().for_each(|value| {
53        let mut row = Row::new(vec![TableCell::new(value)]);
54        row.has_separator = false;
55        table.add_row(row);
56    });
57
58    if table.rows.len() > 1 {
59        table.rows[1].has_separator = true;
60    }
61
62    Ok(table.render())
63}
64
65pub fn get_lister(format: Format) -> ListerResult {
66    match format {
67        Format::value => Ok(value_lister),
68        Format::table => Ok(table_lister),
69        Format::yaml => Ok(yaml_lister),
70        Format::json => Ok(json_lister),
71    }
72}
73
74fn value_showone(data: IndexMap<String, String>, retract_fields: Vec<String>) -> Result<String> {
75    Ok(data
76        .iter()
77        .map(|(key, value)| {
78            if retract_fields.contains(key) {
79                "<redacted>"
80            } else {
81                value
82            }
83        })
84        .fold(String::new(), |acc, value| acc + value + "\n"))
85}
86
87fn table_showone(data: IndexMap<String, String>, retract_fields: Vec<String>) -> Result<String> {
88    let mut table = Table::new();
89    table.style = term_table::TableStyle::simple();
90    table.separate_rows = false;
91
92    data.iter().for_each(|(key, value)| {
93        let value = {
94            if retract_fields.contains(key) {
95                "<redacted>"
96            } else {
97                value.as_str()
98            }
99        };
100
101        table.add_row(Row::new(vec![TableCell::new(key), TableCell::new(value)]));
102    });
103
104    Ok(table.render())
105}
106
107fn yaml_showone(data: IndexMap<String, String>, retract_fields: Vec<String>) -> Result<String> {
108    let redacted = String::from("<redacted>");
109    let mut temp: HashMap<_, _> = data.iter().collect();
110    data.iter().for_each(|(key, value)| {
111        let _ = temp.insert(key, {
112            if retract_fields.contains(key) {
113                &redacted
114            } else {
115                value
116            }
117        });
118    });
119    serde_yaml::to_string(&temp).context("Failed to output one result as yaml.")
120}
121
122fn json_showone(data: IndexMap<String, String>, retract_fields: Vec<String>) -> Result<String> {
123    let redacted = String::from("<redacted>");
124    let mut temp: HashMap<_, _> = data.iter().collect();
125    data.iter().for_each(|(key, value)| {
126        let _ = temp.insert(key, {
127            if retract_fields.contains(key) {
128                &redacted
129            } else {
130                value
131            }
132        });
133    });
134    serde_json::to_string_pretty(&temp).context("Failed to output one result as json.")
135}
136
137pub fn get_showone(format: Format) -> ShowOneResult {
138    match format {
139        Format::value => Ok(value_showone),
140        Format::table => Ok(table_showone),
141        Format::yaml => Ok(yaml_showone),
142        Format::json => Ok(json_showone),
143    }
144}
145
146#[cfg(test)]
147mod tests {
148    use super::*;
149
150    #[test]
151    fn value_lister() {
152        let values = vec!["foo", "bar"];
153        assert_eq!(
154            "foo\nbar\n",
155            get_lister(Format::value).unwrap()("biz", values).unwrap()
156        );
157    }
158
159    #[test]
160    fn value_lister_empty() {
161        let values = Vec::new();
162        assert_eq!(
163            "",
164            get_lister(Format::value).unwrap()("biz", values).unwrap()
165        );
166    }
167
168    #[test]
169    fn table_lister() {
170        let values = vec!["foo", "bar"];
171        assert_eq!(
172            "+-----+\n| biz |\n+-----+\n| foo |\n| bar |\n+-----+\n",
173            get_lister(Format::table).unwrap()("biz", values).unwrap()
174        );
175    }
176
177    #[test]
178    fn table_lister_empty() {
179        let values = vec![];
180        assert_eq!(
181            "+-----+\n| biz |\n+-----+\n",
182            get_lister(Format::table).unwrap()("biz", values).unwrap()
183        );
184    }
185
186    #[test]
187    fn yaml_lister() {
188        let values = vec!["foo", "bar"];
189        assert_eq!(
190            "---\n- biz: foo\n- biz: bar",
191            get_lister(Format::yaml).unwrap()("biz", values).unwrap()
192        );
193    }
194
195    #[test]
196    fn yaml_lister_empty() {
197        let values = Vec::new();
198        assert_eq!(
199            "---\n[]",
200            get_lister(Format::yaml).unwrap()("biz", values).unwrap()
201        );
202    }
203
204    #[test]
205    fn json_lister() {
206        let values = vec!["foo", "bar"];
207        assert_eq!(
208            "[\n  {\n    \"biz\": \"foo\"\n  },\n  {\n    \"biz\": \"bar\"\n  }\n]",
209            get_lister(Format::json).unwrap()("biz", values).unwrap()
210        );
211    }
212
213    #[test]
214    fn json_lister_empty() {
215        let values = Vec::new();
216        assert_eq!(
217            "[]",
218            get_lister(Format::json).unwrap()("biz", values).unwrap()
219        );
220    }
221
222    #[test]
223    fn value_showone() {
224        let mut data = IndexMap::new();
225        let _ = data.insert("foo_key".into(), "foo_value".into());
226        let _ = data.insert("bar_key".into(), "".into());
227
228        assert_eq!(
229            "foo_value\n\n",
230            get_showone(Format::value).unwrap()(data, vec![]).unwrap()
231        );
232    }
233
234    #[test]
235    fn value_showone_empty() {
236        let data = IndexMap::new();
237
238        assert_eq!(
239            "",
240            get_showone(Format::value).unwrap()(data, vec![]).unwrap()
241        );
242    }
243
244    #[test]
245    fn value_showone_redacted() {
246        let mut data = IndexMap::new();
247        let _ = data.insert("foo_key".into(), "foo_value".into());
248        let _ = data.insert("bar_key".into(), "".into());
249
250        assert_eq!(
251            "<redacted>\n\n",
252            get_showone(Format::value).unwrap()(data, vec!["foo_key".into()]).unwrap()
253        );
254    }
255
256    #[test]
257    fn table_showone() {
258        let mut map = indexmap::IndexMap::new();
259        let _ = map.insert("foo".into(), "bar".into());
260
261        assert_eq!(
262            "+-----+-----+\n| foo | bar |\n+-----+-----+\n",
263            get_showone(Format::table).unwrap()(map, vec![]).unwrap()
264        );
265    }
266
267    #[test]
268    fn table_showone_empty() {
269        let map = indexmap::IndexMap::new();
270
271        assert_eq!(
272            "",
273            get_showone(Format::table).unwrap()(map, vec![]).unwrap()
274        );
275    }
276
277    #[test]
278    fn table_showone_redacted() {
279        let mut map = indexmap::IndexMap::new();
280        let _ = map.insert("foo".into(), "bar".into());
281
282        assert_eq!(
283            "+-----+------------+\n| foo | <redacted> |\n+-----+------------+\n",
284            get_showone(Format::table).unwrap()(map, vec!["foo".into()]).unwrap()
285        );
286    }
287
288    #[test]
289    fn yaml_showone() {
290        let mut map = indexmap::IndexMap::new();
291        let _ = map.insert("foo".into(), "bar".into());
292
293        assert_eq!(
294            "---\nfoo: bar",
295            get_showone(Format::yaml).unwrap()(map, vec![]).unwrap()
296        );
297    }
298
299    #[test]
300    fn yaml_showone_empty() {
301        let map = indexmap::IndexMap::new();
302
303        assert_eq!(
304            "---\n{}",
305            get_showone(Format::yaml).unwrap()(map, vec![]).unwrap()
306        );
307    }
308
309    #[test]
310    fn yaml_showone_redacted() {
311        let mut map = indexmap::IndexMap::new();
312        let _ = map.insert("foo".into(), "bar".into());
313
314        assert_eq!(
315            "---\nfoo: \"<redacted>\"",
316            get_showone(Format::yaml).unwrap()(map, vec!["foo".into()]).unwrap()
317        );
318    }
319
320    #[test]
321    fn json_showone() {
322        let mut map = indexmap::IndexMap::new();
323        let _ = map.insert("foo".into(), "bar".into());
324
325        assert_eq!(
326            "{\n  \"foo\": \"bar\"\n}",
327            get_showone(Format::json).unwrap()(map, vec![]).unwrap()
328        );
329    }
330
331    #[test]
332    fn json_showone_empty() {
333        let map = indexmap::IndexMap::new();
334
335        assert_eq!(
336            "{}",
337            get_showone(Format::json).unwrap()(map, vec![]).unwrap()
338        );
339    }
340
341    #[test]
342    fn json_showone_redacted() {
343        let mut map = indexmap::IndexMap::new();
344        let _ = map.insert("foo".into(), "bar".into());
345
346        assert_eq!(
347            "{\n  \"foo\": \"<redacted>\"\n}",
348            get_showone(Format::json).unwrap()(map, vec!["foo".into()]).unwrap()
349        );
350    }
351}