Skip to main content

zlink_names/
field_name.rs

1//! Varlink field names.
2
3use zcheapstr::CheapStr;
4
5use crate::parse;
6
7/// A Varlink field name, e.g. `user_name`.
8///
9/// Struct fields, method parameters and enum variants are named this way: an ASCII letter,
10/// followed by ASCII letters and digits, each of which may be preceded by a single underscore.
11///
12/// # Examples
13///
14/// ```
15/// use zlink_names::FieldName;
16///
17/// let name = FieldName::try_from("user_name")?;
18/// assert_eq!(name, "user_name");
19/// assert!(FieldName::try_from("user-name").is_err());
20/// # Ok::<(), zlink_names::Error>(())
21/// ```
22#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
23pub struct FieldName<'a>(pub(crate) CheapStr<'a>);
24
25/// An owned [`FieldName`], independent of the data it was created from.
26#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
27pub struct OwnedFieldName(FieldName<'static>);
28
29impl_name!(
30    FieldName,
31    OwnedFieldName,
32    is_valid_field_name,
33    crate::Error::InvalidFieldName,
34    "a Varlink field name"
35);
36
37/// Whether `name` is a valid Varlink field name.
38///
39/// Struct fields, method parameters and enum variants all follow this rule.
40///
41/// # Examples
42///
43/// ```
44/// use zlink_names::is_valid_field_name;
45///
46/// assert!(is_valid_field_name("user_name"));
47/// assert!(!is_valid_field_name("user-name"));
48/// ```
49pub fn is_valid_field_name(name: &str) -> bool {
50    parse::parses_fully(parse::field_name, name)
51}
52
53#[cfg(test)]
54mod tests {
55    use super::*;
56    use crate::Error;
57
58    #[test]
59    fn field_names() {
60        let valid = ["a", "Z", "user_name", "user0", "a_0_b", "type", "foo123"];
61        for name in valid {
62            assert!(is_valid_field_name(name), "`{name}` must be valid");
63        }
64
65        let invalid = [
66            "",
67            "_foo",
68            "0foo",
69            "user-name",
70            "user name",
71            "user.name",
72            "user!",
73            "üser",
74            "user\u{e9}",
75            "x__",
76            "foo__bar",
77        ];
78        for name in invalid {
79            assert!(!is_valid_field_name(name), "`{name}` must be invalid");
80        }
81    }
82
83    #[test]
84    fn try_from() {
85        let name = FieldName::try_from("user_name").unwrap();
86        assert_eq!(name.as_str(), "user_name");
87        let name = FieldName::try_from(String::from("user_name")).unwrap();
88        assert_eq!(name.as_str(), "user_name");
89
90        assert_eq!(
91            FieldName::try_from("user-name"),
92            Err(Error::InvalidFieldName)
93        );
94        assert_eq!(
95            FieldName::try_from(String::from("_user")),
96            Err(Error::InvalidFieldName)
97        );
98    }
99
100    #[test]
101    fn owned_try_from() {
102        let name = OwnedFieldName::try_from("user_name").unwrap();
103        assert_eq!(name.as_str(), "user_name");
104        let name = OwnedFieldName::try_from(String::from("user_name")).unwrap();
105        assert_eq!(name.as_str(), "user_name");
106
107        assert_eq!(
108            OwnedFieldName::try_from("user-name"),
109            Err(Error::InvalidFieldName)
110        );
111        assert_eq!(
112            OwnedFieldName::try_from(String::new()),
113            Err(Error::InvalidFieldName)
114        );
115    }
116
117    // The tests below cover the API that all the name families share, through this one.
118
119    #[test]
120    fn owned_copies_are_independent() {
121        let source = String::from("user_name");
122        let name = FieldName::try_from(source.as_str()).unwrap();
123        let owned: OwnedFieldName = name.to_owned();
124        let owned_too: OwnedFieldName = name.clone().into_owned();
125        let owned_from = OwnedFieldName::from(name);
126        // The owned names don't borrow from `source`.
127        drop(source);
128
129        assert_eq!(owned, "user_name");
130        assert_eq!(owned_too, owned);
131        assert_eq!(owned_from, owned);
132    }
133
134    #[test]
135    fn static_names_are_not_copied() {
136        const NAME: FieldName<'static> = FieldName::from_static_str_unchecked("user_name");
137
138        let name = NAME;
139        assert_eq!(name, "user_name");
140        assert_eq!(
141            name.to_owned().as_str().as_ptr(),
142            name.as_str().as_ptr(),
143            "an owned copy of a static name shares its data"
144        );
145    }
146
147    #[test]
148    fn as_str_in_const() {
149        const NAME: &FieldName<'static> = &FieldName::from_static_str_unchecked("user_name");
150        const NAME_STR: &str = NAME.as_str();
151
152        assert_eq!(NAME_STR, "user_name");
153    }
154
155    #[test]
156    fn borrowed_views_share_data() {
157        let owned = OwnedFieldName::try_from(String::from("user_name")).unwrap();
158        let ptr = owned.as_str().as_ptr();
159
160        let view = owned.as_ref();
161        assert_eq!(view.as_str().as_ptr(), ptr);
162        assert_eq!(FieldName::from(&owned).as_str().as_ptr(), ptr);
163        assert_eq!(FieldName::from(&view).as_str().as_ptr(), ptr);
164        assert_eq!(owned.inner().as_str().as_ptr(), ptr);
165
166        let inner = owned.into_inner();
167        assert_eq!(inner.as_str().as_ptr(), ptr);
168        assert_eq!(FieldName::from(OwnedFieldName::from(inner)), "user_name");
169    }
170
171    #[test]
172    fn comparing() {
173        let name = FieldName::try_from("user_name").unwrap();
174        assert!(name == *"user_name");
175        assert!(name == "user_name");
176        assert!(*"user_name" == name);
177        assert!("user_name" == name);
178        assert!(name != "other");
179        assert!("other" != name);
180
181        let owned = name.to_owned();
182        assert!(owned == *"user_name");
183        assert!(owned == "user_name");
184        assert!(*"user_name" == owned);
185        assert!("user_name" == owned);
186        assert!(owned != "other");
187
188        // Borrowed and owned names compare with each other, and so do names that hold their
189        // data differently.
190        assert!(name == owned);
191        assert!(owned == name);
192        assert_eq!(name, FieldName::from_static_str_unchecked("user_name"));
193        assert!(FieldName::from_static_str_unchecked("a") < FieldName::try_from("b").unwrap());
194    }
195
196    #[test]
197    fn string_like_access() {
198        let name = FieldName::try_from("user_name").unwrap();
199        assert_eq!(name.to_string(), "user_name");
200        assert_eq!(format!("{name:>11}"), "  user_name");
201        assert_eq!(OwnedFieldName::from(name.clone()).to_string(), "user_name");
202
203        // `Deref` to `str`.
204        assert_eq!(name.len(), 9);
205        assert!(name.starts_with("user"));
206        // `AsRef<str>`.
207        let as_ref: &str = name.as_ref();
208        assert_eq!(as_ref, "user_name");
209        // The owned name coerces to `&str` as well.
210        let owned = name.to_owned();
211        let as_str: &str = &owned;
212        assert_eq!(as_str, "user_name");
213    }
214}