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