1use super::{BaseHttpClient, Form, Headers, Query};
4
5use std::{io, time::Duration};
6
7use maybe_async::sync_impl;
8use serde_json::Value;
9use ureq::{Request, Response};
10
11#[derive(thiserror::Error, Debug)]
35pub enum UreqError {
36 #[error("transport: {0}")]
39 Transport(#[from] ureq::Transport),
40
41 #[error("I/O: {0}")]
43 Io(#[from] io::Error),
44
45 #[error("status code {}", ureq::Response::status(.0))]
50 StatusCode(ureq::Response),
51}
52
53#[derive(Debug, Clone)]
54pub struct UreqClient {
55 agent: ureq::Agent,
56}
57
58impl Default for UreqClient {
59 fn default() -> Self {
60 let agent = ureq::AgentBuilder::new()
61 .try_proxy_from_env(true)
62 .timeout(Duration::from_secs(10));
63
64 #[cfg(feature = "ureq-native-tls")]
65 let agent = agent.tls_connector(std::sync::Arc::new(
66 native_tls::TlsConnector::builder()
67 .min_protocol_version(Some(native_tls::Protocol::Tlsv12))
69 .build()
70 .expect("Failed to initialize TLS connector"),
71 ));
72
73 Self {
74 agent: agent.build(),
75 }
76 }
77}
78
79impl UreqClient {
80 fn request<D>(
89 &self,
90 mut request: Request,
91 headers: Option<&Headers>,
92 send_request: D,
93 ) -> Result<String, UreqError>
94 where
95 D: Fn(Request) -> Result<Response, ureq::Error>,
96 {
97 if let Some(headers) = headers {
99 for (key, val) in headers.iter() {
100 request = request.set(key, val);
101 }
102 }
103
104 log::info!("Making request {request:#?}");
105 match send_request(request) {
107 Ok(response) => response.into_string().map_err(Into::into),
108 Err(err) => match err {
109 ureq::Error::Status(_, response) => Err(UreqError::StatusCode(response)),
110 ureq::Error::Transport(transport) => Err(UreqError::Transport(transport)),
111 },
112 }
113 }
114}
115
116#[sync_impl]
117impl BaseHttpClient for UreqClient {
118 type Error = UreqError;
119
120 #[inline]
121 fn get(
122 &self,
123 url: &str,
124 headers: Option<&Headers>,
125 payload: &Query,
126 ) -> Result<String, Self::Error> {
127 let request = self.agent.get(url);
128 let sender = |mut req: Request| {
129 for (key, val) in payload.iter() {
130 req = req.query(key, val);
131 }
132 req.call()
133 };
134 self.request(request, headers, sender)
135 }
136
137 #[inline]
138 fn post(
139 &self,
140 url: &str,
141 headers: Option<&Headers>,
142 payload: &Value,
143 ) -> Result<String, Self::Error> {
144 let request = self.agent.post(url);
145 let sender = |req: Request| req.send_json(payload.clone());
146 self.request(request, headers, sender)
147 }
148
149 #[inline]
150 fn post_form(
151 &self,
152 url: &str,
153 headers: Option<&Headers>,
154 payload: &Form<'_>,
155 ) -> Result<String, Self::Error> {
156 let request = self.agent.post(url);
157 let sender = |req: Request| {
158 let payload = payload
159 .iter()
160 .map(|(key, val)| (*key, *val))
161 .collect::<Vec<_>>();
162
163 req.send_form(&payload)
164 };
165
166 self.request(request, headers, sender)
167 }
168
169 #[inline]
170 fn put(
171 &self,
172 url: &str,
173 headers: Option<&Headers>,
174 payload: &Value,
175 ) -> Result<String, Self::Error> {
176 let request = self.agent.put(url);
177 let sender = |req: Request| req.send_json(payload.clone());
178 self.request(request, headers, sender)
179 }
180
181 #[inline]
182 fn delete(
183 &self,
184 url: &str,
185 headers: Option<&Headers>,
186 payload: &Value,
187 ) -> Result<String, Self::Error> {
188 let request = self.agent.delete(url);
189 let sender = |req: Request| req.send_json(payload.clone());
190 self.request(request, headers, sender)
191 }
192}