use std::future::Future;
use serde::{de::DeserializeOwned, Deserialize};
use tracing::instrument;
use crate::{
endpoints::{accounts, balance, feed_items, pots, transactions, who_am_i, Endpoint},
Result,
};
pub mod inner;
pub trait Inner: Send + Sync + std::fmt::Debug {
fn execute<E>(
&self,
endpoint: &E,
) -> impl Future<Output = reqwest::Result<reqwest::Response>> + Send
where
E: Endpoint;
fn access_token(&self) -> &String;
fn set_access_token(&mut self, access_token: String);
fn url(&self) -> &str;
#[instrument(skip(self, endpoint), fields(url = self.url(), endpoint = endpoint.endpoint()))]
fn handle_request<E, R>(&self, endpoint: &E) -> impl Future<Output = Result<R>> + Send
where
R: DeserializeOwned,
E: Endpoint,
{
async {
tracing::info!("sending request");
let response = self.execute(endpoint).await?;
tracing::info!("response received");
let result = handle_response(response).await;
match &result {
Ok(_) => {
tracing::info!("request successful");
}
Err(e) => {
tracing::info!("request failed: {}", e);
}
}
result
}
}
}
#[derive(Debug)]
pub struct Client<C>
where
C: Inner,
{
inner_client: C,
}
impl<C> Client<C>
where
C: Inner,
{
#[must_use]
pub fn access_token(&self) -> &String {
self.inner_client.access_token()
}
pub fn set_access_token(&mut self, access_token: impl Into<String>) {
self.inner_client.set_access_token(access_token.into());
}
pub async fn accounts(&self) -> Result<Vec<accounts::Account>> {
#[derive(Deserialize)]
pub struct Response {
accounts: Vec<accounts::Account>,
}
let response: Response = self.inner_client.handle_request(&accounts::List).await?;
Ok(response.accounts)
}
pub async fn balance(&self, account_id: &str) -> Result<balance::Balance> {
self.inner_client
.handle_request(&balance::Get::new(account_id))
.await
}
pub async fn pots(&self, account_id: &str) -> Result<Vec<pots::Pot>> {
#[derive(Deserialize)]
struct Response {
pots: Vec<pots::Pot>,
}
let response: Response = self
.inner_client
.handle_request(&pots::List::new(account_id))
.await?;
Ok(response.pots)
}
pub const fn basic_feed_item<'a>(
&'a self,
account_id: &'a str,
title: &'a str,
image_url: &'a str,
) -> feed_items::basic::Request<'a, C> {
feed_items::basic::Request::new(&self.inner_client, account_id, title, image_url)
}
pub async fn deposit_into_pot(
&self,
pot_id: &str,
source_account_id: &str,
amount: u32,
) -> Result<pots::Pot> {
self.inner_client
.handle_request(&pots::Deposit::new(pot_id, source_account_id, amount))
.await
}
pub async fn withdraw_from_pot(
&self,
pot_id: &str,
destination_account_id: &str,
amount: u32,
) -> Result<pots::Pot> {
self.inner_client
.handle_request(&pots::Withdraw::new(pot_id, destination_account_id, amount))
.await
}
pub fn transactions<'a>(&'a self, account_id: &'a str) -> transactions::List<'a, C> {
transactions::List::new(&self.inner_client, account_id)
}
pub fn transaction<'a>(&'a self, transaction_id: &'a str) -> transactions::Get<'a, C> {
transactions::Get::new(&self.inner_client, transaction_id)
}
pub async fn who_am_i(&self) -> Result<who_am_i::Response> {
self.inner_client.handle_request(&who_am_i::Request).await
}
}
async fn handle_response<R>(response: reqwest::Response) -> Result<R>
where
R: DeserializeOwned,
{
let status = response.status();
if status.is_success() {
Ok(serde_json::from_slice(&response.bytes().await?)?)
} else {
Err(status.into())
}
}