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