seaplane_cli/ops/
metadata.rs1use std::io::Write;
2
3use seaplane::api::metadata::v1::KeyValue as KeyValueModel;
4use serde::Serialize;
5use tabwriter::TabWriter;
6use unicode_segmentation::UnicodeSegmentation;
7
8use crate::{
9 context::Ctx,
10 error::{CliError, Result},
11 ops::EncodedString,
12 printer::{printer, Output},
13};
14
15#[derive(Debug, Default, Clone, Serialize)]
20pub struct KeyValue {
21 pub key: EncodedString,
22 pub value: EncodedString,
23}
24
25impl KeyValue {
26 pub fn new<S: Into<String>>(key: S, value: S) -> Self {
29 Self { key: EncodedString::new(key.into()), value: EncodedString::new(value.into()) }
30 }
31
32 pub fn new_unencoded<S: AsRef<str>>(key: S, value: S) -> Self {
34 let engine = ::base64::engine::fast_portable::FastPortable::from(
35 &::base64::alphabet::URL_SAFE,
36 ::base64::engine::fast_portable::NO_PAD,
37 );
38 Self::new(
39 base64::encode_engine(key.as_ref(), &engine),
40 base64::encode_engine(value.as_ref(), &engine),
41 )
42 }
43
44 pub fn from_key_unencoded<S: AsRef<str>>(key: S) -> Self {
46 let engine = ::base64::engine::fast_portable::FastPortable::from(
47 &::base64::alphabet::URL_SAFE,
48 ::base64::engine::fast_portable::NO_PAD,
49 );
50 Self::from_key(base64::encode_engine(key.as_ref(), &engine))
51 }
52
53 pub fn from_key<S: Into<String>>(key: S) -> Self {
56 Self { key: EncodedString::new(key.into()), ..Self::default() }
57 }
58
59 pub fn set_value<S: Into<String>>(&mut self, value: S) {
61 self.value = EncodedString::new(value.into())
62 }
63}
64
65#[derive(Debug, Default, Clone, Serialize)]
66#[serde(transparent)]
67pub struct KeyValues {
68 inner: Vec<KeyValue>,
69}
70
71impl KeyValues {
72 pub fn from_model(model: Vec<KeyValueModel>) -> Self {
73 Self {
74 inner: model
75 .iter()
76 .map(|kv| KeyValue::new(kv.key.as_ref(), kv.value.as_ref()))
77 .collect(),
78 }
79 }
80
81 pub fn insert<S: Into<String>>(&mut self, key: S, value: S) {
83 self.inner.push(KeyValue::new(key, value));
84 }
85
86 pub fn iter(&self) -> impl Iterator<Item = &KeyValue> { self.inner.iter() }
87
88 pub fn iter_mut(&mut self) -> impl Iterator<Item = &mut KeyValue> { self.inner.iter_mut() }
89
90 pub fn push(&mut self, kv: KeyValue) { self.inner.push(kv) }
91
92 pub fn keys(&self) -> impl Iterator<Item = EncodedString> + '_ {
93 self.inner.iter().map(|kv| kv.key.clone())
94 }
95}
96
97impl Output for KeyValues {
98 fn print_json(&self, _ctx: &Ctx) -> Result<()> {
99 cli_println!("{}", serde_json::to_string(self)?);
100 Ok(())
101 }
102
103 fn print_table(&self, ctx: &Ctx) -> Result<()> {
104 let mdctx = ctx.md_ctx.get_or_init();
105 let headers = !mdctx.no_header;
106 let decode = mdctx.decode;
107 let decode_safe = mdctx.decode_safe;
108 let only_keys = mdctx.no_values;
109 let only_values = mdctx.no_keys;
110 let keys_width_limit = mdctx.keys_width_limit;
111 let values_width_limit = mdctx.values_width_limit;
112
113 let mut tw = TabWriter::new(Vec::new());
114
115 if headers {
116 match [only_keys, only_values] {
117 [true, false] => writeln!(tw, "KEY")?,
118 [false, true] => writeln!(tw, "VALUE")?,
119 [..] => writeln!(tw, "KEY\tVALUE")?,
120 }
121 }
122
123 fn truncate_vec(vec: Vec<u8>, max: usize) -> Vec<u8> {
126 if max == 0 || vec.len() <= max {
127 vec
128 } else {
129 let mut vec = vec;
130 vec.truncate(max);
131 vec.extend_from_slice("…".as_bytes());
132 vec
133 }
134 }
135
136 fn truncate_string(str: String, max: usize) -> String {
139 if max == 0 || str.graphemes(true).count() <= max {
140 str
141 } else {
142 let mut s: String = str.graphemes(true).take(max).collect();
143 s.push('…');
144 s
145 }
146 }
147
148 for kv in self.iter() {
149 if !only_values {
150 if decode {
151 tw.write_all(&truncate_vec(kv.key.decoded()?, keys_width_limit))?;
152 } else if decode_safe {
153 write!(tw, "{}", truncate_string(kv.key.decoded_safe()?, keys_width_limit))?;
154 } else {
155 write!(tw, "{}", truncate_string(kv.key.to_string(), keys_width_limit))?;
156 }
157
158 if !only_keys {
159 tw.write_all(b"\t")?
160 };
161 };
162
163 if !only_keys {
164 if decode {
165 tw.write_all(&truncate_vec(kv.value.decoded()?, values_width_limit))?;
166 } else if decode_safe {
167 write!(
168 tw,
169 "{}",
170 truncate_string(kv.value.decoded_safe()?, values_width_limit)
171 )?;
172 } else {
173 write!(tw, "{}", truncate_string(kv.value.to_string(), values_width_limit))?;
174 }
175 };
176 writeln!(tw)?;
177 }
178 tw.flush()?;
179
180 let mut ptr = printer();
181 let page = tw
182 .into_inner()
183 .map_err(|_| CliError::bail("IO flush error writing key-values"))?;
184 ptr.write_all(&page)?;
185 ptr.flush()?;
186
187 Ok(())
188 }
189}
190
191#[cfg(test)]
192mod tests {
193 use serde_json::json;
194
195 use super::*;
196
197 fn build_kvs() -> KeyValues {
198 KeyValues {
199 inner: vec![
200 KeyValue {
201 key: EncodedString::new("a2V5MQ".into()),
202 value: EncodedString::new("dmFsdWUx".into()),
203 },
204 KeyValue {
205 key: EncodedString::new("a2V5Mg".into()),
206 value: EncodedString::new("dmFsdWUy".into()),
207 },
208 KeyValue {
209 key: EncodedString::new("a2V5Mw".into()),
210 value: EncodedString::new("dmFsdWUz".into()),
211 },
212 ],
213 }
214 }
215
216 #[test]
217 fn serialize_keyvalues_base64() {
218 let kvs = build_kvs();
219
220 assert_eq!(
221 serde_json::to_string(&kvs).unwrap(),
222 json!([{"key": "a2V5MQ", "value": "dmFsdWUx"}, {"key": "a2V5Mg", "value": "dmFsdWUy"}, {"key": "a2V5Mw", "value": "dmFsdWUz"}]).to_string()
223 );
224 }
225}