pub mod error;
mod payload;
pub mod response;
use reqwest::Url;
use serde::{Deserialize, Serialize};
use tracing::{instrument, trace};
#[derive(Deserialize, Serialize, Debug, PartialEq)]
#[serde(rename_all(deserialize = "SCREAMING_SNAKE_CASE"))]
pub enum ApprovalStatus {
Approved,
ApprovalRevoked,
Deleted,
KeyRevoked,
Pending,
Rejected,
UnknownKey,
}
#[derive(Deserialize, Debug, PartialEq)]
pub struct KeyStatus {
pub fingerprint: String,
pub status: ApprovalStatus,
}
#[derive(Debug)]
pub struct Portal {
base_url: Url,
client: reqwest::Client,
}
impl Portal {
pub fn new(base_url: impl AsRef<str>) -> Result<Portal, error::CreateError> {
let url = Url::parse(base_url.as_ref()).map_err(error::UrlParseError::from)?;
if url.scheme() == "https" || url.scheme() == "http" {
Ok(Portal {
base_url: url,
client: reqwest::Client::builder().use_rustls_tls().build()?,
})
} else {
Err(error::CreateError::InvalidScheme(url.as_ref().to_string()))
}
}
#[instrument(skip(fingerprints))]
pub async fn get_key_status<T>(
&self,
fingerprints: &[T],
) -> Result<Vec<KeyStatus>, error::Error>
where
T: AsRef<str> + std::fmt::Debug,
{
const PGPKEY_STATUS_ENDPOINT: &str = "/backend/open-pgp-keys/status/";
let answer = self
.client
.post(
self.base_url
.join(PGPKEY_STATUS_ENDPOINT)
.map_err(error::UrlParseError::from)?,
)
.json(&payload::KeyStatusPayload { fingerprints })
.send()
.await?
.json()
.await?;
trace!(?answer, ?fingerprints, "key status response");
Ok(answer)
}
#[instrument]
pub async fn check_package(
&self,
package_metadata: &crate::package::Metadata,
package_name: &str,
) -> Result<response::CheckPackage, error::Error<error::InvalidPackage>> {
const CHECK_PACKAGE_ENDPOINT: &str = "/backend/data-package/check/";
let response = self
.client
.post(
self.base_url
.join(CHECK_PACKAGE_ENDPOINT)
.map_err(error::UrlParseError::from)?,
)
.json(&payload::CheckPackage {
metadata: package_metadata,
file_name: package_name,
})
.send()
.await?;
let status = response.status();
if status.is_success() {
Ok(response.json().await?)
} else if status.is_client_error() {
let payload = response.json::<response::PortalError>().await?;
Err(error::InvalidPackage(payload.detail).into())
} else {
Err(error::InvalidPackage(response.text().await?).into())
}
}
#[cfg(feature = "auth")]
pub async fn get_s3_connection_info(
&self,
permission: &super::remote::s3::AccessPermission,
token: impl AsRef<str>,
) -> Result<super::remote::s3::ConnectionInfo, error::Error<error::FetchS3Credentials>> {
use super::remote::s3::AccessPermission;
const STS_READ_ENDPOINT: &str = "/api/v2/sts/read";
const STS_WRITE_ENDPOINT: &str = "/api/v2/sts/write";
let endpoint = match permission {
AccessPermission::GetObject(_) => STS_READ_ENDPOINT,
AccessPermission::ListObjects(_) => STS_READ_ENDPOINT,
AccessPermission::PutObject(_) => STS_WRITE_ENDPOINT,
};
let response = self
.client
.post(
self.base_url
.join(endpoint)
.map_err(error::UrlParseError::from)?,
)
.header(
reqwest::header::AUTHORIZATION,
format!("Bearer {}", token.as_ref()),
)
.json(&payload::Sts::from_permission(permission))
.send()
.await?;
let status = response.status();
if status.is_success() {
Ok(response
.json::<response::S3ConnectionInfoRaw>()
.await?
.into())
} else if status.is_client_error() {
let payload = response.json::<response::PortalError>().await?;
Err(error::FetchS3Credentials(payload.detail).into())
} else {
Err(error::FetchS3Credentials(response.text().await?).into())
}
}
#[cfg(feature = "auth")]
pub async fn get_data_transfers(
&self,
token: impl AsRef<str>,
) -> Result<Vec<response::DataTransfer>, error::Error<error::FetchDataTransfer>> {
const DTR_ENDPOINT: &str = "/backend/data-transfer/";
let response = self
.client
.get(
self.base_url
.join(DTR_ENDPOINT)
.map_err(error::UrlParseError::from)?,
)
.header(
reqwest::header::AUTHORIZATION,
format!("Bearer {}", token.as_ref()),
)
.send()
.await?;
let status = response.status();
if status.is_success() {
let data_transfers: Vec<response::DataTransfer> = response.json().await?;
Ok(data_transfers)
} else if status.is_client_error() {
let payload = response.json::<response::PortalError>().await?;
Err(error::FetchDataTransfer(payload.detail).into())
} else {
Err(error::FetchDataTransfer(response.text().await?).into())
}
}
}