1717
1818//! Path abstraction for Object Storage
1919
20- use itertools:: Itertools ;
2120use percent_encoding:: percent_decode;
2221use std:: fmt:: Formatter ;
2322#[ cfg( not( target_arch = "wasm32" ) ) ]
@@ -29,9 +28,12 @@ pub const DELIMITER: &str = "/";
2928/// The path delimiter as a single byte
3029pub const DELIMITER_BYTE : u8 = DELIMITER . as_bytes ( ) [ 0 ] ;
3130
31+ /// The path delimiter as a single char
32+ pub const DELIMITER_CHAR : char = DELIMITER_BYTE as char ;
33+
3234mod parts;
3335
34- pub use parts:: { InvalidPart , PathPart } ;
36+ pub use parts:: { InvalidPart , PathPart , PathParts } ;
3537
3638/// Error returned by [`Path::parse`]
3739#[ derive( Debug , thiserror:: Error ) ]
@@ -157,6 +159,18 @@ pub struct Path {
157159}
158160
159161impl Path {
162+ /// An empty [`Path`] that points to the root of the store, equivalent to `Path::from("/")`.
163+ ///
164+ /// See also [`Path::is_root`].
165+ ///
166+ /// # Example
167+ ///
168+ /// ```
169+ /// # use object_store::path::Path;
170+ /// assert_eq!(Path::ROOT, Path::from("/"));
171+ /// ```
172+ pub const ROOT : Self = Self { raw : String :: new ( ) } ;
173+
160174 /// Parse a string as a [`Path`], returning a [`Error`] if invalid,
161175 /// as defined on the docstring for [`Path`]
162176 ///
@@ -255,14 +269,53 @@ impl Path {
255269 Self :: parse ( decoded)
256270 }
257271
258- /// Returns the [`PathPart`] of this [`Path`]
259- pub fn parts ( & self ) -> impl Iterator < Item = PathPart < ' _ > > {
260- self . raw
261- . split_terminator ( DELIMITER )
262- . map ( |s| PathPart { raw : s. into ( ) } )
272+ /// Returns the number of [`PathPart`]s in this [`Path`]
273+ ///
274+ /// This is equivalent to calling `.parts().count()` manually.
275+ ///
276+ /// # Performance
277+ ///
278+ /// This operation is `O(n)`.
279+ #[ doc( alias = "len" ) ]
280+ pub fn parts_count ( & self ) -> usize {
281+ self . raw . split_terminator ( DELIMITER ) . count ( )
282+ }
283+
284+ /// True if this [`Path`] points to the root of the store, equivalent to `Path::from("/")`.
285+ ///
286+ /// See also [`Path::root`].
287+ ///
288+ /// # Example
289+ ///
290+ /// ```
291+ /// # use object_store::path::Path;
292+ /// assert!(Path::from("/").is_root());
293+ /// assert!(Path::parse("").unwrap().is_root());
294+ /// ```
295+ pub fn is_root ( & self ) -> bool {
296+ self . raw . is_empty ( )
297+ }
298+
299+ /// Returns the [`PathPart`]s of this [`Path`]
300+ pub fn parts ( & self ) -> PathParts < ' _ > {
301+ PathParts :: new ( self )
302+ }
303+
304+ /// Returns a copy of this [`Path`] with the last path segment removed
305+ ///
306+ /// Returns `None` if this path has zero segments.
307+ #[ doc( alias = "folder" ) ]
308+ pub fn prefix ( & self ) -> Option < Self > {
309+ let prefix = self . raw . rsplit_once ( DELIMITER ) ?. 0 ;
310+
311+ Some ( Self {
312+ raw : prefix. to_string ( ) ,
313+ } )
263314 }
264315
265316 /// Returns the last path segment containing the filename stored in this [`Path`]
317+ ///
318+ /// Returns `None` only if this path is the root path.
266319 pub fn filename ( & self ) -> Option < & str > {
267320 match self . raw . is_empty ( ) {
268321 true => None ,
@@ -348,13 +401,22 @@ where
348401 I : Into < PathPart < ' a > > ,
349402{
350403 fn from_iter < T : IntoIterator < Item = I > > ( iter : T ) -> Self {
351- let raw = T :: into_iter ( iter )
352- . map ( |s| s . into ( ) )
353- . filter ( |s| !s . raw . is_empty ( ) )
354- . map ( |s| s . raw )
355- . join ( DELIMITER ) ;
404+ let mut this = Self :: ROOT ;
405+ this . extend ( iter ) ;
406+ this
407+ }
408+ }
356409
357- Self { raw }
410+ impl < ' a , I : Into < PathPart < ' a > > > Extend < I > for Path {
411+ fn extend < T : IntoIterator < Item = I > > ( & mut self , iter : T ) {
412+ for s in iter. into_iter ( ) {
413+ let s = s. into ( ) ;
414+ if s. raw . is_empty ( ) {
415+ continue ;
416+ }
417+ self . raw . push ( DELIMITER_CHAR ) ;
418+ self . raw . push_str ( & s. raw ) ;
419+ }
358420 }
359421}
360422
0 commit comments