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#[derive(thiserror::Error, Debug)]
13pub enum GlooRequestError<D: Error + Debug> {
14 #[error("getting response")]
16 Request(#[from] gloo_net::Error),
17 #[error("decoding response")]
19 Decode(#[source] D),
20}
21
22pub type ResponseError<T> = GlooRequestError<T>;
23
24#[async_trait(?Send)]
26pub trait GlooResponse {
27 type Target;
29 type Error: Error + Debug;
31
32 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
49pub 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
83pub 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}