nestrs_http/lib.rs
1//! Outbound HTTP client for the [`nestrs`](https://crates.io/crates/nestrs)
2//! framework — the Rust equivalent of NestJS's
3//! [`@nestjs/axios`](https://docs.nestjs.com/techniques/http-module).
4//!
5//! See the [`README`](https://github.com/Joshyahweh/nestrs/tree/main/nestrs-http)
6//! for the high-level API. The umbrella's pre-extraction `nestrs::HttpService`
7//! (in `nestrs/src/http_client.rs`) becomes a 1-line shim over this crate;
8//! the real implementation lives here.
9//!
10//! ## Feature flags
11//! - `default = []` — base crate.
12//! - `reqwest` — re-exports `reqwest` at the crate root.
13
14#![deny(missing_docs)]
15
16use nestrs_core::{Injectable, ProviderRegistry};
17use std::sync::Arc;
18
19/// Default overall timeout for every request issued through [`HttpService`].
20///
21/// reqwest has **no** default timeout — without this, a TCP-connected but
22/// unresponsive upstream hangs the handler future forever, and concurrent
23/// hung calls accumulate until the runtime is saturated.
24pub const DEFAULT_REQUEST_TIMEOUT: std::time::Duration = std::time::Duration::from_secs(30);
25
26/// Default TCP/TLS connect timeout for [`HttpService`] requests.
27pub const DEFAULT_CONNECT_TIMEOUT: std::time::Duration = std::time::Duration::from_secs(10);
28
29/// Shared [`reqwest::Client`] for outbound HTTP (inject where needed).
30pub struct HttpService {
31 client: reqwest::Client,
32}
33
34/// Tunables for the shared [`HttpService`] client.
35///
36/// All values are `Duration`s applied via reqwest's `timeout` (whole request)
37/// and `connect_timeout` (TCP+TLS establishment). Callers can still override
38/// the overall timeout per request with `.timeout()` on the returned builder.
39///
40/// `HttpModule` registers a service with these defaults. To inject a tuned
41/// instance instead, build it with [`HttpService::from_options`] and register
42/// the value (this wins over the module's default provider):
43///
44/// ```no_run
45/// # use std::sync::Arc;
46/// # use nestrs_http::{HttpService, HttpServiceOptions};
47/// # use nestrs_core::ProviderRegistry;
48/// let mut registry = ProviderRegistry::new();
49/// let options = HttpServiceOptions {
50/// request_timeout: std::time::Duration::from_secs(5),
51/// ..Default::default()
52/// };
53/// registry.register_use_value::<HttpService>(Arc::new(HttpService::from_options(&options)));
54/// ```
55#[derive(Clone, Debug)]
56pub struct HttpServiceOptions {
57 /// Whole-request deadline (connect + writes + reads + body). Default
58 /// [`DEFAULT_REQUEST_TIMEOUT`].
59 pub request_timeout: std::time::Duration,
60 /// TCP/TLS establishment deadline. Default [`DEFAULT_CONNECT_TIMEOUT`].
61 pub connect_timeout: std::time::Duration,
62}
63
64impl Default for HttpServiceOptions {
65 fn default() -> Self {
66 Self {
67 request_timeout: DEFAULT_REQUEST_TIMEOUT,
68 connect_timeout: DEFAULT_CONNECT_TIMEOUT,
69 }
70 }
71}
72
73impl Injectable for HttpService {
74 fn construct(_registry: &ProviderRegistry) -> Arc<Self> {
75 Arc::new(Self::from_options(&HttpServiceOptions::default()))
76 }
77}
78
79impl HttpService {
80 /// Build a service from explicit options.
81 pub fn from_options(options: &HttpServiceOptions) -> Self {
82 let client = reqwest::Client::builder()
83 .connect_timeout(options.connect_timeout)
84 .timeout(options.request_timeout)
85 .build()
86 .unwrap_or_else(|e| {
87 panic!("nestrs_http HttpService: reqwest::Client::build failed: {e}")
88 });
89 Self { client }
90 }
91
92 /// Borrow the underlying `reqwest::Client`. Most callers won't need this
93 /// — the typed wrapper exposes every operation we ship — but advanced
94 /// users (custom middleware, retry policies, interceptors) can drop down.
95 pub fn client(&self) -> &reqwest::Client {
96 &self.client
97 }
98
99 /// Start a `GET` request to `url`.
100 pub fn get(&self, url: impl reqwest::IntoUrl) -> reqwest::RequestBuilder {
101 self.client.get(url)
102 }
103
104 /// Start a `POST` request to `url`.
105 pub fn post(&self, url: impl reqwest::IntoUrl) -> reqwest::RequestBuilder {
106 self.client.post(url)
107 }
108
109 /// Start a `PUT` request to `url`.
110 pub fn put(&self, url: impl reqwest::IntoUrl) -> reqwest::RequestBuilder {
111 self.client.put(url)
112 }
113
114 /// Start a `PATCH` request to `url`.
115 pub fn patch(&self, url: impl reqwest::IntoUrl) -> reqwest::RequestBuilder {
116 self.client.patch(url)
117 }
118
119 /// Start a `DELETE` request to `url`.
120 pub fn delete(&self, url: impl reqwest::IntoUrl) -> reqwest::RequestBuilder {
121 self.client.delete(url)
122 }
123}
124
125/// Registers a singleton [`HttpService`] (and re-exports it). Mirror of the
126/// pre-extraction `nestrs::HttpModule` shape.
127pub struct HttpModule;
128
129impl HttpModule {
130 /// Install the module — registers the singleton [`HttpService`]
131 /// provider.
132 pub fn register() -> Self {
133 Self
134 }
135}
136
137impl std::fmt::Debug for HttpModule {
138 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
139 f.debug_struct("HttpModule").finish()
140 }
141}
142
143impl std::fmt::Debug for HttpService {
144 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
145 f.debug_struct("HttpService").finish_non_exhaustive()
146 }
147}
148
149#[cfg(feature = "reqwest")]
150pub use reqwest;