open_cmd/
path_or_uri.rs

1use std::{fmt::Display, path::PathBuf, str::FromStr};
2
3use crate::{Error, Result};
4use path_clean::PathClean;
5use url::Url;
6
7#[derive(Clone, Debug, PartialEq, Eq)]
8#[allow(variant_size_differences)]
9/// A local file path or a remote URI.
10pub enum PathOrURI {
11    /// A local file path.
12    Path(PathBuf),
13    /// A URI of some kind.
14    URI(Url),
15}
16
17impl PathOrURI {
18    /// Returns whether the contained value is a path.
19    #[must_use]
20    pub fn is_path(&self) -> bool {
21        matches!(self, PathOrURI::Path(_))
22    }
23
24    /// Returns whether the contained value is a URI.
25    ///
26    /// When created from a URI using [`From`], a `file://` URI will be converted to a [`PathBuf`]
27    /// and this method will return `false`.
28    #[must_use]
29    pub fn is_uri(&self) -> bool {
30        matches!(self, PathOrURI::URI(_))
31    }
32
33    /// Returns the contained value as a URI.
34    ///
35    /// # Errors
36    ///
37    /// - The cleaned path does not resolve to an absolute path.
38    /// - If the contained path is relative and the current directory does not exist or cannot be
39    ///   accessed (i.e. [`std::env::current_dir`] fails).
40    pub fn uri(&self) -> Result<Url> {
41        match self {
42            Self::URI(url) => Ok(url.clone()),
43            Self::Path(path) => {
44                let new_path = std::env::current_dir()?.join(path).clean();
45                Url::from_file_path(new_path).map_err(|()| Error::FileToURI(path.clone()))
46            }
47        }
48    }
49}
50
51impl FromStr for PathOrURI {
52    type Err = std::convert::Infallible;
53
54    fn from_str(s: &str) -> Result<Self, Self::Err> {
55        match s.parse::<Url>() {
56            Ok(url) => Ok(Self::from(url)),
57            Err(_) => Ok(Self::from(PathBuf::from(s))),
58        }
59    }
60}
61
62impl From<PathBuf> for PathOrURI {
63    fn from(value: PathBuf) -> Self {
64        Self::Path(value)
65    }
66}
67
68impl From<Url> for PathOrURI {
69    fn from(value: Url) -> Self {
70        if value.scheme() == "file" {
71            Self::from(PathBuf::from(value.path()))
72        } else {
73            Self::URI(value)
74        }
75    }
76}
77
78impl Display for PathOrURI {
79    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
80        match self {
81            Self::Path(path) => write!(f, "{}", path.display()),
82            Self::URI(uri) => write!(f, "{uri}"),
83        }
84    }
85}
86
87#[cfg(test)]
88mod tests {
89    use super::*;
90
91    #[test]
92    fn test_from_path() {
93        let path = PathBuf::from("/test/path/dir/");
94        assert_eq!(PathOrURI::from(path.clone()), PathOrURI::Path(path));
95    }
96
97    #[test]
98    fn test_from_web_uri() {
99        let url: Url = "https://example.com/subdir/".parse().unwrap();
100        assert_eq!(PathOrURI::from(url.clone()), PathOrURI::URI(url),);
101    }
102
103    #[test]
104    fn test_from_file_uri() {
105        let path = PathBuf::from("/test/path");
106        let url: Url = "file:///test/path".parse().unwrap();
107        assert_eq!(PathOrURI::from(url), PathOrURI::Path(path),);
108    }
109
110    #[test]
111    fn test_from_str() {
112        assert_eq!(
113            PathOrURI::from_str("/test/path").unwrap(),
114            PathOrURI::Path(PathBuf::from("/test/path"))
115        );
116
117        assert_eq!(
118            PathOrURI::from_str("https://example.com").unwrap(),
119            PathOrURI::URI("https://example.com".parse().unwrap())
120        );
121
122        assert_eq!(
123            PathOrURI::from_str("file:///test/path").unwrap(),
124            PathOrURI::Path(PathBuf::from("/test/path"))
125        );
126    }
127
128    #[test]
129    fn test_is_uri() {
130        assert!(PathOrURI::URI("https://example.com".parse().unwrap()).is_uri());
131        assert!(!PathOrURI::Path(PathBuf::from("/test/path")).is_uri());
132    }
133
134    #[test]
135    fn test_is_path() {
136        assert!(!PathOrURI::URI("https://example.com".parse().unwrap()).is_path());
137        assert!(PathOrURI::Path(PathBuf::from("/test/path")).is_path());
138    }
139
140    #[test]
141    fn test_to_uri() {
142        let uri: Url = "https://example.com/test/path".parse().unwrap();
143        let path = PathBuf::from("./test/next/../file.txt");
144        let path_uri =
145            Url::from_file_path(std::env::current_dir().unwrap().join(&path).clean()).unwrap();
146
147        assert_eq!(uri.clone(), PathOrURI::URI(uri).uri().unwrap());
148        assert_eq!(path_uri, PathOrURI::Path(path).uri().unwrap());
149    }
150}