]>
Commit | Line | Data |
---|---|---|
0a29b90c FG |
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. | |
42 | 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. | |
51 | 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. | |
63 | 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<'_> { | |
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)] | |
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] | |
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 | } |