use std::{future::Future, iter::IntoIterator, pin::Pin, vec::IntoIter};
use async_trait::async_trait;
use reqwest::{Response, StatusCode};
use serde::{de::DeserializeOwned, Serialize};
use thiserror::Error;
use crate::{models::{Items, ResultSet}, ClientREST, UriBuilder};
type PinnedFuture<'f, O> = Pin<Box<dyn Future<Output = anyhow::Result<O>> + 'f>>;
#[derive(Debug, Error)]
pub enum CrudError {
#[error("CRUD operation requires `{0}`")]
IdentifierRequired(String),
#[error("host XNAT experienced an internal error ({0})")]
HostError(StatusCode),
#[error("resource is not available ({0})")]
NotAvailable(StatusCode),
#[error("could not create resource ({0})")]
NotCreated(StatusCode),
#[error("could not retrieve resource ({0})")]
NotFound(StatusCode),
}
#[async_trait(?Send)]
pub trait Create<M>
where
M: Clone + Serialize,
{
#[inline(never)]
fn create_many(&self, models: M) -> Vec<PinnedFuture<'_, M>>
where
M: IntoIterator<Item = M, IntoIter = IntoIter<M>>,
{
models
.into_iter()
.map(|m| self.create_once(m))
.collect::<Vec<_>>()
}
async fn create_once(&self, model: M) -> anyhow::Result<M>;
}
#[async_trait(?Send)]
pub trait Retrieve<M>
where
M: Clone + DeserializeOwned,
{
#[inline(never)]
async fn get_all(&self) -> anyhow::Result<Vec<M>>
where
M: Default,
{
self.get_any_from(&M::default()).await
}
async fn get_any_from(&self, model: &M) -> anyhow::Result<Vec<M>>;
#[inline(never)]
async fn get_any_items_from<UB>(&self, uri: &UB, model: &M) -> anyhow::Result<Items<M>>
where
Self: ClientREST,
M: Serialize,
UB: UriBuilder,
{
let res = try_retrieve(
self.get(uri).await?.query(model).send().await?,
|r| async { r }
).await?;
Ok(res.json::<Items<M>>().await?)
}
#[allow(unused_variables)]
#[inline(never)]
async fn get_any_result_from<UB>(&self, uri: &UB, model: &M) -> anyhow::Result<ResultSet<M>>
where
Self: ClientREST,
M: Serialize,
UB: UriBuilder,
{
let res = try_retrieve(
self.get(uri).await?.query(model).send().await?,
|r| async { r }
).await?;
Ok(res.json::<ResultSet<M>>().await?)
}
#[inline(never)]
async fn get_one_from(&self, model: &M) -> anyhow::Result<M>
{
match self.get_any_from(model).await?.first() {
None => Err(CrudError::NotFound(StatusCode::from_u16(400)?).into()),
Some(m) => Ok(m.to_owned())
}
}
}
#[async_trait(?Send)]
pub trait Update<M>
where
M: Clone + Serialize,
{
#[inline(never)]
fn update_many(&self, models: M) -> Vec<PinnedFuture<'_, M>>
where
M: IntoIterator<Item = M, IntoIter = IntoIter<M>>,
{
models
.into_iter()
.map(|m| self.update_once(m))
.collect::<Vec<_>>()
}
async fn update_once(&self, model: M) -> anyhow::Result<M>;
}
#[async_trait(?Send)]
pub trait Delete<M>
where
M: Clone + Serialize
{
#[inline(never)]
fn delete_many(&self, models: M) -> Vec<PinnedFuture<'_, M>>
where
M: IntoIterator<Item = M, IntoIter = IntoIter<M>>,
{
models
.into_iter()
.map(|m| self.delete_once(m))
.collect::<Vec<_>>()
}
async fn delete_once(&self, model: M) -> anyhow::Result<M>;
}
pub async fn try_retrieve<T, Callback, F>(response: Response, call: Callback) -> anyhow::Result<T>
where
F: Future<Output = T>,
Callback: FnOnce(Response) -> F,
{
let status = response.status();
if status.is_success() {
Ok(call(response).await)
} else if status.is_client_error() && status == 400 {
Err(CrudError::NotFound(status).into())
} else if status.is_client_error() {
Err(CrudError::NotAvailable(status).into())
} else {
Err(CrudError::HostError(status).into())
}
}