use reqwest::Url;
use seaplane::{
api::{
identity::v0::AccessToken,
locks::v1::{
HeldLock as HeldLockModel, LockId, LockInfo as LockInfoModel, LockInfoRange, LockName,
LocksRequest, LocksRequestBuilder,
},
shared::v1::{Directory, RangeQueryContext},
ApiErrorKind,
},
error::SeaplaneError,
};
use crate::{
api::request_token,
context::Ctx,
error::{CliError, Result},
};
#[derive(Debug)]
pub struct LocksReq {
api_key: String,
lock_id: Option<String>,
name: Option<LockName>,
token: Option<AccessToken>,
inner: Option<LocksRequest>,
identity_url: Option<Url>,
locks_url: Option<Url>,
insecure_urls: bool,
invalid_certs: bool,
}
impl LocksReq {
pub fn new(ctx: &Ctx) -> Result<Self> {
Ok(Self {
api_key: ctx.args.api_key()?.into(),
lock_id: None,
name: None,
token: None,
inner: None,
identity_url: ctx.identity_url.clone(),
locks_url: ctx.locks_url.clone(),
#[cfg(feature = "allow_insecure_urls")]
insecure_urls: ctx.insecure_urls,
#[cfg(not(feature = "allow_insecure_urls"))]
insecure_urls: false,
#[cfg(feature = "allow_invalid_certs")]
invalid_certs: ctx.invalid_certs,
#[cfg(not(feature = "allow_invalid_certs"))]
invalid_certs: false,
})
}
pub fn set_identifiers<S: Into<String>>(
&mut self,
name: Option<LockName>,
lock_id: Option<S>,
) -> Result<()> {
self.name = name;
self.lock_id = lock_id.map(|s| s.into());
self.refresh_inner()
}
pub fn set_name(&mut self, name: LockName) -> Result<()> {
self.name = Some(name);
self.refresh_inner()
}
pub fn refresh_token(&mut self) -> Result<()> {
self.token = Some(request_token(
&self.api_key,
self.identity_url.as_ref(),
self.insecure_urls,
self.invalid_certs,
)?);
Ok(())
}
fn refresh_inner(&mut self) -> Result<()> {
let mut builder = LocksRequest::builder().token(self.token_or_refresh()?);
#[cfg(feature = "allow_insecure_urls")]
{
builder = builder.allow_http(self.insecure_urls);
}
#[cfg(feature = "allow_invalid_certs")]
{
builder = builder.allow_invalid_certs(self.invalid_certs);
}
if self.name.is_none() {
panic!("all LocksRequests must have a name")
}
match &self.lock_id {
Some(lock_id) => {
let default_sequencer_value = 0u32;
builder = builder.held_lock(HeldLockModel::new(
self.name.clone().unwrap(),
LockId::from_encoded(lock_id),
default_sequencer_value,
));
}
None => builder = builder.lock_name(self.name.clone().unwrap()),
}
if let Some(url) = &self.locks_url {
builder = builder.base_url(url);
}
self.inner = Some(builder.build().map_err(CliError::from)?);
Ok(())
}
pub fn token_or_refresh(&mut self) -> Result<&str> {
if self.token.is_none() {
self.refresh_token()?;
}
Ok(&self.token.as_ref().unwrap().token)
}
pub fn get_page(
&mut self,
next_key: Option<LockName>,
dir: Option<LockName>,
) -> Result<LockInfoRange> {
let mut range = RangeQueryContext::new();
if let Some(k) = next_key {
range.set_from(k);
}
if let Some(d) = dir {
range.set_directory(Directory::from_encoded(d.encoded()));
}
let mut builder = LocksRequestBuilder::new()
.token(self.token_or_refresh()?)
.range(range.clone());
#[cfg(feature = "allow_insecure_urls")]
{
builder = builder.allow_http(self.insecure_urls);
}
#[cfg(feature = "allow_invalid_certs")]
{
builder = builder.allow_http(self.invalid_certs);
}
if let Some(url) = &self.locks_url {
builder = builder.base_url(url);
}
let req = builder.build().unwrap();
match req.get_page() {
Err(SeaplaneError::ApiResponse(ae)) if ae.kind == ApiErrorKind::Unauthorized => {
self.token = Some(request_token(
&self.api_key,
self.identity_url.as_ref(),
self.insecure_urls,
self.invalid_certs,
)?);
let next_req = LocksRequestBuilder::new()
.token(self.token_or_refresh()?)
.range(range)
.build()
.unwrap();
Ok(next_req.get_page()?)
}
result => result.map_err(CliError::from),
}
}
}
impl LocksReq {
pub fn acquire(&mut self, ttl: u32, client_id: &str) -> Result<HeldLockModel> {
maybe_retry!(self.acquire(ttl, client_id))
}
pub fn release(&mut self) -> Result<()> { maybe_retry!(self.release()) }
pub fn renew(&mut self, ttl: u32) -> Result<()> { maybe_retry!(self.renew(ttl)) }
pub fn get_lock_info(&mut self) -> Result<LockInfoModel> { maybe_retry!(self.get_lock_info()) }
}