Skip to content

Commit

Permalink
Try using bstr
Browse files Browse the repository at this point in the history
  • Loading branch information
gsoltis committed May 5, 2023
1 parent 3ea8a16 commit e7fac37
Show file tree
Hide file tree
Showing 7 changed files with 110 additions and 53 deletions.
9 changes: 5 additions & 4 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions crates/turbopath/Cargo.toml
Expand Up @@ -7,6 +7,7 @@ edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
bstr = "1.4.0"
path-slash = "0.2.1"
# TODO: Make this a crate feature
serde = { workspace = true }
Expand Down
20 changes: 14 additions & 6 deletions crates/turbopath/src/anchored_system_path_buf.rs
Expand Up @@ -3,8 +3,7 @@ use std::path::{Path, PathBuf};
use serde::{Deserialize, Serialize};

use crate::{
AbsoluteSystemPathBuf, IntoSystem, IntoUnix, PathError, PathValidationError,
RelativeUnixPathBuf,
AbsoluteSystemPathBuf, IntoSystem, PathError, PathValidationError, RelativeUnixPathBuf,
};

#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Default, Serialize, Deserialize)]
Expand All @@ -15,7 +14,8 @@ impl TryFrom<&Path> for AnchoredSystemPathBuf {

fn try_from(path: &Path) -> Result<Self, Self::Error> {
if path.is_absolute() {
return Err(PathValidationError::NotRelative(path.to_path_buf()).into());
let bad_path = path.display().to_string();
return Err(PathValidationError::NotRelative(bad_path).into());
}

Ok(AnchoredSystemPathBuf(path.into_system()?))
Expand Down Expand Up @@ -52,9 +52,17 @@ impl AnchoredSystemPathBuf {
.ok_or_else(|| PathValidationError::InvalidUnicode(self.0.clone()).into())
}

pub fn to_unix(&self) -> Result<RelativeUnixPathBuf, PathValidationError> {
let p = self.as_path().into_unix()?;
RelativeUnixPathBuf::new(p)
pub fn to_unix(&self) -> Result<RelativeUnixPathBuf, PathError> {
#[cfg(unix)]
{
use std::os::unix::ffi::OsStrExt;
let bytes = self.0.as_os_str().as_bytes();
return RelativeUnixPathBuf::new(bytes);
}
#[cfg(not(unix))]
{
unimplemented!()
}
}
}

Expand Down
9 changes: 8 additions & 1 deletion crates/turbopath/src/lib.rs
Expand Up @@ -26,6 +26,8 @@ pub enum PathError {
IO(#[from] io::Error),
#[error("Path prefix error: {0}")]
PrefixError(#[from] StripPrefixError),
#[error("Invalid UTF8: {0}")]
Utf8Error(#[from] bstr::Utf8Error),
}

impl PathError {
Expand All @@ -45,13 +47,18 @@ pub enum PathValidationError {
#[error("Path is not absolute: {0}")]
NotAbsolute(PathBuf),
#[error("Path is not relative: {0}")]
NotRelative(PathBuf),
NotRelative(String),
#[error("Path {0} is not parent of {1}")]
NotParent(String, String),
#[error("Path {0} is not a unix path")]
NotUnix(String),
}

pub(crate) fn not_relative_error(bytes: &[u8]) -> PathValidationError {
let s = String::from_utf8_lossy(bytes).to_string();
PathValidationError::NotRelative(s)
}

trait IntoSystem {
fn into_system(self) -> Result<PathBuf, PathValidationError>;
}
Expand Down
3 changes: 2 additions & 1 deletion crates/turbopath/src/relative_system_path_buf.rs
Expand Up @@ -36,7 +36,8 @@ impl RelativeSystemPathBuf {
pub fn new(unchecked_path: impl Into<PathBuf>) -> Result<Self, PathValidationError> {
let unchecked_path = unchecked_path.into();
if unchecked_path.is_absolute() {
return Err(PathValidationError::NotRelative(unchecked_path));
let bad_path = unchecked_path.display().to_string();
return Err(PathValidationError::NotRelative(bad_path));
}

let system_path = unchecked_path.into_system()?;
Expand Down
41 changes: 32 additions & 9 deletions crates/turbopath/src/relative_unix_path.rs
@@ -1,25 +1,48 @@
use std::path::Path;
use std::{ffi::OsString, path::PathBuf};

use crate::{IntoSystem, PathError, PathValidationError, RelativeSystemPathBuf};
use bstr::BStr;

use crate::{not_relative_error, PathError, RelativeSystemPathBuf};

#[repr(transparent)]
pub struct RelativeUnixPath {
inner: Path,
inner: BStr,
}

impl RelativeUnixPath {
pub fn new<P: AsRef<Path>>(value: &P) -> Result<&Self, PathError> {
pub fn new<P: AsRef<BStr>>(value: &P) -> Result<&Self, PathError> {
let path = value.as_ref();
if path.is_absolute() {
return Err(PathValidationError::NotRelative(path.to_owned()).into());
if path[0] == b'/' {
return Err(not_relative_error(path).into());
}
// copied from stdlib path.rs: relies on the representation of
// RelativeUnixPath being just a Path, the same way Path relies on
// just being an OsStr
Ok(unsafe { &*(path as *const Path as *const Self) })
Ok(unsafe { &*(path as *const BStr as *const Self) })
}

pub fn to_system_path(&self) -> Result<RelativeSystemPathBuf, PathError> {
let system_path = self.inner.into_system()?;
Ok(RelativeSystemPathBuf::new_unchecked(system_path))
#[cfg(unix)]
{
// On unix, unix paths are already system paths. Copy the bytes
// but skip validation.
use std::os::unix::prelude::OsStringExt;
let path = PathBuf::from(OsString::from_vec(self.inner.to_vec()));
Ok(RelativeSystemPathBuf::new_unchecked(path))
}

#[cfg(windows)]
{
let system_path_bytes = self
.inner
.iter()
.map(|byte| if *byte == b'/' { b'\\' } else { *byte })
.collect::<Vec<u8>>();
// Is this safe to do? We think we have utf8 bytes or bytes that roundtrip
// through utf8
let system_path_string = unsafe { String::from_utf8_unchecked(system_path_bytes) };
let system_path_buf = PathBuf::from(system_path_string);
Ok(RelativeSystemPathBuf::new_unchecked(system_path_buf))
}
}
}
80 changes: 48 additions & 32 deletions crates/turbopath/src/relative_unix_path_buf.rs
@@ -1,11 +1,9 @@
use std::path::PathBuf;
use bstr::{BString, ByteSlice};

use serde::Serialize;
use crate::{not_relative_error, PathError, PathValidationError};

use crate::{IntoUnix, PathError, PathValidationError};

#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Default, Serialize)]
pub struct RelativeUnixPathBuf(PathBuf);
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
pub struct RelativeUnixPathBuf(BString);

impl RelativeUnixPathBuf {
/// Create a new RelativeUnixPathBuf from a PathBuf by calling `into_unix()`
Expand All @@ -24,32 +22,49 @@ impl RelativeUnixPathBuf {
///
/// ```
/// ```
pub fn new(path: impl Into<PathBuf>) -> Result<Self, PathValidationError> {
let path = path.into();
if path.is_absolute() {
return Err(PathValidationError::NotRelative(path));
}
// pub fn new(path: impl Into<PathBuf>) -> Result<Self, PathValidationError> {
// let path = path.into();
// if path.is_absolute() {
// return Err(PathValidationError::NotRelative(path));
// }

Ok(RelativeUnixPathBuf(path.into_unix()?))
// Ok(RelativeUnixPathBuf(path.into_unix()?))
// }
pub fn new(path: impl Into<Vec<u8>>) -> Result<Self, PathError> {
let bytes: Vec<u8> = path.into();
if bytes[0] == b'/' {
return Err(not_relative_error(&bytes).into());
}
Ok(Self(BString::new(bytes)))
}

pub fn to_str(&self) -> Result<&str, PathValidationError> {
self.0
.to_str()
.ok_or_else(|| PathValidationError::InvalidUnicode(self.0.clone()))
pub fn as_str(&self) -> Result<&str, PathError> {
let s = self.0.to_str()?;
Ok(s)
}

// pub fn to_str(&self) -> Result<&str, PathValidationError> {
// self.0
// .to_str()
// .ok_or_else(|| PathValidationError::InvalidUnicode(self.0.clone()))
// }

pub fn strip_prefix(&self, prefix: &RelativeUnixPathBuf) -> Result<Self, PathError> {
let stripped = self.0.strip_prefix(&prefix.0)?;
let path = Self::new(stripped)?;
Ok(path)
if !self.0.starts_with(&prefix.0) {
return Err(PathError::PathValidationError(
PathValidationError::NotParent(prefix.0.to_string(), self.0.to_string()),
));
}
let tail_slice = &self.0[prefix.0.len()..];
Self::new(tail_slice)
}

pub fn join(&self, tail: &RelativeUnixPathBuf) -> Result<Self, PathError> {
// TODO: fix this to always be /
let path = self.0.join(&tail.0);
let path = path.as_path().into_unix()?;
Ok(RelativeUnixPathBuf(path))
pub fn join(&self, tail: &RelativeUnixPathBuf) -> Self {
let buffer = Vec::with_capacity(self.0.len() + 1 + tail.0.len());
let mut path = BString::new(buffer);
path.push(b'/');
path.extend_from_slice(&tail.0);
Self(path)
}
}

Expand All @@ -62,22 +77,23 @@ mod tests {

#[test]
fn test_relative_unix_path_buf() {
let path = RelativeUnixPathBuf::new(PathBuf::from("foo/bar")).unwrap();
assert_eq!(path.to_str().unwrap(), "foo/bar");
let path = RelativeUnixPathBuf::new("foo/bar").unwrap();
assert_eq!(path.as_str().unwrap(), "foo/bar");
}

#[test]
fn test_relative_unix_path_buf_with_extension() {
let path = RelativeUnixPathBuf::new(PathBuf::from("foo/bar.txt")).unwrap();
assert_eq!(path.to_str().unwrap(), "foo/bar.txt");
let path = RelativeUnixPathBuf::new("foo/bar.txt").unwrap();
assert_eq!(path.as_str().unwrap(), "foo/bar.txt");
}

#[test]
fn test_relative_unix_path_buf_errors() {
#[cfg(not(windows))]
assert!(RelativeUnixPathBuf::new(PathBuf::from("/foo/bar")).is_err());
#[cfg(windows)]
assert!(RelativeUnixPathBuf::new(PathBuf::from("C:\\foo\\bar")).is_err());
assert!(RelativeUnixPathBuf::new("/foo/bar").is_err());
// Note: this shouldn't be an error, this is a valid relative unix path
// #[cfg(windows)]
// assert!(RelativeUnixPathBuf::new(PathBuf::from("C:\\foo\\bar")).
// is_err());
}

#[cfg(windows)]
Expand Down

0 comments on commit e7fac37

Please sign in to comment.