refactor: add methods and implement traits for FilePath and SafeFilePath (#1727)

* refactor: add methods and implement traits for `FilePath` and `SafeFilePath`

closes #1726

* clippy

* path -> as_path

* fix prettierignore

* Discard changes to Cargo.lock

* Discard changes to Cargo.toml

* update tauri deps
This commit is contained in:
Amr Bashir
2024-09-05 14:12:40 +03:00
committed by GitHub
parent d00519e3e3
commit a2fe55512f
17 changed files with 370 additions and 233 deletions
+1
View File
@@ -30,6 +30,7 @@ uuid = { version = "1", features = ["v4"] }
glob = "0.3"
notify = { version = "6", optional = true, features = ["serde"] }
notify-debouncer-full = { version = "0.3", optional = true }
dunce = { workspace = true }
[features]
watch = ["notify", "notify-debouncer-full"]
+3 -76
View File
@@ -7,7 +7,7 @@ use serde::{Deserialize, Serialize, Serializer};
use serde_repr::{Deserialize_repr, Serialize_repr};
use tauri::{
ipc::{CommandScope, GlobalScope},
path::{BaseDirectory, SafePathBuf},
path::BaseDirectory,
utils::config::FsScope,
AppHandle, Manager, Resource, ResourceId, Runtime, Webview,
};
@@ -22,80 +22,7 @@ use std::{
time::{SystemTime, UNIX_EPOCH},
};
use crate::{scope::Entry, Error, FilePath, FsExt};
// TODO: Combine this with FilePath
#[derive(Debug)]
pub enum SafeFilePath {
Url(url::Url),
Path(SafePathBuf),
}
impl<'de> serde::Deserialize<'de> for SafeFilePath {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
struct SafeFilePathVisitor;
impl<'de> serde::de::Visitor<'de> for SafeFilePathVisitor {
type Value = SafeFilePath;
fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
formatter.write_str("a string representing an file URL or a path")
}
fn visit_str<E>(self, s: &str) -> std::result::Result<Self::Value, E>
where
E: serde::de::Error,
{
SafeFilePath::from_str(s).map_err(|e| {
serde::de::Error::invalid_value(
serde::de::Unexpected::Str(s),
&e.to_string().as_str(),
)
})
}
}
deserializer.deserialize_str(SafeFilePathVisitor)
}
}
impl From<SafeFilePath> for FilePath {
fn from(value: SafeFilePath) -> Self {
match value {
SafeFilePath::Url(url) => FilePath::Url(url),
SafeFilePath::Path(p) => FilePath::Path(p.as_ref().to_owned()),
}
}
}
impl FromStr for SafeFilePath {
type Err = CommandError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
if let Ok(url) = url::Url::from_str(s) {
if url.scheme().len() != 1 {
return Ok(Self::Url(url));
}
}
Ok(Self::Path(SafePathBuf::new(s.into())?))
}
}
impl SafeFilePath {
#[inline]
fn into_path(self) -> CommandResult<SafePathBuf> {
match self {
Self::Url(url) => SafePathBuf::new(
url.to_file_path()
.map_err(|_| format!("failed to get path from {url}"))?,
)
.map_err(Into::into),
Self::Path(p) => Ok(p),
}
}
}
use crate::{scope::Entry, Error, FsExt, SafeFilePath};
#[derive(Debug, thiserror::Error)]
pub enum CommandError {
@@ -1052,7 +979,7 @@ pub fn resolve_path<R: Runtime>(
let path = if let Some(base_dir) = base_dir {
webview.path().resolve(&path, base_dir)?
} else {
path.as_ref().to_path_buf()
path
};
let scope = tauri::scope::fs::Scope::new(
+5
View File
@@ -7,6 +7,7 @@ use std::path::PathBuf;
use serde::{Serialize, Serializer};
#[derive(Debug, thiserror::Error)]
#[non_exhaustive]
pub enum Error {
#[error(transparent)]
Json(#[from] serde_json::Error),
@@ -26,6 +27,10 @@ pub enum Error {
#[cfg(target_os = "android")]
#[error(transparent)]
PluginInvoke(#[from] tauri::plugin::mobile::PluginInvokeError),
#[error("URL is not a valid path")]
InvalidPathUrl,
#[error("Unsafe PathBuf: {0}")]
UnsafePathBuf(&'static str),
}
impl Serialize for Error {
+314
View File
@@ -0,0 +1,314 @@
// Copyright 2019-2023 Tauri Programme within The Commons Conservancy
// SPDX-License-Identifier: Apache-2.0
// SPDX-License-Identifier: MIT
use std::{
convert::Infallible,
path::{Path, PathBuf},
str::FromStr,
};
use serde::Serialize;
use tauri::path::SafePathBuf;
use crate::{Error, Result};
/// Represents either a filesystem path or a URI pointing to a file
/// such as `file://` URIs or Android `content://` URIs.
#[derive(Debug, Serialize, Clone)]
#[serde(untagged)]
pub enum FilePath {
/// `file://` URIs or Android `content://` URIs.
Url(url::Url),
/// Regular [`PathBuf`]
Path(PathBuf),
}
/// Represents either a safe filesystem path or a URI pointing to a file
/// such as `file://` URIs or Android `content://` URIs.
#[derive(Debug, Clone, Serialize)]
pub enum SafeFilePath {
/// `file://` URIs or Android `content://` URIs.
Url(url::Url),
/// Safe [`PathBuf`], see [`SafePathBuf``].
Path(SafePathBuf),
}
impl FilePath {
/// Get a reference to the contaiend [`Path`] if the variant is [`FilePath::Path`].
///
/// Use [`FilePath::into_path`] to try to convert the [`FilePath::Url`] variant as well.
#[inline]
pub fn as_path(&self) -> Option<&Path> {
match self {
Self::Url(_) => None,
Self::Path(p) => Some(p),
}
}
/// Try to convert into [`PathBuf`] if possible.
///
/// This calls [`Url::to_file_path`](url::Url::to_file_path) if the variant is [`FilePath::Url`],
/// otherwise returns the contained [PathBuf] as is.
#[inline]
pub fn into_path(self) -> Result<PathBuf> {
match self {
Self::Url(url) => url
.to_file_path()
.map(PathBuf::from)
.map_err(|_| Error::InvalidPathUrl),
Self::Path(p) => Ok(p),
}
}
/// Takes the contained [`PathBuf`] if the variant is [`FilePath::Path`],
/// and when possible, converts Windows UNC paths to regular paths.
#[inline]
pub fn simplified(self) -> Self {
match self {
Self::Url(url) => Self::Url(url),
Self::Path(p) => Self::Path(dunce::simplified(&p).to_path_buf()),
}
}
}
impl SafeFilePath {
/// Get a reference to the contaiend [`Path`] if the variant is [`SafeFilePath::Path`].
///
/// Use [`SafeFilePath::into_path`] to try to convert the [`SafeFilePath::Url`] variant as well.
#[inline]
pub fn as_path(&self) -> Option<&Path> {
match self {
Self::Url(_) => None,
Self::Path(p) => Some(p.as_ref()),
}
}
/// Try to convert into [`PathBuf`] if possible.
///
/// This calls [`Url::to_file_path`](url::Url::to_file_path) if the variant is [`SafeFilePath::Url`],
/// otherwise returns the contained [PathBuf] as is.
#[inline]
pub fn into_path(self) -> Result<PathBuf> {
match self {
Self::Url(url) => url
.to_file_path()
.map(PathBuf::from)
.map_err(|_| Error::InvalidPathUrl),
Self::Path(p) => Ok(p.as_ref().to_owned()),
}
}
/// Takes the contained [`PathBuf`] if the variant is [`SafeFilePath::Path`],
/// and when possible, converts Windows UNC paths to regular paths.
#[inline]
pub fn simplified(self) -> Self {
match self {
Self::Url(url) => Self::Url(url),
Self::Path(p) => {
// Safe to unwrap since it was a safe file path already
Self::Path(SafePathBuf::new(dunce::simplified(p.as_ref()).to_path_buf()).unwrap())
}
}
}
}
impl std::fmt::Display for FilePath {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Url(u) => u.fmt(f),
Self::Path(p) => p.display().fmt(f),
}
}
}
impl std::fmt::Display for SafeFilePath {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Url(u) => u.fmt(f),
Self::Path(p) => p.display().fmt(f),
}
}
}
impl<'de> serde::Deserialize<'de> for FilePath {
fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
struct FilePathVisitor;
impl<'de> serde::de::Visitor<'de> for FilePathVisitor {
type Value = FilePath;
fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
formatter.write_str("a string representing an file URL or a path")
}
fn visit_str<E>(self, s: &str) -> std::result::Result<Self::Value, E>
where
E: serde::de::Error,
{
FilePath::from_str(s).map_err(|e| {
serde::de::Error::invalid_value(
serde::de::Unexpected::Str(s),
&e.to_string().as_str(),
)
})
}
}
deserializer.deserialize_str(FilePathVisitor)
}
}
impl<'de> serde::Deserialize<'de> for SafeFilePath {
fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
struct SafeFilePathVisitor;
impl<'de> serde::de::Visitor<'de> for SafeFilePathVisitor {
type Value = SafeFilePath;
fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
formatter.write_str("a string representing an file URL or a path")
}
fn visit_str<E>(self, s: &str) -> std::result::Result<Self::Value, E>
where
E: serde::de::Error,
{
SafeFilePath::from_str(s).map_err(|e| {
serde::de::Error::invalid_value(
serde::de::Unexpected::Str(s),
&e.to_string().as_str(),
)
})
}
}
deserializer.deserialize_str(SafeFilePathVisitor)
}
}
impl FromStr for FilePath {
type Err = Infallible;
fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
if let Ok(url) = url::Url::from_str(s) {
if url.scheme().len() != 1 {
return Ok(Self::Url(url));
}
}
Ok(Self::Path(PathBuf::from(s)))
}
}
impl FromStr for SafeFilePath {
type Err = Error;
fn from_str(s: &str) -> Result<Self> {
if let Ok(url) = url::Url::from_str(s) {
if url.scheme().len() != 1 {
return Ok(Self::Url(url));
}
}
SafePathBuf::new(s.into())
.map(SafeFilePath::Path)
.map_err(Error::UnsafePathBuf)
}
}
impl From<PathBuf> for FilePath {
fn from(value: PathBuf) -> Self {
Self::Path(value)
}
}
impl TryFrom<PathBuf> for SafeFilePath {
type Error = Error;
fn try_from(value: PathBuf) -> Result<Self> {
SafePathBuf::new(value)
.map(SafeFilePath::Path)
.map_err(Error::UnsafePathBuf)
}
}
impl From<&Path> for FilePath {
fn from(value: &Path) -> Self {
Self::Path(value.to_owned())
}
}
impl TryFrom<&Path> for SafeFilePath {
type Error = Error;
fn try_from(value: &Path) -> Result<Self> {
SafePathBuf::new(value.to_path_buf())
.map(SafeFilePath::Path)
.map_err(Error::UnsafePathBuf)
}
}
impl From<&PathBuf> for FilePath {
fn from(value: &PathBuf) -> Self {
Self::Path(value.to_owned())
}
}
impl TryFrom<&PathBuf> for SafeFilePath {
type Error = Error;
fn try_from(value: &PathBuf) -> Result<Self> {
SafePathBuf::new(value.to_owned())
.map(SafeFilePath::Path)
.map_err(Error::UnsafePathBuf)
}
}
impl From<url::Url> for FilePath {
fn from(value: url::Url) -> Self {
Self::Url(value)
}
}
impl From<url::Url> for SafeFilePath {
fn from(value: url::Url) -> Self {
Self::Url(value)
}
}
impl TryFrom<FilePath> for PathBuf {
type Error = Error;
fn try_from(value: FilePath) -> Result<Self> {
value.into_path()
}
}
impl TryFrom<SafeFilePath> for PathBuf {
type Error = Error;
fn try_from(value: SafeFilePath) -> Result<Self> {
value.into_path()
}
}
impl From<SafeFilePath> for FilePath {
fn from(value: SafeFilePath) -> Self {
match value {
SafeFilePath::Url(url) => FilePath::Url(url),
SafeFilePath::Path(p) => FilePath::Path(p.as_ref().to_owned()),
}
}
}
impl TryFrom<FilePath> for SafeFilePath {
type Error = Error;
fn try_from(value: FilePath) -> Result<Self> {
match value {
FilePath::Url(url) => Ok(SafeFilePath::Url(url)),
FilePath::Path(p) => SafePathBuf::new(p)
.map(SafeFilePath::Path)
.map_err(Error::UnsafePathBuf),
}
}
}
+7 -92
View File
@@ -11,13 +11,7 @@
html_favicon_url = "https://github.com/tauri-apps/tauri/raw/dev/app-icon.png"
)]
use std::{
convert::Infallible,
fmt,
io::Read,
path::{Path, PathBuf},
str::FromStr,
};
use std::io::Read;
use serde::Deserialize;
use tauri::{
@@ -32,6 +26,7 @@ mod config;
#[cfg(not(target_os = "android"))]
mod desktop;
mod error;
mod file_path;
#[cfg(target_os = "android")]
mod mobile;
#[cfg(target_os = "android")]
@@ -48,93 +43,11 @@ pub use mobile::Fs;
pub use error::Error;
pub use scope::{Event as ScopeEvent, Scope};
pub use file_path::FilePath;
pub use file_path::SafeFilePath;
type Result<T> = std::result::Result<T, Error>;
// TODO: Combine this with SafeFilePath
/// Represents either a filesystem path or a URI pointing to a file
/// such as `file://` URIs or Android `content://` URIs.
#[derive(Debug)]
pub enum FilePath {
Url(url::Url),
Path(PathBuf),
}
impl<'de> serde::Deserialize<'de> for FilePath {
fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
struct FilePathVisitor;
impl<'de> serde::de::Visitor<'de> for FilePathVisitor {
type Value = FilePath;
fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
formatter.write_str("a string representing an file URL or a path")
}
fn visit_str<E>(self, s: &str) -> std::result::Result<Self::Value, E>
where
E: serde::de::Error,
{
FilePath::from_str(s).map_err(|e| {
serde::de::Error::invalid_value(
serde::de::Unexpected::Str(s),
&e.to_string().as_str(),
)
})
}
}
deserializer.deserialize_str(FilePathVisitor)
}
}
impl FromStr for FilePath {
type Err = Infallible;
fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
if let Ok(url) = url::Url::from_str(s) {
if url.scheme().len() != 1 {
return Ok(Self::Url(url));
}
}
Ok(Self::Path(PathBuf::from(s)))
}
}
impl From<PathBuf> for FilePath {
fn from(value: PathBuf) -> Self {
Self::Path(value)
}
}
impl From<&Path> for FilePath {
fn from(value: &Path) -> Self {
Self::Path(value.to_owned())
}
}
impl From<&PathBuf> for FilePath {
fn from(value: &PathBuf) -> Self {
Self::Path(value.to_owned())
}
}
impl From<url::Url> for FilePath {
fn from(value: url::Url) -> Self {
Self::Url(value)
}
}
impl fmt::Display for FilePath {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Url(u) => u.fmt(f),
Self::Path(p) => p.display().fmt(f),
}
}
}
#[derive(Debug, Default, Clone, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct OpenOptions {
@@ -151,8 +64,10 @@ pub struct OpenOptions {
#[serde(default)]
create_new: bool,
#[serde(default)]
#[allow(unused)]
mode: Option<u32>,
#[serde(default)]
#[allow(unused)]
custom_flags: Option<i32>,
}
+2 -1
View File
@@ -22,8 +22,9 @@ use std::{
};
use crate::{
commands::{resolve_path, CommandResult, SafeFilePath},
commands::{resolve_path, CommandResult},
scope::Entry,
SafeFilePath,
};
struct InnerWatcher {