meilisearch_sdk/
request.rs

1use std::convert::Infallible;
2
3use async_trait::async_trait;
4use log::{error, trace, warn};
5use serde::{de::DeserializeOwned, Serialize};
6use serde_json::{from_str, to_vec};
7
8use crate::errors::{Error, MeilisearchCommunicationError, MeilisearchError};
9
10#[derive(Debug)]
11pub enum Method<Q, B> {
12    Get { query: Q },
13    Post { query: Q, body: B },
14    Patch { query: Q, body: B },
15    Put { query: Q, body: B },
16    Delete { query: Q },
17}
18
19impl<Q, B> Method<Q, B> {
20    pub fn map_body<B2>(self, f: impl Fn(B) -> B2) -> Method<Q, B2> {
21        match self {
22            Method::Get { query } => Method::Get { query },
23            Method::Delete { query } => Method::Delete { query },
24            Method::Post { query, body } => Method::Post {
25                query,
26                body: f(body),
27            },
28            Method::Patch { query, body } => Method::Patch {
29                query,
30                body: f(body),
31            },
32            Method::Put { query, body } => Method::Put {
33                query,
34                body: f(body),
35            },
36        }
37    }
38
39    pub fn query(&self) -> &Q {
40        match self {
41            Method::Get { query } => query,
42            Method::Delete { query } => query,
43            Method::Post { query, .. } => query,
44            Method::Put { query, .. } => query,
45            Method::Patch { query, .. } => query,
46        }
47    }
48
49    pub fn body(&self) -> Option<&B> {
50        match self {
51            Method::Get { query: _ } | Method::Delete { query: _ } => None,
52            Method::Post { body, query: _ } => Some(body),
53            Method::Put { body, query: _ } => Some(body),
54            Method::Patch { body, query: _ } => Some(body),
55        }
56    }
57
58    pub fn into_body(self) -> Option<B> {
59        match self {
60            Method::Get { query: _ } | Method::Delete { query: _ } => None,
61            Method::Post { body, query: _ } => Some(body),
62            Method::Put { body, query: _ } => Some(body),
63            Method::Patch { body, query: _ } => Some(body),
64        }
65    }
66}
67
68#[cfg_attr(feature = "futures-unsend", async_trait(?Send))]
69#[cfg_attr(not(feature = "futures-unsend"), async_trait)]
70pub trait HttpClient: Clone + Send + Sync {
71    async fn request<Query, Body, Output>(
72        &self,
73        url: &str,
74        method: Method<Query, Body>,
75        expected_status_code: u16,
76    ) -> Result<Output, Error>
77    where
78        Query: Serialize + Send + Sync,
79        Body: Serialize + Send + Sync,
80        Output: DeserializeOwned + 'static + Send,
81    {
82        use futures_util::io::Cursor;
83
84        self.stream_request(
85            url,
86            method.map_body(|body| Cursor::new(to_vec(&body).unwrap())),
87            "application/json",
88            expected_status_code,
89        )
90        .await
91    }
92
93    async fn stream_request<
94        Query: Serialize + Send + Sync,
95        Body: futures_io::AsyncRead + Send + Sync + 'static,
96        Output: DeserializeOwned + 'static,
97    >(
98        &self,
99        url: &str,
100        method: Method<Query, Body>,
101        content_type: &str,
102        expected_status_code: u16,
103    ) -> Result<Output, Error>;
104
105    fn is_tokio(&self) -> bool {
106        false
107    }
108}
109
110pub fn parse_response<Output: DeserializeOwned>(
111    status_code: u16,
112    expected_status_code: u16,
113    body: &str,
114    url: String,
115) -> Result<Output, Error> {
116    if status_code == expected_status_code {
117        return match from_str::<Output>(body) {
118            Ok(output) => {
119                trace!("Request succeed");
120                Ok(output)
121            }
122            Err(e) => {
123                error!("Request succeeded but failed to parse response");
124                Err(Error::ParseError(e))
125            }
126        };
127    }
128
129    warn!("Expected response code {expected_status_code}, got {status_code}");
130
131    match from_str::<MeilisearchError>(body) {
132        Ok(e) => Err(Error::from(e)),
133        Err(e) => {
134            if status_code >= 400 {
135                return Err(Error::MeilisearchCommunication(
136                    MeilisearchCommunicationError {
137                        status_code,
138                        message: None,
139                        url,
140                    },
141                ));
142            }
143            Err(Error::ParseError(e))
144        }
145    }
146}
147
148#[cfg_attr(feature = "futures-unsend", async_trait(?Send))]
149#[cfg_attr(not(feature = "futures-unsend"), async_trait)]
150impl HttpClient for Infallible {
151    async fn request<Query, Body, Output>(
152        &self,
153        _url: &str,
154        _method: Method<Query, Body>,
155        _expected_status_code: u16,
156    ) -> Result<Output, Error>
157    where
158        Query: Serialize + Send + Sync,
159        Body: Serialize + Send + Sync,
160        Output: DeserializeOwned + 'static + Send,
161    {
162        unreachable!()
163    }
164
165    async fn stream_request<
166        Query: Serialize + Send + Sync,
167        Body: futures_io::AsyncRead + Send + Sync + 'static,
168        Output: DeserializeOwned + 'static,
169    >(
170        &self,
171        _url: &str,
172        _method: Method<Query, Body>,
173        _content_type: &str,
174        _expected_status_code: u16,
175    ) -> Result<Output, Error> {
176        unreachable!()
177    }
178}