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_NAME_LENGTH as usize + 1])
150 .unwrap()
151 .to_string()
152 ),
153 Err(ParseNameError::TooLong(_))
154 );
155 assert_matches!(
156 parse_name(
157 std::str::from_utf8(&vec![65; fio::MAX_NAME_LENGTH as usize]).unwrap().to_string()
158 ),
159 Ok(_)
160 );
161 assert_matches!(parse_name("".to_string()), Err(ParseNameError::Empty));
162 assert_matches!(parse_name(".".to_string()), Err(ParseNameError::Dot));
163 assert_matches!(parse_name("..".to_string()), Err(ParseNameError::DotDot));
164 assert_matches!(parse_name(".a".to_string()), Ok(Name(name)) if &name == ".a");
165 assert_matches!(parse_name("..a".to_string()), Ok(Name(name)) if &name == "..a");
166 assert_matches!(parse_name("a/b".to_string()), Err(ParseNameError::Slash));
167 assert_matches!(parse_name("a\0b".to_string()), Err(ParseNameError::EmbeddedNul));
168 assert_matches!(parse_name("abc".to_string()), Ok(Name(name)) if &name == "abc");
169 }
170
171 #[test]
172 fn test_validate_name() {
173 assert_matches!(validate_name(&"a".repeat(1000)), Err(ParseNameError::TooLong(_)));
174 assert_matches!(
175 validate_name(
176 std::str::from_utf8(&vec![65; fio::MAX_NAME_LENGTH as usize + 1]).unwrap()
177 ),
178 Err(ParseNameError::TooLong(_))
179 );
180 assert_matches!(
181 validate_name(std::str::from_utf8(&vec![65; fio::MAX_NAME_LENGTH as usize]).unwrap()),
182 Ok(())
183 );
184 assert_matches!(validate_name(""), Err(ParseNameError::Empty));
185 assert_matches!(validate_name("."), Err(ParseNameError::Dot));
186 assert_matches!(validate_name(".."), Err(ParseNameError::DotDot));
187 assert_matches!(validate_name(".a"), Ok(()));
188 assert_matches!(validate_name("..a"), Ok(()));
189 assert_matches!(validate_name("a/b"), Err(ParseNameError::Slash));
190 assert_matches!(validate_name("a\0b"), Err(ParseNameError::EmbeddedNul));
191 assert_matches!(validate_name("abc"), Ok(()));
192 }
193
194 #[test]
195 fn test_try_from() {
196 assert_matches!(Name::try_from("a".repeat(1000)), Err(ParseNameError::TooLong(_)));
197 assert_matches!(Name::try_from("abc".to_string()), Ok(Name(name)) if &name == "abc");
198 }
199
200 #[test]
201 fn test_into() {
202 let name = Name::try_from("a".to_string()).unwrap();
203 let name: String = name.into();
204 assert_eq!(name, "a".to_string());
205 }
206
207 #[test]
208 fn test_deref() {
209 let name = Name::try_from("a".to_string()).unwrap();
210 let name: &str = &name;
211 assert_eq!(name, "a");
212 }
213}