use std::fmt;
use std::marker::PhantomData;
use http::{StatusCode, Uri};
use serde::Serialize;
use serde::de::DeserializeOwned;
use thiserror::Error;
use crate::model::{Document, DocumentError, DriftError, Operation};
use crate::request::{Invocation, ValueError};
use crate::transport::{AsyncClient, HttpRequest, HttpResponse, SyncClient};
use crate::values::Values;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct NoContent;
impl<'de> serde::Deserialize<'de> for NoContent {
fn deserialize<D: serde::Deserializer<'de>>(de: D) -> Result<Self, D::Error> {
serde::de::IgnoredAny::deserialize(de).map(|_| Self)
}
}
#[derive(Debug, Error)]
pub enum Error {
#[error("the document does not load: {0}")]
Document(#[from] DocumentError),
#[error(transparent)]
Drift(#[from] DriftError),
#[error(transparent)]
Values(#[from] ValueError),
#[error("cannot build the request: {0}")]
Request(#[from] http::Error),
#[error("cannot serialise the request body: {0}")]
Encode(#[source] serde_json::Error),
#[error("transport: {0}")]
Transport(#[source] Box<dyn std::error::Error + Send + Sync>),
#[error("{status}: {body}")]
Status { status: StatusCode, body: String },
#[error("the response is not the shape the document promises: {source}")]
Decode {
#[source]
source: serde_json::Error,
raw: String,
},
}
#[derive(Debug)]
pub struct Client {
document: Document,
base: Uri,
}
impl Client {
pub fn over(document: Document) -> Result<Self, Error> {
let base = document.base().clone();
Ok(Self { document, base })
}
#[must_use]
pub fn with_base(mut self, base: Uri) -> Self {
self.base = base;
self
}
#[must_use]
pub fn base(&self) -> &Uri {
&self.base
}
#[must_use]
pub fn document(&self) -> &Document {
&self.document
}
pub fn call<'a, T>(&'a self, op: &'a Operation, values: Values) -> Result<Call<'a, T>, Error> {
Ok(Call {
invocation: Invocation::new(op, values)?,
base: &self.base,
response: PhantomData,
})
}
}
#[derive(Debug, Error)]
#[error("{op}: the request body does not fit the schema the document declares")]
pub struct BodyError {
pub op: String,
#[source]
pub source: serde_json::Error,
}
pub fn fits<T: DeserializeOwned>(op: &str, body: &serde_json::Value) -> Result<(), BodyError> {
serde_json::from_value::<T>(body.clone())
.map(drop)
.map_err(|source| BodyError {
op: op.to_owned(),
source,
})
}
pub struct Call<'a, T> {
invocation: Invocation<'a>,
base: &'a Uri,
response: PhantomData<fn() -> T>,
}
impl<T> fmt::Debug for Call<'_, T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Call")
.field("operation", &self.invocation.operation().id())
.field("response", &std::any::type_name::<T>())
.finish()
}
}
impl<T: DeserializeOwned> Call<'_, T> {
#[must_use]
pub fn operation(&self) -> &Operation {
self.invocation.operation()
}
pub fn request(&self) -> Result<HttpRequest, Error> {
Ok(self.invocation.request(self.base)?)
}
pub fn send<C: SyncClient>(&self, client: &C) -> Result<T, Error> {
parse(&client.send(self.request()?).map_err(transport)?)
}
pub async fn send_async<C: AsyncClient>(&self, client: &C) -> Result<T, Error> {
parse(&client.send(self.request()?).await.map_err(transport)?)
}
}
fn transport<E: std::error::Error + Send + Sync + 'static>(error: E) -> Error {
Error::Transport(Box::new(error))
}
fn parse<T: DeserializeOwned>(response: &HttpResponse) -> Result<T, Error> {
let body = response.body();
if !response.status().is_success() {
return Err(Error::Status {
status: response.status(),
body: String::from_utf8_lossy(body).into_owned(),
});
}
let bytes = if body.is_empty() { b"null" } else { &body[..] };
serde_json::from_slice(bytes).map_err(|source| Error::Decode {
source,
raw: String::from_utf8_lossy(body).into_owned(),
})
}
pub fn to_json<T: Serialize>(value: &T) -> Result<serde_json::Value, Error> {
serde_json::to_value(value).map_err(Error::Encode)
}