Skip to main content

uri_builder/
lib.rs

1use std::default::Default;
2use itertools::Itertools;
3
4/// Struct that corresponds with URI.
5pub struct URI<'a> {
6    scheme: &'a str,
7    userinfo: Option<&'a str>,
8    host: Option<&'a str>,
9    port: Option<u16>,
10    path: Vec<&'a str>,
11    query: Vec<(String, String)>,
12    fragment: Option<&'a str>,
13}
14
15impl<'a> Default for URI<'a> {
16    /// Construct URI `http://localhost:80/`.
17    fn default() -> URI<'a> {
18        URI {
19            scheme: "http",
20            userinfo: None,
21            host: Some("localhost"),
22            port: Some(80),
23            path: Vec::new(),
24            query: Vec::new(),
25            fragment: None,
26        }
27    }
28}
29
30impl<'a> URI<'a> {
31    /// Construct empty URI with scheme only.
32    pub fn new(scheme: &'a str) -> URI<'a> {
33        URI {
34            scheme,
35            userinfo: None,
36            host: None,
37            port: None,
38            path: Vec::new(),
39            query: Vec::new(),
40            fragment: None,
41        }
42    }
43
44
45    /// Set userinfo.
46    pub fn userinfo(&'a mut self, value: &'a str) -> &'a mut URI {
47        self.userinfo = Some(value);
48        self
49    }
50
51    /// Set host.
52    pub fn host(&'a mut self, value: &'a str) -> &'a mut URI {
53        self.host = Some(value);
54        self
55    }
56
57    /// Set port.
58    pub fn port(&'a mut self, value: u16) -> &'a mut URI {
59        self.port = Some(value);
60        self
61    }
62
63    /// Add passed segment to path.
64    pub fn path(&'a mut self, segment: &'a str) -> &'a mut URI {
65        self.path.push(segment);
66        self
67    }
68
69    /// Add passed multiple segments to path.
70    pub fn path_vec(&'a mut self, segments: &Vec<&'a str>) -> &'a mut URI {
71        self.path.extend(segments);
72        self
73    }
74
75    /// Add passed segment to query.
76    pub fn query<T, U>(&'a mut self, attribute: U, value: T) -> &'a mut URI
77        where T: ToString,
78              U: ToString,
79    {
80        self.query.push((attribute.to_string(), value.to_string()));
81        self
82    }
83
84    /// Add passed multiple segments to query.
85    pub fn query_vec<T, U>(&'a mut self, values: &Vec<(T, U)>) -> &'a mut URI
86        where T: ToString,
87              U: ToString,
88    {
89        self.query.extend(values.iter()
90            .map(|(a, v)| (a.to_string(), v.to_string()))
91            .collect::<Vec<(String, String)>>()
92        );
93        self
94    }
95
96    /// Set fragment.
97    pub fn fragment(&'a mut self, value: &'a str) -> &'a mut URI {
98        self.fragment = Some(value);
99        self
100    }
101
102    /// Build URI form received parts.
103    ///
104    /// ```rust
105    /// use uri_builder::URI;
106    ///
107    /// let uri: String = URI::new("https")
108    ///     .host("github.com")
109    ///     .path("repos")
110    ///     .query("page", 1)
111    ///     .build();
112    /// ```
113    ///
114    /// The example above transforms to `https://github.com/repos?page=1`.
115    pub fn build(&'a self) -> String {
116        let authority = match self.userinfo {
117            Some(a) => format!("{}@", a),
118            None => String::new(),
119        };
120        let authority = match self.host {
121            Some(h) => format!("{}{}", authority, h),
122            None => authority,
123        };
124        let authority = match self.port {
125            Some(p) => format!("{}:{}", authority, p),
126            None => authority,
127        };
128
129        let scheme = if authority.is_empty() {
130            format!("{}:", self.scheme)
131        } else {
132            format!("{}://", self.scheme)
133        };
134
135        let path = self.path.iter()
136            .format("/")
137            .to_string();
138        let path = if authority.is_empty() {
139            path
140        } else {
141            format!("/{}", path)
142        };
143
144        let query = self.query.iter()
145            .map(|(a, v)| format!("{}={}", a, v))
146            .format("&")
147            .to_string();
148        let query = if query.is_empty() {
149            query
150        } else {
151            format!("?{}", query)
152        };
153
154        let fragment = match self.fragment {
155            Some(f) => format!("#{}", f),
156            None => String::new(),
157        };
158
159        format!("{}{}{}{}{}",
160                scheme, authority, path, query, fragment)
161    }
162}
163
164#[cfg(test)]
165mod tests {
166    use crate::URI;
167
168    #[test]
169    fn test_builder() {
170        let uri = URI::default().build();
171        assert_eq!("http://localhost:80/", uri);
172
173        let uri = URI::new("https")
174            .host("github.com")
175            .path("repos")
176            .query("page", 1)
177            .build();
178        assert_eq!("https://github.com/repos?page=1", uri);
179
180        let uri = URI::new("https")
181            .userinfo("john.doe")
182            .host("www.example.com")
183            .port(123)
184            .path_vec(vec!["forum", "questions"].as_ref())
185            .query_vec(vec![("tag", "networking"), ("order", "newest")].as_ref())
186            .fragment("top")
187            .build();
188        assert_eq!("https://john.doe@www.example.com:123/forum/questions?tag=networking&order=newest#top", uri);
189
190        let uri = URI::new("mailto")
191            .path("John.Doe@example.com")
192            .build();
193        assert_eq!("mailto:John.Doe@example.com", uri);
194
195        let uri = URI::new("news")
196            .path("comp.infosystems.www.servers.unix")
197            .build();
198        assert_eq!("news:comp.infosystems.www.servers.unix", uri);
199
200        let uri = URI::new("telnet")
201            .host("192.0.2.16")
202            .port(80)
203            .path("")
204            .build();
205        assert_eq!("telnet://192.0.2.16:80/", uri);
206
207        let uri = URI::new("tel")
208            .path("+1-816-555-1212")
209            .build();
210        assert_eq!("tel:+1-816-555-1212", uri);
211    }
212}