You've already forked AstralRinth
forked from didirus/AstralRinth
* Fix Mural payout status syncing * Make Mural payout code more resilient * prepare sqlx * fix test
123 lines
3.8 KiB
Rust
123 lines
3.8 KiB
Rust
mod error;
|
|
pub use error::*;
|
|
use {
|
|
reqwest::{IntoUrl, RequestBuilder},
|
|
secrecy::{ExposeSecret, SecretString},
|
|
};
|
|
|
|
#[cfg(feature = "mock")]
|
|
mod mock;
|
|
#[cfg(feature = "mock")]
|
|
pub use mock::MuralPayMock;
|
|
use serde::de::DeserializeOwned;
|
|
|
|
#[derive(Debug, Clone)]
|
|
pub struct Client {
|
|
pub http: reqwest::Client,
|
|
pub api_url: String,
|
|
pub api_key: SecretString,
|
|
pub transfer_api_key: SecretString,
|
|
#[cfg(feature = "mock")]
|
|
pub mock: std::sync::Arc<arc_swap::ArcSwapOption<mock::MuralPayMock>>,
|
|
}
|
|
|
|
impl Client {
|
|
pub fn new(
|
|
api_url: impl Into<String>,
|
|
api_key: impl Into<SecretString>,
|
|
transfer_api_key: impl Into<SecretString>,
|
|
) -> Self {
|
|
Self {
|
|
http: reqwest::Client::new(),
|
|
api_url: api_url.into(),
|
|
api_key: api_key.into(),
|
|
transfer_api_key: transfer_api_key.into(),
|
|
#[cfg(feature = "mock")]
|
|
mock: std::sync::Arc::new(arc_swap::ArcSwapOption::empty()),
|
|
}
|
|
}
|
|
|
|
/// Creates a client which mocks responses.
|
|
#[cfg(feature = "mock")]
|
|
#[must_use]
|
|
pub fn from_mock(mock: mock::MuralPayMock) -> Self {
|
|
Self {
|
|
http: reqwest::Client::new(),
|
|
api_url: String::new(),
|
|
api_key: SecretString::from(String::new()),
|
|
transfer_api_key: SecretString::from(String::new()),
|
|
mock: std::sync::Arc::new(arc_swap::ArcSwapOption::from_pointee(mock)),
|
|
}
|
|
}
|
|
|
|
fn http_req(&self, make_req: impl FnOnce() -> RequestBuilder) -> RequestBuilder {
|
|
make_req()
|
|
.bearer_auth(self.api_key.expose_secret())
|
|
.header("accept", "application/json")
|
|
.header("content-type", "application/json")
|
|
}
|
|
|
|
pub(crate) fn http_get<U: IntoUrl>(&self, make_url: impl FnOnce(&str) -> U) -> RequestBuilder {
|
|
self.http_req(|| self.http.get(make_url(&self.api_url)))
|
|
}
|
|
|
|
pub(crate) fn http_post<U: IntoUrl>(&self, make_url: impl FnOnce(&str) -> U) -> RequestBuilder {
|
|
self.http_req(|| self.http.post(make_url(&self.api_url)))
|
|
}
|
|
|
|
pub(crate) fn http_put<U: IntoUrl>(&self, make_url: impl FnOnce(&str) -> U) -> RequestBuilder {
|
|
self.http_req(|| self.http.put(make_url(&self.api_url)))
|
|
}
|
|
|
|
pub(crate) fn http_delete<U: IntoUrl>(
|
|
&self,
|
|
make_url: impl FnOnce(&str) -> U,
|
|
) -> RequestBuilder {
|
|
self.http_req(|| self.http.delete(make_url(&self.api_url)))
|
|
}
|
|
|
|
pub async fn health(&self) -> reqwest::Result<()> {
|
|
self.http_get(|base| format!("{base}/api/health"))
|
|
.send()
|
|
.await?
|
|
.error_for_status()?;
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
pub trait RequestExt: Sized {
|
|
#[must_use]
|
|
fn transfer_auth(self, client: &Client) -> Self;
|
|
|
|
fn send_mural<T: DeserializeOwned>(
|
|
self,
|
|
) -> impl Future<Output = crate::Result<T>> + Send + Sync;
|
|
}
|
|
|
|
const HEADER_TRANSFER_API_KEY: &str = "transfer-api-key";
|
|
|
|
impl RequestExt for reqwest::RequestBuilder {
|
|
fn transfer_auth(self, client: &Client) -> Self {
|
|
self.header(
|
|
HEADER_TRANSFER_API_KEY,
|
|
client.transfer_api_key.expose_secret(),
|
|
)
|
|
}
|
|
|
|
async fn send_mural<T: DeserializeOwned>(self) -> crate::Result<T> {
|
|
let resp = self.send().await?;
|
|
let status = resp.status();
|
|
if status.is_client_error() || status.is_server_error() {
|
|
let json = resp.bytes().await?;
|
|
let err = serde_json::from_slice::<ApiError>(&json)
|
|
.map_err(|source| MuralError::DecodeError { source, json })?;
|
|
Err(MuralError::Api(err))
|
|
} else {
|
|
let json = resp.bytes().await?;
|
|
let t = serde_json::from_slice::<T>(&json)
|
|
.map_err(|source| MuralError::Decode { source, json })?;
|
|
Ok(t)
|
|
}
|
|
}
|
|
}
|