use std::io::Write;
use seaplane::api::metadata::v1::KeyValue as KeyValueModel;
use serde::Serialize;
use tabwriter::TabWriter;
use unicode_segmentation::UnicodeSegmentation;
use crate::{
context::Ctx,
error::{CliError, Result},
ops::EncodedString,
printer::{printer, Output},
};
#[derive(Debug, Default, Clone, Serialize)]
pub struct KeyValue {
pub key: EncodedString,
pub value: EncodedString,
}
impl KeyValue {
pub fn new<S: Into<String>>(key: S, value: S) -> Self {
Self { key: EncodedString::new(key.into()), value: EncodedString::new(value.into()) }
}
pub fn new_unencoded<S: AsRef<str>>(key: S, value: S) -> Self {
let engine = ::base64::engine::fast_portable::FastPortable::from(
&::base64::alphabet::URL_SAFE,
::base64::engine::fast_portable::NO_PAD,
);
Self::new(
base64::encode_engine(key.as_ref(), &engine),
base64::encode_engine(value.as_ref(), &engine),
)
}
pub fn from_key_unencoded<S: AsRef<str>>(key: S) -> Self {
let engine = ::base64::engine::fast_portable::FastPortable::from(
&::base64::alphabet::URL_SAFE,
::base64::engine::fast_portable::NO_PAD,
);
Self::from_key(base64::encode_engine(key.as_ref(), &engine))
}
pub fn from_key<S: Into<String>>(key: S) -> Self {
Self { key: EncodedString::new(key.into()), ..Self::default() }
}
pub fn set_value<S: Into<String>>(&mut self, value: S) {
self.value = EncodedString::new(value.into())
}
}
#[derive(Debug, Default, Clone, Serialize)]
#[serde(transparent)]
pub struct KeyValues {
inner: Vec<KeyValue>,
}
impl KeyValues {
pub fn from_model(model: Vec<KeyValueModel>) -> Self {
Self {
inner: model
.iter()
.map(|kv| KeyValue::new(kv.key.as_ref(), kv.value.as_ref()))
.collect(),
}
}
pub fn insert<S: Into<String>>(&mut self, key: S, value: S) {
self.inner.push(KeyValue::new(key, value));
}
pub fn iter(&self) -> impl Iterator<Item = &KeyValue> { self.inner.iter() }
pub fn iter_mut(&mut self) -> impl Iterator<Item = &mut KeyValue> { self.inner.iter_mut() }
pub fn push(&mut self, kv: KeyValue) { self.inner.push(kv) }
pub fn keys(&self) -> impl Iterator<Item = EncodedString> + '_ {
self.inner.iter().map(|kv| kv.key.clone())
}
}
impl Output for KeyValues {
fn print_json(&self, _ctx: &Ctx) -> Result<()> {
cli_println!("{}", serde_json::to_string(self)?);
Ok(())
}
fn print_table(&self, ctx: &Ctx) -> Result<()> {
let mdctx = ctx.md_ctx.get_or_init();
let headers = !mdctx.no_header;
let decode = mdctx.decode;
let decode_safe = mdctx.decode_safe;
let only_keys = mdctx.no_values;
let only_values = mdctx.no_keys;
let keys_width_limit = mdctx.keys_width_limit;
let values_width_limit = mdctx.values_width_limit;
let mut tw = TabWriter::new(Vec::new());
if headers {
match [only_keys, only_values] {
[true, false] => writeln!(tw, "KEY")?,
[false, true] => writeln!(tw, "VALUE")?,
[..] => writeln!(tw, "KEY\tVALUE")?,
}
}
fn truncate_vec(vec: Vec<u8>, max: usize) -> Vec<u8> {
if max == 0 || vec.len() <= max {
vec
} else {
let mut vec = vec;
vec.truncate(max);
vec.extend_from_slice("…".as_bytes());
vec
}
}
fn truncate_string(str: String, max: usize) -> String {
if max == 0 || str.graphemes(true).count() <= max {
str
} else {
let mut s: String = str.graphemes(true).take(max).collect();
s.push('…');
s
}
}
for kv in self.iter() {
if !only_values {
if decode {
tw.write_all(&truncate_vec(kv.key.decoded()?, keys_width_limit))?;
} else if decode_safe {
write!(tw, "{}", truncate_string(kv.key.decoded_safe()?, keys_width_limit))?;
} else {
write!(tw, "{}", truncate_string(kv.key.to_string(), keys_width_limit))?;
}
if !only_keys {
tw.write_all(b"\t")?
};
};
if !only_keys {
if decode {
tw.write_all(&truncate_vec(kv.value.decoded()?, values_width_limit))?;
} else if decode_safe {
write!(
tw,
"{}",
truncate_string(kv.value.decoded_safe()?, values_width_limit)
)?;
} else {
write!(tw, "{}", truncate_string(kv.value.to_string(), values_width_limit))?;
}
};
writeln!(tw)?;
}
tw.flush()?;
let mut ptr = printer();
let page = tw
.into_inner()
.map_err(|_| CliError::bail("IO flush error writing key-values"))?;
ptr.write_all(&page)?;
ptr.flush()?;
Ok(())
}
}
#[cfg(test)]
mod tests {
use serde_json::json;
use super::*;
fn build_kvs() -> KeyValues {
KeyValues {
inner: vec![
KeyValue {
key: EncodedString::new("a2V5MQ".into()),
value: EncodedString::new("dmFsdWUx".into()),
},
KeyValue {
key: EncodedString::new("a2V5Mg".into()),
value: EncodedString::new("dmFsdWUy".into()),
},
KeyValue {
key: EncodedString::new("a2V5Mw".into()),
value: EncodedString::new("dmFsdWUz".into()),
},
],
}
}
#[test]
fn serialize_keyvalues_base64() {
let kvs = build_kvs();
assert_eq!(
serde_json::to_string(&kvs).unwrap(),
json!([{"key": "a2V5MQ", "value": "dmFsdWUx"}, {"key": "a2V5Mg", "value": "dmFsdWUy"}, {"key": "a2V5Mw", "value": "dmFsdWUz"}]).to_string()
);
}
}