git_bug/replica/entity/id/
prefix.rs1use std::fmt::{Display, Write};
14
15use gix::Repository;
16
17use super::{Id, entity_id::EntityId};
18use crate::replica::{
19 entity::{Entity, EntityRead},
20 entity_iter::EntityIdIter,
21};
22
23#[derive(Debug, Clone, Copy)]
27pub struct IdPrefix {
28 first: [u8; Self::REQUIRED_LENGTH],
30
31 rest: [Option<u8>; 32 - Self::REQUIRED_LENGTH],
36
37 uneven_offset: Option<u8>,
43}
44
45impl Display for IdPrefix {
46 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
47 let mut buffer = [0; Self::REQUIRED_LENGTH * 2];
51 f.write_str(
52 &faster_hex::hex_encode(&self.first, &mut buffer).expect("We can count")
53 [..Self::REQUIRED_LENGTH * 2],
54 )?;
55
56 for next in self
58 .rest
59 .iter()
60 .filter_map(|a| a.as_ref())
61 .chain(self.uneven_offset.iter())
62 .flat_map(|value| {
63 let second = value & 0xF;
64 let first = (value >> 4) & 0xF;
65
66 assert!(first < 16);
67 assert!(second < 16);
68
69 [first, second]
70 })
71 .take(7 - (Self::REQUIRED_LENGTH * 2))
72 {
73 f.write_char(encode_hex(next))?;
74 }
75
76 Ok(())
77 }
78}
79
80impl From<Id> for IdPrefix {
81 fn from(value: Id) -> Self {
82 let mut first = [0; Self::REQUIRED_LENGTH];
83 let mut rest = [None; 32 - Self::REQUIRED_LENGTH];
84
85 first.copy_from_slice(&value.as_bytes()[..Self::REQUIRED_LENGTH]);
86 for (slot, byte) in rest
87 .iter_mut()
88 .zip(&value.as_bytes()[Self::REQUIRED_LENGTH..])
89 {
90 *slot = Some(*byte);
91 }
92
93 Self {
94 first,
95 rest,
96 uneven_offset: None,
97 }
98 }
99}
100
101impl IdPrefix {
102 pub const REQUIRED_LENGTH: usize = 2;
106
107 pub fn from_hex_bytes(input: &[u8]) -> Result<Self, decode::Error> {
117 if input.len() > 64 || input.len() < Self::REQUIRED_LENGTH * 2 {
118 return Err(decode::Error::InvalidLen(input.len()));
119 }
120
121 let first = {
122 let mut first_raw = [0; Self::REQUIRED_LENGTH * 2];
123 first_raw.copy_from_slice(&input[..Self::REQUIRED_LENGTH * 2]);
124
125 let mut dst = [0; Self::REQUIRED_LENGTH];
126 faster_hex::hex_decode(&first_raw, &mut dst).map_err(|err| match err {
127 faster_hex::Error::InvalidChar => decode::Error::InvalidChar,
128 faster_hex::Error::InvalidLength(_) | faster_hex::Error::Overflow => {
129 unreachable!("We checked our numbers")
130 }
131 })?;
132 dst
133 };
134
135 let mut uneven_offset: Option<u8> = None;
136
137 let rest = {
138 let mut rest_raw = [0; 64 - (Self::REQUIRED_LENGTH * 2)];
139
140 for (slot, source) in rest_raw.iter_mut().zip(&input[Self::REQUIRED_LENGTH * 2..]) {
141 *slot = *source;
142 }
143
144 let populated_rest = input[Self::REQUIRED_LENGTH * 2..].len();
145 if populated_rest == 0 {
146 [None; 32 - Self::REQUIRED_LENGTH]
147 } else {
148 let mut trimmed_rest = &rest_raw[..populated_rest];
149
150 if (trimmed_rest.len() & 1) == 1 {
151 uneven_offset = Some({
154 let base = trimmed_rest[trimmed_rest.len() - 1];
155
156 decode_hex(base)?
157 });
158
159 trimmed_rest = &trimmed_rest[..(trimmed_rest.len() - 1)];
160 }
161
162 let mut dst = [0; 32 - (Self::REQUIRED_LENGTH)];
163 faster_hex::hex_decode(trimmed_rest, &mut dst[..trimmed_rest.len() / 2]).map_err(
164 |err| match err {
165 faster_hex::Error::InvalidChar => decode::Error::InvalidChar,
166 err @ (faster_hex::Error::InvalidLength(_)
167 | faster_hex::Error::Overflow) => {
168 unreachable!("We checked our numbers: {err}")
169 }
170 },
171 )?;
172
173 let set_rest = &dst[..trimmed_rest.len() / 2];
174
175 let mut base = [None; 32 - Self::REQUIRED_LENGTH];
176 for (slot, byte) in base.iter_mut().zip(set_rest.iter()) {
177 *slot = Some(*byte);
178 }
179
180 base
181 }
182 };
183
184 Ok(Self {
185 first,
186 rest,
187 uneven_offset,
188 })
189 }
190
191 pub fn resolve<E: Entity + EntityRead>(
201 self,
202 repo: &Repository,
203 ) -> Result<EntityId<E>, resolve::Error> {
204 let matching = EntityIdIter::<E>::new(repo)?
205 .filter_map(|maybe_id| {
206 if let Ok(id) = maybe_id {
207 if self.is_prefix_of(id.as_id()) {
208 Some(Ok(id))
209 } else {
210 None
211 }
212 } else {
213 Some(maybe_id)
214 }
215 })
216 .collect::<Result<Vec<_>, _>>()?;
217
218 match matching.len().cmp(&1) {
219 std::cmp::Ordering::Less => Err(resolve::Error::NotFound),
220 std::cmp::Ordering::Equal => Ok(matching[0]),
221 std::cmp::Ordering::Greater => Err(resolve::Error::TooManyFound(matching.len())),
222 }
223 }
224
225 #[must_use]
227 #[allow(clippy::missing_panics_doc)]
229 pub fn is_prefix_of(&self, id: Id) -> bool {
230 let mut output = true;
231
232 if self.first == id.sha256_value[..Self::REQUIRED_LENGTH] {
233 for ((prefix, is_uneven_offset), id) in self.rest[..]
234 .iter()
235 .filter_map(|a| a.map(|b| (b, false)))
236 .chain(self.uneven_offset.iter().map(|a| (*a, true)))
237 .zip(id.sha256_value[Self::REQUIRED_LENGTH..].iter())
238 {
239 if is_uneven_offset {
242 let pr_a = (prefix >> 4) & 0xF;
243 assert_eq!(pr_a, 0);
244
245 let pr_b = prefix & 0xF;
246
247 let id_a = (id >> 4) & 0xF;
250
251 output = pr_b == id_a;
252 } else {
253 let pr_a = (prefix >> 4) & 0xF;
254 let pr_b = prefix & 0xF;
255
256 let id_a = (id >> 4) & 0xF;
257 let id_b = id & 0xF;
258
259 output = output && pr_a == id_a && pr_b == id_b;
260 }
261 }
262
263 output
264 } else {
265 false
266 }
267 }
268}
269
270#[allow(missing_docs)]
271pub mod resolve {
272 #[derive(Debug, thiserror::Error)]
273 pub enum Error {
276 #[error("Failed to resolve this id, because an id de-referencing operation failed: {0}")]
277 FailedGet(#[from] crate::replica::get::Error),
278
279 #[error("Failed to resolve prefix, because we did not find a matching id.")]
280 NotFound,
281
282 #[error("Failed to resolve prefix, because we found too many matching ids ({0})")]
283 TooManyFound(usize),
284 }
285}
286
287#[allow(missing_docs)]
288pub mod decode {
289 use std::str::FromStr;
290
291 use crate::replica::entity::id::prefix::IdPrefix;
292
293 #[derive(Debug, thiserror::Error, Clone, Copy)]
294 pub enum Error {
296 #[error(
297 "Your prefix input was {0} bytes long, but should be at least {len} bytes and \
298 not more than 64.",
299 len = IdPrefix::REQUIRED_LENGTH * 2
300 )]
301 InvalidLen(usize),
302
303 #[error(
304 "Your prefix input was not some for the required {first} bytes",
305 first = IdPrefix::REQUIRED_LENGTH * 2
306 )]
307 FirstNotSome,
308
309 #[error("Your hex bytes contained an invalid hex char.")]
310 InvalidChar,
311 }
312
313 impl FromStr for IdPrefix {
314 type Err = Error;
315
316 fn from_str(s: &str) -> Result<Self, Self::Err> {
317 Self::from_hex_bytes(s.as_bytes())
318 }
319 }
320
321 impl TryFrom<&str> for IdPrefix {
322 type Error = Error;
323
324 fn try_from(value: &str) -> Result<Self, Self::Error> {
325 <Self as FromStr>::from_str(value)
326 }
327 }
328}
329
330fn decode_hex(base: u8) -> Result<u8, decode::Error> {
331 let val = match base {
332 b'0'..=b'9' => base - b'0',
333 b'a'..=b'f' => base - b'a' + 10,
334 b'A'..=b'F' => base - b'A' + 10,
335 _ => return Err(decode::Error::InvalidChar),
336 };
337 Ok(val)
338}
339fn encode_hex(base: u8) -> char {
340 match base {
341 0..=9 => (b'0' + base) as char,
342 10..=15 => (b'a' + (base - 10)) as char,
343 _ => unreachable!(),
344 }
345}
346
347#[cfg(test)]
348mod tests {
349 use std::str::FromStr;
350
351 use super::IdPrefix;
352 use crate::replica::entity::id::{
353 Id,
354 prefix::{decode_hex, encode_hex},
355 };
356
357 const HEX_CHARS: [(u8, char); 16] = [
358 (0, '0'),
359 (1, '1'),
360 (2, '2'),
361 (3, '3'),
362 (4, '4'),
363 (5, '5'),
364 (6, '6'),
365 (7, '7'),
366 (8, '8'),
367 (9, '9'),
368 (10, 'a'),
369 (11, 'b'),
370 (12, 'c'),
371 (13, 'd'),
372 (14, 'e'),
373 (15, 'f'),
374 ];
375
376 #[test]
377 fn test_print() {
378 let id = Id::from_hex(b"e30589c8275f1cafd4485f1d414a9c2da0d9cb9b4122f37aa2f557d988e1f87f")
379 .unwrap();
380 let prefix = id.shorten();
381
382 assert_eq!(prefix.to_string().as_str(), "e30589c");
383 }
384
385 #[test]
386 fn test_print_2() {
387 let id = Id::from_hex(b"b8f4a62333974e95eb69e6956d07e799662acefd28b00ab83fcd72d1ef6522eb")
388 .unwrap();
389
390 let prefix = id.shorten();
391 assert_eq!(prefix.to_string().as_str(), "b8f4a62");
392 }
393
394 #[test]
395 fn test_prefix_of_wrong() {
396 let id = Id::from_hex(b"a7c3efc6b86b96023641e49b2ec96a4d54406fc5f164d7e79d2bdf66170de892")
397 .unwrap();
398 let prefix = IdPrefix::from_str("a7c3f").unwrap();
399
400 assert!(!prefix.is_prefix_of(id));
401 }
402
403 #[test]
404 fn test_prefix_of() {
405 let id_str = "fe5ae5b4657a04784c6306473d0e0ec692b40a993195ab16a0e5019d19fbc01c";
406 let id = Id::from_hex(id_str.as_bytes()).unwrap();
407
408 for len in (IdPrefix::REQUIRED_LENGTH * 2)..(id_str.len()) {
409 let prefix_str = &id_str[..len];
410
411 eprintln!("Testing prefix with len: {len} ({prefix_str})");
412
413 let prefix = IdPrefix::from_str(prefix_str).unwrap();
414 assert!(prefix.is_prefix_of(id));
415 }
416 }
417
418 #[test]
419 fn decode_0() {
420 assert_eq!(decode_hex(b'0').unwrap(), 0);
421 }
422 #[test]
423 fn decode_a() {
424 assert_eq!(decode_hex(b'a').unwrap(), 10);
425 }
426 #[test]
427 #[allow(non_snake_case)]
428 fn decode_F() {
429 assert_eq!(decode_hex(b'F').unwrap(), 15);
430 }
431
432 #[test]
433 fn test_all() {
434 for (value, name) in HEX_CHARS {
435 assert_eq!(value, decode_hex(name as u8).unwrap());
436 assert_eq!(encode_hex(value), name);
437 }
438 }
439
440 #[test]
441 fn encode_0() {
442 assert_eq!(encode_hex(0), '0');
443 }
444 #[test]
445 fn encode_a() {
446 assert_eq!(encode_hex(10), 'a');
447 }
448 #[test]
449 fn encode_f() {
450 assert_eq!(encode_hex(15), 'f');
451 }
452}