1use std::fmt;
2use std::str::FromStr;
3
4use crate::error::ParseUriError;
5use crate::sip_uri::SipUri;
6use crate::tel_uri::TelUri;
7use crate::urn_uri::UrnUri;
8
9#[derive(Debug, Clone, PartialEq, Eq)]
15#[non_exhaustive]
16pub enum Uri {
17 Sip(SipUri),
19 Tel(TelUri),
21 Urn(UrnUri),
23 Other(String),
25}
26
27impl Uri {
28 pub fn as_sip(&self) -> Option<&SipUri> {
30 match self {
31 Uri::Sip(u) => Some(u),
32 _ => None,
33 }
34 }
35
36 pub fn as_tel(&self) -> Option<&TelUri> {
38 match self {
39 Uri::Tel(u) => Some(u),
40 _ => None,
41 }
42 }
43
44 pub fn as_urn(&self) -> Option<&UrnUri> {
46 match self {
47 Uri::Urn(u) => Some(u),
48 _ => None,
49 }
50 }
51
52 pub fn as_other(&self) -> Option<&str> {
54 match self {
55 Uri::Other(s) => Some(s),
56 _ => None,
57 }
58 }
59
60 pub fn into_sip(self) -> Option<SipUri> {
62 match self {
63 Uri::Sip(u) => Some(u),
64 _ => None,
65 }
66 }
67
68 pub fn into_tel(self) -> Option<TelUri> {
70 match self {
71 Uri::Tel(u) => Some(u),
72 _ => None,
73 }
74 }
75
76 pub fn into_urn(self) -> Option<UrnUri> {
78 match self {
79 Uri::Urn(u) => Some(u),
80 _ => None,
81 }
82 }
83
84 pub fn into_other(self) -> Option<String> {
86 match self {
87 Uri::Other(s) => Some(s),
88 _ => None,
89 }
90 }
91
92 pub fn scheme(&self) -> &str {
94 match self {
95 Uri::Sip(u) => match u.scheme() {
96 crate::Scheme::Sip => "sip",
97 crate::Scheme::Sips => "sips",
98 },
99 Uri::Tel(_) => "tel",
100 Uri::Urn(_) => "urn",
101 Uri::Other(s) => s
103 .find(':')
104 .map(|i| &s[..i])
105 .unwrap_or(s),
106 }
107 }
108}
109
110impl From<SipUri> for Uri {
111 fn from(u: SipUri) -> Self {
112 Uri::Sip(u)
113 }
114}
115
116impl From<TelUri> for Uri {
117 fn from(u: TelUri) -> Self {
118 Uri::Tel(u)
119 }
120}
121
122impl From<UrnUri> for Uri {
123 fn from(u: UrnUri) -> Self {
124 Uri::Urn(u)
125 }
126}
127
128impl FromStr for Uri {
129 type Err = ParseUriError;
130
131 fn from_str(s: &str) -> Result<Self, Self::Err> {
132 if s == "*" {
133 return Err(ParseUriError(
134 "wildcard '*' is not a URI; handle it at the protocol layer (Contact: * or OPTIONS * SIP/2.0)".into(),
135 ));
136 }
137
138 let colon = s
140 .find(':')
141 .ok_or_else(|| ParseUriError("missing scheme".into()))?;
142 let scheme = &s[..colon];
143
144 if scheme.eq_ignore_ascii_case("tel") {
145 Ok(Uri::Tel(s.parse::<TelUri>()?))
146 } else if scheme.eq_ignore_ascii_case("sip") || scheme.eq_ignore_ascii_case("sips") {
147 Ok(Uri::Sip(s.parse::<SipUri>()?))
148 } else if scheme.eq_ignore_ascii_case("urn") {
149 Ok(Uri::Urn(s.parse::<UrnUri>()?))
150 } else {
151 Ok(Uri::Other(s.to_string()))
152 }
153 }
154}
155
156impl fmt::Display for Uri {
157 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
158 match self {
159 Uri::Sip(u) => write!(f, "{u}"),
160 Uri::Tel(u) => write!(f, "{u}"),
161 Uri::Urn(u) => write!(f, "{u}"),
162 Uri::Other(s) => write!(f, "{s}"),
163 }
164 }
165}
166
167#[cfg(test)]
168mod tests {
169 use super::*;
170
171 #[test]
172 fn dispatch_sip() {
173 let uri: Uri = "sip:alice@example.com"
174 .parse()
175 .unwrap();
176 assert!(uri
177 .as_sip()
178 .is_some());
179 assert!(uri
180 .as_tel()
181 .is_none());
182 assert!(uri
183 .as_urn()
184 .is_none());
185 }
186
187 #[test]
188 fn dispatch_sips() {
189 let uri: Uri = "sips:bob@secure.example.com"
190 .parse()
191 .unwrap();
192 assert!(uri
193 .as_sip()
194 .is_some());
195 }
196
197 #[test]
198 fn dispatch_tel() {
199 let uri: Uri = "tel:+15551234567"
200 .parse()
201 .unwrap();
202 assert!(uri
203 .as_tel()
204 .is_some());
205 assert!(uri
206 .as_sip()
207 .is_none());
208 }
209
210 #[test]
211 fn dispatch_urn() {
212 let uri: Uri = "urn:service:sos"
213 .parse()
214 .unwrap();
215 assert!(uri
216 .as_urn()
217 .is_some());
218 assert!(uri
219 .as_sip()
220 .is_none());
221 assert!(uri
222 .as_tel()
223 .is_none());
224 }
225
226 #[test]
227 fn unknown_scheme_stored_as_other() {
228 let uri: Uri = "http://example.com"
229 .parse()
230 .unwrap();
231 assert_eq!(uri.as_other(), Some("http://example.com"));
232 assert_eq!(uri.scheme(), "http");
233 assert!(uri
234 .as_sip()
235 .is_none());
236 assert!(uri
237 .as_tel()
238 .is_none());
239 assert!(uri
240 .as_urn()
241 .is_none());
242 }
243
244 #[test]
245 fn other_display_roundtrip() {
246 let input = "https://example.com/photo.jpg";
247 let uri: Uri = input
248 .parse()
249 .unwrap();
250 assert_eq!(uri.to_string(), input);
251 }
252
253 #[test]
254 fn missing_scheme_fails() {
255 assert!("no-colon-here"
256 .parse::<Uri>()
257 .is_err());
258 }
259
260 #[test]
261 fn display_roundtrip() {
262 let input = "sip:alice@example.com;transport=tcp";
263 let uri: Uri = input
264 .parse()
265 .unwrap();
266 assert_eq!(uri.to_string(), input);
267 }
268
269 #[test]
270 fn display_roundtrip_urn() {
271 let input = "urn:service:sos";
272 let uri: Uri = input
273 .parse()
274 .unwrap();
275 assert_eq!(uri.to_string(), input);
276 }
277
278 #[test]
279 fn from_sip_uri() {
280 let sip: SipUri = "sip:alice@example.com"
281 .parse()
282 .unwrap();
283 let uri: Uri = sip.into();
284 assert!(uri
285 .as_sip()
286 .is_some());
287 }
288
289 #[test]
290 fn from_urn_uri() {
291 let urn: UrnUri = "urn:service:sos"
292 .parse()
293 .unwrap();
294 let uri: Uri = urn.into();
295 assert!(uri
296 .as_urn()
297 .is_some());
298 }
299}