Skip to content

Commit 4b62bb0

Browse files
committed
refactor
* move `RelativePath` up to crate level. * deduplicate try-from * move tests into integration test - make them platform independent * do not support initialization from an array (use slice instead)
1 parent 88cbf7f commit 4b62bb0

File tree

9 files changed

+214
-258
lines changed

9 files changed

+214
-258
lines changed

gix-path/src/lib.rs

+1
Original file line numberDiff line numberDiff line change
@@ -65,3 +65,4 @@ pub mod env;
6565

6666
///
6767
pub mod relative_path;
68+
pub use relative_path::types::RelativePath;

gix-path/src/relative_path.rs

+49-250
Original file line numberDiff line numberDiff line change
@@ -2,21 +2,32 @@ use bstr::BStr;
22
use bstr::BString;
33
use bstr::ByteSlice;
44
use gix_validate::path::component::Options;
5-
use std::borrow::Cow;
5+
use std::path::Path;
66

77
use crate::os_str_into_bstr;
88
use crate::try_from_bstr;
99
use crate::try_from_byte_slice;
1010

11-
/// A wrapper for `BStr`. It is used to enforce the following constraints:
12-
///
13-
/// - The path separator always is `/`, independent of the platform.
14-
/// - Only normal components are allowed.
15-
/// - It is always represented as a bunch of bytes.
16-
#[derive()]
17-
pub struct RelativePath {
18-
inner: BStr,
11+
pub(super) mod types {
12+
use bstr::{BStr, ByteSlice};
13+
/// A wrapper for `BStr`. It is used to enforce the following constraints:
14+
///
15+
/// - The path separator always is `/`, independent of the platform.
16+
/// - Only normal components are allowed.
17+
/// - It is always represented as a bunch of bytes.
18+
#[derive()]
19+
pub struct RelativePath {
20+
inner: BStr,
21+
}
22+
23+
impl AsRef<[u8]> for RelativePath {
24+
#[inline]
25+
fn as_ref(&self) -> &[u8] {
26+
self.inner.as_bytes()
27+
}
28+
}
1929
}
30+
use types::RelativePath;
2031

2132
impl RelativePath {
2233
fn new_unchecked(value: &BStr) -> Result<&RelativePath, Error> {
@@ -26,12 +37,6 @@ impl RelativePath {
2637
std::mem::transmute(value)
2738
}
2839
}
29-
30-
/// TODO
31-
/// Needs docs.
32-
pub fn ends_with(&self, needle: &[u8]) -> bool {
33-
self.inner.ends_with(needle)
34-
}
3540
}
3641

3742
/// The error used in [`RelativePath`].
@@ -46,49 +51,45 @@ pub enum Error {
4651
IllegalUtf8(#[from] crate::Utf8Error),
4752
}
4853

49-
impl<'a> TryFrom<&'a str> for &'a RelativePath {
50-
type Error = Error;
51-
52-
fn try_from(value: &'a str) -> Result<Self, Self::Error> {
53-
use std::path::Path;
54+
fn relative_path_from_value_and_path<'a>(path_bstr: &'a BStr, path: &Path) -> Result<&'a RelativePath, Error> {
55+
if path.is_absolute() {
56+
return Err(Error::IsAbsolute);
57+
}
5458

55-
let path: &std::path::Path = Path::new(value);
59+
let options = Options::default();
5660

57-
if path.is_absolute() {
58-
return Err(Error::IsAbsolute);
59-
}
60-
61-
let options: Options = Default::default();
61+
for component in path.components() {
62+
let component = os_str_into_bstr(component.as_os_str())?;
63+
gix_validate::path::component(component, None, options)?;
64+
}
6265

63-
for component in path.components() {
64-
let component = os_str_into_bstr(component.as_os_str())?;
66+
RelativePath::new_unchecked(BStr::new(path_bstr.as_bytes()))
67+
}
6568

66-
gix_validate::path::component(component, None, options)?;
67-
}
69+
impl<'a> TryFrom<&'a str> for &'a RelativePath {
70+
type Error = Error;
6871

69-
RelativePath::new_unchecked(BStr::new(value.as_bytes()))
72+
fn try_from(value: &'a str) -> Result<Self, Self::Error> {
73+
relative_path_from_value_and_path(value.into(), Path::new(value))
7074
}
7175
}
7276

7377
impl<'a> TryFrom<&'a BStr> for &'a RelativePath {
7478
type Error = Error;
7579

7680
fn try_from(value: &'a BStr) -> Result<Self, Self::Error> {
77-
let path: &std::path::Path = &try_from_bstr(value)?;
78-
79-
if path.is_absolute() {
80-
return Err(Error::IsAbsolute);
81-
}
82-
83-
let options: Options = Default::default();
84-
85-
for component in path.components() {
86-
let component = os_str_into_bstr(component.as_os_str())?;
81+
let path = try_from_bstr(value)?;
82+
relative_path_from_value_and_path(value, &path)
83+
}
84+
}
8785

88-
gix_validate::path::component(component, None, options)?;
89-
}
86+
impl<'a> TryFrom<&'a [u8]> for &'a RelativePath {
87+
type Error = Error;
9088

91-
RelativePath::new_unchecked(value)
89+
#[inline]
90+
fn try_from(value: &'a [u8]) -> Result<Self, Self::Error> {
91+
let path = try_from_byte_slice(value)?;
92+
relative_path_from_value_and_path(value.as_bstr(), path)
9293
}
9394
}
9495

@@ -97,218 +98,16 @@ impl<'a, const N: usize> TryFrom<&'a [u8; N]> for &'a RelativePath {
9798

9899
#[inline]
99100
fn try_from(value: &'a [u8; N]) -> Result<Self, Self::Error> {
100-
let path: &std::path::Path = try_from_byte_slice(value)?;
101-
102-
if path.is_absolute() {
103-
return Err(Error::IsAbsolute);
104-
}
105-
106-
let options: Options = Default::default();
107-
108-
for component in path.components() {
109-
let component = os_str_into_bstr(component.as_os_str())?;
110-
111-
gix_validate::path::component(component, None, options)?;
112-
}
113-
114-
RelativePath::new_unchecked(value.into())
101+
let path = try_from_byte_slice(value.as_bstr())?;
102+
relative_path_from_value_and_path(value.as_bstr(), path)
115103
}
116104
}
117105

118106
impl<'a> TryFrom<&'a BString> for &'a RelativePath {
119107
type Error = Error;
120108

121109
fn try_from(value: &'a BString) -> Result<Self, Self::Error> {
122-
let path: &std::path::Path = &try_from_bstr(value.as_bstr())?;
123-
124-
if path.is_absolute() {
125-
return Err(Error::IsAbsolute);
126-
}
127-
128-
let options: Options = Default::default();
129-
130-
for component in path.components() {
131-
let component = os_str_into_bstr(component.as_os_str())?;
132-
133-
gix_validate::path::component(component, None, options)?;
134-
}
135-
136-
RelativePath::new_unchecked(value.as_bstr())
137-
}
138-
}
139-
140-
/// This is required by a trait bound on [`from_str`](crate::from_bstr).
141-
impl<'a> From<&'a RelativePath> for Cow<'a, BStr> {
142-
#[inline]
143-
fn from(value: &'a RelativePath) -> Cow<'a, BStr> {
144-
Cow::Borrowed(&value.inner)
145-
}
146-
}
147-
148-
impl AsRef<[u8]> for RelativePath {
149-
#[inline]
150-
fn as_ref(&self) -> &[u8] {
151-
self.inner.as_bytes()
152-
}
153-
}
154-
155-
#[cfg(test)]
156-
mod tests {
157-
use super::*;
158-
159-
#[cfg(not(windows))]
160-
#[test]
161-
fn absolute_paths_return_err() {
162-
let path_str: &str = "/refs/heads";
163-
let path_bstr: &BStr = path_str.into();
164-
let path_u8: &[u8; 11] = b"/refs/heads";
165-
let path_bstring: BString = "/refs/heads".into();
166-
167-
assert!(matches!(
168-
TryInto::<&RelativePath>::try_into(path_str),
169-
Err(Error::IsAbsolute)
170-
));
171-
assert!(matches!(
172-
TryInto::<&RelativePath>::try_into(path_bstr),
173-
Err(Error::IsAbsolute)
174-
));
175-
assert!(matches!(
176-
TryInto::<&RelativePath>::try_into(path_u8),
177-
Err(Error::IsAbsolute)
178-
));
179-
assert!(matches!(
180-
TryInto::<&RelativePath>::try_into(&path_bstring),
181-
Err(Error::IsAbsolute)
182-
));
183-
}
184-
185-
#[cfg(windows)]
186-
#[test]
187-
fn absolute_paths_return_err() {
188-
let path_str: &str = r"c:\refs\heads";
189-
let path_bstr: &BStr = path_str.into();
190-
let path_u8: &[u8; 13] = b"c:\\refs\\heads";
191-
let path_bstring: BString = r"c:\refs\heads".into();
192-
193-
assert!(matches!(
194-
TryInto::<&RelativePath>::try_into(path_str),
195-
Err(Error::IsAbsolute)
196-
));
197-
assert!(matches!(
198-
TryInto::<&RelativePath>::try_into(path_bstr),
199-
Err(Error::IsAbsolute)
200-
));
201-
assert!(matches!(
202-
TryInto::<&RelativePath>::try_into(path_u8),
203-
Err(Error::IsAbsolute)
204-
));
205-
assert!(matches!(
206-
TryInto::<&RelativePath>::try_into(&path_bstring),
207-
Err(Error::IsAbsolute)
208-
));
209-
}
210-
211-
#[cfg(not(windows))]
212-
#[test]
213-
fn dots_in_paths_return_err() {
214-
let path_str: &str = "./heads";
215-
let path_bstr: &BStr = path_str.into();
216-
let path_u8: &[u8; 7] = b"./heads";
217-
let path_bstring: BString = "./heads".into();
218-
219-
assert!(matches!(
220-
TryInto::<&RelativePath>::try_into(path_str),
221-
Err(Error::ContainsInvalidComponent(_))
222-
));
223-
assert!(matches!(
224-
TryInto::<&RelativePath>::try_into(path_bstr),
225-
Err(Error::ContainsInvalidComponent(_))
226-
));
227-
assert!(matches!(
228-
TryInto::<&RelativePath>::try_into(path_u8),
229-
Err(Error::ContainsInvalidComponent(_))
230-
));
231-
assert!(matches!(
232-
TryInto::<&RelativePath>::try_into(&path_bstring),
233-
Err(Error::ContainsInvalidComponent(_))
234-
));
235-
}
236-
237-
#[cfg(windows)]
238-
#[test]
239-
fn dots_in_paths_return_err() {
240-
let path_str: &str = r".\heads";
241-
let path_bstr: &BStr = path_str.into();
242-
let path_u8: &[u8; 7] = b".\\heads";
243-
let path_bstring: BString = r".\heads".into();
244-
245-
assert!(matches!(
246-
TryInto::<&RelativePath>::try_into(path_str),
247-
Err(Error::ContainsInvalidComponent(_))
248-
));
249-
assert!(matches!(
250-
TryInto::<&RelativePath>::try_into(path_bstr),
251-
Err(Error::ContainsInvalidComponent(_))
252-
));
253-
assert!(matches!(
254-
TryInto::<&RelativePath>::try_into(path_u8),
255-
Err(Error::ContainsInvalidComponent(_))
256-
));
257-
assert!(matches!(
258-
TryInto::<&RelativePath>::try_into(&path_bstring),
259-
Err(Error::ContainsInvalidComponent(_))
260-
));
261-
}
262-
263-
#[cfg(not(windows))]
264-
#[test]
265-
fn double_dots_in_paths_return_err() {
266-
let path_str: &str = "../heads";
267-
let path_bstr: &BStr = path_str.into();
268-
let path_u8: &[u8; 8] = b"../heads";
269-
let path_bstring: BString = "../heads".into();
270-
271-
assert!(matches!(
272-
TryInto::<&RelativePath>::try_into(path_str),
273-
Err(Error::ContainsInvalidComponent(_))
274-
));
275-
assert!(matches!(
276-
TryInto::<&RelativePath>::try_into(path_bstr),
277-
Err(Error::ContainsInvalidComponent(_))
278-
));
279-
assert!(matches!(
280-
TryInto::<&RelativePath>::try_into(path_u8),
281-
Err(Error::ContainsInvalidComponent(_))
282-
));
283-
assert!(matches!(
284-
TryInto::<&RelativePath>::try_into(&path_bstring),
285-
Err(Error::ContainsInvalidComponent(_))
286-
));
287-
}
288-
289-
#[cfg(windows)]
290-
#[test]
291-
fn double_dots_in_paths_return_err() {
292-
let path_str: &str = r"..\heads";
293-
let path_bstr: &BStr = path_str.into();
294-
let path_u8: &[u8; 8] = b"..\\heads";
295-
let path_bstring: BString = r"..\heads".into();
296-
297-
assert!(matches!(
298-
TryInto::<&RelativePath>::try_into(path_str),
299-
Err(Error::ContainsInvalidComponent(_))
300-
));
301-
assert!(matches!(
302-
TryInto::<&RelativePath>::try_into(path_bstr),
303-
Err(Error::ContainsInvalidComponent(_))
304-
));
305-
assert!(matches!(
306-
TryInto::<&RelativePath>::try_into(path_u8),
307-
Err(Error::ContainsInvalidComponent(_))
308-
));
309-
assert!(matches!(
310-
TryInto::<&RelativePath>::try_into(&path_bstring),
311-
Err(Error::ContainsInvalidComponent(_))
312-
));
110+
let path = try_from_bstr(value.as_bstr())?;
111+
relative_path_from_value_and_path(value.as_bstr(), &path)
313112
}
314113
}

gix-path/tests/path/main.rs

+1
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ pub type Result<T = ()> = std::result::Result<T, Box<dyn std::error::Error>>;
22

33
mod convert;
44
mod realpath;
5+
mod relative_path;
56
mod home_dir {
67
#[test]
78
fn returns_existing_directory() {

0 commit comments

Comments
 (0)