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)]
9pub enum PathOrURI {
11 Path(PathBuf),
13 URI(Url),
15}
16
17impl PathOrURI {
18 #[must_use]
20 pub fn is_path(&self) -> bool {
21 matches!(self, PathOrURI::Path(_))
22 }
23
24 #[must_use]
29 pub fn is_uri(&self) -> bool {
30 matches!(self, PathOrURI::URI(_))
31 }
32
33 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}