Skip to main content

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;