trillium_client/
client.rs1use crate::{Conn, IntoUrl, Pool, USER_AGENT};
2use std::{fmt::Debug, sync::Arc};
3use trillium_http::{
4 transport::BoxedTransport, HeaderName, HeaderValues, Headers, KnownHeaderName, Method,
5 ReceivedBodyState,
6};
7use trillium_server_common::{
8 url::{Origin, Url},
9 Connector, ObjectSafeConnector,
10};
11
12#[derive(Clone, Debug)]
18pub struct Client {
19 config: Arc<dyn ObjectSafeConnector>,
20 pool: Option<Pool<Origin, BoxedTransport>>,
21 base: Option<Arc<Url>>,
22 default_headers: Arc<Headers>,
23}
24
25macro_rules! method {
26 ($fn_name:ident, $method:ident) => {
27 method!(
28 $fn_name,
29 $method,
30 concat!(
31 "Builds a new client conn with the ",
33 stringify!($fn_name),
34 " http method and the provided url.
35
36```
37# use trillium_testing::prelude::*;
38# use trillium_smol::ClientConfig;
39# use trillium_client::Client;
40let client = Client::new(ClientConfig::default());
41let conn = client.",
42 stringify!($fn_name),
43 "(\"http://localhost:8080/some/route\"); //<-
44
45assert_eq!(conn.method(), Method::",
46 stringify!($method),
47 ");
48assert_eq!(conn.url().to_string(), \"http://localhost:8080/some/route\");
49```
50"
51 )
52 );
53 };
54
55 ($fn_name:ident, $method:ident, $doc_comment:expr) => {
56 #[doc = $doc_comment]
57 pub fn $fn_name(&self, url: impl IntoUrl) -> Conn {
58 self.build_conn(Method::$method, url)
59 }
60 };
61}
62
63pub(crate) fn default_request_headers() -> Headers {
64 Headers::new()
65 .with_inserted_header(KnownHeaderName::UserAgent, USER_AGENT)
66 .with_inserted_header(KnownHeaderName::Accept, "*/*")
67}
68
69impl Client {
70 pub fn new(config: impl Connector) -> Self {
72 Self {
73 config: config.arced(),
74 pool: None,
75 base: None,
76 default_headers: Arc::new(default_request_headers()),
77 }
78 }
79
80 pub fn without_default_header(mut self, name: impl Into<HeaderName<'static>>) -> Self {
82 self.default_headers_mut().remove(name);
83 self
84 }
85
86 pub fn with_default_header(
88 mut self,
89 name: impl Into<HeaderName<'static>>,
90 value: impl Into<HeaderValues>,
91 ) -> Self {
92 self.default_headers_mut().insert(name, value);
93 self
94 }
95
96 pub fn default_headers(&self) -> &Headers {
98 &self.default_headers
99 }
100
101 pub fn default_headers_mut(&mut self) -> &mut Headers {
105 Arc::make_mut(&mut self.default_headers)
106 }
107
108 pub fn with_default_pool(mut self) -> Self {
122 self.pool = Some(Pool::default());
123 self
124 }
125
126 pub fn build_conn<M>(&self, method: M, url: impl IntoUrl) -> Conn
146 where
147 M: TryInto<Method>,
148 <M as TryInto<Method>>::Error: Debug,
149 {
150 Conn {
151 url: self.build_url(url).unwrap(),
152 method: method.try_into().unwrap(),
153 request_headers: Headers::clone(&self.default_headers),
154 response_headers: Headers::new(),
155 transport: None,
156 status: None,
157 request_body: None,
158 pool: self.pool.clone(),
159 buffer: Vec::with_capacity(128).into(),
160 response_body_state: ReceivedBodyState::Start,
161 config: Arc::clone(&self.config),
162 headers_finalized: false,
163 }
164 }
165
166 pub fn connector(&self) -> &Arc<dyn ObjectSafeConnector> {
168 &self.config
169 }
170
171 pub fn clean_up_pool(&self) {
178 if let Some(pool) = &self.pool {
179 pool.cleanup();
180 }
181 }
182
183 pub fn with_base(mut self, base: impl IntoUrl) -> Self {
185 self.set_base(base).unwrap();
186 self
187 }
188
189 pub fn base(&self) -> Option<&Url> {
191 self.base.as_deref()
192 }
193
194 pub fn build_url(&self, url: impl IntoUrl) -> crate::Result<Url> {
196 url.into_url(self.base())
197 }
198
199 pub fn set_base(&mut self, base: impl IntoUrl) -> crate::Result<()> {
201 let mut base = base.into_url(None)?;
202
203 if !base.path().ends_with('/') {
204 log::warn!("appending a trailing / to {base}");
205 base.set_path(&format!("{}/", base.path()));
206 }
207
208 self.base = Some(Arc::new(base));
209 Ok(())
210 }
211
212 method!(get, Get);
213 method!(post, Post);
214 method!(put, Put);
215 method!(delete, Delete);
216 method!(patch, Patch);
217}
218
219impl<T: Connector> From<T> for Client {
220 fn from(connector: T) -> Self {
221 Self::new(connector)
222 }
223}