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, PartialEq, Eq)]
40pub enum ParseNameError {
41 #[error("name is too long")]
42 TooLong,
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 Display for Name {
70 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
71 f.write_str(&self.0)
72 }
73}
74
75pub fn validate_name(name: &str) -> Result<(), ParseNameError> {
83 let len = name.len();
84 if len > MAX_NAME_LENGTH {
85 return Err(ParseNameError::TooLong);
86 }
87 if len == 0 {
88 return Err(ParseNameError::Empty);
89 }
90 let bytes = name.as_bytes();
91 if bytes[0] == b'.' {
92 if len == 1 {
93 return Err(ParseNameError::Dot);
94 }
95 if len == 2 && bytes[1] == b'.' {
96 return Err(ParseNameError::DotDot);
97 }
98 }
99 if let Some(idx) = memchr::memchr2(b'/', 0, name.as_bytes()) {
100 if name.as_bytes()[idx] == 0 {
101 return Err(ParseNameError::EmbeddedNul);
102 } else {
103 return Err(ParseNameError::Slash);
104 }
105 }
106 Ok(())
107}
108
109impl From<Name> for String {
110 fn from(value: Name) -> Self {
111 value.0.into_string()
112 }
113}
114
115impl TryFrom<String> for Name {
116 type Error = ParseNameError;
117
118 fn try_from(value: String) -> Result<Name, ParseNameError> {
119 validate_name(&value)?;
120 Ok(Name(value.into_boxed_str()))
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_validate_name() {
145 assert_matches!(validate_name(&"a".repeat(1000)), Err(ParseNameError::TooLong));
146 assert_matches!(
147 validate_name(
148 std::str::from_utf8(&vec![65; fio::MAX_NAME_LENGTH as usize + 1]).unwrap()
149 ),
150 Err(ParseNameError::TooLong)
151 );
152 assert_matches!(
153 validate_name(std::str::from_utf8(&vec![65; fio::MAX_NAME_LENGTH as usize]).unwrap()),
154 Ok(())
155 );
156 assert_matches!(validate_name(""), Err(ParseNameError::Empty));
157 assert_matches!(validate_name("."), Err(ParseNameError::Dot));
158 assert_matches!(validate_name(".."), Err(ParseNameError::DotDot));
159 assert_matches!(validate_name(".a"), Ok(()));
160 assert_matches!(validate_name("..a"), Ok(()));
161 assert_matches!(validate_name("a/b"), Err(ParseNameError::Slash));
162 assert_matches!(validate_name("a\0b"), Err(ParseNameError::EmbeddedNul));
163 assert_matches!(validate_name("abc"), Ok(()));
164 }
165
166 #[test]
167 fn test_try_from() {
168 assert_matches!(Name::try_from("a".repeat(1000)), Err(ParseNameError::TooLong));
169 assert_matches!(Name::try_from("abc".to_string()), Ok(Name(name)) if &*name == "abc");
170 }
171
172 #[test]
173 fn test_into() {
174 let name = Name::try_from("a".to_string()).unwrap();
175 let name: String = name.into();
176 assert_eq!(name, "a".to_string());
177 }
178
179 #[test]
180 fn test_deref() {
181 let name = Name::try_from("a".to_string()).unwrap();
182 let name: &str = &name;
183 assert_eq!(name, "a");
184 }
185}