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}