use std::marker::PhantomData;
use serde::{Serialize, de::DeserializeOwned};
use serde_json::Value;
use crate::{
ApiClient, KobanError, Resource, Result,
models::{
Client, Credit, Data, Expense, Invoice, Paginated, Payment, Product, Project, Quote, Task,
Vendor,
},
};
impl ApiClient {
fn resource_base(resource: Resource) -> String {
format!("api/v1/{}", resource.path())
}
pub async fn list_resource<T: DeserializeOwned>(
&self,
resource: Resource,
query: &[(String, String)],
) -> Result<Paginated<T>> {
let value = self.get_json(&Self::resource_base(resource), query).await?;
decode(value)
}
pub async fn get_resource<T: DeserializeOwned>(
&self,
resource: Resource,
id: &str,
) -> Result<T> {
let path = format!("{}/{id}", Self::resource_base(resource));
let value = self.get_json(&path, &[]).await?;
Ok(decode::<Data<T>>(value)?.data)
}
pub async fn create_resource<T, B>(&self, resource: Resource, body: &B) -> Result<T>
where
T: DeserializeOwned,
B: Serialize + ?Sized,
{
let value = self
.post_json(&Self::resource_base(resource), &[], &to_value(body)?)
.await?;
Ok(decode::<Data<T>>(value)?.data)
}
pub async fn update_resource<T, B>(&self, resource: Resource, id: &str, body: &B) -> Result<T>
where
T: DeserializeOwned,
B: Serialize + ?Sized,
{
let path = format!("{}/{id}", Self::resource_base(resource));
let value = self.put_json(&path, &[], &to_value(body)?).await?;
Ok(decode::<Data<T>>(value)?.data)
}
pub async fn delete_resource<T: DeserializeOwned>(
&self,
resource: Resource,
id: &str,
) -> Result<T> {
let path = format!("{}/{id}", Self::resource_base(resource));
let value = self.delete_json(&path, &[]).await?;
Ok(decode::<Data<T>>(value)?.data)
}
pub fn resource<T>(&self, resource: Resource) -> Resources<'_, T> {
Resources::new(self, resource)
}
pub fn clients(&self) -> Resources<'_, Client> {
self.resource(Resource::Clients)
}
pub fn invoices(&self) -> Resources<'_, Invoice> {
self.resource(Resource::Invoices)
}
pub fn payments(&self) -> Resources<'_, Payment> {
self.resource(Resource::Payments)
}
pub fn quotes(&self) -> Resources<'_, Quote> {
self.resource(Resource::Quotes)
}
pub fn credits(&self) -> Resources<'_, Credit> {
self.resource(Resource::Credits)
}
pub fn products(&self) -> Resources<'_, Product> {
self.resource(Resource::Products)
}
pub fn expenses(&self) -> Resources<'_, Expense> {
self.resource(Resource::Expenses)
}
pub fn vendors(&self) -> Resources<'_, Vendor> {
self.resource(Resource::Vendors)
}
pub fn projects(&self) -> Resources<'_, Project> {
self.resource(Resource::Projects)
}
pub fn tasks(&self) -> Resources<'_, Task> {
self.resource(Resource::Tasks)
}
}
pub struct Resources<'a, T> {
client: &'a ApiClient,
resource: Resource,
_marker: PhantomData<fn() -> T>,
}
impl<'a, T> Resources<'a, T> {
pub(crate) fn new(client: &'a ApiClient, resource: Resource) -> Self {
Self {
client,
resource,
_marker: PhantomData,
}
}
pub fn resource(&self) -> Resource {
self.resource
}
}
impl<T: DeserializeOwned> Resources<'_, T> {
pub async fn get(&self, id: &str) -> Result<T> {
self.client.get_resource(self.resource, id).await
}
pub async fn list(&self) -> Result<Vec<T>> {
Ok(self
.client
.list_resource::<T>(self.resource, &[])
.await?
.data)
}
pub async fn list_paginated(&self, query: &[(String, String)]) -> Result<Paginated<T>> {
self.client.list_resource(self.resource, query).await
}
pub async fn create<B: Serialize + ?Sized>(&self, body: &B) -> Result<T> {
self.client.create_resource(self.resource, body).await
}
pub async fn update<B: Serialize + ?Sized>(&self, id: &str, body: &B) -> Result<T> {
self.client.update_resource(self.resource, id, body).await
}
pub async fn delete(&self, id: &str) -> Result<T> {
self.client.delete_resource(self.resource, id).await
}
}
fn decode<T: DeserializeOwned>(value: Value) -> Result<T> {
serde_json::from_value(value).map_err(|source| KobanError::Decode {
message: source.to_string(),
})
}
fn to_value<B: Serialize + ?Sized>(body: &B) -> Result<Value> {
serde_json::to_value(body).map_err(|source| KobanError::Decode {
message: source.to_string(),
})
}