seaplane_cli/api/
metadata.rs

1use reqwest::Url;
2use seaplane::{
3    api::{
4        identity::v0::AccessToken,
5        metadata::v1::{
6            Key, KeyValue as KeyValueModel, KeyValueRange as KeyValueRangeModel, MetadataRequest,
7            Value as ValueModel,
8        },
9        shared::v1::RangeQueryContext,
10        ApiErrorKind,
11    },
12    error::SeaplaneError,
13};
14
15use crate::{
16    api::request_token,
17    context::Ctx,
18    error::{CliError, Result},
19};
20
21/// Wraps an SDK `MetadataRequest` where we do additional things like re-use request access
22/// tokens, allow changing the Formation this request is pointed to, and map errors appropriately.
23#[derive(Debug)]
24pub struct MetadataReq {
25    api_key: String,
26    key: Option<String>,
27    range: Option<RangeQueryContext<Key>>,
28    token: Option<AccessToken>,
29    inner: Option<MetadataRequest>,
30    identity_url: Option<Url>,
31    metadata_url: Option<Url>,
32    insecure_urls: bool,
33    invalid_certs: bool,
34}
35
36impl MetadataReq {
37    pub fn new(ctx: &Ctx) -> Result<Self> {
38        Ok(Self {
39            api_key: ctx.args.api_key()?.into(),
40            key: None,
41            range: None,
42            token: None,
43            inner: None,
44            identity_url: ctx.identity_url.clone(),
45            metadata_url: ctx.metadata_url.clone(),
46            #[cfg(feature = "allow_insecure_urls")]
47            insecure_urls: ctx.insecure_urls,
48            #[cfg(not(feature = "allow_insecure_urls"))]
49            insecure_urls: false,
50            #[cfg(feature = "allow_invalid_certs")]
51            invalid_certs: ctx.invalid_certs,
52            #[cfg(not(feature = "allow_invalid_certs"))]
53            invalid_certs: false,
54        })
55    }
56
57    pub fn set_key<S: Into<String>>(&mut self, key: S) -> Result<()> {
58        self.key = Some(key.into());
59        self.range = None;
60        self.refresh_inner()
61    }
62
63    pub fn set_dir(&mut self, dir: RangeQueryContext<Key>) -> Result<()> {
64        self.range = Some(dir);
65        self.key = None;
66        self.refresh_inner()
67    }
68
69    /// Request a new Access Token
70    pub fn refresh_token(&mut self) -> Result<()> {
71        self.token = Some(request_token(
72            &self.api_key,
73            self.identity_url.as_ref(),
74            self.insecure_urls,
75            self.invalid_certs,
76        )?);
77        Ok(())
78    }
79
80    /// Re-build the inner `MetadataRequest`. This is mostly useful when one wants to point at
81    /// different Metadata than the original request was pointed at. This method will also refresh
82    /// the access token, only if required.
83    fn refresh_inner(&mut self) -> Result<()> {
84        let mut builder = MetadataRequest::builder().token(self.token_or_refresh()?);
85
86        #[cfg(feature = "allow_insecure_urls")]
87        {
88            builder = builder.allow_http(self.insecure_urls);
89        }
90        #[cfg(feature = "allow_invalid_certs")]
91        {
92            builder = builder.allow_invalid_certs(self.invalid_certs);
93        }
94        if let Some(url) = &self.metadata_url {
95            builder = builder.base_url(url);
96        }
97
98        if let Some(key) = &self.key {
99            builder = builder.encoded_key(key);
100        }
101
102        if let Some(range) = &self.range {
103            builder = builder.range(range.clone());
104        }
105
106        self.inner = Some(builder.build().map_err(CliError::from)?);
107        Ok(())
108    }
109
110    /// Retrieves the JWT access token, requesting a new one if required.
111    pub fn token_or_refresh(&mut self) -> Result<&str> {
112        if self.token.is_none() {
113            self.refresh_token()?;
114        }
115        Ok(&self.token.as_ref().unwrap().token)
116    }
117}
118
119// Wrapped MetadataRequest methods to handle expired token retries
120impl MetadataReq {
121    pub fn get_value(&mut self) -> Result<ValueModel> { maybe_retry!(self.get_value()) }
122    pub fn put_value_unencoded<S: AsRef<[u8]>>(&mut self, value: S) -> Result<()> {
123        maybe_retry!(self.put_value_unencoded(value.as_ref()))
124    }
125    pub fn put_value(&mut self, value: ValueModel) -> Result<()> {
126        maybe_retry_cloned!(self.put_value(value))
127    }
128    pub fn delete_value(&mut self) -> Result<()> { maybe_retry!(self.delete_value()) }
129    pub fn get_page(&mut self) -> Result<KeyValueRangeModel> { maybe_retry!(self.get_page()) }
130    pub fn get_all_pages(&mut self) -> Result<Vec<KeyValueModel>> {
131        maybe_retry_cloned!(self.get_all_pages())
132    }
133}