1use crate::raw;
2use std::ptr;
3use std::str;
4
5#[derive(Debug, Clone, Copy, Eq)]
11pub enum AttrValue<'string> {
12    True,
14    False,
16    String(&'string str),
18    Bytes(&'string [u8]),
20    Unspecified,
22}
23
24macro_rules! from_value {
25    ($value:expr => $string:expr) => {
26        match unsafe { raw::git_attr_value($value.map_or(ptr::null(), |v| v.as_ptr().cast())) } {
27            raw::GIT_ATTR_VALUE_TRUE => Self::True,
28            raw::GIT_ATTR_VALUE_FALSE => Self::False,
29            raw::GIT_ATTR_VALUE_STRING => $string,
30            raw::GIT_ATTR_VALUE_UNSPECIFIED => Self::Unspecified,
31            _ => unreachable!(),
32        }
33    };
34}
35
36impl<'string> AttrValue<'string> {
37    pub fn from_string(value: Option<&'string str>) -> Self {
43        from_value!(value => Self::String(value.unwrap()))
44    }
45
46    pub fn from_bytes(value: Option<&'string [u8]>) -> Self {
52        let mut value = Self::always_bytes(value);
53        if let Self::Bytes(bytes) = value {
54            if let Ok(string) = str::from_utf8(bytes) {
55                value = Self::String(string);
56            }
57        }
58        value
59    }
60
61    pub fn always_bytes(value: Option<&'string [u8]>) -> Self {
64        from_value!(value => Self::Bytes(value.unwrap()))
65    }
66}
67
68impl PartialEq for AttrValue<'_> {
73    fn eq(&self, other: &AttrValue<'_>) -> bool {
74        match (self, other) {
75            (Self::True, AttrValue::True)
76            | (Self::False, AttrValue::False)
77            | (Self::Unspecified, AttrValue::Unspecified) => true,
78            (AttrValue::String(string), AttrValue::Bytes(bytes))
79            | (AttrValue::Bytes(bytes), AttrValue::String(string)) => string.as_bytes() == *bytes,
80            (AttrValue::String(left), AttrValue::String(right)) => left == right,
81            (AttrValue::Bytes(left), AttrValue::Bytes(right)) => left == right,
82            _ => false,
83        }
84    }
85}
86
87#[cfg(test)]
88mod tests {
89    use super::AttrValue;
90
91    macro_rules! test_attr_value {
92        ($function:ident, $variant:ident) => {
93            const ATTR_TRUE: &str = "[internal]__TRUE__";
94            const ATTR_FALSE: &str = "[internal]__FALSE__";
95            const ATTR_UNSET: &str = "[internal]__UNSET__";
96            let as_bytes = AsRef::<[u8]>::as_ref;
97            assert!(matches!(
100                AttrValue::$function(Some(ATTR_TRUE.as_ref())),
101                AttrValue::$variant(s) if as_bytes(s) == ATTR_TRUE.as_bytes()
102            ));
103            assert!(matches!(
104                AttrValue::$function(Some(ATTR_FALSE.as_ref())),
105                AttrValue::$variant(s) if as_bytes(s) == ATTR_FALSE.as_bytes()
106            ));
107            assert!(matches!(
108                AttrValue::$function(Some(ATTR_UNSET.as_ref())),
109                AttrValue::$variant(s) if as_bytes(s) == ATTR_UNSET.as_bytes()
110            ));
111            assert!(matches!(
112                AttrValue::$function(Some("foo".as_ref())),
113                AttrValue::$variant(s) if as_bytes(s) == b"foo"
114            ));
115            assert!(matches!(
116                AttrValue::$function(Some("bar".as_ref())),
117                AttrValue::$variant(s) if as_bytes(s) == b"bar"
118            ));
119            assert_eq!(AttrValue::$function(None), AttrValue::Unspecified);
120        };
121    }
122
123    #[test]
124    fn attr_value_from_string() {
125        test_attr_value!(from_string, String);
126    }
127
128    #[test]
129    fn attr_value_from_bytes() {
130        test_attr_value!(from_bytes, String);
131        assert!(matches!(
132            AttrValue::from_bytes(Some(&[0xff])),
133            AttrValue::Bytes(&[0xff])
134        ));
135        assert!(matches!(
136            AttrValue::from_bytes(Some(b"\xffoobar")),
137            AttrValue::Bytes(b"\xffoobar")
138        ));
139    }
140
141    #[test]
142    fn attr_value_always_bytes() {
143        test_attr_value!(always_bytes, Bytes);
144        assert!(matches!(
145            AttrValue::always_bytes(Some(&[0xff; 2])),
146            AttrValue::Bytes(&[0xff, 0xff])
147        ));
148        assert!(matches!(
149            AttrValue::always_bytes(Some(b"\xffoo")),
150            AttrValue::Bytes(b"\xffoo")
151        ));
152    }
153
154    #[test]
155    fn attr_value_partial_eq() {
156        assert_eq!(AttrValue::True, AttrValue::True);
157        assert_eq!(AttrValue::False, AttrValue::False);
158        assert_eq!(AttrValue::String("foo"), AttrValue::String("foo"));
159        assert_eq!(AttrValue::Bytes(b"foo"), AttrValue::Bytes(b"foo"));
160        assert_eq!(AttrValue::String("bar"), AttrValue::Bytes(b"bar"));
161        assert_eq!(AttrValue::Bytes(b"bar"), AttrValue::String("bar"));
162        assert_eq!(AttrValue::Unspecified, AttrValue::Unspecified);
163        assert_ne!(AttrValue::True, AttrValue::False);
164        assert_ne!(AttrValue::False, AttrValue::Unspecified);
165        assert_ne!(AttrValue::Unspecified, AttrValue::True);
166        assert_ne!(AttrValue::True, AttrValue::String("true"));
167        assert_ne!(AttrValue::Unspecified, AttrValue::Bytes(b"unspecified"));
168        assert_ne!(AttrValue::Bytes(b"false"), AttrValue::False);
169        assert_ne!(AttrValue::String("unspecified"), AttrValue::Unspecified);
170        assert_ne!(AttrValue::String("foo"), AttrValue::String("bar"));
171        assert_ne!(AttrValue::Bytes(b"foo"), AttrValue::Bytes(b"bar"));
172        assert_ne!(AttrValue::String("foo"), AttrValue::Bytes(b"bar"));
173        assert_ne!(AttrValue::Bytes(b"foo"), AttrValue::String("bar"));
174    }
175}