Coverage Report

Created: 2026-08-31 06:59

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/http/src/uri/builder.rs
Line
Count
Source
1
use std::convert::TryInto;
2
3
use super::{Authority, Parts, PathAndQuery, Scheme};
4
use crate::Uri;
5
6
/// A builder for `Uri`s.
7
///
8
/// This type can be used to construct an instance of `Uri`
9
/// through a builder pattern.
10
#[derive(Debug)]
11
pub struct Builder {
12
    parts: Result<Parts, crate::Error>,
13
}
14
15
impl Builder {
16
    /// Creates a new default instance of `Builder` to construct a `Uri`.
17
    ///
18
    /// # Examples
19
    ///
20
    /// ```
21
    /// # use http::*;
22
    ///
23
    /// let uri = uri::Builder::new()
24
    ///     .scheme("https")
25
    ///     .authority("hyper.rs")
26
    ///     .path_and_query("/")
27
    ///     .build()
28
    ///     .unwrap();
29
    /// ```
30
    #[inline]
31
0
    pub fn new() -> Builder {
32
0
        Builder::default()
33
0
    }
34
35
    /// Set the `Scheme` for this URI.
36
    ///
37
    /// # Examples
38
    ///
39
    /// ```
40
    /// # use http::*;
41
    ///
42
    /// let mut builder = uri::Builder::new();
43
    /// builder.scheme("https");
44
    /// ```
45
    pub fn scheme<T>(self, scheme: T) -> Self
46
    where
47
        T: TryInto<Scheme>,
48
        <T as TryInto<Scheme>>::Error: Into<crate::Error>,
49
    {
50
        self.map(move |mut parts| {
51
            let scheme = scheme.try_into().map_err(Into::into)?;
52
            parts.scheme = Some(scheme);
53
            Ok(parts)
54
        })
55
    }
56
57
    /// Set the `Authority` for this URI.
58
    ///
59
    /// # Examples
60
    ///
61
    /// ```
62
    /// # use http::*;
63
    ///
64
    /// let uri = uri::Builder::new()
65
    ///     .authority("tokio.rs")
66
    ///     .build()
67
    ///     .unwrap();
68
    /// ```
69
    pub fn authority<T>(self, auth: T) -> Self
70
    where
71
        T: TryInto<Authority>,
72
        <T as TryInto<Authority>>::Error: Into<crate::Error>,
73
    {
74
        self.map(move |mut parts| {
75
            let auth = auth.try_into().map_err(Into::into)?;
76
            parts.authority = Some(auth);
77
            Ok(parts)
78
        })
79
    }
80
81
    /// Set the `PathAndQuery` for this URI.
82
    ///
83
    /// # Examples
84
    ///
85
    /// ```
86
    /// # use http::*;
87
    ///
88
    /// let uri = uri::Builder::new()
89
    ///     .path_and_query("/hello?foo=bar")
90
    ///     .build()
91
    ///     .unwrap();
92
    /// ```
93
    pub fn path_and_query<T>(self, p_and_q: T) -> Self
94
    where
95
        T: TryInto<PathAndQuery>,
96
        <T as TryInto<PathAndQuery>>::Error: Into<crate::Error>,
97
    {
98
        self.map(move |mut parts| {
99
            let p_and_q = match p_and_q.try_into() {
100
                Ok(p_and_q) => p_and_q,
101
                Err(err) => {
102
                    let err = err.into();
103
                    if err.is_empty_uri() {
104
                        PathAndQuery::empty()
105
                    } else {
106
                        return Err(err);
107
                    }
108
                }
109
            };
110
            parts.path_and_query = Some(p_and_q);
111
            Ok(parts)
112
        })
113
    }
114
115
    /// Consumes this builder, and tries to construct a valid `Uri` from
116
    /// the configured pieces.
117
    ///
118
    /// # Errors
119
    ///
120
    /// This function may return an error if any previously configured argument
121
    /// failed to parse or get converted to the internal representation. For
122
    /// example if an invalid `scheme` was specified via `scheme("!@#%/^")`
123
    /// the error will be returned when this function is called rather than
124
    /// when `scheme` was called.
125
    ///
126
    /// Additionally, the various forms of URI require certain combinations of
127
    /// parts to be set to be valid. If the parts don't fit into any of the
128
    /// valid forms of URI, a new error is returned.
129
    ///
130
    /// # Examples
131
    ///
132
    /// ```
133
    /// # use http::*;
134
    ///
135
    /// let uri = Uri::builder()
136
    ///     .build()
137
    ///     .unwrap();
138
    /// ```
139
0
    pub fn build(self) -> Result<Uri, crate::Error> {
140
0
        let parts = self.parts?;
141
0
        Uri::from_parts(parts).map_err(Into::into)
142
0
    }
143
144
    // private
145
146
    fn map<F>(self, func: F) -> Self
147
    where
148
        F: FnOnce(Parts) -> Result<Parts, crate::Error>,
149
    {
150
        Builder {
151
            parts: self.parts.and_then(func),
152
        }
153
    }
154
}
155
156
impl Default for Builder {
157
    #[inline]
158
0
    fn default() -> Builder {
159
0
        Builder {
160
0
            parts: Ok(Parts::default()),
161
0
        }
162
0
    }
163
}
164
165
impl From<Uri> for Builder {
166
0
    fn from(uri: Uri) -> Self {
167
0
        Self {
168
0
            parts: Ok(uri.into_parts()),
169
0
        }
170
0
    }
171
}
172
173
#[cfg(test)]
174
mod tests {
175
    use super::*;
176
177
    #[test]
178
    fn build_from_str() {
179
        let uri = Builder::new()
180
            .scheme(Scheme::HTTP)
181
            .authority("hyper.rs")
182
            .path_and_query("/foo?a=1")
183
            .build()
184
            .unwrap();
185
        assert_eq!(uri.scheme_str(), Some("http"));
186
        assert_eq!(uri.authority().unwrap().host(), "hyper.rs");
187
        assert_eq!(uri.path(), "/foo");
188
        assert_eq!(uri.query(), Some("a=1"));
189
    }
190
191
    #[test]
192
    fn build_from_string() {
193
        for i in 1..10 {
194
            let uri = Builder::new()
195
                .path_and_query(format!("/foo?a={}", i))
196
                .build()
197
                .unwrap();
198
            let expected_query = format!("a={}", i);
199
            assert_eq!(uri.path(), "/foo");
200
            assert_eq!(uri.query(), Some(expected_query.as_str()));
201
        }
202
    }
203
204
    #[test]
205
    fn build_from_string_ref() {
206
        for i in 1..10 {
207
            let p_a_q = format!("/foo?a={}", i);
208
            let uri = Builder::new().path_and_query(&p_a_q).build().unwrap();
209
            let expected_query = format!("a={}", i);
210
            assert_eq!(uri.path(), "/foo");
211
            assert_eq!(uri.query(), Some(expected_query.as_str()));
212
        }
213
    }
214
215
    #[test]
216
    fn build_from_empty_path_and_query() {
217
        let uri = Builder::new()
218
            .scheme(Scheme::HTTP)
219
            .authority("localhost:8080")
220
            .path_and_query("")
221
            .build()
222
            .unwrap();
223
224
        assert_eq!(uri, "http://localhost:8080");
225
        assert_eq!(uri.path(), "/");
226
    }
227
228
    #[test]
229
    fn empty_path_and_query_remains_strict() {
230
        assert!(PathAndQuery::try_from("").is_err());
231
    }
232
233
    #[test]
234
    fn authority_form_path_and_query_remains_strict() {
235
        assert!(Builder::new()
236
            .path_and_query("localhost:8080")
237
            .build()
238
            .is_err());
239
    }
240
241
    #[test]
242
    fn build_from_uri() {
243
        let original_uri = Uri::default();
244
        let uri = Builder::from(original_uri.clone()).build().unwrap();
245
        assert_eq!(original_uri, uri);
246
    }
247
248
    #[test]
249
    fn build_star_for_http2() {
250
        let uri = Builder::new()
251
            .scheme("https")
252
            .authority("example.com")
253
            .path_and_query("*")
254
            .build()
255
            .unwrap();
256
257
        assert_eq!(uri.scheme(), Some(&Scheme::HTTPS));
258
        assert_eq!(uri.host(), Some("example.com"));
259
        assert_eq!(uri.path(), "*");
260
    }
261
}