1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
use std::collections::HashSet;

use seaplane::api::{
    restrict::v1::{RestrictedDirectory, RestrictionDetails},
    shared::v1::{Provider as ProviderModel, Region as RegionModel},
};

use crate::{
    cli::cmds::restrict::{
        common::{Provider, Region},
        SeaplaneRestrictCommonArgMatches, SeaplaneRestrictListArgMatches,
        SeaplaneRestrictSetArgMatches,
    },
    error::Result,
};

/// Represents the "Source of Truth" i.e. it combines all the CLI options, ENV vars, and config
/// values into a single structure that can be used later to build models for the API or local
/// structs for serializing
// TODO: we may not want to derive this we implement circular references
#[derive(Debug, Default, Clone)]
pub struct RestrictCtx {
    pub api: Option<String>,
    /// A base64 encoded directory
    pub directory: Option<RestrictedDirectory>,
    pub from_api: Option<String>,
    /// A base64 encoded key
    pub from_dir: Option<String>,
    /// Use actual API model since that is ultimately what we want
    pub providers_allowed: HashSet<ProviderModel>,
    /// Use actual API model since that is ultimately what we want
    pub providers_denied: HashSet<ProviderModel>,
    /// Use actual API model since that is ultimately what we want
    pub regions_allowed: HashSet<RegionModel>,
    /// Use actual API model since that is ultimately what we want
    pub regions_denied: HashSet<RegionModel>,
    /// Is the directory already URL safe base64 encoded
    pub base64: bool,
    /// Print with decoding
    pub decode: bool,
    /// Skip the headers in --format=table
    pub no_header: bool,
}

impl RestrictCtx {
    /// Builds a RestictCtx from ArgMatches
    pub fn from_restrict_common(matches: &SeaplaneRestrictCommonArgMatches) -> Result<RestrictCtx> {
        let matches = matches.0;
        let base64 = matches.contains_id("base64");
        let api = matches.get_one::<String>("api").unwrap();
        let dir = matches.get_one::<String>("directory").unwrap();

        Ok(RestrictCtx {
            api: Some(api.into()),
            directory: if base64 {
                // Check that what the user passed really is valid base64
                let _ = base64::decode_config(dir, base64::URL_SAFE_NO_PAD)?;
                Some(RestrictedDirectory::from_encoded(dir))
            } else {
                Some(RestrictedDirectory::from_unencoded(dir))
            },
            base64: true, // At this point all keys and values should be encoded as base64
            ..RestrictCtx::default()
        })
    }

    /// Builds a RestictCtx from ArgMatches
    pub fn from_restrict_list(matches: &SeaplaneRestrictListArgMatches) -> Result<RestrictCtx> {
        let api = matches.0.get_one::<String>("api").map(|a| a.to_owned());

        Ok(RestrictCtx { api, ..RestrictCtx::default() })
    }

    /// Builds a RestictCtx from ArgMatches
    pub fn from_restrict_set(matches: &SeaplaneRestrictSetArgMatches) -> Result<RestrictCtx> {
        let matches = matches.0;
        let base64 = matches.contains_id("base64");
        let raw_api = matches.get_one::<String>("api").unwrap();
        let raw_dir = matches.get_one::<String>("directory").unwrap();

        let providers_allowed: HashSet<ProviderModel> = matches
            .get_many::<Provider>("provider")
            .unwrap_or_default()
            .filter_map(Provider::into_model)
            .collect();
        let providers_denied: HashSet<ProviderModel> = matches
            .get_many::<Provider>("exclude-provider")
            .unwrap_or_default()
            .filter_map(Provider::into_model)
            .collect();
        let regions_allowed: HashSet<RegionModel> = matches
            .get_many::<Region>("region")
            .unwrap_or_default()
            .filter_map(Region::into_model)
            .collect();
        let regions_denied: HashSet<RegionModel> = matches
            .get_many::<Region>("exclude-region")
            .unwrap_or_default()
            .filter_map(Region::into_model)
            .collect();
        Ok(RestrictCtx {
            api: Some(raw_api.into()),
            directory: if base64 {
                // Check that what the user passed really is valid base64
                let _ = base64::decode_config(raw_dir, base64::URL_SAFE_NO_PAD)?;
                Some(RestrictedDirectory::from_encoded(raw_dir))
            } else {
                Some(RestrictedDirectory::from_unencoded(raw_dir))
            },
            base64: true, // At this point all keys and values should be encoded as base64
            providers_allowed,
            providers_denied,
            regions_allowed,
            regions_denied,
            ..RestrictCtx::default()
        })
    }

    pub fn restriction_details(&self) -> Result<RestrictionDetails> {
        let mut builder = RestrictionDetails::builder();

        for item in &self.providers_allowed {
            builder = builder.add_allowed_provider(*item);
        }
        for item in &self.providers_denied {
            builder = builder.add_denied_provider(*item);
        }
        for item in &self.regions_allowed {
            builder = builder.add_allowed_region(*item);
        }
        for item in &self.regions_denied {
            builder = builder.add_denied_region(*item);
        }

        Ok(builder.build()?)
    }
}