postmark/
client.rs

1use std::borrow::Cow;
2
3use async_trait::async_trait;
4use bytes::Bytes;
5use http::{Request, Response};
6use std::error::Error;
7use thiserror::Error;
8
9/// A trait for providing the necessary information for a single REST API endpoint.
10pub trait Endpoint {
11    type Request: serde::Serialize + Send + Sync;
12    type Response: serde::de::DeserializeOwned + Send + Sync;
13
14    /// The path to the endpoint.
15    fn endpoint(&self) -> Cow<'static, str>;
16    /// The body for the endpoint.
17    fn body(&self) -> &Self::Request;
18    /// The http method for the endpoint
19    fn method(&self) -> http::Method {
20        http::Method::POST
21    }
22}
23
24/// A trait which represents an asynchronous query which may be made to a Postmark client.
25#[async_trait]
26pub trait Query<C> {
27    /// The Result of executing a query
28    type Result;
29    /// Perform the query against the client.
30    async fn execute(self, client: &C) -> Self::Result;
31}
32
33/// An error thrown by the [`Query`] trait
34#[derive(Debug, Error)]
35pub enum QueryError<E>
36where
37    E: Error + Send + Sync + 'static,
38{
39    /// The client encountered an error.
40    #[error("client error: {}", source)]
41    Client {
42        /// The client error.
43        source: E,
44    },
45    /// JSON deserialization from GitLab failed.
46    #[error("could not parse JSON response: {}", source)]
47    Json {
48        /// The source of the error.
49        #[from]
50        source: serde_json::Error,
51    },
52    /// Body data could not be created.
53    #[error("failed to create form data: {}", source)]
54    Body {
55        /// The source of the error.
56        #[from]
57        source: http::Error,
58    },
59}
60
61impl<E> QueryError<E>
62where
63    E: Error + Send + Sync + 'static,
64{
65    /// Create an API error in a client error.
66    pub fn client(source: E) -> Self {
67        QueryError::Client { source }
68    }
69}
70
71/// Extension method to all Endpoints to execute themselves againts a query
72#[async_trait]
73impl<T, C> Query<C> for T
74where
75    T: Endpoint + Send + Sync,
76    C: Client + Send + Sync,
77{
78    /// An endpoint return it's Response or the Client's Error
79    type Result = Result<T::Response, QueryError<C::Error>>;
80
81    async fn execute(self, client: &C) -> Self::Result {
82        let body = serde_json::to_vec(self.body())?;
83
84        let http_req = http::Request::builder()
85            .method(self.method())
86            .uri(String::from(self.endpoint()))
87            .header("Accept", "application/json")
88            .header("Content-Type", "application/json")
89            .body(body.into())?;
90
91        let response = client.execute(http_req).await.map_err(QueryError::client)?;
92
93        Ok(serde_json::from_slice(response.body())?)
94    }
95}
96
97/// A trait representing a client which can communicate with a Postmark instance.
98#[async_trait]
99pub trait Client {
100    /// The errors which may occur for this client.
101    type Error: Error + Send + Sync + 'static;
102    /// Execute the request which was formed by [`Endpoint`]
103    async fn execute(&self, req: Request<Bytes>) -> Result<Response<Bytes>, Self::Error>;
104}