Skip to main content

apple_plist/
format.rs

1//! The four property-list serialization formats.
2
3use std::fmt;
4use std::str::FromStr;
5
6use crate::error::Error;
7
8/// A property-list serialization format.
9///
10/// One constant per wire format. Pass one to an
11/// encoder to choose the wire format explicitly; the decoder reports the
12/// variant it detected. The enum is deliberately exhaustive: a fifth format
13/// would be a semver-major event, and downstream `match` needs no wildcard.
14#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
15pub enum Format {
16    /// Apple XML property list (`<?xml ...?>` + `<!DOCTYPE plist ...>`).
17    Xml,
18    /// Apple binary property list (`bplist00`).
19    Binary,
20    /// OpenStep ASCII property list.
21    OpenStep,
22    /// GNUStep ASCII property list — OpenStep with typed `<*...>` literals.
23    GnuStep,
24}
25
26impl Format {
27    const ALL: [Self; 4] = [Self::Xml, Self::Binary, Self::OpenStep, Self::GnuStep];
28
29    /// The human-readable name of this format.
30    ///
31    /// # Examples
32    ///
33    /// ```
34    /// use apple_plist::Format;
35    ///
36    /// assert_eq!(Format::GnuStep.name(), "GNUStep");
37    /// ```
38    #[must_use]
39    pub const fn name(self) -> &'static str {
40        match self {
41            Self::Xml => "XML",
42            Self::Binary => "Binary",
43            Self::OpenStep => "OpenStep",
44            Self::GnuStep => "GNUStep",
45        }
46    }
47}
48
49impl fmt::Display for Format {
50    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
51        f.write_str(self.name())
52    }
53}
54
55/// Parses a format from its name, case-insensitively.
56///
57/// Accepts exactly the four [`Format::name`] spellings in any ASCII case, so
58/// parsing round-trips with [`Display`](fmt::Display).
59///
60/// # Errors
61///
62/// Returns [`Error::Message`] when the input matches none of the four names.
63///
64/// # Examples
65///
66/// ```
67/// use apple_plist::Format;
68///
69/// assert_eq!("xml".parse::<Format>().ok(), Some(Format::Xml));
70/// assert_eq!("GNUSTEP".parse::<Format>().ok(), Some(Format::GnuStep));
71/// assert!("plist".parse::<Format>().is_err());
72/// ```
73impl FromStr for Format {
74    type Err = Error;
75
76    fn from_str(s: &str) -> Result<Self, Self::Err> {
77        Self::ALL
78            .into_iter()
79            .find(|format| s.eq_ignore_ascii_case(format.name()))
80            .ok_or_else(|| Error::Message(format!("unknown format name: {s}")))
81    }
82}
83
84#[cfg(test)]
85mod tests {
86    use super::*;
87
88    #[test]
89    fn name_matches_canonical_spellings() {
90        assert_eq!(Format::Xml.name(), "XML");
91        assert_eq!(Format::Binary.name(), "Binary");
92        assert_eq!(Format::OpenStep.name(), "OpenStep");
93        assert_eq!(Format::GnuStep.name(), "GNUStep");
94    }
95
96    #[test]
97    fn from_str_round_trips_display_case_insensitively() {
98        for format in Format::ALL {
99            assert_eq!(format.to_string().parse::<Format>().ok(), Some(format));
100            assert_eq!(
101                format.name().to_lowercase().parse::<Format>().ok(),
102                Some(format)
103            );
104            assert_eq!(
105                format.name().to_uppercase().parse::<Format>().ok(),
106                Some(format)
107            );
108        }
109    }
110
111    #[test]
112    fn from_str_rejects_unknown_names() {
113        for bad in ["", "plist", "XM L", " xml", "xml ", "GNU Step"] {
114            let err = bad.parse::<Format>();
115            assert!(
116                matches!(err, Err(Error::Message(ref m)) if m.starts_with("unknown format name"))
117            );
118        }
119    }
120}