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