tauri_plugin_fs/
file_path.rs

1// Copyright 2019-2023 Tauri Programme within The Commons Conservancy
2// SPDX-License-Identifier: Apache-2.0
3// SPDX-License-Identifier: MIT
4
5use std::{
6    convert::Infallible,
7    path::{Path, PathBuf},
8    str::FromStr,
9};
10
11use serde::Serialize;
12use tauri::path::SafePathBuf;
13
14use crate::{Error, Result};
15
16/// Represents either a filesystem path or a URI pointing to a file
17/// such as `file://` URIs or Android `content://` URIs.
18#[derive(Debug, Serialize, Clone)]
19#[serde(untagged)]
20pub enum FilePath {
21    /// `file://` URIs or Android `content://` URIs.
22    Url(url::Url),
23    /// Regular [`PathBuf`]
24    Path(PathBuf),
25}
26
27/// Represents either a safe filesystem path or a URI pointing to a file
28/// such as `file://` URIs or Android `content://` URIs.
29#[derive(Debug, Clone, Serialize)]
30pub enum SafeFilePath {
31    /// `file://` URIs or Android `content://` URIs.
32    Url(url::Url),
33    /// Safe [`PathBuf`], see [`SafePathBuf``].
34    Path(SafePathBuf),
35}
36
37impl FilePath {
38    /// Get a reference to the contained [`Path`] if the variant is [`FilePath::Path`].
39    ///
40    /// Use [`FilePath::into_path`] to try to convert the [`FilePath::Url`] variant as well.
41    #[inline]
42    pub fn as_path(&self) -> Option<&Path> {
43        match self {
44            Self::Url(_) => None,
45            Self::Path(p) => Some(p),
46        }
47    }
48
49    /// Try to convert into [`PathBuf`] if possible.
50    ///
51    /// This calls [`Url::to_file_path`](url::Url::to_file_path) if the variant is [`FilePath::Url`],
52    /// otherwise returns the contained [PathBuf] as is.
53    #[inline]
54    pub fn into_path(self) -> Result<PathBuf> {
55        match self {
56            Self::Url(url) => url
57                .to_file_path()
58                .map(PathBuf::from)
59                .map_err(|_| Error::InvalidPathUrl),
60            Self::Path(p) => Ok(p),
61        }
62    }
63
64    /// Takes the contained [`PathBuf`] if the variant is [`FilePath::Path`],
65    /// and when possible, converts Windows UNC paths to regular paths.
66    #[inline]
67    pub fn simplified(self) -> Self {
68        match self {
69            Self::Url(url) => Self::Url(url),
70            Self::Path(p) => Self::Path(dunce::simplified(&p).to_path_buf()),
71        }
72    }
73}
74
75impl SafeFilePath {
76    /// Get a reference to the contained [`Path`] if the variant is [`SafeFilePath::Path`].
77    ///
78    /// Use [`SafeFilePath::into_path`] to try to convert the [`SafeFilePath::Url`] variant as well.
79    #[inline]
80    pub fn as_path(&self) -> Option<&Path> {
81        match self {
82            Self::Url(_) => None,
83            Self::Path(p) => Some(p.as_ref()),
84        }
85    }
86
87    /// Try to convert into [`PathBuf`] if possible.
88    ///
89    /// This calls [`Url::to_file_path`](url::Url::to_file_path) if the variant is [`SafeFilePath::Url`],
90    /// otherwise returns the contained [PathBuf] as is.
91    #[inline]
92    pub fn into_path(self) -> Result<PathBuf> {
93        match self {
94            Self::Url(url) => url
95                .to_file_path()
96                .map(PathBuf::from)
97                .map_err(|_| Error::InvalidPathUrl),
98            Self::Path(p) => Ok(p.as_ref().to_owned()),
99        }
100    }
101
102    /// Takes the contained [`PathBuf`] if the variant is [`SafeFilePath::Path`],
103    /// and when possible, converts Windows UNC paths to regular paths.
104    #[inline]
105    pub fn simplified(self) -> Self {
106        match self {
107            Self::Url(url) => Self::Url(url),
108            Self::Path(p) => {
109                // Safe to unwrap since it was a safe file path already
110                Self::Path(SafePathBuf::new(dunce::simplified(p.as_ref()).to_path_buf()).unwrap())
111            }
112        }
113    }
114}
115
116impl std::fmt::Display for FilePath {
117    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
118        match self {
119            Self::Url(u) => u.fmt(f),
120            Self::Path(p) => p.display().fmt(f),
121        }
122    }
123}
124
125impl std::fmt::Display for SafeFilePath {
126    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
127        match self {
128            Self::Url(u) => u.fmt(f),
129            Self::Path(p) => p.display().fmt(f),
130        }
131    }
132}
133
134impl<'de> serde::Deserialize<'de> for FilePath {
135    fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
136    where
137        D: serde::Deserializer<'de>,
138    {
139        struct FilePathVisitor;
140
141        impl serde::de::Visitor<'_> for FilePathVisitor {
142            type Value = FilePath;
143
144            fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
145                formatter.write_str("a string representing an file URL or a path")
146            }
147
148            fn visit_str<E>(self, s: &str) -> std::result::Result<Self::Value, E>
149            where
150                E: serde::de::Error,
151            {
152                FilePath::from_str(s).map_err(|e| {
153                    serde::de::Error::invalid_value(
154                        serde::de::Unexpected::Str(s),
155                        &e.to_string().as_str(),
156                    )
157                })
158            }
159        }
160
161        deserializer.deserialize_str(FilePathVisitor)
162    }
163}
164
165impl<'de> serde::Deserialize<'de> for SafeFilePath {
166    fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
167    where
168        D: serde::Deserializer<'de>,
169    {
170        struct SafeFilePathVisitor;
171
172        impl serde::de::Visitor<'_> for SafeFilePathVisitor {
173            type Value = SafeFilePath;
174
175            fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
176                formatter.write_str("a string representing an file URL or a path")
177            }
178
179            fn visit_str<E>(self, s: &str) -> std::result::Result<Self::Value, E>
180            where
181                E: serde::de::Error,
182            {
183                SafeFilePath::from_str(s).map_err(|e| {
184                    serde::de::Error::invalid_value(
185                        serde::de::Unexpected::Str(s),
186                        &e.to_string().as_str(),
187                    )
188                })
189            }
190        }
191
192        deserializer.deserialize_str(SafeFilePathVisitor)
193    }
194}
195
196impl FromStr for FilePath {
197    type Err = Infallible;
198    fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
199        if let Ok(url) = url::Url::from_str(s) {
200            if url.scheme().len() != 1 {
201                return Ok(Self::Url(url));
202            }
203        }
204        Ok(Self::Path(PathBuf::from(s)))
205    }
206}
207
208impl FromStr for SafeFilePath {
209    type Err = Error;
210    fn from_str(s: &str) -> Result<Self> {
211        if let Ok(url) = url::Url::from_str(s) {
212            if url.scheme().len() != 1 {
213                return Ok(Self::Url(url));
214            }
215        }
216
217        SafePathBuf::new(s.into())
218            .map(SafeFilePath::Path)
219            .map_err(Error::UnsafePathBuf)
220    }
221}
222
223impl From<PathBuf> for FilePath {
224    fn from(value: PathBuf) -> Self {
225        Self::Path(value)
226    }
227}
228
229impl TryFrom<PathBuf> for SafeFilePath {
230    type Error = Error;
231    fn try_from(value: PathBuf) -> Result<Self> {
232        SafePathBuf::new(value)
233            .map(SafeFilePath::Path)
234            .map_err(Error::UnsafePathBuf)
235    }
236}
237
238impl From<&Path> for FilePath {
239    fn from(value: &Path) -> Self {
240        Self::Path(value.to_owned())
241    }
242}
243
244impl TryFrom<&Path> for SafeFilePath {
245    type Error = Error;
246    fn try_from(value: &Path) -> Result<Self> {
247        SafePathBuf::new(value.to_path_buf())
248            .map(SafeFilePath::Path)
249            .map_err(Error::UnsafePathBuf)
250    }
251}
252
253impl From<&PathBuf> for FilePath {
254    fn from(value: &PathBuf) -> Self {
255        Self::Path(value.to_owned())
256    }
257}
258
259impl TryFrom<&PathBuf> for SafeFilePath {
260    type Error = Error;
261    fn try_from(value: &PathBuf) -> Result<Self> {
262        SafePathBuf::new(value.to_owned())
263            .map(SafeFilePath::Path)
264            .map_err(Error::UnsafePathBuf)
265    }
266}
267
268impl From<url::Url> for FilePath {
269    fn from(value: url::Url) -> Self {
270        Self::Url(value)
271    }
272}
273
274impl From<url::Url> for SafeFilePath {
275    fn from(value: url::Url) -> Self {
276        Self::Url(value)
277    }
278}
279
280impl TryFrom<FilePath> for PathBuf {
281    type Error = Error;
282    fn try_from(value: FilePath) -> Result<Self> {
283        value.into_path()
284    }
285}
286
287impl TryFrom<SafeFilePath> for PathBuf {
288    type Error = Error;
289    fn try_from(value: SafeFilePath) -> Result<Self> {
290        value.into_path()
291    }
292}
293
294impl From<SafeFilePath> for FilePath {
295    fn from(value: SafeFilePath) -> Self {
296        match value {
297            SafeFilePath::Url(url) => FilePath::Url(url),
298            SafeFilePath::Path(p) => FilePath::Path(p.as_ref().to_owned()),
299        }
300    }
301}
302
303impl TryFrom<FilePath> for SafeFilePath {
304    type Error = Error;
305
306    fn try_from(value: FilePath) -> Result<Self> {
307        match value {
308            FilePath::Url(url) => Ok(SafeFilePath::Url(url)),
309            FilePath::Path(p) => SafePathBuf::new(p)
310                .map(SafeFilePath::Path)
311                .map_err(Error::UnsafePathBuf),
312        }
313    }
314}