seaplane_cli/ops/
restrict.rs

1use std::{collections::BTreeSet, fmt::Display, io::Write};
2
3use seaplane::api::restrict::v1::{RestrictedDirectory as RestrictedDirectoryModel, Restriction};
4use serde::Serialize;
5use tabwriter::TabWriter;
6
7use super::EncodedString;
8use crate::{
9    context::Ctx,
10    error::{CliError, Result},
11    printer::{printer, Output},
12};
13
14/// We use our own RestrictedDirectory instead of the models because we need to
15/// *not* enforce base64 encoding, and implement a bunch of additional methods
16/// and traits that wouldn't make sense for the models
17///
18/// We also need to keep track if the values are encoded or not
19#[derive(Debug, Clone, Serialize)]
20pub struct RestrictedDirectory {
21    pub directory: EncodedString,
22}
23
24impl RestrictedDirectory {
25    /// Creates a new RestrictedDirectory from an encoded directory.
26    /// The directory must be URL safe base64 encoded or Bad Things may happen.
27    pub fn new<S: Into<String>>(directory: S) -> Self {
28        Self { directory: EncodedString::new(directory.into()) }
29    }
30
31    /// Creates a new RestrictedDirectoryModel from self's data.
32    pub fn to_model(&self) -> RestrictedDirectoryModel {
33        RestrictedDirectoryModel::from_encoded(self.directory.to_string())
34    }
35}
36
37impl Output for Restriction {
38    fn print_json(&self, _ctx: &Ctx) -> Result<()> {
39        cli_println!("{}", serde_json::to_string(self)?);
40        Ok(())
41    }
42
43    fn print_table(&self, ctx: &Ctx) -> Result<()> {
44        let restrict_ctx = ctx.restrict_ctx.get_or_init();
45        let restrictions = Restrictions::from_model(vec![self.clone()]);
46        restrictions.impl_print_table(!restrict_ctx.no_header, restrict_ctx.decode)?;
47        Ok(())
48    }
49}
50
51#[derive(Debug, Default, Clone, Serialize)]
52#[serde(transparent)]
53pub struct Restrictions {
54    inner: Vec<Restriction>,
55}
56
57impl Restrictions {
58    pub fn from_model(model: Vec<Restriction>) -> Self { Self { inner: model } }
59
60    pub fn iter(&self) -> impl Iterator<Item = &Restriction> { self.inner.iter() }
61
62    // print a table in whatever state we happen to be in (encoded/unencoded)
63    fn impl_print_table(&self, headers: bool, decode: bool) -> Result<()> {
64        let mut tw = TabWriter::new(Vec::new());
65
66        // Helper function for displaying region and provider HashSets
67        fn join_set<S: Display>(set: BTreeSet<S>) -> String {
68            let mut vec = set.iter().map(|s| s.to_string()).collect::<Vec<String>>();
69            vec.sort();
70            vec.join(",")
71        }
72
73        if headers {
74            writeln!(tw, "API\tDIRECTORY\tSTATE\tREGIONS ALLOWED\tREGIONS DENIED\tPROVIDERS ALLOWED\tPROVIDERS DENIED")?;
75        }
76
77        for restriction in self.iter() {
78            let rd = RestrictedDirectory::new(restriction.directory.encoded());
79            write!(tw, "{}\t", restriction.api)?;
80
81            if decode {
82                tw.write_all(&rd.directory.decoded()?)?;
83            } else {
84                write!(tw, "{}", rd.directory)?;
85            }
86
87            writeln!(
88                tw,
89                "\t{}\t[{}]\t[{}]\t[{}]\t[{}]",
90                restriction.state,
91                join_set(restriction.details.regions_allowed.clone()),
92                join_set(restriction.details.regions_denied.clone()),
93                join_set(restriction.details.providers_allowed.clone()),
94                join_set(restriction.details.providers_denied.clone())
95            )?;
96        }
97        tw.flush()?;
98
99        let mut ptr = printer();
100        let page = tw
101            .into_inner()
102            .map_err(|_| CliError::bail("IO flush error writing restrictions"))?;
103        ptr.write_all(&page)?;
104        ptr.flush()?;
105
106        Ok(())
107    }
108}
109
110impl Output for Restrictions {
111    fn print_json(&self, _ctx: &Ctx) -> Result<()> {
112        cli_println!("{}", serde_json::to_string(self)?);
113        Ok(())
114    }
115
116    fn print_table(&self, ctx: &Ctx) -> Result<()> {
117        let restrict_ctx = ctx.restrict_ctx.get_or_init();
118        self.impl_print_table(!restrict_ctx.no_header, restrict_ctx.decode)
119    }
120}