seaplane_cli/api/
restrict.rs

1use reqwest::Url;
2use seaplane::{
3    api::{
4        identity::v0::AccessToken,
5        restrict::v1::{
6            RestrictRequest, RestrictedDirectory, Restriction, RestrictionDetails, RestrictionRange,
7        },
8        shared::v1::RangeQueryContext,
9        ApiErrorKind,
10    },
11    error::SeaplaneError,
12};
13
14use crate::{
15    api::request_token,
16    context::Ctx,
17    error::{CliError, Result},
18};
19
20/// Wraps an SDK `RestrictRequest` where we do additional things like re-use
21/// request access tokens, and map errors appropriately.
22#[derive(Debug)]
23pub struct RestrictReq {
24    api_key: String,
25    api: Option<String>,
26    directory: Option<String>,
27    from_api: Option<String>,
28    from_dir: Option<String>,
29    token: Option<AccessToken>,
30    inner: Option<RestrictRequest>,
31    identity_url: Option<Url>,
32    metadata_url: Option<Url>,
33    insecure_urls: bool,
34    invalid_certs: bool,
35}
36
37impl RestrictReq {
38    pub fn new(ctx: &Ctx) -> Result<Self> {
39        Ok(Self {
40            api_key: ctx.args.api_key()?.into(),
41            api: None,
42            directory: None,
43            from_api: None,
44            from_dir: None,
45            token: None,
46            inner: None,
47            identity_url: ctx.identity_url.clone(),
48            metadata_url: ctx.metadata_url.clone(),
49            #[cfg(feature = "allow_insecure_urls")]
50            insecure_urls: ctx.insecure_urls,
51            #[cfg(not(feature = "allow_insecure_urls"))]
52            insecure_urls: false,
53            #[cfg(feature = "allow_invalid_certs")]
54            invalid_certs: ctx.invalid_certs,
55            #[cfg(not(feature = "allow_invalid_certs"))]
56            invalid_certs: false,
57        })
58    }
59
60    pub fn set_api<S: Into<String>>(&mut self, api: S) -> Result<()> {
61        self.api = Some(api.into());
62        self.refresh_inner()
63    }
64
65    pub fn set_directory<S: Into<String>>(&mut self, dir: S) -> Result<()> {
66        self.directory = Some(dir.into());
67        self.refresh_inner()
68    }
69
70    pub fn set_from_api<S: Into<String>>(&mut self, api: S) -> Result<()> {
71        self.from_api = Some(api.into());
72        self.refresh_inner()
73    }
74
75    pub fn set_from_dir<S: Into<String>>(&mut self, dir: S) -> Result<()> {
76        self.from_dir = Some(dir.into());
77        self.refresh_inner()
78    }
79
80    /// Request a new Access Token
81    pub fn refresh_token(&mut self) -> Result<()> {
82        self.token = Some(request_token(
83            &self.api_key,
84            self.identity_url.as_ref(),
85            self.insecure_urls,
86            self.invalid_certs,
87        )?);
88        Ok(())
89    }
90
91    /// Re-build the inner `RestrictRequest`. This is mostly useful when one
92    /// wants to point at different Restriction than the original request was
93    /// pointed at. This method will also refresh the access token, only if
94    /// required.
95    fn refresh_inner(&mut self) -> Result<()> {
96        let mut builder = RestrictRequest::builder().token(self.token_or_refresh()?);
97
98        #[cfg(feature = "allow_insecure_urls")]
99        {
100            builder = builder.allow_http(self.insecure_urls);
101        }
102        #[cfg(feature = "allow_invalid_certs")]
103        {
104            builder = builder.allow_invalid_certs(self.invalid_certs);
105        }
106
107        if let Some(url) = &self.metadata_url {
108            builder = builder.base_url(url);
109        }
110
111        match [&self.api, &self.directory] {
112            [Some(api), Some(directory)] => builder = builder.single_restriction(api, directory),
113            [Some(api), None] => {
114                let mut context = RangeQueryContext::<RestrictedDirectory>::new();
115                if let Some(from_dir) = &self.from_dir {
116                    context.set_from(RestrictedDirectory::from_encoded(from_dir));
117                };
118                builder = builder.api_range(api, context)
119            }
120            [None, None] => {
121                let mut context = RangeQueryContext::<RestrictedDirectory>::new();
122                if let Some(from_dir) = &self.from_dir {
123                    context.set_from(RestrictedDirectory::from_encoded(from_dir));
124                };
125                builder = builder.all_range(self.from_api.clone(), context)
126            }
127            [..] => {}
128        };
129
130        self.inner = Some(builder.build().map_err(CliError::from)?);
131        Ok(())
132    }
133
134    /// Retrieves the JWT access token, requesting a new one if required.
135    pub fn token_or_refresh(&mut self) -> Result<&str> {
136        if self.token.is_none() {
137            self.refresh_token()?;
138        }
139        Ok(&self.token.as_ref().unwrap().token)
140    }
141}
142
143// Wrapped RestrictRequest methods to handle expired token retries
144impl RestrictReq {
145    pub fn get_restriction(&mut self) -> Result<Restriction> {
146        maybe_retry!(self.get_restriction())
147    }
148
149    pub fn set_restriction(&mut self, details: RestrictionDetails) -> Result<()> {
150        maybe_retry_cloned!(self.set_restriction(details))
151    }
152    pub fn delete_restriction(&mut self) -> Result<()> { maybe_retry!(self.delete_restriction()) }
153
154    pub fn get_page(&mut self) -> Result<RestrictionRange> { maybe_retry!(self.get_page()) }
155    pub fn get_all_pages(&mut self) -> Result<Vec<Restriction>> {
156        maybe_retry!(self.get_all_pages())
157    }
158}