Coverage Report

Created: 2026-07-30 06:46

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/rust/registry/src/index.crates.io-1949cf8c6b5b557f/ureq-3.3.0/src/request_ext.rs
Line
Count
Source
1
use crate::config::typestate::RequestExtScope;
2
use crate::config::{Config, ConfigBuilder, RequestLevelConfig};
3
use crate::{Agent, AsSendBody, Body, Error, http};
4
use std::ops::Deref;
5
use ureq_proto::http::{Request, Response};
6
7
/// Extension trait for [`http::Request<impl AsSendBody>`].
8
///
9
/// Adds additional convenience methods to the `Request` that are not available
10
/// in the plain http API.
11
pub trait RequestExt<S>
12
where
13
    S: AsSendBody,
14
{
15
    /// Allows configuring the request behaviour, starting with the default [`Agent`].
16
    ///
17
    /// This method allows configuring the request by using the default Agent, and performing
18
    /// additional configurations on top.
19
    /// This method returns a `WithAgent` struct that it is possible to call `configure()` and `run()`
20
    /// on to configure the request behaviour, or run the request.
21
    ///
22
    /// # Example
23
    ///
24
    /// ```
25
    /// use ureq::{http, RequestExt, Error};
26
    ///
27
    /// let request: Result<http::Response<_>, Error> = http::Request::builder()
28
    ///             .method(http::Method::GET)
29
    ///             .uri("http://foo.bar")
30
    ///             .body(())
31
    ///             .unwrap()
32
    ///             .with_default_agent()
33
    ///             .configure()
34
    ///             .http_status_as_error(false)
35
    ///             .run();
36
    /// ```
37
0
    fn with_default_agent(self) -> WithAgent<'static, S>
38
0
    where
39
0
        Self: Sized,
40
    {
41
0
        let agent = Agent::new_with_defaults();
42
0
        Self::with_agent(self, agent)
43
0
    }
44
45
    /// Allows configuring this request behaviour, using a specific [`Agent`].
46
    ///
47
    /// This method allows configuring the request by using a user-provided `Agent` and performing
48
    /// additional configurations on top.
49
    /// This method returns a `WithAgent` struct that it is possible to call `configure()` and `run()`
50
    /// on to configure the request behaviour, or run the request.
51
    ///
52
    /// # Example
53
    ///
54
    /// ```
55
    /// use ureq::{http, Agent, RequestExt, Error};
56
    /// use std::time::Duration;
57
    /// let agent = Agent::config_builder()
58
    ///     .timeout_global(Some(Duration::from_secs(30)))
59
    ///     .build()
60
    ///     .new_agent();
61
    ///
62
    /// let request: Result<http::Response<_>, Error> = http::Request::builder()
63
    ///             .method(http::Method::GET)
64
    ///             .uri("http://foo.bar")
65
    ///             .body(())
66
    ///             .unwrap()
67
    ///             .with_agent(&agent)
68
    ///             .run();
69
    /// ```
70
    /// # Example with further customizations
71
    ///
72
    /// In this example we use a specific agent, but apply a request-specific configuration on top.
73
    ///
74
    /// ```
75
    /// use ureq::{http, Agent, RequestExt, Error};
76
    /// use std::time::Duration;
77
    /// let mut agent = Agent::config_builder()
78
    ///     .timeout_global(Some(Duration::from_secs(30)))
79
    ///     .build()
80
    ///     .new_agent();
81
    ///
82
    /// let request: Result<http::Response<_>, Error> = http::Request::builder()
83
    ///             .method(http::Method::GET)
84
    ///             .uri("http://foo.bar")
85
    ///             .body(())
86
    ///             .unwrap()
87
    ///             .with_agent(&agent)
88
    ///             .configure()
89
    ///             .http_status_as_error(false)
90
    ///             .run();
91
    /// ```
92
    fn with_agent<'a>(self, agent: impl Into<AgentRef<'a>>) -> WithAgent<'a, S>;
93
}
94
95
/// Wrapper struct that holds a [`Request`] associated with an [`Agent`].
96
pub struct WithAgent<'a, S: AsSendBody> {
97
    pub(crate) agent: AgentRef<'a>,
98
    pub(crate) request: Request<S>,
99
}
100
101
impl<'a, S: AsSendBody> WithAgent<'a, S> {
102
    /// Returns a [`ConfigBuilder`] for configuring the request.
103
    ///
104
    /// This allows setting additional request-specific options before sending the request.
105
0
    pub fn configure(self) -> ConfigBuilder<RequestExtScope<'a, S>> {
106
0
        ConfigBuilder(RequestExtScope(self))
107
0
    }
108
109
    /// Executes the request using the associated [`Agent`].
110
0
    pub fn run(self) -> Result<Response<Body>, Error> {
111
0
        self.agent.run(self.request)
112
0
    }
113
}
114
115
impl<'a, S: AsSendBody> WithAgent<'a, S> {
116
0
    pub(crate) fn request_level_config(&mut self) -> &mut Config {
117
0
        let request_level_config = self
118
0
            .request
119
0
            .extensions_mut()
120
0
            .get_mut::<RequestLevelConfig>();
121
122
0
        if request_level_config.is_none() {
123
0
            self.request
124
0
                .extensions_mut()
125
0
                .insert(self.agent.new_request_level_config());
126
0
        }
127
128
        // Unwrap is safe because of the above check
129
0
        let req_level: &mut RequestLevelConfig = self
130
0
            .request
131
0
            .extensions_mut()
132
0
            .get_mut::<RequestLevelConfig>()
133
0
            .unwrap();
134
135
0
        &mut req_level.0
136
0
    }
137
}
138
139
/// Reference type to hold an owned or borrowed [`Agent`].
140
pub enum AgentRef<'a> {
141
    Owned(Agent),
142
    Borrowed(&'a Agent),
143
}
144
145
impl<S: AsSendBody> RequestExt<S> for http::Request<S> {
146
0
    fn with_agent<'a>(self, agent: impl Into<AgentRef<'a>>) -> WithAgent<'a, S> {
147
0
        WithAgent {
148
0
            agent: agent.into(),
149
0
            request: self,
150
0
        }
151
0
    }
152
}
153
154
impl From<Agent> for AgentRef<'static> {
155
0
    fn from(value: Agent) -> Self {
156
0
        AgentRef::Owned(value)
157
0
    }
158
}
159
160
impl<'a> From<&'a Agent> for AgentRef<'a> {
161
0
    fn from(value: &'a Agent) -> Self {
162
0
        AgentRef::Borrowed(value)
163
0
    }
164
}
165
166
impl Deref for AgentRef<'_> {
167
    type Target = Agent;
168
169
0
    fn deref(&self) -> &Self::Target {
170
0
        match self {
171
0
            AgentRef::Owned(agent) => agent,
172
0
            AgentRef::Borrowed(agent) => agent,
173
        }
174
0
    }
175
}
176
177
#[cfg(test)]
178
mod tests {
179
    use super::*;
180
    use crate::config::RequestLevelConfig;
181
    use std::time::Duration;
182
183
    #[test]
184
    fn configure_request_with_default_agent() {
185
        // Create `http` crate request and configure with trait
186
        let request = http::Request::builder()
187
            .method(http::Method::GET)
188
            .uri("http://foo.bar")
189
            .body(())
190
            .unwrap()
191
            .with_default_agent()
192
            .configure()
193
            .https_only(true)
194
            .build();
195
196
        // Assert that the request-level configuration has been set
197
        let request_config = request
198
            .request
199
            .extensions()
200
            .get::<RequestLevelConfig>()
201
            .cloned()
202
            .unwrap();
203
204
        assert!(request_config.0.https_only());
205
    }
206
207
    #[test]
208
    fn configure_request_default_agent_2() {
209
        // Create `http` crate request and configure with trait
210
        let request = http::Request::builder()
211
            .method(http::Method::GET)
212
            .uri("http://foo.bar")
213
            .body(())
214
            .unwrap()
215
            .with_default_agent()
216
            .configure()
217
            .https_only(false)
218
            .build();
219
220
        // Assert that the request-level configuration has been set
221
        let request_config = request
222
            .request
223
            .extensions()
224
            .get::<RequestLevelConfig>()
225
            .cloned()
226
            .unwrap();
227
228
        assert!(!request_config.0.https_only());
229
    }
230
231
    #[test]
232
    fn configure_request_default_agent_3() {
233
        // Create `http` crate request
234
        let request = http::Request::builder()
235
            .method(http::Method::POST)
236
            .uri("http://foo.bar")
237
            .body("Some body")
238
            .unwrap();
239
240
        // Configure with the trait
241
        let request = request
242
            .with_default_agent()
243
            .configure()
244
            .http_status_as_error(true)
245
            .build();
246
247
        let request_config = request
248
            .request
249
            .extensions()
250
            .get::<RequestLevelConfig>()
251
            .cloned()
252
            .unwrap();
253
254
        assert!(request_config.0.http_status_as_error());
255
    }
256
257
    #[test]
258
    fn configure_request_default_agent_4() {
259
        // Create `http` crate request
260
        let request = http::Request::builder()
261
            .method(http::Method::POST)
262
            .uri("http://foo.bar")
263
            .body("Some body")
264
            .unwrap();
265
266
        // Configure with the trait
267
        let request = request
268
            .with_default_agent()
269
            .configure()
270
            .http_status_as_error(false)
271
            .build();
272
273
        let request_config = request
274
            .request
275
            .extensions()
276
            .get::<RequestLevelConfig>()
277
            .cloned()
278
            .unwrap();
279
280
        assert!(!request_config.0.http_status_as_error());
281
    }
282
283
    #[test]
284
    fn configure_request_specified_agent() {
285
        // Create `http` crate request
286
        let request = http::Request::builder()
287
            .method(http::Method::POST)
288
            .uri("http://foo.bar")
289
            .body("Some body")
290
            .unwrap();
291
292
        // Configure with the trait
293
        let agent = Agent::config_builder()
294
            .timeout_per_call(Some(Duration::from_secs(60)))
295
            .build()
296
            .new_agent();
297
298
        let request = request
299
            .with_agent(&agent)
300
            .configure()
301
            .http_status_as_error(false)
302
            .build();
303
304
        let request_config = request
305
            .request
306
            .extensions()
307
            .get::<RequestLevelConfig>()
308
            .cloned()
309
            .unwrap();
310
311
        // The request-level config is the agent defaults + the explicitly configured stuff
312
        assert!(!request_config.0.http_status_as_error());
313
        assert_eq!(
314
            request_config.0.timeouts().per_call,
315
            Some(Duration::from_secs(60))
316
        );
317
    }
318
}