1use std::{borrow::Borrow, fmt, ops::Deref, str::FromStr};
35
36use thiserror::Error;
37
38#[derive(Debug, Error, PartialEq, Eq)]
43pub enum PathError {
44 #[error("Invalid component '{component}': {reason}")]
46 InvalidComponent { component: String, reason: String },
47}
48
49pub fn normalize_path(input: &str) -> String {
70 if input.is_empty() {
71 return String::new();
72 }
73
74 input
75 .split('.')
76 .filter(|component| !component.is_empty())
77 .collect::<Vec<_>>()
78 .join(".")
79}
80
81#[derive(Debug, Clone, PartialEq, Eq, Hash)]
101pub struct Component {
102 inner: String,
103}
104
105impl Component {
106 pub fn new(s: impl Into<String>) -> Result<Self, PathError> {
112 let s = s.into();
113
114 if s.contains('.') {
115 return Err(PathError::InvalidComponent {
116 component: s.clone(),
117 reason: "components cannot contain dots".to_string(),
118 });
119 }
120
121 Ok(Component { inner: s })
122 }
123
124 pub fn as_str(&self) -> &str {
126 &self.inner
127 }
128}
129
130impl AsRef<str> for Component {
131 fn as_ref(&self) -> &str {
132 &self.inner
133 }
134}
135
136impl fmt::Display for Component {
137 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
138 write!(f, "{}", self.inner)
139 }
140}
141
142impl FromStr for Component {
143 type Err = PathError;
144
145 fn from_str(s: &str) -> Result<Self, Self::Err> {
146 Component::new(s)
147 }
148}
149
150impl TryFrom<String> for Component {
151 type Error = PathError;
152
153 fn try_from(s: String) -> Result<Self, Self::Error> {
154 Component::new(s)
155 }
156}
157
158impl TryFrom<&str> for Component {
159 type Error = PathError;
160
161 fn try_from(s: &str) -> Result<Self, Self::Error> {
162 Component::new(s)
163 }
164}
165
166#[derive(Debug, Clone, PartialEq, Eq, Hash)]
192pub struct PathBuf {
193 inner: String,
194}
195
196#[derive(Debug, PartialEq, Eq, Hash)]
204pub struct Path {
205 inner: str,
206}
207
208impl PathBuf {
209 pub fn new() -> Self {
211 Self {
212 inner: String::new(),
213 }
214 }
215
216 pub fn from_component(component: Component) -> Self {
218 Self {
219 inner: component.inner,
220 }
221 }
222
223 pub fn push(mut self, path: impl AsRef<str>) -> Self {
243 let normalized = normalize_path(path.as_ref());
244 if normalized.is_empty() {
245 return self;
246 }
247
248 if self.inner.is_empty() {
249 self.inner = normalized;
250 } else {
251 self.inner.push('.');
252 self.inner.push_str(&normalized);
253 }
254 self
255 }
256
257 pub fn push_component(mut self, component: Component) -> Self {
259 if self.inner.is_empty() {
260 self.inner = component.inner;
261 } else {
262 self.inner.push('.');
263 self.inner.push_str(&component.inner);
264 }
265 self
266 }
267
268 pub fn join(mut self, other: impl AsRef<Path>) -> Self {
270 let other_path = other.as_ref();
271 if self.inner.is_empty() {
272 self.inner = other_path.inner.to_string();
273 } else if !other_path.inner.is_empty() {
274 self.inner.push('.');
275 self.inner.push_str(&other_path.inner);
276 }
277 self
278 }
279
280 pub fn components(&self) -> impl Iterator<Item = &str> {
282 self.inner.split('.').filter(|s| !s.is_empty())
283 }
284
285 pub fn len(&self) -> usize {
287 if self.inner.is_empty() {
288 0
289 } else {
290 self.inner.split('.').count()
291 }
292 }
293
294 pub fn is_empty(&self) -> bool {
296 self.inner.is_empty()
297 }
298
299 pub fn parent(&self) -> Option<PathBuf> {
301 self.inner.rfind('.').map(|last_dot| PathBuf {
302 inner: self.inner[..last_dot].to_string(),
303 })
304 }
305
306 pub fn file_name(&self) -> Option<&str> {
308 if self.inner.is_empty() {
309 None
310 } else if let Some(last_dot) = self.inner.rfind('.') {
311 Some(&self.inner[last_dot + 1..])
312 } else {
313 Some(&self.inner)
314 }
315 }
316
317 fn from_normalized(normalized: String) -> Self {
322 PathBuf { inner: normalized }
323 }
324
325 pub fn normalize(path: &str) -> Self {
329 Self::from_normalized(normalize_path(path))
330 }
331}
332
333impl Path {
334 pub fn new(s: &str) -> &Path {
341 unsafe { Path::from_str_unchecked(s) }
342 }
343
344 pub unsafe fn from_str_unchecked(s: &str) -> &Path {
354 unsafe { &*(s as *const str as *const Path) }
356 }
357
358 pub fn components(&self) -> impl Iterator<Item = &str> {
360 self.inner.split('.').filter(|s| !s.is_empty())
361 }
362
363 pub fn len(&self) -> usize {
365 if self.inner.is_empty() {
366 0
367 } else {
368 self.inner.split('.').count()
369 }
370 }
371
372 pub fn is_empty(&self) -> bool {
374 self.inner.is_empty()
375 }
376
377 pub fn file_name(&self) -> Option<&str> {
379 if self.inner.is_empty() {
380 None
381 } else {
382 self.inner.split('.').next_back()
383 }
384 }
385
386 pub fn as_str(&self) -> &str {
388 &self.inner
389 }
390
391 pub fn to_path_buf(&self) -> PathBuf {
393 PathBuf {
394 inner: self.inner.to_string(),
395 }
396 }
397}
398
399impl Default for PathBuf {
400 fn default() -> Self {
401 Self::new()
402 }
403}
404
405impl Deref for PathBuf {
406 type Target = Path;
407
408 fn deref(&self) -> &Self::Target {
409 unsafe { crate::crdt::doc::path::Path::from_str_unchecked(self.inner.as_str()) }
411 }
412}
413
414impl AsRef<Path> for PathBuf {
415 fn as_ref(&self) -> &Path {
416 self.deref()
417 }
418}
419
420impl AsRef<PathBuf> for PathBuf {
421 fn as_ref(&self) -> &PathBuf {
422 self
423 }
424}
425
426impl AsRef<Path> for Path {
427 fn as_ref(&self) -> &Path {
428 self
429 }
430}
431
432impl AsRef<str> for Path {
433 fn as_ref(&self) -> &str {
434 &self.inner
435 }
436}
437
438impl AsRef<str> for PathBuf {
439 fn as_ref(&self) -> &str {
440 &self.inner
441 }
442}
443
444impl AsRef<Path> for str {
445 fn as_ref(&self) -> &Path {
446 Path::new(self)
447 }
448}
449
450impl AsRef<Path> for String {
451 fn as_ref(&self) -> &Path {
452 self.as_str().as_ref()
453 }
454}
455
456impl Borrow<Path> for PathBuf {
457 fn borrow(&self) -> &Path {
458 self.deref()
459 }
460}
461
462impl FromStr for PathBuf {
463 type Err = std::convert::Infallible;
464
465 fn from_str(s: &str) -> Result<Self, Self::Err> {
466 Ok(Self::normalize(s))
467 }
468}
469
470pub trait FromStrResult {
475 fn from_str_result(s: &str) -> Result<PathBuf, PathError>;
476}
477
478impl FromStrResult for PathBuf {
479 fn from_str_result(s: &str) -> Result<PathBuf, PathError> {
480 Ok(Self::normalize(s))
482 }
483}
484
485impl From<&PathBuf> for PathBuf {
486 fn from(path: &PathBuf) -> Self {
487 path.clone()
488 }
489}
490
491impl fmt::Display for PathBuf {
492 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
493 if self.inner.is_empty() {
494 write!(f, "(empty path)")
495 } else {
496 write!(f, "{}", self.inner)
497 }
498 }
499}
500
501impl fmt::Display for Path {
502 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
503 if self.inner.is_empty() {
504 write!(f, "(empty path)")
505 } else {
506 write!(f, "{}", &self.inner)
507 }
508 }
509}
510
511#[derive(Debug, Clone)]
513pub struct PathBuilder {
514 inner: String,
515}
516
517impl PathBuilder {
518 pub fn new() -> Self {
520 Self {
521 inner: String::new(),
522 }
523 }
524
525 pub fn component(mut self, component: impl Into<String>) -> Result<Self, PathError> {
527 let component = Component::new(component)?;
528 if self.inner.is_empty() {
529 self.inner = component.inner;
530 } else {
531 self.inner.push('.');
532 self.inner.push_str(&component.inner);
533 }
534 Ok(self)
535 }
536
537 pub fn push_component(mut self, component: Component) -> Self {
539 if self.inner.is_empty() {
540 self.inner = component.inner;
541 } else {
542 self.inner.push('.');
543 self.inner.push_str(&component.inner);
544 }
545 self
546 }
547
548 pub fn build(self) -> PathBuf {
550 PathBuf { inner: self.inner }
551 }
552}
553
554impl Default for PathBuilder {
555 fn default() -> Self {
556 Self::new()
557 }
558}
559
560#[macro_export]
594macro_rules! path {
595 () => {
597 $crate::crdt::doc::PathBuf::new()
598 };
599
600 ($single:literal) => {{
602 const NORMALIZED: &str = $crate::crdt::doc::path::normalize_const($single);
604 unsafe { $crate::crdt::doc::path::Path::from_str_unchecked(NORMALIZED) }
606 }};
607
608 ($first:expr $(, $rest:expr)* $(,)?) => {{
610 let mut path = $crate::crdt::doc::PathBuf::new();
611
612 fn add_component(path: &mut $crate::crdt::doc::PathBuf, component: impl AsRef<str>) {
614 let component_str = component.as_ref().trim();
615 if !component_str.is_empty() {
616 *path = std::mem::take(path).push(component_str);
618 }
619 }
620
621 let first_str = $first.to_string();
623 add_component(&mut path, first_str);
624
625 $(
627 let rest_str = $rest.to_string();
628 add_component(&mut path, rest_str);
629 )*
630
631 path
632 }};
633}
634
635pub const fn normalize_const(path: &str) -> &str {
643 if path.is_empty() {
646 return "";
647 }
648
649 path
652}
653
654#[cfg(test)]
655mod tests {
656 use super::*;
657
658 #[test]
659 fn test_pathbuf_construction() {
660 let path = PathBuf::new();
661 assert!(path.is_empty());
662 assert_eq!(path.len(), 0);
663
664 let component = Component::new("test").unwrap();
665 let path = PathBuf::from_component(component);
666 assert!(!path.is_empty());
667 assert_eq!(path.len(), 1);
668 assert_eq!(path.file_name(), Some("test"));
669 }
670
671 #[test]
672 fn test_pathbuf_push() {
673 let path = PathBuf::new().push("user").push("profile").push("name");
675
676 assert_eq!(path.len(), 3);
677 let components: Vec<&str> = path.components().collect();
678 assert_eq!(components, vec!["user", "profile", "name"]);
679 assert_eq!(path.file_name(), Some("name"));
680
681 let base = PathBuf::new().push("user");
683 let suffix = PathBuf::from_str("profile.name").unwrap();
684 let path = base.push(&suffix);
685 assert_eq!(path.as_str(), "user.profile.name");
686
687 let path = PathBuf::new()
689 .push("user")
690 .push(PathBuf::from_str("profile").unwrap())
691 .push("name");
692 assert_eq!(path.as_str(), "user.profile.name");
693 }
694
695 #[test]
696 fn test_pathbuf_push_normalization() {
697 let path = PathBuf::new().push("user.name");
699 assert_eq!(path.as_str(), "user.name");
700
701 let path = PathBuf::new().push("");
703 assert!(path.is_empty());
704
705 let path = PathBuf::new().push("user..name");
707 assert_eq!(path.as_str(), "user.name");
708 }
709
710 #[test]
711 fn test_pathbuf_parent() {
712 let path = PathBuf::from_str("user.profile.name").unwrap();
713 let parent = path.parent().unwrap();
714
715 let parent_components: Vec<&str> = parent.components().collect();
716 assert_eq!(parent_components, vec!["user", "profile"]);
717
718 let root = PathBuf::from_str("user").unwrap();
719 assert!(root.parent().is_none());
720 }
721
722 #[test]
723 fn test_path_validation_success() {
724 let valid_paths = vec!["simple", "user.profile", "user.profile.name", "a.b.c.d.e"];
725
726 for path_str in valid_paths {
727 let path = PathBuf::from_str(path_str);
728 assert!(path.is_ok(), "Path '{path_str}' should be valid");
729 }
730 }
731
732 #[test]
733 fn test_path_normalization_behavior() {
734 let test_cases = vec![
735 ("", ""),
737 (".user", "user"),
738 ("user.", "user"),
739 ("user..profile", "user.profile"),
740 ("user...profile", "user.profile"),
741 ("...user...profile...", "user.profile"),
742 ("...", ""),
743 ];
744
745 for (input, expected_normalized) in test_cases {
746 let result = PathBuf::from_str(input);
747 assert_eq!(
748 result.unwrap().as_str(),
749 expected_normalized,
750 "Path '{input}' should normalize to '{expected_normalized}'"
751 );
752 }
753 }
754
755 #[test]
756 fn test_path_deref() {
757 let pathbuf = PathBuf::from_str("user.profile.name").unwrap();
758 let path: &Path = &pathbuf;
759
760 assert_eq!(path.as_str(), "user.profile.name");
761 let components: Vec<&str> = path.components().collect();
762 assert_eq!(components, vec!["user", "profile", "name"]);
763 }
764
765 #[test]
766 fn test_path_builder() {
767 let path = PathBuilder::new()
768 .component("user")
769 .unwrap()
770 .component("profile")
771 .unwrap()
772 .component("name")
773 .unwrap()
774 .build();
775
776 let components: Vec<&str> = path.components().collect();
777 assert_eq!(components, vec!["user", "profile", "name"]);
778 }
779
780 #[test]
781 fn test_display() {
782 let path = PathBuf::from_str("user.profile.name").unwrap();
783 assert_eq!(format!("{path}"), "user.profile.name");
784
785 let empty = PathBuf::new();
786 assert_eq!(format!("{empty}"), "(empty path)");
787 }
788
789 #[test]
790 fn test_from_str() {
791 let from_str = PathBuf::from_str("user.profile.name").unwrap();
792
793 let components: Vec<&str> = from_str.components().collect();
794 assert_eq!(components, vec!["user", "profile", "name"]);
795 }
796
797 #[test]
798 fn test_from_str_normalization() {
799 let result = PathBuf::from_str("user..invalid");
800 assert!(result.is_ok()); assert_eq!(result.unwrap().as_str(), "user.invalid");
802 }
803
804 #[test]
805 fn test_path_join() {
806 let base = PathBuf::from_str("user").unwrap();
807 let suffix = PathBuf::from_str("profile.name").unwrap();
808
809 let joined = base.join(&suffix);
810 let components: Vec<&str> = joined.components().collect();
811 assert_eq!(components, vec!["user", "profile", "name"]);
812 }
813
814 #[test]
815 fn test_path_macro_from_string() {
816 let path = path!("user.profile.name");
817 let components: Vec<&str> = path.components().collect();
818 assert_eq!(components, vec!["user", "profile", "name"]);
819 }
820
821 #[test]
822 fn test_path_macro_from_components() {
823 let path = path!("user", "profile", "name");
824 let components: Vec<&str> = path.components().collect();
825 assert_eq!(components, vec!["user", "profile", "name"]);
826
827 let path = path!("user", "profile", "name",);
829 let components: Vec<&str> = path.components().collect();
830 assert_eq!(components, vec!["user", "profile", "name"]);
831 }
832
833 #[test]
834 fn test_path_macro_mixed() {
835 let base = "user";
836 let path = path!(base, "profile", "name");
837 let components: Vec<&str> = path.components().collect();
838 assert_eq!(components, vec!["user", "profile", "name"]);
839 }
840
841 #[test]
842 fn test_path_macro_empty_and_edge_cases() {
843 let empty = path!();
845 assert!(empty.is_empty());
846 assert_eq!(empty.len(), 0);
847
848 let empty_str = path!("");
850 assert!(empty_str.is_empty());
851 assert_eq!(empty_str.len(), 0);
852 }
853
854 #[test]
855 fn test_path_normalization() {
856 assert_eq!(normalize_path(""), "");
858 assert_eq!(normalize_path("user"), "user");
859 assert_eq!(normalize_path(".user"), "user");
860 assert_eq!(normalize_path("user."), "user");
861 assert_eq!(normalize_path("user..profile"), "user.profile");
862 assert_eq!(normalize_path("...user...profile..."), "user.profile");
863 assert_eq!(normalize_path("..."), "");
864 assert_eq!(normalize_path("user.profile.name"), "user.profile.name");
865 }
866
867 #[test]
868 fn test_pathbuf_normalization() {
869 let cases = vec![
871 ("", ""),
872 (".user", "user"),
873 ("user.", "user"),
874 ("user..profile", "user.profile"),
875 ("...user...profile...", "user.profile"),
876 ("...", ""),
877 ("user.profile.name", "user.profile.name"),
878 ];
879
880 for (input, expected) in cases {
881 let path = PathBuf::from_str(input).unwrap();
882 assert_eq!(
883 path.as_str(),
884 expected,
885 "Input '{input}' should normalize to '{expected}'"
886 );
887 }
888 }
889
890 #[test]
891 fn test_unified_macro_behavior() {
892 let literal = path!("user.profile.name");
894 let components = path!("user", "profile", "name");
895 let base = "user";
896 let mixed = path!(base, "profile", "name");
897
898 let literal_vec: Vec<&str> = literal.components().collect();
900 let components_vec: Vec<&str> = components.components().collect();
901 let mixed_vec: Vec<&str> = mixed.components().collect();
902
903 assert_eq!(literal_vec, vec!["user", "profile", "name"]);
904 assert_eq!(components_vec, vec!["user", "profile", "name"]);
905 assert_eq!(mixed_vec, vec!["user", "profile", "name"]);
906
907 fn accepts_path_ref(p: impl AsRef<Path>) -> String {
909 p.as_ref().as_str().to_string()
910 }
911
912 assert_eq!(accepts_path_ref(literal), "user.profile.name");
913 assert_eq!(accepts_path_ref(&components), "user.profile.name");
914 assert_eq!(accepts_path_ref(&mixed), "user.profile.name");
915 }
916
917 #[test]
918 fn test_component_validation() {
919 assert!(Component::new("user").is_ok());
921 assert!(Component::new("profile123").is_ok());
922 assert!(Component::new("_internal").is_ok());
923 assert!(Component::new("").is_ok()); assert!(Component::new("user.name").is_err()); }
928}