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(Box<str>);
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 f.write_str(&self.0)
78 }
79}
80
81pub fn parse_name(name: String) -> Result<Name, ParseNameError> {
83 validate_name(&name)?;
84 Ok(Name(name.into_boxed_str()))
85}
86
87pub fn validate_name(name: &str) -> Result<(), ParseNameError> {
89 if name.len() > MAX_NAME_LENGTH {
90 return Err(ParseNameError::TooLong(name.to_string()));
91 }
92 if name.len() == 0 {
93 return Err(ParseNameError::Empty);
94 }
95 if name == "." {
96 return Err(ParseNameError::Dot);
97 }
98 if name == ".." {
99 return Err(ParseNameError::DotDot);
100 }
101 if name.chars().any(|c: char| c == '/') {
102 return Err(ParseNameError::Slash);
103 }
104 if name.chars().any(|c: char| c == '\0') {
105 return Err(ParseNameError::EmbeddedNul);
106 }
107 Ok(())
108}
109
110impl From<Name> for String {
111 fn from(value: Name) -> Self {
112 value.0.into_string()
113 }
114}
115
116impl TryFrom<String> for Name {
117 type Error = ParseNameError;
118
119 fn try_from(value: String) -> Result<Name, ParseNameError> {
120 parse_name(value)
121 }
122}
123
124impl Deref for Name {
125 type Target = str;
126
127 fn deref(&self) -> &Self::Target {
128 &self.0
129 }
130}
131
132impl Borrow<str> for Name {
133 fn borrow(&self) -> &str {
134 &*self
135 }
136}
137
138#[cfg(test)]
139mod tests {
140 use super::*;
141 use assert_matches::assert_matches;
142
143 #[test]
144 fn test_parse_name() {
145 assert_matches!(parse_name("a".repeat(1000)), Err(ParseNameError::TooLong(_)));
146 assert_matches!(
147 parse_name(
148 std::str::from_utf8(&vec![65; fio::MAX_NAME_LENGTH as usize + 1])
149 .unwrap()
150 .to_string()
151 ),
152 Err(ParseNameError::TooLong(_))
153 );
154 assert_matches!(
155 parse_name(
156 std::str::from_utf8(&vec![65; fio::MAX_NAME_LENGTH as usize]).unwrap().to_string()
157 ),
158 Ok(_)
159 );
160 assert_matches!(parse_name("".to_string()), Err(ParseNameError::Empty));
161 assert_matches!(parse_name(".".to_string()), Err(ParseNameError::Dot));
162 assert_matches!(parse_name("..".to_string()), Err(ParseNameError::DotDot));
163 assert_matches!(parse_name(".a".to_string()), Ok(Name(name)) if &*name == ".a");
164 assert_matches!(parse_name("..a".to_string()), Ok(Name(name)) if &*name == "..a");
165 assert_matches!(parse_name("a/b".to_string()), Err(ParseNameError::Slash));
166 assert_matches!(parse_name("a\0b".to_string()), Err(ParseNameError::EmbeddedNul));
167 assert_matches!(parse_name("abc".to_string()), Ok(Name(name)) if &*name == "abc");
168 }
169
170 #[test]
171 fn test_validate_name() {
172 assert_matches!(validate_name(&"a".repeat(1000)), Err(ParseNameError::TooLong(_)));
173 assert_matches!(
174 validate_name(
175 std::str::from_utf8(&vec![65; fio::MAX_NAME_LENGTH as usize + 1]).unwrap()
176 ),
177 Err(ParseNameError::TooLong(_))
178 );
179 assert_matches!(
180 validate_name(std::str::from_utf8(&vec![65; fio::MAX_NAME_LENGTH as usize]).unwrap()),
181 Ok(())
182 );
183 assert_matches!(validate_name(""), Err(ParseNameError::Empty));
184 assert_matches!(validate_name("."), Err(ParseNameError::Dot));
185 assert_matches!(validate_name(".."), Err(ParseNameError::DotDot));
186 assert_matches!(validate_name(".a"), Ok(()));
187 assert_matches!(validate_name("..a"), Ok(()));
188 assert_matches!(validate_name("a/b"), Err(ParseNameError::Slash));
189 assert_matches!(validate_name("a\0b"), Err(ParseNameError::EmbeddedNul));
190 assert_matches!(validate_name("abc"), Ok(()));
191 }
192
193 #[test]
194 fn test_try_from() {
195 assert_matches!(Name::try_from("a".repeat(1000)), Err(ParseNameError::TooLong(_)));
196 assert_matches!(Name::try_from("abc".to_string()), Ok(Name(name)) if &*name == "abc");
197 }
198
199 #[test]
200 fn test_into() {
201 let name = Name::try_from("a".to_string()).unwrap();
202 let name: String = name.into();
203 assert_eq!(name, "a".to_string());
204 }
205
206 #[test]
207 fn test_deref() {
208 let name = Name::try_from("a".to_string()).unwrap();
209 let name: &str = &name;
210 assert_eq!(name, "a");
211 }
212}