1 use crate::raw;
2 use std::ptr;
3 use std::str;
4 
5 /// All possible states of an attribute.
6 ///
7 /// This enum is used to interpret the value returned by
8 /// [`Repository::get_attr`](crate::Repository::get_attr) and
9 /// [`Repository::get_attr_bytes`](crate::Repository::get_attr_bytes).
10 #[derive(Debug, Clone, Copy, Eq)]
11 pub enum AttrValue<'string> {
12     /// The attribute is set to true.
13     True,
14     /// The attribute is unset (set to false).
15     False,
16     /// The attribute is set to a [valid UTF-8 string](prim@str).
17     String(&'string str),
18     /// The attribute is set to a string that might not be [valid UTF-8](prim@str).
19     Bytes(&'string [u8]),
20     /// The attribute is not specified.
21     Unspecified,
22 }
23 
24 macro_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 
36 impl<'string> AttrValue<'string> {
37     /// Returns the state of an attribute by inspecting its [value](crate::Repository::get_attr)
38     /// by a [string](prim@str).
39     ///
40     /// This function always returns [`AttrValue::String`] and never returns [`AttrValue::Bytes`]
41     /// when the attribute is set to a string.
from_string(value: Option<&'string str>) -> Self42     pub fn from_string(value: Option<&'string str>) -> Self {
43         from_value!(value => Self::String(value.unwrap()))
44     }
45 
46     /// Returns the state of an attribute by inspecting its [value](crate::Repository::get_attr_bytes)
47     /// by a [byte](u8) [slice].
48     ///
49     /// This function will perform UTF-8 validation when the attribute is set to a string, returns
50     /// [`AttrValue::String`] if it's valid UTF-8 and [`AttrValue::Bytes`] otherwise.
from_bytes(value: Option<&'string [u8]>) -> Self51     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     /// Returns the state of an attribute just like [`AttrValue::from_bytes`], but skips UTF-8
62     /// validation and always returns [`AttrValue::Bytes`] when it's set to a string.
always_bytes(value: Option<&'string [u8]>) -> Self63     pub fn always_bytes(value: Option<&'string [u8]>) -> Self {
64         from_value!(value => Self::Bytes(value.unwrap()))
65     }
66 }
67 
68 /// Compare two [`AttrValue`]s.
69 ///
70 /// Note that this implementation does not differentiate between [`AttrValue::String`] and
71 /// [`AttrValue::Bytes`].
72 impl PartialEq for AttrValue<'_> {
eq(&self, other: &AttrValue<'_>) -> bool73     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)]
88 mod 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             // Use `matches!` here since the `PartialEq` implementation does not differentiate
98             // between `String` and `Bytes`.
99             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]
attr_value_from_string()124     fn attr_value_from_string() {
125         test_attr_value!(from_string, String);
126     }
127 
128     #[test]
attr_value_from_bytes()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]
attr_value_always_bytes()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]
attr_value_partial_eq()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 }
176