1use std::default::Default;
2use itertools::Itertools;
3
4pub 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 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 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 pub fn userinfo(&'a mut self, value: &'a str) -> &'a mut URI {
47 self.userinfo = Some(value);
48 self
49 }
50
51 pub fn host(&'a mut self, value: &'a str) -> &'a mut URI {
53 self.host = Some(value);
54 self
55 }
56
57 pub fn port(&'a mut self, value: u16) -> &'a mut URI {
59 self.port = Some(value);
60 self
61 }
62
63 pub fn path(&'a mut self, segment: &'a str) -> &'a mut URI {
65 self.path.push(segment);
66 self
67 }
68
69 pub fn path_vec(&'a mut self, segments: &Vec<&'a str>) -> &'a mut URI {
71 self.path.extend(segments);
72 self
73 }
74
75 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 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 pub fn fragment(&'a mut self, value: &'a str) -> &'a mut URI {
98 self.fragment = Some(value);
99 self
100 }
101
102 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}