1use zx_status::Status;
11
12#[derive(Clone, Debug)]
13pub struct Path {
14 is_dir: bool,
15 inner: String,
16 next: usize,
17}
18
19impl Path {
20 pub fn dot() -> Path {
23 Path { is_dir: false, inner: String::new(), next: 0 }
26 }
27
28 pub fn is_dot(&self) -> bool {
29 self.inner.is_empty()
30 }
31
32 pub fn validate_and_split<Source>(path: Source) -> Result<Path, Status>
36 where
37 Source: Into<String>,
38 {
39 let path = path.into();
40
41 if (path.len() as u64) > libc::PATH_MAX as u64 - 1 {
44 return Err(Status::BAD_PATH);
45 }
46
47 match path.as_str() {
48 "." | "/" => Ok(Self::dot()),
49 _ => {
50 let is_dir = path.ends_with('/');
51
52 let next = if path.len() > 1 && path.starts_with('/') { 1 } else { 0 };
54
55 let mut check = path[next..].split('/');
56
57 if is_dir {
59 let _ = check.next_back();
60 }
61
62 for c in check {
65 crate::name::validate_name(c)?;
66 }
67
68 Ok(Path { is_dir, inner: path, next })
69 }
70 }
71 }
72
73 pub fn is_empty(&self) -> bool {
75 self.next >= self.inner.len()
76 }
77
78 pub fn is_dir(&self) -> bool {
81 self.is_dir
82 }
83
84 pub fn is_single_component(&self) -> bool {
87 let end = if self.is_dir { self.inner.len() - 1 } else { self.inner.len() };
88 self.next < self.inner.len() && self.inner[self.next..end].find('/').is_none()
89 }
90
91 pub fn next(&mut self) -> Option<&str> {
97 self.next_with_ref().1
98 }
99
100 pub fn next_with_ref(&mut self) -> (&Self, Option<&str>) {
110 match self.inner[self.next..].find('/') {
111 Some(i) => {
112 let from = self.next;
113 self.next = self.next + i + 1;
114 (self, Some(&self.inner[from..from + i]))
115 }
116 None => {
117 if self.next >= self.inner.len() {
118 (self, None)
119 } else {
120 let from = self.next;
121 self.next = self.inner.len();
122 (self, Some(&self.inner[from..]))
123 }
124 }
125 }
126 }
127
128 pub fn peek(&self) -> Option<&str> {
132 match self.inner[self.next..].find('/') {
133 Some(i) => Some(&self.inner[self.next..self.next + i]),
134 None => {
135 if self.next >= self.inner.len() {
136 None
137 } else {
138 Some(&self.inner[self.next..])
139 }
140 }
141 }
142 }
143
144 pub fn into_string(mut self) -> String {
148 self.inner.drain(0..self.next);
149 self.inner
150 }
151
152 pub fn as_str(&self) -> &str {
154 self.inner.as_str()
155 }
156
157 pub fn with_prefix(&self, prefix: &Self) -> Self {
159 if prefix.is_empty() {
160 Self { is_dir: self.is_dir, inner: self.inner[self.next..].to_string(), next: 0 }
161 } else {
162 let end = if prefix.is_dir { prefix.inner.len() - 1 } else { prefix.inner.len() };
163 if self.is_empty() && !self.is_dir {
164 Self { is_dir: false, inner: prefix.inner[prefix.next..end].to_string(), next: 0 }
165 } else {
166 Self {
167 is_dir: self.is_dir,
168 inner: format!(
169 "{}/{}",
170 &prefix.inner[prefix.next..end],
171 &self.inner[self.next..]
172 ),
173 next: 0,
174 }
175 }
176 }
177 }
178
179 fn remainder(&self) -> &str {
182 if self.is_empty() {
183 "."
184 } else {
185 &self.inner[self.next..]
186 }
187 }
188}
189
190impl PartialEq for Path {
191 fn eq(&self, other: &Self) -> bool {
192 self.remainder() == other.remainder()
193 }
194}
195impl Eq for Path {}
196
197impl AsRef<str> for Path {
198 fn as_ref(&self) -> &str {
201 self.remainder()
202 }
203}
204
205impl TryInto<Path> for String {
206 type Error = Status;
207
208 fn try_into(self) -> Result<Path, Self::Error> {
209 Path::validate_and_split(self)
210 }
211}
212
213impl TryInto<Path> for &str {
214 type Error = Status;
215
216 fn try_into(self) -> Result<Path, Self::Error> {
217 Path::validate_and_split(self)
218 }
219}
220
221#[cfg(test)]
222mod tests {
223 use super::*;
224 use fidl_fuchsia_io as fio;
225
226 macro_rules! simple_construction_test {
227 (path: $str:expr, $path:ident => $body:block) => {
228 match Path::validate_and_split($str) {
229 Ok($path) => $body,
230 Err(status) => panic!("'{}' construction failed: {}", stringify!(path), status),
231 }
232 };
233 (path: $str:expr, mut $path:ident => $body:block) => {
234 match Path::validate_and_split($str) {
235 Ok(mut $path) => $body,
236 Err(status) => panic!("'{}' construction failed: {}", stringify!(path), status),
237 }
238 };
239 }
240
241 macro_rules! negative_construction_test {
242 (path: $path:expr, $details:expr, $status:expr) => {
243 match Path::validate_and_split($path) {
244 Ok(path) => {
245 panic!("Constructed '{}' with {}: {:?}", stringify!($path), $details, path)
246 }
247 Err(status) => assert_eq!(status, $status),
248 }
249 };
250 }
251
252 fn path(s: &str) -> Path {
253 match Path::validate_and_split(s) {
254 Ok(path) => path,
255 Err(e) => panic!("'{}' construction failed: {}", s, e),
256 }
257 }
258
259 #[test]
260 fn empty() {
261 negative_construction_test! {
262 path: "",
263 "empty path",
264 Status::INVALID_ARGS
265 };
266 }
267
268 #[test]
269 fn forward_slash_only() {
270 simple_construction_test! {
271 path: "/",
272 mut path => {
273 assert!(path.is_empty());
274 assert!(!path.is_dir()); assert!(!path.is_single_component());
276 assert_eq!(path.as_ref(), ".");
277 assert_eq!(path.peek(), None);
278 assert_eq!(path.next(), None);
279 assert_eq!(path.as_ref(), ".");
280 assert_eq!(path.into_string(), String::new());
281 }
282 };
283 }
284
285 #[test]
286 fn one_component_short() {
287 simple_construction_test! {
288 path: "a",
289 mut path => {
290 assert!(!path.is_empty());
291 assert!(!path.is_dir());
292 assert!(path.is_single_component());
293 assert_eq!(path.peek(), Some("a"));
294 assert_eq!(path.peek(), Some("a"));
295 assert_eq!(path.next(), Some("a"));
296 assert_eq!(path.peek(), None);
297 assert_eq!(path.next(), None);
298 assert_eq!(path.as_ref(), ".");
299 assert_eq!(path.into_string(), String::new());
300 }
301 };
302 }
303
304 #[test]
305 fn one_component() {
306 simple_construction_test! {
307 path: "some",
308 mut path => {
309 assert!(!path.is_empty());
310 assert!(!path.is_dir());
311 assert!(path.is_single_component());
312 assert_eq!(path.peek(), Some("some"));
313 assert_eq!(path.peek(), Some("some"));
314 assert_eq!(path.next(), Some("some"));
315 assert_eq!(path.peek(), None);
316 assert_eq!(path.next(), None);
317 assert_eq!(path.as_ref(), ".");
318 assert_eq!(path.into_string(), String::new());
319 }
320 };
321 }
322
323 #[test]
324 fn one_component_dir() {
325 simple_construction_test! {
326 path: "some/",
327 mut path => {
328 assert!(!path.is_empty());
329 assert!(path.is_dir());
330 assert!(path.is_single_component());
331 assert_eq!(path.peek(), Some("some"));
332 assert_eq!(path.peek(), Some("some"));
333 assert_eq!(path.next(), Some("some"));
334 assert_eq!(path.peek(), None);
335 assert_eq!(path.next(), None);
336 assert_eq!(path.as_ref(), ".");
337 assert_eq!(path.into_string(), String::new());
338 }
339 };
340 }
341
342 #[test]
343 fn two_component_short() {
344 simple_construction_test! {
345 path: "a/b",
346 mut path => {
347 assert!(!path.is_empty());
348 assert!(!path.is_dir());
349 assert!(!path.is_single_component());
350 assert_eq!(path.peek(), Some("a"));
351 assert_eq!(path.peek(), Some("a"));
352 assert_eq!(path.next(), Some("a"));
353 assert_eq!(path.peek(), Some("b"));
354 assert_eq!(path.peek(), Some("b"));
355 assert_eq!(path.next(), Some("b"));
356 assert_eq!(path.peek(), None);
357 assert_eq!(path.next(), None);
358 assert_eq!(path.as_ref(), ".");
359 assert_eq!(path.into_string(), String::new());
360 }
361 };
362 }
363
364 #[test]
365 fn two_component() {
366 simple_construction_test! {
367 path: "some/path",
368 mut path => {
369 assert!(!path.is_empty());
370 assert!(!path.is_dir());
371 assert!(!path.is_single_component());
372 assert_eq!(path.peek(), Some("some"));
373 assert_eq!(path.peek(), Some("some"));
374 assert_eq!(path.next(), Some("some"));
375 assert_eq!(path.peek(), Some("path"));
376 assert_eq!(path.peek(), Some("path"));
377 assert_eq!(path.next(), Some("path"));
378 assert_eq!(path.peek(), None);
379 assert_eq!(path.next(), None);
380 assert_eq!(path.as_ref(), ".");
381 assert_eq!(path.into_string(), String::new());
382 }
383 };
384 }
385
386 #[test]
387 fn two_component_dir() {
388 simple_construction_test! {
389 path: "some/path/",
390 mut path => {
391 assert!(!path.is_empty());
392 assert!(path.is_dir());
393 assert!(!path.is_single_component());
394 assert_eq!(path.peek(), Some("some"));
395 assert_eq!(path.peek(), Some("some"));
396 assert_eq!(path.next(), Some("some"));
397 assert_eq!(path.peek(), Some("path"));
398 assert_eq!(path.peek(), Some("path"));
399 assert_eq!(path.next(), Some("path"));
400 assert_eq!(path.peek(), None);
401 assert_eq!(path.next(), None);
402 assert_eq!(path.as_ref(), ".");
403 assert_eq!(path.into_string(), String::new());
404 }
405 };
406 }
407
408 #[test]
409 fn into_string_half_way() {
410 simple_construction_test! {
411 path: "into/string/half/way",
412 mut path => {
413 assert!(!path.is_empty());
414 assert!(!path.is_dir());
415 assert!(!path.is_single_component());
416 assert_eq!(path.peek(), Some("into"));
417 assert_eq!(path.peek(), Some("into"));
418 assert_eq!(path.next(), Some("into"));
419 assert_eq!(path.peek(), Some("string"));
420 assert_eq!(path.peek(), Some("string"));
421 assert_eq!(path.next(), Some("string"));
422 assert_eq!(path.peek(), Some("half"));
423 assert_eq!(path.peek(), Some("half"));
424 assert_eq!(path.as_ref(), "half/way");
425 assert_eq!(path.into_string(), "half/way".to_string());
426 }
427 };
428 }
429
430 #[test]
431 fn into_string_half_way_dir() {
432 simple_construction_test! {
433 path: "into/string/half/way/",
434 mut path => {
435 assert!(!path.is_empty());
436 assert!(path.is_dir());
437 assert!(!path.is_single_component());
438 assert_eq!(path.peek(), Some("into"));
439 assert_eq!(path.peek(), Some("into"));
440 assert_eq!(path.next(), Some("into"));
441 assert_eq!(path.peek(), Some("string"));
442 assert_eq!(path.peek(), Some("string"));
443 assert_eq!(path.next(), Some("string"));
444 assert_eq!(path.peek(), Some("half"));
445 assert_eq!(path.peek(), Some("half"));
446 assert_eq!(path.as_ref(), "half/way/");
447 assert_eq!(path.into_string(), "half/way/".to_string());
448 }
449 };
450 }
451
452 #[test]
453 fn into_string_dir_last_component() {
454 simple_construction_test! {
455 path: "into/string/",
456 mut path => {
457 assert!(!path.is_empty());
458 assert!(path.is_dir());
459 assert!(!path.is_single_component());
460 assert_eq!(path.peek(), Some("into"));
461 assert_eq!(path.peek(), Some("into"));
462 assert_eq!(path.next(), Some("into"));
463 assert_eq!(path.peek(), Some("string"));
464 assert_eq!(path.peek(), Some("string"));
465 assert_eq!(path.next(), Some("string"));
466 assert_eq!(path.peek(), None);
467 assert_eq!(path.as_ref(), ".");
468 assert_eq!(path.into_string(), "".to_string());
469 }
470 };
471 }
472
473 #[test]
474 fn no_empty_components() {
475 negative_construction_test! {
476 path: "//",
477 "empty components",
478 Status::INVALID_ARGS
479 };
480 }
481
482 #[test]
483 fn absolute_paths() {
484 simple_construction_test! {
485 path: "/a/b/c",
486 mut path => {
487 assert!(!path.is_empty());
488 assert!(!path.is_dir());
489 assert!(!path.is_single_component());
490 assert_eq!(path.as_ref(), "a/b/c");
491 assert_eq!(path.clone().into_string(), "a/b/c");
492 assert_eq!(path.peek(), Some("a"));
493 assert_eq!(path.peek(), Some("a"));
494 assert_eq!(path.next(), Some("a"));
495 assert_eq!(path.next(), Some("b"));
496 assert_eq!(path.next(), Some("c"));
497 assert_eq!(path.peek(), None);
498 assert_eq!(path.next(), None);
499 assert_eq!(path.as_ref(), ".");
500 assert_eq!(path.into_string(), "".to_string());
501 }
502 }
503 }
504
505 #[test]
506 fn as_str() {
507 simple_construction_test! {
508 path: "a/b/c",
509 mut path => {
510 assert!(!path.is_empty());
511 assert!(!path.is_dir());
512 assert!(!path.is_single_component());
513 assert_eq!(path.as_ref(), "a/b/c");
514 assert_eq!(path.as_str(), "a/b/c");
515 assert_eq!(path.next(), Some("a"));
516 assert_eq!(path.as_str(), "a/b/c");
517 assert_eq!(path.next(), Some("b"));
518 assert_eq!(path.as_str(), "a/b/c");
519 assert_eq!(path.next(), Some("c"));
520 assert_eq!(path.as_str(), "a/b/c");
521 assert_eq!(path.next(), None);
522 assert_eq!(path.as_str(), "a/b/c");
523 }
524 };
525 }
526
527 #[test]
528 fn dot_components() {
529 negative_construction_test! {
530 path: "a/./b",
531 "'.' components",
532 Status::INVALID_ARGS
533 };
534 }
535
536 #[test]
537 fn dot_dot_components() {
538 negative_construction_test! {
539 path: "a/../b",
540 "'..' components",
541 Status::INVALID_ARGS
542 };
543 }
544
545 #[test]
546 fn too_long_filename() {
547 let string = "a".repeat(fio::MAX_NAME_LENGTH as usize + 1);
548 negative_construction_test! {
549 path: &string,
550 "filename too long",
551 Status::BAD_PATH
552 };
553 }
554
555 #[test]
556 fn too_long_path() {
557 let filename = "a".repeat(fio::MAX_NAME_LENGTH as usize);
558 const OVER_LIMIT_LENGTH: usize = fio::MAX_PATH_LENGTH as usize + 1;
559 let mut path = String::new();
560 while path.len() < OVER_LIMIT_LENGTH as usize {
561 path.push('/');
562 path.push_str(&filename);
563 }
564 assert_eq!(path.len(), OVER_LIMIT_LENGTH as usize);
565 negative_construction_test! {
566 path: &path,
567 "path too long",
568 Status::BAD_PATH
569 };
570 }
571
572 #[test]
573 fn long_path() {
574 #[cfg(not(target_os = "macos"))]
575 let max_path_len = fio::MAX_PATH_LENGTH as usize;
576 #[cfg(target_os = "macos")]
577 let max_path_len = libc::PATH_MAX as usize - 1;
578
579 let mut path = "a/".repeat(max_path_len / 2);
580 if path.len() < max_path_len {
581 path.push('a');
582 }
583 assert_eq!(path.len(), max_path_len);
584 simple_construction_test! {
585 path: &path,
586 mut path => {
587 assert!(!path.is_empty());
588 assert_eq!(path.next(), Some("a"));
589 }
590 };
591 }
592
593 #[test]
594 fn long_filename() {
595 let string = "a".repeat(fio::MAX_NAME_LENGTH as usize);
596 simple_construction_test! {
597 path: &string,
598 mut path => {
599 assert!(!path.is_empty());
600 assert!(path.is_single_component());
601 assert_eq!(path.next(), Some(string.as_str()));
602 }
603 };
604 }
605
606 #[test]
607 fn dot() {
608 for mut path in
609 [Path::dot(), Path::validate_and_split(".").expect("validate_and_split failed")]
610 {
611 assert!(path.is_dot());
612 assert!(path.is_empty());
613 assert!(!path.is_dir());
614 assert!(!path.is_single_component());
615 assert_eq!(path.next(), None);
616 assert_eq!(path.peek(), None);
617 assert_eq!(path.as_ref(), ".");
618 assert_eq!(path.as_ref(), ".");
619 assert_eq!(path.into_string(), "");
620 }
621 }
622
623 #[test]
624 fn eq_compares_remainder() {
625 let mut pos = path("a/b/c");
626
627 assert_eq!(pos, path("a/b/c"));
628 assert_ne!(pos, path("b/c"));
629 assert_ne!(pos, path("c"));
630 assert_ne!(pos, path("."));
631
632 assert_eq!(pos.next(), Some("a"));
633
634 assert_ne!(pos, path("a/b/c"));
635 assert_eq!(pos, path("b/c"));
636 assert_ne!(pos, path("c"));
637 assert_ne!(pos, path("."));
638
639 assert_eq!(pos.next(), Some("b"));
640
641 assert_ne!(pos, path("a/b/c"));
642 assert_ne!(pos, path("b/c"));
643 assert_eq!(pos, path("c"));
644 assert_ne!(pos, path("."));
645
646 assert_eq!(pos.next(), Some("c"));
647
648 assert_ne!(pos, path("a/b/c"));
649 assert_ne!(pos, path("b/c"));
650 assert_ne!(pos, path("c"));
651 assert_eq!(pos, path("."));
652 }
653
654 #[test]
655 fn eq_considers_is_dir() {
656 let mut pos_not = path("a/b");
657 let mut pos_dir = path("a/b/");
658
659 assert_ne!(pos_not, pos_dir);
660 assert_eq!(pos_not, path("a/b"));
661 assert_eq!(pos_dir, path("a/b/"));
662
663 pos_not.next();
664 pos_dir.next();
665
666 assert_ne!(pos_not, pos_dir);
667 assert_eq!(pos_not, path("b"));
668 assert_eq!(pos_dir, path("b/"));
669
670 pos_not.next();
671 pos_dir.next();
672
673 assert_eq!(pos_not, pos_dir);
675 assert_eq!(pos_not, path("."));
676 assert_eq!(pos_dir, path("."));
677 }
678
679 #[test]
680 fn eq_does_not_consider_absolute_different_from_relative() {
681 let abs = path("/a/b");
682 let rel = path("a/b");
683
684 assert_eq!(abs, rel);
685 assert_ne!(abs, path("different/path"));
686 assert_ne!(rel, path("different/path"));
687 }
688
689 #[test]
690 fn as_ref_is_remainder() {
691 let mut path = Path::validate_and_split(".").unwrap();
692 assert_eq!(path.as_ref(), ".");
693 path.next();
694 assert_eq!(path.as_ref(), ".");
695
696 let mut path = Path::validate_and_split("a/b/c").unwrap();
697 assert_eq!(path.as_ref(), "a/b/c");
698 path.next();
699 assert_eq!(path.as_ref(), "b/c");
700 path.next();
701 assert_eq!(path.as_ref(), "c");
702 path.next();
703 assert_eq!(path.as_ref(), ".");
704
705 let mut path = Path::validate_and_split("/a/b/c").unwrap();
706 assert_eq!(path.as_ref(), "a/b/c");
707 path.next();
708 assert_eq!(path.as_ref(), "b/c");
709 path.next();
710 assert_eq!(path.as_ref(), "c");
711 path.next();
712 assert_eq!(path.as_ref(), ".");
713
714 let mut path = Path::validate_and_split("/a/b/c/").unwrap();
715 assert_eq!(path.as_ref(), "a/b/c/");
716 path.next();
717 assert_eq!(path.as_ref(), "b/c/");
718 path.next();
719 assert_eq!(path.as_ref(), "c/");
720 path.next();
721 assert_eq!(path.as_ref(), ".");
722 }
723
724 #[test]
725 fn str_try_into() {
726 let valid = "abc";
727 let path: Path = valid.try_into().unwrap();
728 assert_eq!(path.as_str(), "abc");
729
730 let invalid = "..";
731 let path: Result<Path, _> = invalid.try_into();
732 assert!(path.is_err());
733 }
734
735 #[test]
736 fn string_try_into() {
737 let valid = "abc".to_string();
738 let path: Path = valid.try_into().unwrap();
739 assert_eq!(path.as_str(), "abc");
740
741 let invalid = "..".to_string();
742 let path: Result<Path, _> = invalid.try_into();
743 assert!(path.is_err());
744 }
745
746 #[test]
747 fn with_prefix() {
748 let mut foo: Path = "apple/ball".try_into().unwrap();
749 let mut bar: Path = "cat/dog/".try_into().unwrap();
750
751 let combined = bar.with_prefix(&foo);
752 assert_eq!(combined.as_str(), "apple/ball/cat/dog/");
753 assert!(combined.is_dir());
754
755 let combined = foo.with_prefix(&bar);
756 assert_eq!(combined.as_str(), "cat/dog/apple/ball");
757 assert!(!combined.is_dir());
758
759 let combined = bar.with_prefix(&bar);
760 assert_eq!(combined.as_str(), "cat/dog/cat/dog/");
761 assert!(combined.is_dir());
762
763 bar.next();
764 let combined = foo.with_prefix(&bar);
765 assert_eq!(combined.as_str(), "dog/apple/ball");
766 assert!(!combined.is_dir());
767
768 foo.next();
769 foo.next();
770 let combined = foo.with_prefix(&bar);
771 assert_eq!(combined.as_str(), "dog");
772 assert!(!combined.is_dir());
773
774 let combined = Path::dot().with_prefix(&Path::dot());
775 assert_eq!(combined.as_str(), "");
776 assert!(!combined.is_dir());
777
778 let combined = bar.with_prefix(&Path::dot());
779 assert_eq!(combined.as_str(), "dog/");
780 assert!(combined.is_dir());
781 }
782}