use std::borrow::Borrow;
use std::fmt;
use inspector::ResultInspector;
use serde::{de, ser};
use uuid::Uuid;
use crate::error::RexError;
use crate::latest;
use crate::output::{IntoOutput, Output};
use crate::show::Show;
pub(crate) use endpoint::ApiEndpoint;
mod endpoint;
mod realm;
mod volume;
pub(crate) type ApiResult<T> = Result<Output<T>, anyhow::Error>;
#[derive(Debug)]
pub(crate) struct Api {
api: ApiEndpoint,
base: String,
token: Option<String>,
json: bool,
raw: bool,
verbose: bool,
}
impl Api {
pub(crate) fn new(
management: &str,
api: ApiEndpoint,
token: &Option<String>,
json: bool,
raw: bool,
verbose: bool,
) -> Self {
let base = if management.starts_with("http") {
format!("{}/{}", management, api.endpoint())
} else if management.starts_with("api.") && management.ends_with(".replix.io") {
format!("https://{}/{}", management, api.endpoint())
} else {
format!("http://{}:63214/{}", management, api.endpoint())
};
let token = token.clone();
Self {
api,
base,
token,
json,
raw,
verbose,
}
}
pub(crate) fn realm(self) -> realm::Realm {
realm::Realm::new(self)
}
pub(crate) fn volume(self) -> volume::Volume {
volume::Volume::new(self)
}
fn url(&self, path: impl fmt::Display) -> String {
format!("{}{}", self.base, path)
}
pub(crate) fn job(
&self,
all: bool,
job_id: Option<Uuid>,
r#type: Option<&str>,
status: &str,
) -> Result<String, anyhow::Error> {
let text = if all {
self.get_jobs().map(Show::show)
} else if let Some(job_id) = job_id {
self.get_job(job_id).map(Show::show)
} else {
self.get_jobs()
.map(|jobs| {
jobs.into_iter()
.filter(|job| job.status.value == status)
.filter(|job| r#type.map(|r#type| job.r#type == r#type).unwrap_or(true))
.collect::<Vec<_>>()
})
.map(Show::show)
}?;
Ok(text)
}
pub(crate) fn version(&self, _crates: bool) -> Result<String, anyhow::Error> {
self.get_version().map(Show::show)
}
fn get_job(&self, job_id: impl fmt::Display) -> ApiResult<latest::Job> {
self.get(format!("/internal/jobs/{}", job_id))
}
fn get_jobs(&self) -> ApiResult<Vec<latest::Job>> {
self.get("/internal/jobs")
}
fn get_version(&self) -> ApiResult<latest::InternalVersion> {
self.get("/internal/version")
}
fn inspect<T>(&self, output: &Output<T>)
where
T: fmt::Debug,
{
if self.verbose {
println!("{:#?}", output);
}
}
fn del<P, I, K, V, T>(&self, path: P, params: I) -> ApiResult<T>
where
P: fmt::Display,
T: de::DeserializeOwned + fmt::Debug,
I: IntoIterator,
I::Item: Borrow<(K, V)>,
K: AsRef<str>,
V: ToString,
{
let url = self.url(path);
let output = attohttpc::delete(url)
.optionally_bearer_auth(self.token.as_ref())
.param("include", "job")
.params(params)
.send()?
.rexerror_for_status()?
.into_output(self.raw, self.json)
.inspect(|output| self.inspect(output))?;
Ok(output)
}
fn get<P, T>(&self, path: P) -> ApiResult<T>
where
P: fmt::Display,
T: de::DeserializeOwned + fmt::Debug,
{
let url = self.url(path);
let output = attohttpc::get(url)
.optionally_bearer_auth(self.token.as_ref())
.param("include", "job")
.send()?
.rexerror_for_status()?
.into_output(self.raw, self.json)
.inspect(|output| self.inspect(output))?;
Ok(output)
}
fn post<P, T, U>(&self, path: P, body: T) -> ApiResult<U>
where
P: fmt::Display,
T: ser::Serialize,
U: de::DeserializeOwned + fmt::Debug,
{
let url = self.url(path);
let output = attohttpc::post(url)
.optionally_bearer_auth(self.token.as_ref())
.param("include", "job")
.json(&body)?
.send()?
.rexerror_for_status()?
.into_output(self.raw, self.json)
.inspect(|output| self.inspect(output))?;
Ok(output)
}
fn put<P, T>(&self, path: P) -> ApiResult<T>
where
P: fmt::Display,
T: de::DeserializeOwned + fmt::Debug,
{
let url = format!("{}{}", self.base, path);
let output = attohttpc::put(url)
.optionally_bearer_auth(self.token.as_ref())
.param("include", "job")
.send()?
.rexerror_for_status()?
.into_output(self.raw, self.json)
.inspect(|output| self.inspect(output))?;
Ok(output)
}
}
trait Optionally {
fn optionally_bearer_auth(self, token: Option<impl Into<String>>) -> Self;
fn optionally<T, F>(self, option: Option<T>, f: F) -> Self
where
F: FnOnce(Self, T) -> Self,
Self: Sized,
{
if let Some(option) = option {
f(self, option)
} else {
self
}
}
}
impl Optionally for attohttpc::RequestBuilder {
fn optionally_bearer_auth(self, token: Option<impl Into<String>>) -> Self {
self.optionally(token, |this, token| this.bearer_auth(token))
}
}