use std::path::Path; use bstr::{BStr, BString, ByteSlice}; use gix_validate::path::component::Options; use crate::{os_str_into_bstr, try_from_bstr, try_from_byte_slice}; pub(super) mod types { use bstr::{BStr, ByteSlice}; /// A wrapper for `BStr`. It is used to enforce the following constraints: /// /// - The path separator always is `/`, independent of the platform. /// - Only normal components are allowed. /// - It is always represented as a bunch of bytes. #[repr(transparent)] pub struct RelativePath { inner: BStr, } impl AsRef<[u8]> for RelativePath { #[inline] fn as_ref(&self) -> &[u8] { self.inner.as_bytes() } } } use types::RelativePath; impl RelativePath { fn new_unchecked(value: &BStr) -> Result<&RelativePath, Error> { // SAFETY: `RelativePath` is transparent and equivalent to a `&BStr` if provided as reference. #[expect(unsafe_code)] unsafe { Ok(std::mem::transmute::<&BStr, &RelativePath>(value)) } } } /// The error used in [`RelativePath`]. #[derive(Debug, thiserror::Error)] #[expect(missing_docs)] pub enum Error { #[error("A RelativePath is not allowed to be absolute")] IsAbsolute, #[error(transparent)] ContainsInvalidComponent(#[from] gix_validate::path::component::Error), #[error(transparent)] IllegalUtf8(#[from] crate::Utf8Error), } fn relative_path_from_value_and_path<'a>(path_bstr: &'a BStr, path: &Path) -> Result<&'a RelativePath, Error> { if path.is_absolute() { return Err(Error::IsAbsolute); } let options = Options::default(); for component in path.components() { let component = os_str_into_bstr(component.as_os_str())?; gix_validate::path::component(component, None, options)?; } RelativePath::new_unchecked(BStr::new(path_bstr.as_bytes())) } impl<'a> TryFrom<&'a str> for &'a RelativePath { type Error = Error; fn try_from(value: &'a str) -> Result { relative_path_from_value_and_path(value.into(), Path::new(value)) } } impl<'a> TryFrom<&'a BStr> for &'a RelativePath { type Error = Error; fn try_from(value: &'a BStr) -> Result { let path = try_from_bstr(value)?; relative_path_from_value_and_path(value, &path) } } impl<'a> TryFrom<&'a [u8]> for &'a RelativePath { type Error = Error; #[inline] fn try_from(value: &'a [u8]) -> Result { let path = try_from_byte_slice(value)?; relative_path_from_value_and_path(value.as_bstr(), path) } } impl<'a, const N: usize> TryFrom<&'a [u8; N]> for &'a RelativePath { type Error = Error; #[inline] fn try_from(value: &'a [u8; N]) -> Result { let path = try_from_byte_slice(value.as_bstr())?; relative_path_from_value_and_path(value.as_bstr(), path) } } impl<'a> TryFrom<&'a BString> for &'a RelativePath { type Error = Error; fn try_from(value: &'a BString) -> Result { let path = try_from_bstr(value.as_bstr())?; relative_path_from_value_and_path(value.as_bstr(), &path) } }