restless_gloo/
lib.rs

1use async_trait::async_trait;
2use gloo_net::{
3    http::{self as gloo, RequestBuilder, Response},
4    Error as GlooError,
5};
6use restless_core::{Decodable, Request};
7use restless_data::Json;
8use serde::{de::DeserializeOwned, Serialize};
9use std::{error::Error, fmt::Debug};
10
11/// Error decoding a gloo [`Response`].
12#[derive(thiserror::Error, Debug)]
13pub enum GlooRequestError<D: Error + Debug> {
14    /// Error from gloo.
15    #[error("getting response")]
16    Request(#[from] gloo_net::Error),
17    /// Error during decoding of response body.
18    #[error("decoding response")]
19    Decode(#[source] D),
20}
21
22pub type ResponseError<T> = GlooRequestError<T>;
23
24/// Decodable from a gloo [`Response`].
25#[async_trait(?Send)]
26pub trait GlooResponse {
27    /// Type that this decodes into.
28    type Target;
29    /// Error that this type can return during decoding.
30    type Error: Error + Debug;
31
32    /// Method to decode a gloo [`Response`].
33    async fn from_gloo_response(
34        response: &Response,
35    ) -> Result<Self::Target, ResponseError<Self::Error>>;
36}
37
38#[async_trait(?Send)]
39impl<T: DeserializeOwned + Clone + 'static> GlooResponse for Json<T> {
40    type Target = T;
41    type Error = std::convert::Infallible;
42    async fn from_gloo_response(
43        response: &Response,
44    ) -> Result<Self::Target, ResponseError<Self::Error>> {
45        response.json::<T>().await.map_err(ResponseError::Request)
46    }
47}
48
49/// Decode this type using the [`Decodable`] trait via bytes.
50pub trait GlooResponseBinary: Decodable {}
51
52#[async_trait(?Send)]
53impl<T: GlooResponseBinary> GlooResponse for T {
54    type Target = <T as Decodable>::Target;
55    type Error = <T as Decodable>::Error;
56    async fn from_gloo_response(
57        response: &Response,
58    ) -> Result<Self::Target, ResponseError<Self::Error>> {
59        let data = response.binary().await.map_err(ResponseError::Request)?;
60        T::decode(&data).map_err(ResponseError::Decode)
61    }
62}
63
64impl GlooResponseBinary for () {}
65impl GlooResponseBinary for Vec<u8> {}
66
67pub trait ToGlooRequest {
68    fn to_gloo_request(&self, request: RequestBuilder) -> Result<gloo::Request, GlooError>;
69}
70
71impl ToGlooRequest for () {
72    fn to_gloo_request(&self, builder: RequestBuilder) -> Result<gloo::Request, GlooError> {
73        builder.build().map_err(Into::into)
74    }
75}
76
77impl<T: Serialize> ToGlooRequest for Json<T> {
78    fn to_gloo_request(&self, builder: RequestBuilder) -> Result<gloo::Request, GlooError> {
79        builder.json(&self.0).map_err(Into::into)
80    }
81}
82
83/// Build a gloo [`RequestBuilder`] for a given HTTP [`Request`].
84pub fn request_builder<R: Request>(prefix: &str, request: &R) -> RequestBuilder {
85    let path = format!("{prefix}{}", request.uri());
86    let method = request.method().into();
87    RequestBuilder::new(&path).method(method)
88}
89
90#[async_trait(?Send)]
91pub trait GlooRequest {
92    type Response;
93    type Error: Error + Debug;
94
95    async fn send(&self) -> Result<Self::Response, Self::Error> {
96        self.send_prefix("/").await
97    }
98
99    async fn send_prefix(&self, prefix: &str) -> Result<Self::Response, Self::Error>;
100}
101
102#[async_trait(?Send)]
103impl<T: Request> GlooRequest for T
104where
105    T::Response: GlooResponse,
106    T::Request: ToGlooRequest,
107    <T::Response as GlooResponse>::Error: 'static,
108{
109    type Response = <T::Response as GlooResponse>::Target;
110    type Error = GlooRequestError<<T::Response as GlooResponse>::Error>;
111    async fn send_prefix(&self, prefix: &str) -> Result<Self::Response, Self::Error> {
112        let builder = request_builder(prefix, self);
113        let request = self.body().to_gloo_request(builder)?;
114        let response = request.send().await?;
115        <T::Response as GlooResponse>::from_gloo_response(&response).await
116    }
117}