use reqwest::{Client, StatusCode};
use url::Url;
use crate::error::Error;
use crate::models::directory::{ApiRecord, ServiceInfo};
#[derive(Clone, Debug)]
pub struct DirectoryServiceClient {
inner: Client,
base_url: Url,
}
impl DirectoryServiceClient {
pub fn new(base_url: Url, client: Client) -> Self {
Self {
inner: client,
base_url,
}
}
pub fn new_insecure(base_url: Url) -> Result<Self, Error> {
let client = Client::builder()
.build()
.map_err(|e| Error::Transport(e.to_string()))?;
Ok(Self::new(base_url, client))
}
pub async fn get_service_info(&self) -> Result<ServiceInfo, Error> {
let url = self.url("info/service/v1")?;
let resp = self.inner.get(url).send().await?;
check_ok(resp).await?.json().await.map_err(Into::into)
}
pub async fn get_record(
&self,
provider_id: &str,
api_id: &str,
major_version: i32,
) -> Result<(ApiRecord, String, String), Error> {
let url = self.record_url(provider_id, api_id, major_version)?;
let resp = self.inner.get(url).send().await?;
match resp.status() {
StatusCode::OK => {
let cert = header_value(&resp, "X-BDEW-CERT");
let sig = header_value(&resp, "X-BDEW-SIGNATURE");
let record: ApiRecord = resp.json().await?;
Ok((record, cert, sig))
}
StatusCode::TEMPORARY_REDIRECT => {
let location = header_value(&resp, "Location");
Err(Error::Redirect { url: location })
}
StatusCode::NOT_FOUND => Err(Error::NotFound),
s => Err(Error::Http {
status: s.as_u16(),
body: resp.text().await.unwrap_or_default(),
}),
}
}
pub async fn put_record(
&self,
record: &ApiRecord,
signing_cert: &str,
signature: &str,
) -> Result<bool, Error> {
let url = self.record_url(&record.provider_id, &record.api_id, record.major_version)?;
let resp = self
.inner
.put(url)
.header("X-BDEW-CERT", signing_cert)
.header("X-BDEW-SIGNATURE", signature)
.json(record)
.send()
.await?;
match resp.status() {
StatusCode::CREATED => Ok(true),
StatusCode::NO_CONTENT => Ok(false),
s => Err(Error::Http {
status: s.as_u16(),
body: resp.text().await.unwrap_or_default(),
}),
}
}
pub async fn delete_record(
&self,
provider_id: &str,
api_id: &str,
major_version: i32,
) -> Result<(), Error> {
let url = self.record_url(provider_id, api_id, major_version)?;
let resp = self.inner.delete(url).send().await?;
match resp.status() {
StatusCode::NO_CONTENT => Ok(()),
s => Err(Error::Http {
status: s.as_u16(),
body: resp.text().await.unwrap_or_default(),
}),
}
}
pub async fn put_redirect(
&self,
provider_id: &str,
api_id: &str,
major_version: i32,
target_url: &str,
) -> Result<(), Error> {
let url = self.redirect_url(provider_id, api_id, major_version)?;
let resp = self
.inner
.put(url)
.query(&[("url", target_url)])
.send()
.await?;
match resp.status() {
StatusCode::CREATED => Ok(()),
s => Err(Error::Http {
status: s.as_u16(),
body: resp.text().await.unwrap_or_default(),
}),
}
}
pub async fn delete_redirect(
&self,
provider_id: &str,
api_id: &str,
major_version: i32,
) -> Result<(), Error> {
let url = self.redirect_url(provider_id, api_id, major_version)?;
let resp = self.inner.delete(url).send().await?;
match resp.status() {
StatusCode::OK => Ok(()),
s => Err(Error::Http {
status: s.as_u16(),
body: resp.text().await.unwrap_or_default(),
}),
}
}
fn url(&self, path: &str) -> Result<Url, Error> {
self.base_url.join(path).map_err(Error::Url)
}
fn record_url(
&self,
provider_id: &str,
api_id: &str,
major_version: i32,
) -> Result<Url, Error> {
self.url(&format!(
"record/{}/{}/{}/v1",
encode_path_segment(provider_id),
encode_path_segment(api_id),
major_version,
))
}
fn redirect_url(
&self,
provider_id: &str,
api_id: &str,
major_version: i32,
) -> Result<Url, Error> {
self.url(&format!(
"redirect/{}/{}/{}/v1",
encode_path_segment(provider_id),
encode_path_segment(api_id),
major_version,
))
}
}
async fn check_ok(resp: reqwest::Response) -> Result<reqwest::Response, Error> {
if resp.status().is_success() {
Ok(resp)
} else {
Err(Error::Http {
status: resp.status().as_u16(),
body: resp.text().await.unwrap_or_default(),
})
}
}
fn header_value(resp: &reqwest::Response, name: &str) -> String {
resp.headers()
.get(name)
.and_then(|v| v.to_str().ok())
.unwrap_or("")
.to_owned()
}
fn encode_path_segment(s: &str) -> String {
url::form_urlencoded::byte_serialize(s.as_bytes()).collect()
}