Skip to content

Commit f7280c8

Browse files
fix(fs): can't use Windows path (#1710)
* Fix fs can't use Windows path * Add change file * Implement `Deserialize` instead * Rename FilePath's visitor to FilePathVisitor * Add todo and test * Clippy * Unused variable
1 parent 0cb99bd commit f7280c8

File tree

3 files changed

+95
-10
lines changed

3 files changed

+95
-10
lines changed

.changes/fs-windows-path.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"fs": patch
3+
---
4+
5+
Fix can't use Windows paths like `C:/Users/UserName/file.txt`

plugins/fs/src/commands.rs

Lines changed: 53 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -23,13 +23,44 @@ use std::{
2323

2424
use crate::{scope::Entry, Error, FilePath, FsExt};
2525

26-
#[derive(Debug, serde::Deserialize)]
27-
#[serde(untagged)]
26+
// TODO: Combine this with FilePath
27+
#[derive(Debug)]
2828
pub enum SafeFilePath {
2929
Url(url::Url),
3030
Path(SafePathBuf),
3131
}
3232

33+
impl<'de> serde::Deserialize<'de> for SafeFilePath {
34+
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
35+
where
36+
D: serde::Deserializer<'de>,
37+
{
38+
struct SafeFilePathVisitor;
39+
40+
impl<'de> serde::de::Visitor<'de> for SafeFilePathVisitor {
41+
type Value = SafeFilePath;
42+
43+
fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
44+
formatter.write_str("a string representing an file URL or a path")
45+
}
46+
47+
fn visit_str<E>(self, s: &str) -> std::result::Result<Self::Value, E>
48+
where
49+
E: serde::de::Error,
50+
{
51+
SafeFilePath::from_str(s).map_err(|e| {
52+
serde::de::Error::invalid_value(
53+
serde::de::Unexpected::Str(s),
54+
&e.to_string().as_str(),
55+
)
56+
})
57+
}
58+
}
59+
60+
deserializer.deserialize_str(SafeFilePathVisitor)
61+
}
62+
}
63+
3364
impl From<SafeFilePath> for FilePath {
3465
fn from(value: SafeFilePath) -> Self {
3566
match value {
@@ -43,10 +74,11 @@ impl FromStr for SafeFilePath {
4374
type Err = CommandError;
4475
fn from_str(s: &str) -> Result<Self, Self::Err> {
4576
if let Ok(url) = url::Url::from_str(s) {
46-
Ok(Self::Url(url))
47-
} else {
48-
Ok(Self::Path(SafePathBuf::new(s.into())?))
77+
if url.scheme().len() != 1 {
78+
return Ok(Self::Url(url));
79+
}
4980
}
81+
Ok(Self::Path(SafePathBuf::new(s.into())?))
5082
}
5183
}
5284

@@ -1168,3 +1200,19 @@ fn get_stat(metadata: std::fs::Metadata) -> FileInfo {
11681200
blocks: usm!(blocks),
11691201
}
11701202
}
1203+
1204+
mod test {
1205+
#[test]
1206+
fn safe_file_path_parse() {
1207+
use super::SafeFilePath;
1208+
1209+
assert!(matches!(
1210+
serde_json::from_str::<SafeFilePath>("\"C:/Users\""),
1211+
Ok(SafeFilePath::Path(_))
1212+
));
1213+
assert!(matches!(
1214+
serde_json::from_str::<SafeFilePath>("\"file:///C:/Users\""),
1215+
Ok(SafeFilePath::Url(_))
1216+
));
1217+
}
1218+
}

plugins/fs/src/lib.rs

Lines changed: 37 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -50,23 +50,55 @@ pub use scope::{Event as ScopeEvent, Scope};
5050

5151
type Result<T> = std::result::Result<T, Error>;
5252

53+
// TODO: Combine this with SafeFilePath
5354
/// Represents either a filesystem path or a URI pointing to a file
5455
/// such as `file://` URIs or Android `content://` URIs.
55-
#[derive(Debug, serde::Deserialize)]
56-
#[serde(untagged)]
56+
#[derive(Debug)]
5757
pub enum FilePath {
5858
Url(url::Url),
5959
Path(PathBuf),
6060
}
6161

62+
impl<'de> serde::Deserialize<'de> for FilePath {
63+
fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
64+
where
65+
D: serde::Deserializer<'de>,
66+
{
67+
struct FilePathVisitor;
68+
69+
impl<'de> serde::de::Visitor<'de> for FilePathVisitor {
70+
type Value = FilePath;
71+
72+
fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
73+
formatter.write_str("a string representing an file URL or a path")
74+
}
75+
76+
fn visit_str<E>(self, s: &str) -> std::result::Result<Self::Value, E>
77+
where
78+
E: serde::de::Error,
79+
{
80+
FilePath::from_str(s).map_err(|e| {
81+
serde::de::Error::invalid_value(
82+
serde::de::Unexpected::Str(s),
83+
&e.to_string().as_str(),
84+
)
85+
})
86+
}
87+
}
88+
89+
deserializer.deserialize_str(FilePathVisitor)
90+
}
91+
}
92+
6293
impl FromStr for FilePath {
6394
type Err = Infallible;
6495
fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
6596
if let Ok(url) = url::Url::from_str(s) {
66-
Ok(Self::Url(url))
67-
} else {
68-
Ok(Self::Path(PathBuf::from(s)))
97+
if url.scheme().len() != 1 {
98+
return Ok(Self::Url(url));
99+
}
69100
}
101+
Ok(Self::Path(PathBuf::from(s)))
70102
}
71103
}
72104

0 commit comments

Comments
 (0)