1use crate::{Method, Request, Response, Uri, headers::HeaderExt};
2use rama_core::{
3 Context, Service,
4 error::{BoxError, ErrorExt, OpaqueError},
5};
6
7pub trait HttpClientExt<State>:
11 private::HttpClientExtSealed<State> + Sized + Send + Sync + 'static
12{
13 type ExecuteResponse;
15 type ExecuteError;
17
18 fn get(&self, url: impl IntoUrl) -> RequestBuilder<Self, State, Self::ExecuteResponse>;
26
27 fn post(&self, url: impl IntoUrl) -> RequestBuilder<Self, State, Self::ExecuteResponse>;
35
36 fn put(&self, url: impl IntoUrl) -> RequestBuilder<Self, State, Self::ExecuteResponse>;
44
45 fn patch(&self, url: impl IntoUrl) -> RequestBuilder<Self, State, Self::ExecuteResponse>;
53
54 fn delete(&self, url: impl IntoUrl) -> RequestBuilder<Self, State, Self::ExecuteResponse>;
62
63 fn head(&self, url: impl IntoUrl) -> RequestBuilder<Self, State, Self::ExecuteResponse>;
69
70 fn request(
83 &self,
84 method: Method,
85 url: impl IntoUrl,
86 ) -> RequestBuilder<Self, State, Self::ExecuteResponse>;
87
88 fn execute(
94 &self,
95 ctx: Context<State>,
96 request: Request,
97 ) -> impl Future<Output = Result<Self::ExecuteResponse, Self::ExecuteError>>;
98}
99
100impl<State, S, Body> HttpClientExt<State> for S
101where
102 S: Service<State, Request, Response = Response<Body>, Error: Into<BoxError>>,
103{
104 type ExecuteResponse = Response<Body>;
105 type ExecuteError = S::Error;
106
107 fn get(&self, url: impl IntoUrl) -> RequestBuilder<Self, State, Self::ExecuteResponse> {
108 self.request(Method::GET, url)
109 }
110
111 fn post(&self, url: impl IntoUrl) -> RequestBuilder<Self, State, Self::ExecuteResponse> {
112 self.request(Method::POST, url)
113 }
114
115 fn put(&self, url: impl IntoUrl) -> RequestBuilder<Self, State, Self::ExecuteResponse> {
116 self.request(Method::PUT, url)
117 }
118
119 fn patch(&self, url: impl IntoUrl) -> RequestBuilder<Self, State, Self::ExecuteResponse> {
120 self.request(Method::PATCH, url)
121 }
122
123 fn delete(&self, url: impl IntoUrl) -> RequestBuilder<Self, State, Self::ExecuteResponse> {
124 self.request(Method::DELETE, url)
125 }
126
127 fn head(&self, url: impl IntoUrl) -> RequestBuilder<Self, State, Self::ExecuteResponse> {
128 self.request(Method::HEAD, url)
129 }
130
131 fn request(
132 &self,
133 method: Method,
134 url: impl IntoUrl,
135 ) -> RequestBuilder<Self, State, Self::ExecuteResponse> {
136 let uri = match url.into_url() {
137 Ok(uri) => uri,
138 Err(err) => {
139 return RequestBuilder {
140 http_client_service: self,
141 state: RequestBuilderState::Error(err),
142 _phantom: std::marker::PhantomData,
143 };
144 }
145 };
146
147 let builder = crate::dep::http::request::Builder::new()
148 .method(method)
149 .uri(uri);
150
151 RequestBuilder {
152 http_client_service: self,
153 state: RequestBuilderState::PreBody(builder),
154 _phantom: std::marker::PhantomData,
155 }
156 }
157
158 fn execute(
159 &self,
160 ctx: Context<State>,
161 request: Request,
162 ) -> impl Future<Output = Result<Self::ExecuteResponse, Self::ExecuteError>> {
163 Service::serve(self, ctx, request)
164 }
165}
166
167pub trait IntoUrl: private::IntoUrlSealed {}
173
174impl IntoUrl for Uri {}
175impl IntoUrl for &str {}
176impl IntoUrl for String {}
177impl IntoUrl for &String {}
178
179pub trait IntoHeaderName: private::IntoHeaderNameSealed {}
185
186impl IntoHeaderName for crate::HeaderName {}
187impl IntoHeaderName for Option<crate::HeaderName> {}
188impl IntoHeaderName for &str {}
189impl IntoHeaderName for String {}
190impl IntoHeaderName for &String {}
191impl IntoHeaderName for &[u8] {}
192
193pub trait IntoHeaderValue: private::IntoHeaderValueSealed {}
199
200impl IntoHeaderValue for crate::HeaderValue {}
201impl IntoHeaderValue for &str {}
202impl IntoHeaderValue for String {}
203impl IntoHeaderValue for &String {}
204impl IntoHeaderValue for &[u8] {}
205
206mod private {
207 use rama_http_types::HeaderName;
208 use rama_net::Protocol;
209
210 use super::*;
211
212 pub trait IntoUrlSealed {
213 fn into_url(self) -> Result<Uri, OpaqueError>;
214 }
215
216 impl IntoUrlSealed for Uri {
217 fn into_url(self) -> Result<Uri, OpaqueError> {
218 let protocol: Option<Protocol> = self.scheme().map(Into::into);
219 match protocol {
220 Some(protocol) => {
221 if protocol.is_http() {
222 Ok(self)
223 } else {
224 Err(OpaqueError::from_display(format!(
225 "Unsupported protocol: {protocol}"
226 )))
227 }
228 }
229 None => Err(OpaqueError::from_display("Missing scheme in URI")),
230 }
231 }
232 }
233
234 impl IntoUrlSealed for &str {
235 fn into_url(self) -> Result<Uri, OpaqueError> {
236 match self.parse::<Uri>() {
237 Ok(uri) => uri.into_url(),
238 Err(_) => Err(OpaqueError::from_display(format!("Invalid URL: {}", self))),
239 }
240 }
241 }
242
243 impl IntoUrlSealed for String {
244 fn into_url(self) -> Result<Uri, OpaqueError> {
245 self.as_str().into_url()
246 }
247 }
248
249 impl IntoUrlSealed for &String {
250 fn into_url(self) -> Result<Uri, OpaqueError> {
251 self.as_str().into_url()
252 }
253 }
254
255 pub trait IntoHeaderNameSealed {
256 fn into_header_name(self) -> Result<crate::HeaderName, OpaqueError>;
257 }
258
259 impl IntoHeaderNameSealed for HeaderName {
260 fn into_header_name(self) -> Result<crate::HeaderName, OpaqueError> {
261 Ok(self)
262 }
263 }
264
265 impl IntoHeaderNameSealed for Option<HeaderName> {
266 fn into_header_name(self) -> Result<crate::HeaderName, OpaqueError> {
267 match self {
268 Some(name) => Ok(name),
269 None => Err(OpaqueError::from_display("Header name is required")),
270 }
271 }
272 }
273
274 impl IntoHeaderNameSealed for &str {
275 fn into_header_name(self) -> Result<crate::HeaderName, OpaqueError> {
276 let name = self
277 .parse::<crate::HeaderName>()
278 .map_err(OpaqueError::from_std)?;
279 Ok(name)
280 }
281 }
282
283 impl IntoHeaderNameSealed for String {
284 fn into_header_name(self) -> Result<crate::HeaderName, OpaqueError> {
285 self.as_str().into_header_name()
286 }
287 }
288
289 impl IntoHeaderNameSealed for &String {
290 fn into_header_name(self) -> Result<crate::HeaderName, OpaqueError> {
291 self.as_str().into_header_name()
292 }
293 }
294
295 impl IntoHeaderNameSealed for &[u8] {
296 fn into_header_name(self) -> Result<crate::HeaderName, OpaqueError> {
297 let name = crate::HeaderName::from_bytes(self).map_err(OpaqueError::from_std)?;
298 Ok(name)
299 }
300 }
301
302 pub trait IntoHeaderValueSealed {
303 fn into_header_value(self) -> Result<crate::HeaderValue, OpaqueError>;
304 }
305
306 impl IntoHeaderValueSealed for crate::HeaderValue {
307 fn into_header_value(self) -> Result<crate::HeaderValue, OpaqueError> {
308 Ok(self)
309 }
310 }
311
312 impl IntoHeaderValueSealed for &str {
313 fn into_header_value(self) -> Result<crate::HeaderValue, OpaqueError> {
314 let value = self
315 .parse::<crate::HeaderValue>()
316 .map_err(OpaqueError::from_std)?;
317 Ok(value)
318 }
319 }
320
321 impl IntoHeaderValueSealed for String {
322 fn into_header_value(self) -> Result<crate::HeaderValue, OpaqueError> {
323 self.as_str().into_header_value()
324 }
325 }
326
327 impl IntoHeaderValueSealed for &String {
328 fn into_header_value(self) -> Result<crate::HeaderValue, OpaqueError> {
329 self.as_str().into_header_value()
330 }
331 }
332
333 impl IntoHeaderValueSealed for &[u8] {
334 fn into_header_value(self) -> Result<crate::HeaderValue, OpaqueError> {
335 let value = crate::HeaderValue::from_bytes(self).map_err(OpaqueError::from_std)?;
336 Ok(value)
337 }
338 }
339
340 pub trait HttpClientExtSealed<State> {}
341
342 impl<State, S, Body> HttpClientExtSealed<State> for S where
343 S: Service<State, Request, Response = Response<Body>, Error: Into<BoxError>>
344 {
345 }
346}
347
348pub struct RequestBuilder<'a, S, State, Response> {
352 http_client_service: &'a S,
353 state: RequestBuilderState,
354 _phantom: std::marker::PhantomData<fn(State, Response) -> ()>,
355}
356
357impl<S, State, Response> std::fmt::Debug for RequestBuilder<'_, S, State, Response>
358where
359 S: std::fmt::Debug,
360{
361 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
362 f.debug_struct("RequestBuilder")
363 .field("http_client_service", &self.http_client_service)
364 .field("state", &self.state)
365 .finish()
366 }
367}
368
369#[derive(Debug)]
370enum RequestBuilderState {
371 PreBody(crate::dep::http::request::Builder),
372 PostBody(crate::Request),
373 Error(OpaqueError),
374}
375
376impl<S, State, Body> RequestBuilder<'_, S, State, Response<Body>>
377where
378 S: Service<State, Request, Response = Response<Body>, Error: Into<BoxError>>,
379{
380 pub fn header<K, V>(mut self, key: K, value: V) -> Self
382 where
383 K: IntoHeaderName,
384 V: IntoHeaderValue,
385 {
386 match self.state {
387 RequestBuilderState::PreBody(builder) => {
388 let key = match key.into_header_name() {
389 Ok(key) => key,
390 Err(err) => {
391 self.state = RequestBuilderState::Error(err);
392 return self;
393 }
394 };
395 let value = match value.into_header_value() {
396 Ok(value) => value,
397 Err(err) => {
398 self.state = RequestBuilderState::Error(err);
399 return self;
400 }
401 };
402 self.state = RequestBuilderState::PreBody(builder.header(key, value));
403 self
404 }
405 RequestBuilderState::PostBody(mut request) => {
406 let key = match key.into_header_name() {
407 Ok(key) => key,
408 Err(err) => {
409 self.state = RequestBuilderState::Error(err);
410 return self;
411 }
412 };
413 let value = match value.into_header_value() {
414 Ok(value) => value,
415 Err(err) => {
416 self.state = RequestBuilderState::Error(err);
417 return self;
418 }
419 };
420 request.headers_mut().append(key, value);
421 self.state = RequestBuilderState::PostBody(request);
422 self
423 }
424 RequestBuilderState::Error(err) => {
425 self.state = RequestBuilderState::Error(err);
426 self
427 }
428 }
429 }
430
431 pub fn typed_header<H>(self, header: H) -> Self
435 where
436 H: crate::headers::Header,
437 {
438 self.header(H::name().clone(), header.encode_to_value())
439 }
440
441 pub fn headers(mut self, headers: crate::HeaderMap) -> Self {
445 for (key, value) in headers.into_iter() {
446 self = self.header(key, value);
447 }
448 self
449 }
450
451 pub fn basic_auth<U, P>(self, username: U, password: P) -> Self
453 where
454 U: AsRef<str>,
455 P: AsRef<str>,
456 {
457 let header = crate::headers::Authorization::basic(username.as_ref(), password.as_ref());
458 self.typed_header(header)
459 }
460
461 pub fn bearer_auth<T>(mut self, token: T) -> Self
463 where
464 T: AsRef<str>,
465 {
466 let header = match crate::headers::Authorization::bearer(token.as_ref()) {
467 Ok(header) => header,
468 Err(err) => {
469 self.state = match self.state {
470 RequestBuilderState::Error(original_err) => {
471 RequestBuilderState::Error(original_err)
472 }
473 _ => RequestBuilderState::Error(OpaqueError::from_std(err)),
474 };
475 return self;
476 }
477 };
478
479 self.typed_header(header)
480 }
481
482 pub fn body<T>(mut self, body: T) -> Self
486 where
487 T: TryInto<crate::Body, Error: Into<BoxError>>,
488 {
489 self.state = match self.state {
490 RequestBuilderState::PreBody(builder) => match body.try_into() {
491 Ok(body) => match builder.body(body) {
492 Ok(req) => RequestBuilderState::PostBody(req),
493 Err(err) => RequestBuilderState::Error(OpaqueError::from_std(err)),
494 },
495 Err(err) => RequestBuilderState::Error(OpaqueError::from_boxed(err.into())),
496 },
497 RequestBuilderState::PostBody(mut req) => match body.try_into() {
498 Ok(body) => {
499 *req.body_mut() = body;
500 RequestBuilderState::PostBody(req)
501 }
502 Err(err) => RequestBuilderState::Error(OpaqueError::from_boxed(err.into())),
503 },
504 RequestBuilderState::Error(err) => RequestBuilderState::Error(err),
505 };
506 self
507 }
508
509 pub fn form<T: serde::Serialize + ?Sized>(mut self, form: &T) -> Self {
513 self.state = match self.state {
514 RequestBuilderState::PreBody(mut builder) => match serde_html_form::to_string(form) {
515 Ok(body) => {
516 let builder = match builder.headers_mut() {
517 Some(headers) => {
518 if !headers.contains_key(crate::header::CONTENT_TYPE) {
519 headers.insert(
520 crate::header::CONTENT_TYPE,
521 crate::HeaderValue::from_static(
522 "application/x-www-form-urlencoded",
523 ),
524 );
525 }
526 builder
527 }
528 None => builder.header(
529 crate::header::CONTENT_TYPE,
530 crate::HeaderValue::from_static("application/x-www-form-urlencoded"),
531 ),
532 };
533 match builder.body(body.into()) {
534 Ok(req) => RequestBuilderState::PostBody(req),
535 Err(err) => RequestBuilderState::Error(OpaqueError::from_std(err)),
536 }
537 }
538 Err(err) => RequestBuilderState::Error(OpaqueError::from_std(err)),
539 },
540 RequestBuilderState::PostBody(mut req) => match serde_html_form::to_string(form) {
541 Ok(body) => {
542 if !req.headers().contains_key(crate::header::CONTENT_TYPE) {
543 req.headers_mut().insert(
544 crate::header::CONTENT_TYPE,
545 crate::HeaderValue::from_static("application/x-www-form-urlencoded"),
546 );
547 }
548 *req.body_mut() = body.into();
549 RequestBuilderState::PostBody(req)
550 }
551 Err(err) => RequestBuilderState::Error(OpaqueError::from_std(err)),
552 },
553 RequestBuilderState::Error(err) => RequestBuilderState::Error(err),
554 };
555 self
556 }
557
558 pub fn json<T: serde::Serialize + ?Sized>(mut self, json: &T) -> Self {
562 self.state = match self.state {
563 RequestBuilderState::PreBody(mut builder) => match serde_json::to_vec(json) {
564 Ok(body) => {
565 let builder = match builder.headers_mut() {
566 Some(headers) => {
567 if !headers.contains_key(crate::header::CONTENT_TYPE) {
568 headers.insert(
569 crate::header::CONTENT_TYPE,
570 crate::HeaderValue::from_static("application/json"),
571 );
572 }
573 builder
574 }
575 None => builder.header(
576 crate::header::CONTENT_TYPE,
577 crate::HeaderValue::from_static("application/json"),
578 ),
579 };
580 match builder.body(body.into()) {
581 Ok(req) => RequestBuilderState::PostBody(req),
582 Err(err) => RequestBuilderState::Error(OpaqueError::from_std(err)),
583 }
584 }
585 Err(err) => RequestBuilderState::Error(OpaqueError::from_std(err)),
586 },
587 RequestBuilderState::PostBody(mut req) => match serde_json::to_vec(json) {
588 Ok(body) => {
589 if !req.headers().contains_key(crate::header::CONTENT_TYPE) {
590 req.headers_mut().insert(
591 crate::header::CONTENT_TYPE,
592 crate::HeaderValue::from_static("application/json"),
593 );
594 }
595 *req.body_mut() = body.into();
596 RequestBuilderState::PostBody(req)
597 }
598 Err(err) => RequestBuilderState::Error(OpaqueError::from_std(err)),
599 },
600 RequestBuilderState::Error(err) => RequestBuilderState::Error(err),
601 };
602 self
603 }
604
605 pub fn version(mut self, version: crate::Version) -> Self {
609 match self.state {
610 RequestBuilderState::PreBody(builder) => {
611 self.state = RequestBuilderState::PreBody(builder.version(version));
612 self
613 }
614 RequestBuilderState::PostBody(mut request) => {
615 *request.version_mut() = version;
616 self.state = RequestBuilderState::PostBody(request);
617 self
618 }
619 RequestBuilderState::Error(err) => {
620 self.state = RequestBuilderState::Error(err);
621 self
622 }
623 }
624 }
625
626 pub async fn send(self, ctx: Context<State>) -> Result<Response<Body>, OpaqueError> {
632 let request = match self.state {
633 RequestBuilderState::PreBody(builder) => builder
634 .body(crate::Body::empty())
635 .map_err(OpaqueError::from_std)?,
636 RequestBuilderState::PostBody(request) => request,
637 RequestBuilderState::Error(err) => return Err(err),
638 };
639
640 let uri = request.uri().clone();
641 match self.http_client_service.serve(ctx, request).await {
642 Ok(response) => Ok(response),
643 Err(err) => Err(OpaqueError::from_boxed(err.into()).context(uri.to_string())),
644 }
645 }
646}
647
648#[cfg(test)]
649mod test {
650 use rama_http_types::StatusCode;
651
652 use super::*;
653 use crate::{
654 IntoResponse,
655 layer::{
656 required_header::AddRequiredRequestHeadersLayer,
657 retry::{ManagedPolicy, RetryLayer},
658 trace::TraceLayer,
659 },
660 };
661 use rama_core::{
662 layer::{Layer, MapResultLayer},
663 service::{BoxService, service_fn},
664 };
665 use rama_utils::backoff::ExponentialBackoff;
666 use std::convert::Infallible;
667
668 async fn fake_client_fn<S, Body>(
669 _ctx: Context<S>,
670 request: Request<Body>,
671 ) -> Result<Response, Infallible>
672 where
673 S: Clone + Send + Sync + 'static,
674 Body: crate::dep::http_body::Body<Data: Send + 'static, Error: Send + 'static>
675 + Send
676 + 'static,
677 {
678 let ua = request.headers().get(crate::header::USER_AGENT).unwrap();
679 assert_eq!(
680 ua.to_str().unwrap(),
681 format!("{}/{}", rama_utils::info::NAME, rama_utils::info::VERSION)
682 );
683
684 Ok(StatusCode::OK.into_response())
685 }
686
687 fn map_internal_client_error<E, Body>(
688 result: Result<Response<Body>, E>,
689 ) -> Result<Response, rama_core::error::BoxError>
690 where
691 E: Into<rama_core::error::BoxError>,
692 Body: crate::dep::http_body::Body<Data = bytes::Bytes, Error: Into<BoxError>>
693 + Send
694 + Sync
695 + 'static,
696 {
697 match result {
698 Ok(response) => Ok(response.map(crate::Body::new)),
699 Err(err) => Err(err.into()),
700 }
701 }
702
703 type OpaqueError = rama_core::error::BoxError;
704 type HttpClient<S> = BoxService<S, Request, Response, OpaqueError>;
705
706 fn client<S: Clone + Send + Sync + 'static>() -> HttpClient<S> {
707 let builder = (
708 MapResultLayer::new(map_internal_client_error),
709 TraceLayer::new_for_http(),
710 );
711
712 #[cfg(feature = "compression")]
713 let builder = (
714 builder,
715 crate::layer::decompression::DecompressionLayer::new(),
716 );
717
718 (
719 builder,
720 RetryLayer::new(ManagedPolicy::default().with_backoff(ExponentialBackoff::default())),
721 AddRequiredRequestHeadersLayer::default(),
722 )
723 .into_layer(service_fn(fake_client_fn))
724 .boxed()
725 }
726
727 #[tokio::test]
728 async fn test_client_happy_path() {
729 let response = client()
730 .get("http://127.0.0.1:8080")
731 .send(Context::default())
732 .await
733 .unwrap();
734 assert_eq!(response.status(), StatusCode::OK);
735 }
736}