1use std::fmt;
2use std::sync::Arc;
3
4use crate::middleware::{Middleware, Next};
5use crate::protocol::{EffectSender, HttpResult, ProtocolRequestBuilder};
6use crate::{Config, RawResponse, Request, RequestBuilder, Result};
7use http::Method;
8use url::Url;
9
10pub struct Client {
38 config: Config,
39 effect_sender: Arc<dyn EffectSender + Send + Sync>,
40 #[allow(clippy::rc_buffer)]
48 middleware: Arc<Vec<Arc<dyn Middleware>>>,
49}
50
51impl Clone for Client {
52 fn clone(&self) -> Self {
59 Self {
60 config: self.config.clone(),
61 effect_sender: Arc::clone(&self.effect_sender),
62 middleware: Arc::new(self.middleware.iter().cloned().collect()),
63 }
64 }
65}
66
67impl fmt::Debug for Client {
68 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
69 write!(f, "Client {{}}")
70 }
71}
72
73impl Client {
74 #[cfg(test)]
75 pub(crate) fn new<Sender>(sender: Sender) -> Self
76 where
77 Sender: EffectSender + Send + Sync + 'static,
78 {
79 Self {
80 config: Config::default(),
81 effect_sender: Arc::new(sender),
82 middleware: Arc::new(vec![]),
83 }
84 }
85
86 #[cfg(test)]
87 pub(crate) fn new_with_config<Sender>(sender: Sender, config: Config) -> Self
88 where
89 Sender: EffectSender + Send + Sync + 'static,
90 {
91 Self {
92 config,
93 effect_sender: Arc::new(sender),
94 middleware: Arc::new(vec![]),
95 }
96 }
97
98 #[allow(dead_code)]
101 pub(crate) fn with(mut self, middleware: impl Middleware) -> Self {
107 let m = Arc::get_mut(&mut self.middleware)
108 .expect("Registering middleware is not possible after the Client has been used");
109 m.push(Arc::new(middleware));
110 self
111 }
112
113 pub async fn send(&self, request: impl Into<Request>) -> Result<RawResponse> {
121 let mut request: Request = request.into();
122
123 for name in self.config.headers.keys() {
127 if request.header(name).is_none() {
128 for value in self.config.headers.get_all(name) {
129 request.append_header(name.clone(), value.clone());
130 }
131 }
132 }
133
134 let middleware = self.middleware.clone();
135
136 let mw_stack = match request.take_middleware() {
137 Some(req_mw) => {
138 let mut mw = Vec::with_capacity(middleware.len() + req_mw.len());
139 mw.extend(middleware.iter().cloned());
140 mw.extend(req_mw);
141 Arc::new(mw)
142 }
143 None => middleware,
144 };
145
146 let next = Next::new(&mw_stack, &|request, client| {
147 Box::pin(async move {
148 let request = request
149 .into_protocol_request()
150 .expect("Failed to create request");
151 match client.effect_sender.send(request).await {
152 HttpResult::Ok(response) => response.try_into(),
153 HttpResult::Err(e) => Err(e),
154 }
155 })
156 });
157
158 let client = Self {
159 config: self.config.clone(),
160 effect_sender: Arc::clone(&self.effect_sender),
161 middleware: Arc::new(vec![]),
164 };
165
166 let response = next.run(request, client).await?;
167 Ok(response)
168 }
169
170 pub async fn recv_bytes(&self, request: impl Into<Request>) -> Result<Vec<u8>> {
175 let mut response = self.send(request.into()).await?;
176 response.body_bytes()
177 }
178
179 pub async fn recv_string(&self, request: impl Into<Request>) -> Result<String> {
184 let mut response = self.send(request.into()).await?;
185 response.body_string()
186 }
187
188 pub async fn recv_json<T: serde::de::DeserializeOwned>(
193 &self,
194 request: impl Into<Request>,
195 ) -> Result<T> {
196 let mut response = self.send(request.into()).await?;
197 response.body_json::<T>()
198 }
199
200 pub async fn recv_form<T: serde::de::DeserializeOwned>(
210 &self,
211 request: impl Into<Request>,
212 ) -> Result<T> {
213 let mut response = self.send(request.into()).await?;
214 response.body_form::<T>()
215 }
216
217 pub fn get(&self, uri: impl AsRef<str>) -> RequestBuilder<()> {
227 RequestBuilder::new_for_middleware(Method::GET, self.url(uri), self.clone())
228 }
229
230 pub fn head(&self, uri: impl AsRef<str>) -> RequestBuilder<()> {
240 RequestBuilder::new_for_middleware(Method::HEAD, self.url(uri), self.clone())
241 }
242
243 pub fn post(&self, uri: impl AsRef<str>) -> RequestBuilder<()> {
253 RequestBuilder::new_for_middleware(Method::POST, self.url(uri), self.clone())
254 }
255
256 pub fn put(&self, uri: impl AsRef<str>) -> RequestBuilder<()> {
266 RequestBuilder::new_for_middleware(Method::PUT, self.url(uri), self.clone())
267 }
268
269 pub fn delete(&self, uri: impl AsRef<str>) -> RequestBuilder<()> {
279 RequestBuilder::new_for_middleware(Method::DELETE, self.url(uri), self.clone())
280 }
281
282 pub fn connect(&self, uri: impl AsRef<str>) -> RequestBuilder<()> {
292 RequestBuilder::new_for_middleware(Method::CONNECT, self.url(uri), self.clone())
293 }
294
295 pub fn options(&self, uri: impl AsRef<str>) -> RequestBuilder<()> {
305 RequestBuilder::new_for_middleware(Method::OPTIONS, self.url(uri), self.clone())
306 }
307
308 pub fn trace(&self, uri: impl AsRef<str>) -> RequestBuilder<()> {
318 RequestBuilder::new_for_middleware(Method::TRACE, self.url(uri), self.clone())
319 }
320
321 pub fn patch(&self, uri: impl AsRef<str>) -> RequestBuilder<()> {
331 RequestBuilder::new_for_middleware(Method::PATCH, self.url(uri), self.clone())
332 }
333
334 pub fn request(&self, verb: Method, uri: impl AsRef<str>) -> RequestBuilder<()> {
344 RequestBuilder::new_for_middleware(verb, self.url(uri), self.clone())
345 }
346
347 #[must_use]
349 #[allow(clippy::missing_const_for_fn)]
350 pub fn config(&self) -> &Config {
351 &self.config
352 }
353
354 fn url(&self, uri: impl AsRef<str>) -> Url {
356 self.config.base_url.as_ref().map_or_else(
357 || uri.as_ref().parse().unwrap(),
358 |base| base.join(uri.as_ref()).unwrap(),
359 )
360 }
361}
362
363#[cfg(test)]
364mod client_tests {
365 use super::Client;
366 use crate::protocol::{HttpRequest, HttpResponse};
367 use crate::testing::FakeShell;
368
369 #[futures_test::test]
370 async fn an_http_get() {
371 let shell = FakeShell::default();
372 shell.provide_response(HttpResponse::ok().body("Hello World!").build());
373
374 let client = Client::new(shell.clone());
375
376 let mut response = client.get("https://example.com").await.unwrap();
377 assert_eq!(response.body_string().unwrap(), "Hello World!");
378
379 assert_eq!(
380 shell.take_requests_received(),
381 vec![HttpRequest::get("https://example.com/").build()]
382 );
383 }
384
385 #[futures_test::test]
386 async fn config_headers_are_sent_with_every_request() {
387 let shell = FakeShell::default();
388 shell.provide_response(HttpResponse::ok().build());
389
390 let config = crate::Config::default()
391 .add_header("x-api-key", "secret")
392 .unwrap();
393 let client = Client::new_with_config(shell.clone(), config);
394
395 client.get("https://example.com").await.unwrap();
396
397 let reqs = shell.take_requests_received();
398 assert_eq!(reqs.len(), 1);
399 assert!(
400 reqs[0]
401 .headers
402 .iter()
403 .any(|h| h.name == "x-api-key" && h.value == "secret"),
404 "x-api-key config header must appear in the outgoing request"
405 );
406 }
407
408 #[futures_test::test]
409 async fn per_request_header_takes_precedence_over_config_header() {
410 let shell = FakeShell::default();
411 shell.provide_response(HttpResponse::ok().build());
412
413 let config = crate::Config::default()
414 .add_header("x-version", "config-value")
415 .unwrap();
416 let client = Client::new_with_config(shell.clone(), config);
417
418 let mut req =
420 crate::Request::new(http::Method::GET, "https://example.com".parse().unwrap());
421 req.insert_header("x-version", http::HeaderValue::from_static("request-value"));
422 client.send(req).await.unwrap();
423
424 let reqs = shell.take_requests_received();
425 let values: Vec<&str> = reqs[0]
426 .headers
427 .iter()
428 .filter(|h| h.name == "x-version")
429 .map(|h| h.value.as_str())
430 .collect();
431
432 assert_eq!(values, ["request-value"], "per-request header must win");
433 }
434
435 #[futures_test::test]
436 async fn recv_bytes_returns_body() {
437 let shell = FakeShell::default();
438 shell.provide_response(HttpResponse::ok().body("bytes").build());
439 let client = Client::new(shell);
440 let bytes = client
441 .recv_bytes(crate::Request::new(
442 http::Method::GET,
443 "https://example.com".parse().unwrap(),
444 ))
445 .await
446 .unwrap();
447 assert_eq!(bytes, b"bytes");
448 }
449
450 #[futures_test::test]
451 async fn recv_string_returns_body() {
452 let shell = FakeShell::default();
453 shell.provide_response(HttpResponse::ok().body("hello").build());
454 let client = Client::new(shell);
455 let text = client
456 .recv_string(crate::Request::new(
457 http::Method::GET,
458 "https://example.com".parse().unwrap(),
459 ))
460 .await
461 .unwrap();
462 assert_eq!(text, "hello");
463 }
464
465 #[futures_test::test]
466 async fn recv_json_deserializes_body() {
467 #[derive(serde::Deserialize, PartialEq, Debug)]
468 struct Payload {
469 value: u32,
470 }
471 let shell = FakeShell::default();
472 shell.provide_response(
473 HttpResponse::ok()
474 .header("content-type", "application/json")
475 .json(serde_json::json!({"value": 42}))
476 .build(),
477 );
478 let client = Client::new(shell);
479 let payload: Payload = client
480 .recv_json(crate::Request::new(
481 http::Method::GET,
482 "https://example.com".parse().unwrap(),
483 ))
484 .await
485 .unwrap();
486 assert_eq!(payload, Payload { value: 42 });
487 }
488
489 #[futures_test::test]
490 async fn recv_form_deserializes_body() {
491 #[derive(serde::Deserialize, PartialEq, Debug)]
492 struct Payload {
493 key: String,
494 }
495 let shell = FakeShell::default();
496 shell.provide_response(HttpResponse::ok().body("key=val").build());
497 let client = Client::new(shell);
498 let payload: Payload = client
499 .recv_form(crate::Request::new(
500 http::Method::GET,
501 "https://example.com".parse().unwrap(),
502 ))
503 .await
504 .unwrap();
505 assert_eq!(payload, Payload { key: "val".into() });
506 }
507}