timvw_rust_runtime/
client.rs1use std::fmt;
2#[cfg(not(target_family = "wasm"))]
3use std::sync::{Mutex, PoisonError};
4use std::task::{Context, Poll};
5
6#[cfg(not(target_family = "wasm"))]
7use tower::util::BoxCloneSyncService;
8use tower::{Layer, Service, ServiceExt};
9
10use crate::maybe::BoxFuture;
11use crate::{Body, BoxError, Error, MaybeSend, MaybeSync, Method, RequestBuilder, Response};
12
13#[cfg(not(target_family = "wasm"))]
18pub type HttpService = BoxCloneSyncService<http::Request<Body>, http::Response<Body>, BoxError>;
19#[cfg(target_family = "wasm")]
24pub type HttpService =
25 crate::LocalBoxCloneService<http::Request<Body>, http::Response<Body>, BoxError>;
26
27#[derive(Clone)]
43pub struct Client {
44 service: HttpService,
45}
46
47impl Client {
48 #[cfg(feature = "reqwest")]
52 pub fn new() -> Self {
53 Self::from_reqwest(reqwest::Client::new())
54 }
55
56 #[cfg(feature = "reqwest")]
62 pub fn from_reqwest(client: reqwest::Client) -> Self {
63 let client = Self::from_service(crate::ReqwestService::new(client));
64 #[cfg(feature = "retry")]
65 let client = client.layer(crate::retry::RetryLayer::default());
66 client
67 }
68
69 pub fn from_service<S, B>(service: S) -> Self
75 where
76 S: Service<http::Request<Body>, Response = http::Response<B>> + Clone + MaybeSend + 'static,
77 S::Error: Into<BoxError>,
78 S::Future: MaybeSend + 'static,
79 B: http_body::Body + MaybeSend + MaybeSync + 'static,
80 B::Data: Into<bytes::Bytes>,
81 B::Error: Into<BoxError>,
82 {
83 let service = service
84 .map_response(|response: http::Response<B>| response.map(Body::wrap))
85 .map_err(Into::into);
86 #[cfg(not(target_family = "wasm"))]
87 let service = BoxCloneSyncService::new(SyncService(Mutex::new(service)));
88 #[cfg(target_family = "wasm")]
89 let service = crate::LocalBoxCloneService::new(service);
90 Self { service }
91 }
92
93 pub fn layer<L, B>(self, layer: L) -> Self
97 where
98 L: Layer<HttpService>,
99 L::Service: Service<http::Request<Body>, Response = http::Response<B>>
100 + Clone
101 + MaybeSend
102 + 'static,
103 <L::Service as Service<http::Request<Body>>>::Error: Into<BoxError>,
104 <L::Service as Service<http::Request<Body>>>::Future: MaybeSend + 'static,
105 B: http_body::Body + MaybeSend + MaybeSync + 'static,
106 B::Data: Into<bytes::Bytes>,
107 B::Error: Into<BoxError>,
108 {
109 Self::from_service(layer.layer(self.service))
110 }
111
112 pub fn service(&self) -> &HttpService {
114 &self.service
115 }
116
117 pub fn request(&self, method: Method, url: &str) -> RequestBuilder {
119 RequestBuilder::new(self.clone(), method, url)
120 }
121
122 pub async fn execute(&self, request: http::Request<Body>) -> Result<Response, Error> {
124 let response = self
125 .service
126 .clone()
127 .oneshot(request)
128 .await
129 .map_err(Error::request)?;
130 Ok(Response::from(response))
131 }
132}
133
134impl Default for Client {
137 fn default() -> Self {
138 #[cfg(feature = "reqwest")]
139 {
140 Self::new()
141 }
142 #[cfg(not(feature = "reqwest"))]
143 {
144 Self::from_service(NoService)
145 }
146 }
147}
148
149impl fmt::Debug for Client {
150 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
151 f.debug_struct("Client").finish_non_exhaustive()
152 }
153}
154
155#[cfg(not(target_family = "wasm"))]
158struct SyncService<S>(Mutex<S>);
159
160#[cfg(not(target_family = "wasm"))]
161impl<S: Clone> Clone for SyncService<S> {
162 fn clone(&self) -> Self {
163 let service = self
164 .0
165 .lock()
166 .unwrap_or_else(PoisonError::into_inner)
167 .clone();
168 Self(Mutex::new(service))
169 }
170}
171
172#[cfg(not(target_family = "wasm"))]
173impl<S: Service<R>, R> Service<R> for SyncService<S> {
174 type Response = S::Response;
175 type Error = S::Error;
176 type Future = S::Future;
177
178 fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
179 self.0
180 .get_mut()
181 .unwrap_or_else(PoisonError::into_inner)
182 .poll_ready(cx)
183 }
184
185 fn call(&mut self, request: R) -> Self::Future {
186 self.0
187 .get_mut()
188 .unwrap_or_else(PoisonError::into_inner)
189 .call(request)
190 }
191}
192
193#[cfg_attr(feature = "reqwest", allow(dead_code))]
195#[derive(Clone, Copy, Debug)]
196struct NoService;
197
198impl Service<http::Request<Body>> for NoService {
199 type Response = http::Response<Body>;
200 type Error = BoxError;
201 type Future = BoxFuture<'static, Result<Self::Response, Self::Error>>;
202
203 fn poll_ready(&mut self, _: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
204 Poll::Ready(Ok(()))
205 }
206
207 fn call(&mut self, _: http::Request<Body>) -> Self::Future {
208 Box::pin(async {
209 Err("no HTTP client: enable the `reqwest` feature of timvw-rust-runtime or set one with Client::from_service".into())
210 })
211 }
212}