1use fidl_fuchsia_io as fio;
13use static_assertions::const_assert_eq;
14use std::borrow::Borrow;
15use std::fmt::Display;
16use std::ops::Deref;
17use thiserror::Error;
18use zx_status::Status;
19
20#[derive(Debug, Clone, Hash, PartialEq, Eq, PartialOrd, Ord)]
33pub struct Name(String);
34
35pub const MAX_NAME_LENGTH: usize = fio::MAX_NAME_LENGTH as usize;
37const_assert_eq!(MAX_NAME_LENGTH as u64, fio::MAX_NAME_LENGTH);
38
39#[derive(Error, Debug, Clone)]
40pub enum ParseNameError {
41 #[error("name `{0}` is too long")]
42 TooLong(String),
43
44 #[error("name cannot be empty")]
45 Empty,
46
47 #[error("name cannot be `.`")]
48 Dot,
49
50 #[error("name cannot be `..`")]
51 DotDot,
52
53 #[error("name cannot contain `/`")]
54 Slash,
55
56 #[error("name cannot contain embedded NUL")]
57 EmbeddedNul,
58}
59
60impl From<ParseNameError> for Status {
61 fn from(value: ParseNameError) -> Self {
62 match value {
63 ParseNameError::TooLong(_) => Status::BAD_PATH,
64 _ => Status::INVALID_ARGS,
65 }
66 }
67}
68
69impl Name {
70 pub fn from<S: Into<String>>(s: S) -> Result<Name, ParseNameError> {
71 parse_name(s.into())
72 }
73}
74
75impl Display for Name {
76 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
77 let value = &self.0;
78 write!(f, "{value}")
79 }
80}
81
82pub fn parse_name(name: String) -> Result<Name, ParseNameError> {
84 validate_name(&name)?;
85 Ok(Name(name))
86}
87
88pub fn validate_name(name: &str) -> Result<(), ParseNameError> {
90 if name.len() > MAX_NAME_LENGTH {
91 return Err(ParseNameError::TooLong(name.to_string()));
92 }
93 if name.len() == 0 {
94 return Err(ParseNameError::Empty);
95 }
96 if name == "." {
97 return Err(ParseNameError::Dot);
98 }
99 if name == ".." {
100 return Err(ParseNameError::DotDot);
101 }
102 if name.chars().any(|c: char| c == '/') {
103 return Err(ParseNameError::Slash);
104 }
105 if name.chars().any(|c: char| c == '\0') {
106 return Err(ParseNameError::EmbeddedNul);
107 }
108 Ok(())
109}
110
111impl From<Name> for String {
112 fn from(value: Name) -> Self {
113 value.0
114 }
115}
116
117impl TryFrom<String> for Name {
118 type Error = ParseNameError;
119
120 fn try_from(value: String) -> Result<Name, ParseNameError> {
121 parse_name(value)
122 }
123}
124
125impl Deref for Name {
126 type Target = str;
127
128 fn deref(&self) -> &Self::Target {
129 &self.0
130 }
131}
132
133impl Borrow<str> for Name {
134 fn borrow(&self) -> &str {
135 &*self
136 }
137}
138
139#[cfg(test)]
140mod tests {
141 use super::*;
142 use assert_matches::assert_matches;
143
144 #[test]
145 fn test_parse_name() {
146 assert_matches!(parse_name("a".repeat(1000)), Err(ParseNameError::TooLong(_)));
147 assert_matches!(
148 parse_name(
149 std::str::from_utf8(&vec![65; fio::MAX_FILENAME as usize + 1]).unwrap().to_string()
150 ),
151 Err(ParseNameError::TooLong(_))
152 );
153 assert_matches!(
154 parse_name(
155 std::str::from_utf8(&vec![65; fio::MAX_FILENAME as usize]).unwrap().to_string()
156 ),
157 Ok(_)
158 );
159 assert_matches!(parse_name("".to_string()), Err(ParseNameError::Empty));
160 assert_matches!(parse_name(".".to_string()), Err(ParseNameError::Dot));
161 assert_matches!(parse_name("..".to_string()), Err(ParseNameError::DotDot));
162 assert_matches!(parse_name(".a".to_string()), Ok(Name(name)) if &name == ".a");
163 assert_matches!(parse_name("..a".to_string()), Ok(Name(name)) if &name == "..a");
164 assert_matches!(parse_name("a/b".to_string()), Err(ParseNameError::Slash));
165 assert_matches!(parse_name("a\0b".to_string()), Err(ParseNameError::EmbeddedNul));
166 assert_matches!(parse_name("abc".to_string()), Ok(Name(name)) if &name == "abc");
167 }
168
169 #[test]
170 fn test_validate_name() {
171 assert_matches!(validate_name(&"a".repeat(1000)), Err(ParseNameError::TooLong(_)));
172 assert_matches!(
173 validate_name(std::str::from_utf8(&vec![65; fio::MAX_FILENAME as usize + 1]).unwrap()),
174 Err(ParseNameError::TooLong(_))
175 );
176 assert_matches!(
177 validate_name(std::str::from_utf8(&vec![65; fio::MAX_FILENAME as usize]).unwrap()),
178 Ok(())
179 );
180 assert_matches!(validate_name(""), Err(ParseNameError::Empty));
181 assert_matches!(validate_name("."), Err(ParseNameError::Dot));
182 assert_matches!(validate_name(".."), Err(ParseNameError::DotDot));
183 assert_matches!(validate_name(".a"), Ok(()));
184 assert_matches!(validate_name("..a"), Ok(()));
185 assert_matches!(validate_name("a/b"), Err(ParseNameError::Slash));
186 assert_matches!(validate_name("a\0b"), Err(ParseNameError::EmbeddedNul));
187 assert_matches!(validate_name("abc"), Ok(()));
188 }
189
190 #[test]
191 fn test_try_from() {
192 assert_matches!(Name::try_from("a".repeat(1000)), Err(ParseNameError::TooLong(_)));
193 assert_matches!(Name::try_from("abc".to_string()), Ok(Name(name)) if &name == "abc");
194 }
195
196 #[test]
197 fn test_into() {
198 let name = Name::try_from("a".to_string()).unwrap();
199 let name: String = name.into();
200 assert_eq!(name, "a".to_string());
201 }
202
203 #[test]
204 fn test_deref() {
205 let name = Name::try_from("a".to_string()).unwrap();
206 let name: &str = &name;
207 assert_eq!(name, "a");
208 }
209}