seaplane_cli/context/
metadata.rs

1use std::{
2    fs::File,
3    io::{self, Read},
4};
5
6use seaplane::api::{metadata::v1::Key, shared::v1::Directory};
7
8use crate::{
9    cli::cmds::metadata::{SeaplaneMetadataCommonArgMatches, SeaplaneMetadataSetArgMatches},
10    error::{CliError, Context, Result},
11    ops::metadata::{KeyValue, KeyValues},
12    printer::Color,
13};
14
15/// Represents the "Source of Truth" i.e. it combines all the CLI options, ENV vars, and config
16/// values into a single structure that can be used later to build models for the API or local
17/// structs for serializing
18// TODO: we may not want to derive this we implement circular references
19#[derive(Debug, Default, Clone)]
20pub struct MetadataCtx {
21    pub kvs: KeyValues,
22    pub directory: Option<Directory>,
23    /// Is the key or value already URL safe base64 encoded
24    pub base64: bool,
25    /// Print with decoding
26    pub decode: bool,
27    /// Print with safe decoding
28    pub decode_safe: bool,
29    /// Print with no decoding
30    pub no_decode: bool,
31    /// A base64 encoded key
32    pub from: Option<Key>,
33    /// Skip the KEY or VALUE header in --format=table
34    pub no_header: bool,
35    /// Don't print keys
36    pub no_keys: bool,
37    /// Don't print values
38    pub no_values: bool,
39    /// Max width of keys
40    pub keys_width_limit: usize,
41    /// Max width of values
42    pub values_width_limit: usize,
43}
44
45impl MetadataCtx {
46    /// Builds a MetadataCtx from ArgMatches
47    pub fn from_md_common(matches: &SeaplaneMetadataCommonArgMatches) -> Result<MetadataCtx> {
48        let matches = matches.0;
49        let base64 = matches.get_flag("base64");
50        let raw_keys: Vec<_> = matches.get_many::<String>("key").unwrap().collect();
51
52        let mut kvs = KeyValues::default();
53        for key in raw_keys {
54            if base64 {
55                // Check that what the user passed really is valid base64
56                let engine = ::base64::engine::fast_portable::FastPortable::from(
57                    &::base64::alphabet::URL_SAFE,
58                    ::base64::engine::fast_portable::NO_PAD,
59                );
60                let _ = base64::decode_engine(key, &engine)?;
61                kvs.push(KeyValue::from_key(key));
62            } else {
63                kvs.push(KeyValue::from_key_unencoded(key));
64            }
65        }
66
67        Ok(MetadataCtx {
68            kvs,
69            base64: true, // At this point all keys and values should be encoded as base64
70            ..MetadataCtx::default()
71        })
72    }
73
74    /// Builds a MetadataCtx from ArgMatches
75    pub fn from_md_set(matches: &SeaplaneMetadataSetArgMatches) -> Result<MetadataCtx> {
76        let matches = matches.0;
77        let base64 = matches.get_flag("base64");
78        let raw_key = matches.get_one::<String>("key").unwrap();
79        let raw_value = matches.get_one::<String>("value").unwrap();
80        let value = if let Some(val) = raw_value.strip_prefix('@') {
81            if val == "-" {
82                let mut buf: Vec<u8> = Vec::new();
83                let stdin = io::stdin();
84                let mut stdin_lock = stdin.lock();
85                stdin_lock.read_to_end(&mut buf)?;
86                buf
87            } else {
88                let mut f = File::open(val)
89                    .map_err(CliError::from)
90                    .context("\n\tpath: ")
91                    .with_color_context(|| (Color::Yellow, val))?;
92
93                // TODO: @perf we could pre-allocate the vec based on the file size
94                let mut buf = Vec::new();
95
96                f.read_to_end(&mut buf)?;
97                buf
98            }
99        } else {
100            raw_value.as_bytes().to_vec()
101        };
102
103        let engine = ::base64::engine::fast_portable::FastPortable::from(
104            &::base64::alphabet::URL_SAFE,
105            ::base64::engine::fast_portable::NO_PAD,
106        );
107        let kv = if base64 {
108            // make sure it's valid base64
109            let _ = base64::decode_engine(raw_key, &engine)?;
110            let _ = base64::decode_engine(&value, &engine)?;
111            // The user used `--base64` and it is valid base64 so there is no reason the from_utf8
112            // should fail
113            KeyValue::new(raw_key, &String::from_utf8(value)?)
114        } else {
115            KeyValue::new(
116                base64::encode_engine(raw_key, &engine),
117                base64::encode_engine(value, &engine),
118            )
119        };
120
121        let mut kvs = KeyValues::default();
122        kvs.push(kv);
123
124        Ok(MetadataCtx { kvs, base64: true, ..MetadataCtx::default() })
125    }
126}