1#![no_std]
2
3#[macro_use]
4extern crate alloc;
5
6#[cfg(any(feature = "std", test))]
7extern crate std;
8
9mod location;
10mod selection;
11mod source_file;
12mod source_manager;
13mod span;
14
15use alloc::{string::String, sync::Arc};
16
17#[cfg(feature = "serde")]
18use serde::{Deserialize, Serialize};
19#[cfg(feature = "serde")]
20pub use serde_spanned;
21
22#[cfg(feature = "std")]
23pub use self::source_manager::SourceManagerExt;
24pub use self::{
25 location::{FileLineCol, Location},
26 selection::{Position, Selection},
27 source_file::{
28 ByteIndex, ByteOffset, ColumnIndex, ColumnNumber, LineIndex, LineNumber, SourceContent,
29 SourceContentUpdateError, SourceFile, SourceFileRef, SourceLanguage,
30 },
31 source_manager::{DefaultSourceManager, SourceId, SourceManager},
32 span::{SourceSpan, Span, Spanned},
33};
34
35#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
38#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
39pub struct Uri(Arc<str>);
40
41impl Uri {
42 pub fn new(uri: impl AsRef<str>) -> Self {
43 uri.as_ref().into()
44 }
45
46 #[inline]
47 pub fn as_str(&self) -> &str {
48 self.0.as_ref()
49 }
50
51 #[inline]
52 pub fn as_bytes(&self) -> &[u8] {
53 self.0.as_bytes()
54 }
55
56 pub fn scheme(&self) -> Option<&str> {
58 match self.0.split_once(':') {
59 Some((prefix, _))
60 if prefix.contains(|c: char| {
61 !c.is_ascii_alphanumeric() && !matches!(c, '+' | '-' | '.')
62 }) =>
63 {
64 None
65 },
66 Some((prefix, _)) => Some(prefix),
67 None => None,
68 }
69 }
70
71 pub fn authority(&self) -> Option<&str> {
73 let uri = self.0.as_ref();
74 let (_, rest) = uri.split_once("//")?;
75 match rest.split_once(['/', '?', '#']) {
76 Some((authority, _)) => Some(authority),
77 None => Some(rest),
78 }
79 }
80
81 pub fn path(&self) -> &str {
83 let uri = self.0.as_ref();
84 let path = match uri.split_once("//") {
85 Some((_, rest)) => match rest.find('/').map(|pos| rest.split_at(pos)) {
86 Some((_, path)) => path,
87 None => return "",
88 },
89 None => match uri.split_once(':') {
90 Some((prefix, _))
91 if prefix.contains(|c: char| {
92 !c.is_ascii_alphanumeric() && !matches!(c, '+' | '-' | '.')
93 }) =>
94 {
95 uri
96 },
97 Some((_, path)) => path,
98 None => uri,
99 },
100 };
101 match path.split_once(['?', '#']) {
102 Some((path, _)) => path,
103 None => path,
104 }
105 }
106}
107
108impl core::fmt::Display for Uri {
109 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
110 core::fmt::Display::fmt(&self.0, f)
111 }
112}
113
114impl AsRef<str> for Uri {
115 fn as_ref(&self) -> &str {
116 self.0.as_ref()
117 }
118}
119
120impl From<&str> for Uri {
121 #[inline]
122 fn from(value: &str) -> Self {
123 use alloc::string::ToString;
124
125 value.to_string().into()
126 }
127}
128
129impl From<Uri> for Arc<str> {
130 fn from(value: Uri) -> Self {
131 value.0
132 }
133}
134
135impl From<Arc<str>> for Uri {
136 #[inline]
137 fn from(uri: Arc<str>) -> Self {
138 Self(uri)
139 }
140}
141
142impl From<alloc::boxed::Box<str>> for Uri {
143 #[inline]
144 fn from(uri: alloc::boxed::Box<str>) -> Self {
145 Self(uri.into())
146 }
147}
148
149impl From<String> for Uri {
150 #[inline]
151 fn from(uri: String) -> Self {
152 Self(uri.into_boxed_str().into())
153 }
154}
155
156#[cfg(feature = "std")]
157impl<'a> From<&'a std::path::Path> for Uri {
158 fn from(path: &'a std::path::Path) -> Self {
159 use alloc::string::ToString;
160
161 Self::from(path.display().to_string())
162 }
163}
164
165impl core::str::FromStr for Uri {
166 type Err = ();
167
168 fn from_str(s: &str) -> Result<Self, Self::Err> {
169 Ok(Self::from(s))
170 }
171}
172
173#[cfg(test)]
174mod tests {
175 use super::*;
176
177 #[test]
178 fn uri_scheme_extraction() {
179 let relative_file = Uri::new("foo.masm");
180 let relative_file_path = Uri::new("./foo.masm");
181 let relative_file_path_with_scheme = Uri::new("file:foo.masm");
182 let absolute_file_path = Uri::new("file:///tmp/foo.masm");
183 let http_simple_uri = Uri::new("http://www.example.com");
184 let http_simple_uri_with_userinfo = Uri::new("http://foo:bar@www.example.com");
185 let http_simple_uri_with_userinfo_and_port = Uri::new("http://foo:bar@www.example.com:443");
186 let http_simple_uri_with_userinfo_and_path =
187 Uri::new("http://foo:bar@www.example.com/api/v1");
188 let http_simple_uri_with_userinfo_and_query =
189 Uri::new("http://foo:bar@www.example.com?param=1");
190 let http_simple_uri_with_userinfo_and_fragment =
191 Uri::new("http://foo:bar@www.example.com#about");
192 let http_simple_uri_with_userinfo_and_path_and_query =
193 Uri::new("http://foo:bar@www.example.com/api/v1/user?id=1");
194 let http_simple_uri_with_userinfo_and_path_and_query_and_fragment =
195 Uri::new("http://foo:bar@www.example.com/api/v1/user?id=1#redirect=/home");
196
197 assert_eq!(relative_file.scheme(), None);
198 assert_eq!(relative_file_path.scheme(), None);
199 assert_eq!(relative_file_path_with_scheme.scheme(), Some("file"));
200 assert_eq!(absolute_file_path.scheme(), Some("file"));
201 assert_eq!(http_simple_uri.scheme(), Some("http"));
202 assert_eq!(http_simple_uri_with_userinfo.scheme(), Some("http"));
203 assert_eq!(http_simple_uri_with_userinfo_and_port.scheme(), Some("http"));
204 assert_eq!(http_simple_uri_with_userinfo_and_path.scheme(), Some("http"));
205 assert_eq!(http_simple_uri_with_userinfo_and_query.scheme(), Some("http"));
206 assert_eq!(http_simple_uri_with_userinfo_and_fragment.scheme(), Some("http"));
207 assert_eq!(http_simple_uri_with_userinfo_and_path_and_query.scheme(), Some("http"));
208 assert_eq!(
209 http_simple_uri_with_userinfo_and_path_and_query_and_fragment.scheme(),
210 Some("http")
211 );
212 }
213
214 #[test]
215 fn uri_authority_extraction() {
216 let relative_file = Uri::new("foo.masm");
217 let relative_file_path = Uri::new("./foo.masm");
218 let relative_file_path_with_scheme = Uri::new("file:foo.masm");
219 let absolute_file_path = Uri::new("file:///tmp/foo.masm");
220 let http_simple_uri = Uri::new("http://www.example.com");
221 let http_simple_uri_with_userinfo = Uri::new("http://foo:bar@www.example.com");
222 let http_simple_uri_with_userinfo_and_port = Uri::new("http://foo:bar@www.example.com:443");
223 let http_simple_uri_with_userinfo_and_path =
224 Uri::new("http://foo:bar@www.example.com/api/v1");
225 let http_simple_uri_with_userinfo_and_query =
226 Uri::new("http://foo:bar@www.example.com?param=1");
227 let http_simple_uri_with_userinfo_and_fragment =
228 Uri::new("http://foo:bar@www.example.com#about");
229 let http_simple_uri_with_userinfo_and_path_and_query =
230 Uri::new("http://foo:bar@www.example.com/api/v1/user?id=1");
231 let http_simple_uri_with_userinfo_and_path_and_query_and_fragment =
232 Uri::new("http://foo:bar@www.example.com/api/v1/user?id=1#redirect=/home");
233
234 assert_eq!(relative_file.authority(), None);
235 assert_eq!(relative_file_path.authority(), None);
236 assert_eq!(relative_file_path_with_scheme.authority(), None);
237 assert_eq!(absolute_file_path.authority(), Some(""));
238 assert_eq!(http_simple_uri.authority(), Some("www.example.com"));
239 assert_eq!(http_simple_uri_with_userinfo.authority(), Some("foo:bar@www.example.com"));
240 assert_eq!(
241 http_simple_uri_with_userinfo_and_port.authority(),
242 Some("foo:bar@www.example.com:443")
243 );
244 assert_eq!(
245 http_simple_uri_with_userinfo_and_path.authority(),
246 Some("foo:bar@www.example.com")
247 );
248 assert_eq!(
249 http_simple_uri_with_userinfo_and_query.authority(),
250 Some("foo:bar@www.example.com")
251 );
252 assert_eq!(
253 http_simple_uri_with_userinfo_and_fragment.authority(),
254 Some("foo:bar@www.example.com")
255 );
256 assert_eq!(
257 http_simple_uri_with_userinfo_and_path_and_query.authority(),
258 Some("foo:bar@www.example.com")
259 );
260 assert_eq!(
261 http_simple_uri_with_userinfo_and_path_and_query_and_fragment.authority(),
262 Some("foo:bar@www.example.com")
263 );
264 }
265
266 #[test]
267 fn uri_path_extraction() {
268 let relative_file = Uri::new("foo.masm");
269 let relative_file_path = Uri::new("./foo.masm");
270 let relative_file_path_with_scheme = Uri::new("file:foo.masm");
271 let absolute_file_path = Uri::new("file:///tmp/foo.masm");
272 let http_simple_uri = Uri::new("http://www.example.com");
273 let http_simple_uri_with_userinfo = Uri::new("http://foo:bar@www.example.com");
274 let http_simple_uri_with_userinfo_and_port = Uri::new("http://foo:bar@www.example.com:443");
275 let http_simple_uri_with_userinfo_and_path =
276 Uri::new("http://foo:bar@www.example.com/api/v1");
277 let http_simple_uri_with_userinfo_and_query =
278 Uri::new("http://foo:bar@www.example.com?param=1");
279 let http_simple_uri_with_userinfo_and_fragment =
280 Uri::new("http://foo:bar@www.example.com#about");
281 let http_simple_uri_with_userinfo_and_path_and_query =
282 Uri::new("http://foo:bar@www.example.com/api/v1/user?id=1");
283 let http_simple_uri_with_userinfo_and_path_and_query_and_fragment =
284 Uri::new("http://foo:bar@www.example.com/api/v1/user?id=1#redirect=/home");
285
286 assert_eq!(relative_file.path(), "foo.masm");
287 assert_eq!(relative_file_path.path(), "./foo.masm");
288 assert_eq!(relative_file_path_with_scheme.path(), "foo.masm");
289 assert_eq!(absolute_file_path.path(), "/tmp/foo.masm");
290 assert_eq!(http_simple_uri.path(), "");
291 assert_eq!(http_simple_uri_with_userinfo.path(), "");
292 assert_eq!(http_simple_uri_with_userinfo_and_port.path(), "");
293 assert_eq!(http_simple_uri_with_userinfo_and_path.path(), "/api/v1");
294 assert_eq!(http_simple_uri_with_userinfo_and_query.path(), "");
295 assert_eq!(http_simple_uri_with_userinfo_and_fragment.path(), "");
296 assert_eq!(http_simple_uri_with_userinfo_and_path_and_query.path(), "/api/v1/user");
297 assert_eq!(
298 http_simple_uri_with_userinfo_and_path_and_query_and_fragment.path(),
299 "/api/v1/user"
300 );
301 }
302}