1use crate::zfs::ValidationResult;
2use std::path::Path;
3
4pub trait PathExt {
5 fn get_pool(&self) -> Option<String>;
6 fn get_snapshot(&self) -> Option<String>;
7 fn get_bookmark(&self) -> Option<String>;
8
9 fn is_snapshot(&self) -> bool {
10 self.get_snapshot().is_some()
11 }
12 fn is_bookmark(&self) -> bool {
13 self.get_bookmark().is_some()
14 }
15 fn is_volume_or_dataset(&self) -> bool {
16 !self.is_bookmark() && !self.is_snapshot()
17 }
18
19 fn is_valid(&self) -> bool {
20 if let Ok(()) = self.validate() {
21 true
22 } else {
23 false
24 }
25 }
26
27 fn validate(&self) -> ValidationResult;
28}
29
30impl PathExt for Path {
31 fn get_pool(&self) -> Option<String> {
32 if self.has_root() || self.components().count() < 2 {
33 return None;
34 }
35 if let Some(root) = self.iter().next() {
36 Some(root.to_string_lossy().to_string())
37 } else {
38 None
39 }
40 }
41
42 fn get_snapshot(&self) -> Option<String> {
43 if let Some(last) = self.file_name() {
44 let as_str = last.to_string_lossy();
45 if as_str.contains('@') {
46 return as_str.rsplit('@').next().map(String::from);
47 }
48 }
49 None
50 }
51
52 fn get_bookmark(&self) -> Option<String> {
53 if let Some(last) = self.file_name() {
54 let as_str = last.to_string_lossy();
55 if as_str.contains('#') {
56 return as_str.rsplit('#').next().map(String::from);
57 }
58 }
59 None
60 }
61
62 fn validate(&self) -> ValidationResult {
63 crate::zfs::validators::validate_name(self)
64 }
65}
66
67impl<P: AsRef<Path>> PathExt for P {
68 fn get_pool(&self) -> Option<String> {
69 self.as_ref().get_pool()
70 }
71
72 fn get_snapshot(&self) -> Option<String> {
73 self.as_ref().get_snapshot()
74 }
75
76 fn get_bookmark(&self) -> Option<String> {
77 self.as_ref().get_bookmark()
78 }
79
80 fn validate(&self) -> ValidationResult {
81 self.as_ref().validate()
82 }
83}
84
85#[cfg(test)]
86mod test {
87 use super::PathExt;
88 use std::path::PathBuf;
89
90 #[test]
91 fn valid_dataset_no_bookmarks_or_snapshots() {
92 let path = PathBuf::from("tank/usr/home");
93
94 assert_eq!(Some(String::from("tank")), path.get_pool());
95 assert!(!path.is_snapshot());
96 assert!(!path.is_bookmark());
97 assert_eq!(None, path.get_snapshot());
98 assert_eq!(None, path.get_bookmark());
99 assert!(path.is_volume_or_dataset());
100 assert!(path.is_valid());
101 }
102
103 #[test]
104 fn not_valid_just_dataset() {
105 let path = PathBuf::from("/usr/home");
106 assert_eq!(None, path.get_pool());
107 assert!(!path.is_snapshot());
108 assert!(!path.is_bookmark());
109 assert_eq!(None, path.get_snapshot());
110 assert_eq!(None, path.get_bookmark());
111 assert!(path.is_volume_or_dataset());
112 assert!(!path.is_valid());
113 }
114
115 #[test]
116 fn valid_snapshot() {
117 let path = PathBuf::from("tank/usr/home@snap");
118
119 assert_eq!(Some(String::from("tank")), path.get_pool());
120 assert!(path.is_snapshot());
121 assert!(!path.is_bookmark());
122 assert_eq!(Some(String::from("snap")), path.get_snapshot());
123 assert_eq!(None, path.get_bookmark());
124 assert!(!path.is_volume_or_dataset());
125 assert!(path.is_valid());
126 }
127 #[test]
128 fn valid_bookmark() {
129 let path = PathBuf::from("tank/usr/home#bookmark");
130
131 assert_eq!(Some(String::from("tank")), path.get_pool());
132 assert!(!path.is_snapshot());
133 assert!(path.is_bookmark());
134 assert_eq!(None, path.get_snapshot());
135 assert_eq!(Some(String::from("bookmark")), path.get_bookmark());
136 assert!(!path.is_volume_or_dataset());
137 assert!(path.is_valid());
138 }
139
140 #[test]
141 fn at_in_wrong_place() {
142 let path = PathBuf::from("tank/usr@wat/home");
143 assert!(!path.is_snapshot());
144 }
145
146 #[test]
147 fn pound_in_wrong_place() {
148 let path = PathBuf::from("tank/usr#wat/home");
149 assert!(!path.is_bookmark());
150 }
151}