1use std::fmt;
13use std::marker::PhantomData;
14
15use http::{StatusCode, Uri};
16use serde::Serialize;
17use serde::de::DeserializeOwned;
18use thiserror::Error;
19
20use crate::model::{Document, DocumentError, DriftError, Effect, Operation};
21use crate::request::{Invocation, ValueError};
22use crate::transport::{AsyncClient, HttpRequest, HttpResponse, SyncClient};
23use crate::values::Values;
24
25#[derive(Debug, Clone, Copy, PartialEq, Eq)]
30pub struct NoContent;
31
32impl<'de> serde::Deserialize<'de> for NoContent {
33 fn deserialize<D: serde::Deserializer<'de>>(de: D) -> Result<Self, D::Error> {
34 serde::de::IgnoredAny::deserialize(de).map(|_| Self)
35 }
36}
37
38#[derive(Debug, Error)]
40pub enum Error {
41 #[error("the document does not load: {0}")]
42 Document(#[from] DocumentError),
43 #[error(transparent)]
44 Drift(#[from] DriftError),
45 #[error(transparent)]
46 Values(#[from] ValueError),
47 #[error("cannot build the request: {0}")]
48 Request(#[from] http::Error),
49 #[error("cannot serialise the request body: {0}")]
50 Encode(#[source] serde_json::Error),
51 #[error("transport: {0}")]
52 Transport(#[source] Box<dyn std::error::Error + Send + Sync>),
53 #[error("{status}: {body}")]
54 Status { status: StatusCode, body: String },
55 #[error("the response is not the shape the document promises: {source}")]
56 Decode {
57 #[source]
58 source: serde_json::Error,
59 raw: String,
60 },
61}
62
63#[derive(Debug)]
65pub struct Client {
66 document: Document,
67 base: Uri,
68}
69
70impl Client {
71 pub fn over(document: Document) -> Result<Self, Error> {
80 let base = document.base().clone();
81 Ok(Self { document, base })
82 }
83
84 #[must_use]
86 pub fn with_base(mut self, base: Uri) -> Self {
87 self.base = base;
88 self
89 }
90
91 #[must_use]
92 pub fn base(&self) -> &Uri {
93 &self.base
94 }
95
96 #[must_use]
99 pub fn document(&self) -> &Document {
100 &self.document
101 }
102
103 pub fn call<'a, T>(&'a self, op: &'a Operation, values: Values) -> Result<Call<'a, T>, Error> {
109 Ok(Call {
110 invocation: Invocation::new(op, values)?,
111 base: &self.base,
112 response: PhantomData,
113 })
114 }
115}
116
117#[derive(Debug, Error)]
123#[error("{op}: the request body does not fit the schema the document declares")]
124pub struct BodyError {
125 pub op: String,
126 #[source]
127 pub source: serde_json::Error,
128}
129
130pub fn fits<T: DeserializeOwned>(op: &str, body: &serde_json::Value) -> Result<(), BodyError> {
136 serde_json::from_value::<T>(body.clone())
137 .map(drop)
138 .map_err(|source| BodyError {
139 op: op.to_owned(),
140 source,
141 })
142}
143
144pub struct Call<'a, T> {
146 invocation: Invocation<'a>,
147 base: &'a Uri,
148 response: PhantomData<fn() -> T>,
149}
150
151impl<T> fmt::Debug for Call<'_, T> {
152 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
153 f.debug_struct("Call")
154 .field("operation", &self.invocation.operation().id())
155 .field("response", &std::any::type_name::<T>())
156 .finish()
157 }
158}
159
160impl<T: DeserializeOwned> Call<'_, T> {
161 #[must_use]
164 pub fn effect(&self) -> Effect {
165 self.invocation.effect()
166 }
167
168 pub fn request(&self) -> Result<HttpRequest, Error> {
171 Ok(self.invocation.request(self.base)?)
172 }
173
174 pub fn send<C: SyncClient>(&self, client: &C) -> Result<T, Error> {
176 parse(&client.send(self.request()?).map_err(transport)?)
177 }
178
179 pub async fn send_async<C: AsyncClient>(&self, client: &C) -> Result<T, Error> {
181 parse(&client.send(self.request()?).await.map_err(transport)?)
182 }
183}
184
185fn transport<E: std::error::Error + Send + Sync + 'static>(error: E) -> Error {
186 Error::Transport(Box::new(error))
187}
188
189fn parse<T: DeserializeOwned>(response: &HttpResponse) -> Result<T, Error> {
192 let body = response.body();
193 if !response.status().is_success() {
194 return Err(Error::Status {
195 status: response.status(),
196 body: String::from_utf8_lossy(body).into_owned(),
197 });
198 }
199 let bytes = if body.is_empty() { b"null" } else { &body[..] };
201 serde_json::from_slice(bytes).map_err(|source| Error::Decode {
202 source,
203 raw: String::from_utf8_lossy(body).into_owned(),
204 })
205}
206
207pub fn to_json<T: Serialize>(value: &T) -> Result<serde_json::Value, Error> {
209 serde_json::to_value(value).map_err(Error::Encode)
210}