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}