timvw_rust_runtime/
client.rs1use std::fmt;
2use std::future::Future;
3use std::pin::Pin;
4use std::sync::{Mutex, PoisonError};
5use std::task::{Context, Poll};
6
7use tower::util::BoxCloneSyncService;
8use tower::{Layer, Service, ServiceExt};
9
10use crate::{Body, BoxError, Error, Method, RequestBuilder, Response};
11
12pub type HttpService = BoxCloneSyncService<http::Request<Body>, http::Response<Body>, BoxError>;
14
15#[derive(Clone)]
31pub struct Client {
32 service: HttpService,
33}
34
35impl Client {
36 #[cfg(feature = "reqwest")]
40 pub fn new() -> Self {
41 Self::from_reqwest(reqwest::Client::new())
42 }
43
44 #[cfg(feature = "reqwest")]
50 pub fn from_reqwest(client: reqwest::Client) -> Self {
51 let client = Self::from_service(crate::ReqwestService::new(client));
52 #[cfg(feature = "retry")]
53 let client = client.layer(tower::retry::RetryLayer::new(
54 crate::retry::RetryPolicy::default(),
55 ));
56 client
57 }
58
59 pub fn from_service<S, B>(service: S) -> Self
64 where
65 S: Service<http::Request<Body>, Response = http::Response<B>> + Clone + Send + 'static,
66 S::Error: Into<BoxError>,
67 S::Future: Send + 'static,
68 B: http_body::Body + Send + Sync + 'static,
69 B::Data: Into<bytes::Bytes>,
70 B::Error: Into<BoxError>,
71 {
72 let service = service
73 .map_response(|response: http::Response<B>| response.map(Body::wrap))
74 .map_err(Into::into);
75 Self {
76 service: BoxCloneSyncService::new(SyncService(Mutex::new(service))),
77 }
78 }
79
80 pub fn layer<L, B>(self, layer: L) -> Self
84 where
85 L: Layer<HttpService>,
86 L::Service:
87 Service<http::Request<Body>, Response = http::Response<B>> + Clone + Send + 'static,
88 <L::Service as Service<http::Request<Body>>>::Error: Into<BoxError>,
89 <L::Service as Service<http::Request<Body>>>::Future: Send + 'static,
90 B: http_body::Body + Send + Sync + 'static,
91 B::Data: Into<bytes::Bytes>,
92 B::Error: Into<BoxError>,
93 {
94 Self::from_service(layer.layer(self.service))
95 }
96
97 pub fn service(&self) -> &HttpService {
99 &self.service
100 }
101
102 pub fn request(&self, method: Method, url: &str) -> RequestBuilder {
104 RequestBuilder::new(self.clone(), method, url)
105 }
106
107 pub async fn execute(&self, request: http::Request<Body>) -> Result<Response, Error> {
109 let response = self
110 .service
111 .clone()
112 .oneshot(request)
113 .await
114 .map_err(Error::request)?;
115 Ok(Response::from(response))
116 }
117}
118
119impl Default for Client {
122 fn default() -> Self {
123 #[cfg(feature = "reqwest")]
124 {
125 Self::new()
126 }
127 #[cfg(not(feature = "reqwest"))]
128 {
129 Self::from_service(NoService)
130 }
131 }
132}
133
134impl fmt::Debug for Client {
135 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
136 f.debug_struct("Client").finish_non_exhaustive()
137 }
138}
139
140struct SyncService<S>(Mutex<S>);
143
144impl<S: Clone> Clone for SyncService<S> {
145 fn clone(&self) -> Self {
146 let service = self
147 .0
148 .lock()
149 .unwrap_or_else(PoisonError::into_inner)
150 .clone();
151 Self(Mutex::new(service))
152 }
153}
154
155impl<S: Service<R>, R> Service<R> for SyncService<S> {
156 type Response = S::Response;
157 type Error = S::Error;
158 type Future = S::Future;
159
160 fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
161 self.0
162 .get_mut()
163 .unwrap_or_else(PoisonError::into_inner)
164 .poll_ready(cx)
165 }
166
167 fn call(&mut self, request: R) -> Self::Future {
168 self.0
169 .get_mut()
170 .unwrap_or_else(PoisonError::into_inner)
171 .call(request)
172 }
173}
174
175#[cfg_attr(feature = "reqwest", allow(dead_code))]
177#[derive(Clone, Copy, Debug)]
178struct NoService;
179
180impl Service<http::Request<Body>> for NoService {
181 type Response = http::Response<Body>;
182 type Error = BoxError;
183 type Future = Pin<Box<dyn Future<Output = Result<Self::Response, Self::Error>> + Send>>;
184
185 fn poll_ready(&mut self, _: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
186 Poll::Ready(Ok(()))
187 }
188
189 fn call(&mut self, _: http::Request<Body>) -> Self::Future {
190 Box::pin(async {
191 Err("no HTTP client: enable the `reqwest` feature of timvw-rust-runtime or set one with Client::from_service".into())
192 })
193 }
194}