1#![allow(missing_docs)]
16
17pub trait ObjectId {
18 fn object_type(&self) -> String;
19 fn as_bytes(&self) -> &[u8];
20 fn to_bytes(&self) -> Vec<u8>;
21 fn hex(&self) -> String;
22}
23
24macro_rules! id_type {
39 ( $(#[$attr:meta])*
40 $vis:vis $name:ident { $hex_method:ident() }
41 ) => {
42 $(#[$attr])*
43 #[derive(ContentHash, PartialEq, Eq, PartialOrd, Ord, Clone, Hash)]
44 $vis struct $name(Vec<u8>);
45 $crate::object_id::impl_id_type!($name, $hex_method);
46 };
47}
48
49macro_rules! impl_id_type {
50 ($name:ident, $hex_method:ident) => {
51 impl $name {
52 pub fn new(value: Vec<u8>) -> Self {
53 Self(value)
54 }
55
56 pub fn from_bytes(bytes: &[u8]) -> Self {
57 Self(bytes.to_vec())
58 }
59
60 pub fn from_hex(hex: &'static str) -> Self {
65 Self::try_from_hex(hex).unwrap()
66 }
67
68 pub fn try_from_hex(hex: &str) -> Result<Self, hex::FromHexError> {
70 hex::decode(hex).map(Self)
71 }
72 }
73
74 impl std::fmt::Debug for $name {
75 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
76 f.debug_tuple(stringify!($name)).field(&self.hex()).finish()
78 }
79 }
80
81 impl std::fmt::Display for $name {
82 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
83 f.pad(&self.$hex_method())
84 }
85 }
86
87 impl crate::object_id::ObjectId for $name {
88 fn object_type(&self) -> String {
89 stringify!($name)
90 .strip_suffix("Id")
91 .unwrap()
92 .to_ascii_lowercase()
93 .to_string()
94 }
95
96 fn as_bytes(&self) -> &[u8] {
97 &self.0
98 }
99
100 fn to_bytes(&self) -> Vec<u8> {
101 self.0.clone()
102 }
103
104 fn hex(&self) -> String {
105 hex::encode(&self.0)
106 }
107 }
108 };
109}
110
111pub(crate) use id_type;
112pub(crate) use impl_id_type;
113
114#[derive(Debug, Clone, PartialEq, Eq)]
117pub struct HexPrefix {
118 min_prefix_bytes: Vec<u8>,
121 has_odd_byte: bool,
122}
123
124impl HexPrefix {
125 pub fn new(prefix: &str) -> Option<HexPrefix> {
128 let has_odd_byte = prefix.len() & 1 != 0;
129 let min_prefix_bytes = if has_odd_byte {
130 hex::decode(prefix.to_owned() + "0").ok()?
131 } else {
132 hex::decode(prefix).ok()?
133 };
134 Some(HexPrefix {
135 min_prefix_bytes,
136 has_odd_byte,
137 })
138 }
139
140 pub fn from_bytes(bytes: &[u8]) -> Self {
141 HexPrefix {
142 min_prefix_bytes: bytes.to_owned(),
143 has_odd_byte: false,
144 }
145 }
146
147 pub fn hex(&self) -> String {
148 let mut hex_string = hex::encode(&self.min_prefix_bytes);
149 if self.has_odd_byte {
150 hex_string.pop().unwrap();
151 }
152 hex_string
153 }
154
155 pub fn min_prefix_bytes(&self) -> &[u8] {
159 &self.min_prefix_bytes
160 }
161
162 pub fn as_full_bytes(&self) -> Option<&[u8]> {
164 (!self.has_odd_byte).then_some(&self.min_prefix_bytes)
165 }
166
167 fn split_odd_byte(&self) -> (Option<u8>, &[u8]) {
168 if self.has_odd_byte {
169 let (&odd, prefix) = self.min_prefix_bytes.split_last().unwrap();
170 (Some(odd), prefix)
171 } else {
172 (None, &self.min_prefix_bytes)
173 }
174 }
175
176 pub fn matches<Q: ObjectId>(&self, id: &Q) -> bool {
178 let id_bytes = id.as_bytes();
179 let (maybe_odd, prefix) = self.split_odd_byte();
180 if id_bytes.starts_with(prefix) {
181 if let Some(odd) = maybe_odd {
182 matches!(id_bytes.get(prefix.len()), Some(v) if v & 0xf0 == odd)
183 } else {
184 true
185 }
186 } else {
187 false
188 }
189 }
190}
191
192#[derive(Debug, Clone, PartialEq, Eq)]
194pub enum PrefixResolution<T> {
195 NoMatch,
196 SingleMatch(T),
197 AmbiguousMatch,
198}
199
200impl<T> PrefixResolution<T> {
201 pub fn map<U>(self, f: impl FnOnce(T) -> U) -> PrefixResolution<U> {
202 match self {
203 PrefixResolution::NoMatch => PrefixResolution::NoMatch,
204 PrefixResolution::SingleMatch(x) => PrefixResolution::SingleMatch(f(x)),
205 PrefixResolution::AmbiguousMatch => PrefixResolution::AmbiguousMatch,
206 }
207 }
208}
209
210impl<T: Clone> PrefixResolution<T> {
211 pub fn plus(&self, other: &PrefixResolution<T>) -> PrefixResolution<T> {
212 match (self, other) {
213 (PrefixResolution::NoMatch, other) => other.clone(),
214 (local, PrefixResolution::NoMatch) => local.clone(),
215 (PrefixResolution::AmbiguousMatch, _) => PrefixResolution::AmbiguousMatch,
216 (_, PrefixResolution::AmbiguousMatch) => PrefixResolution::AmbiguousMatch,
217 (PrefixResolution::SingleMatch(_), PrefixResolution::SingleMatch(_)) => {
218 PrefixResolution::AmbiguousMatch
219 }
220 }
221 }
222}
223
224#[cfg(test)]
225mod tests {
226 use super::*;
227 use crate::backend::ChangeId;
228 use crate::backend::CommitId;
229
230 #[test]
231 fn test_display_object_id() {
232 let commit_id = CommitId::from_hex("deadbeef0123");
233 assert_eq!(format!("{commit_id}"), "deadbeef0123");
234 assert_eq!(format!("{commit_id:.6}"), "deadbe");
235
236 let change_id = ChangeId::from_hex("deadbeef0123");
237 assert_eq!(format!("{change_id}"), "mlpmollkzyxw");
238 assert_eq!(format!("{change_id:.6}"), "mlpmol");
239 }
240
241 #[test]
242 fn test_hex_prefix_prefixes() {
243 let prefix = HexPrefix::new("").unwrap();
244 assert_eq!(prefix.min_prefix_bytes(), b"");
245
246 let prefix = HexPrefix::new("1").unwrap();
247 assert_eq!(prefix.min_prefix_bytes(), b"\x10");
248
249 let prefix = HexPrefix::new("12").unwrap();
250 assert_eq!(prefix.min_prefix_bytes(), b"\x12");
251
252 let prefix = HexPrefix::new("123").unwrap();
253 assert_eq!(prefix.min_prefix_bytes(), b"\x12\x30");
254
255 let bad_prefix = HexPrefix::new("0x123");
256 assert_eq!(bad_prefix, None);
257
258 let bad_prefix = HexPrefix::new("foobar");
259 assert_eq!(bad_prefix, None);
260 }
261
262 #[test]
263 fn test_hex_prefix_matches() {
264 let id = CommitId::from_hex("1234");
265
266 assert!(HexPrefix::new("").unwrap().matches(&id));
267 assert!(HexPrefix::new("1").unwrap().matches(&id));
268 assert!(HexPrefix::new("12").unwrap().matches(&id));
269 assert!(HexPrefix::new("123").unwrap().matches(&id));
270 assert!(HexPrefix::new("1234").unwrap().matches(&id));
271 assert!(!HexPrefix::new("12345").unwrap().matches(&id));
272
273 assert!(!HexPrefix::new("a").unwrap().matches(&id));
274 assert!(!HexPrefix::new("1a").unwrap().matches(&id));
275 assert!(!HexPrefix::new("12a").unwrap().matches(&id));
276 assert!(!HexPrefix::new("123a").unwrap().matches(&id));
277 }
278}