Skip to main content

zlink_names/
interface_name.rs

1//! Varlink interface names.
2
3use zcheapstr::CheapStr;
4
5use crate::parse;
6
7/// A Varlink interface name, e.g. `org.example.Foo`.
8///
9/// Interface names are in reverse-domain notation: at least two dot-separated segments of ASCII
10/// letters, digits and hyphens. The first segment starts with a letter, and no segment starts or
11/// ends with a hyphen.
12///
13/// # Examples
14///
15/// ```
16/// use zlink_names::InterfaceName;
17///
18/// let name = InterfaceName::try_from("org.example.Foo")?;
19/// assert_eq!(name, "org.example.Foo");
20/// assert!(InterfaceName::try_from("Foo").is_err());
21/// # Ok::<(), zlink_names::Error>(())
22/// ```
23#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
24pub struct InterfaceName<'a>(pub(crate) CheapStr<'a>);
25
26/// An owned [`InterfaceName`], independent of the data it was created from.
27#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
28pub struct OwnedInterfaceName(InterfaceName<'static>);
29
30impl_name!(
31    InterfaceName,
32    OwnedInterfaceName,
33    is_valid_interface_name,
34    crate::Error::InvalidInterfaceName,
35    "a Varlink interface name"
36);
37
38/// Whether `name` is a valid Varlink interface name.
39///
40/// Interface names are reverse-domain notation: dot-separated segments, at least one dot.
41///
42/// # Examples
43///
44/// ```
45/// use zlink_names::is_valid_interface_name;
46///
47/// assert!(is_valid_interface_name("org.example.Foo"));
48/// assert!(!is_valid_interface_name("Foo"));
49/// ```
50pub fn is_valid_interface_name(name: &str) -> bool {
51    parse::parses_fully(parse::interface_name, name)
52}
53
54#[cfg(test)]
55mod tests {
56    use super::*;
57    use crate::Error;
58
59    #[test]
60    fn interface_names() {
61        let valid = ["org.example.Foo", "a.b", "org.varlink.service", "a1.b-2.c3"];
62        for name in valid {
63            assert!(is_valid_interface_name(name), "`{name}` must be valid");
64        }
65
66        let invalid = [
67            "",
68            "Foo",          // No dot.
69            "org.",         // Trailing dot, empty segment.
70            ".org",         // Leading dot.
71            "org..example", // Empty segment.
72            "org.example.", // Trailing dot.
73            "1org.example", // First segment must start with a letter.
74            "org.example.Foo bar",
75            "org.example.Foƶ",
76        ];
77        for name in invalid {
78            assert!(!is_valid_interface_name(name), "`{name}` must be invalid");
79        }
80    }
81
82    #[test]
83    fn try_from() {
84        let name = InterfaceName::try_from("org.example.Foo").unwrap();
85        assert_eq!(name.as_str(), "org.example.Foo");
86        let name = InterfaceName::try_from(String::from("org.example.Foo")).unwrap();
87        assert_eq!(name.as_str(), "org.example.Foo");
88
89        assert_eq!(
90            InterfaceName::try_from("Foo"),
91            Err(Error::InvalidInterfaceName)
92        );
93        assert_eq!(
94            InterfaceName::try_from(String::from("org.example-")),
95            Err(Error::InvalidInterfaceName)
96        );
97    }
98
99    #[test]
100    fn owned_try_from() {
101        let name = OwnedInterfaceName::try_from("org.example.Foo").unwrap();
102        assert_eq!(name.as_str(), "org.example.Foo");
103        let name = OwnedInterfaceName::try_from(String::from("org.example.Foo")).unwrap();
104        assert_eq!(name.as_str(), "org.example.Foo");
105
106        assert_eq!(
107            OwnedInterfaceName::try_from("Foo"),
108            Err(Error::InvalidInterfaceName)
109        );
110        assert_eq!(
111            OwnedInterfaceName::try_from(String::new()),
112            Err(Error::InvalidInterfaceName)
113        );
114    }
115}