1use std::cmp::Ordering;
2
3use crate::{ChangeId, ObjectId, Prefix, change_id::ReverseHexDisplay, oid};
4
5#[derive(Debug, thiserror::Error)]
7#[expect(missing_docs)]
8pub enum Error {
9 #[error(
10 "The minimum hex length of a short object id is {}, got {hex_len}",
11 Prefix::MIN_HEX_LEN
12 )]
13 TooShort { hex_len: usize },
14 #[error("An object of kind {object_kind} cannot be larger than {} in hex, but {hex_len} was requested", object_kind.len_in_hex())]
15 TooLong { object_kind: crate::Kind, hex_len: usize },
16}
17
18pub mod from_hex {
20 #[derive(Debug, Eq, PartialEq, thiserror::Error)]
22 #[expect(missing_docs)]
23 pub enum Error {
24 #[error(
25 "The minimum hex length of a short object id is {}, got {hex_len}",
26 super::Prefix::MIN_HEX_LEN
27 )]
28 TooShort { hex_len: usize },
29 #[error("An id cannot be larger than {} chars in hex, but {hex_len} was requested", crate::Kind::longest().len_in_hex())]
30 TooLong { hex_len: usize },
31 #[error("Invalid hex character")]
32 Invalid,
33 }
34}
35
36impl Prefix {
37 pub const MIN_HEX_LEN: usize = 4;
39
40 pub fn new(id: &oid, hex_len: usize) -> Result<Self, Error> {
45 if hex_len > id.kind().len_in_hex() {
46 Err(Error::TooLong {
47 object_kind: id.kind(),
48 hex_len,
49 })
50 } else if hex_len < Self::MIN_HEX_LEN {
51 Err(Error::TooShort { hex_len })
52 } else {
53 let mut prefix = ObjectId::null(id.kind());
54 let b = prefix.as_mut_slice();
55 let copy_len = hex_len.div_ceil(2);
56 b[..copy_len].copy_from_slice(&id.as_bytes()[..copy_len]);
57 if hex_len % 2 == 1 {
58 b[hex_len / 2] &= 0xf0;
59 }
60
61 Ok(Prefix { bytes: prefix, hex_len })
62 }
63 }
64
65 #[inline]
71 #[must_use]
72 pub fn hex_to_buf<'a>(&self, buf: &'a mut [u8]) -> &'a mut str {
73 let complete_bytes = self.hex_len / 2;
74 let complete_hex_len = complete_bytes * 2;
75 if complete_bytes != 0 {
76 faster_hex::hex_encode(&self.bytes.as_bytes()[..complete_bytes], &mut buf[..complete_hex_len])
77 .expect("buffer size was checked before encoding");
78 }
79 if self.hex_len % 2 == 1 {
80 const HEX: &[u8; 16] = b"0123456789abcdef";
81 buf[complete_hex_len] = HEX[usize::from(self.bytes.as_bytes()[complete_bytes] >> 4)];
82 }
83 std::str::from_utf8_mut(&mut buf[..self.hex_len]).expect("hexadecimal object IDs are valid UTF-8")
84 }
85
86 #[inline]
88 pub fn write_hex_to(&self, out: &mut dyn std::io::Write) -> std::io::Result<()> {
89 let mut buf = crate::Kind::hex_buf();
90 out.write_all(self.hex_to_buf(&mut buf).as_bytes())
91 }
92
93 pub fn as_oid(&self) -> &oid {
98 &self.bytes
99 }
100
101 pub fn hex_len(&self) -> usize {
105 self.hex_len
106 }
107
108 pub fn cmp_oid(&self, candidate: &oid) -> Ordering {
111 let common_len = self.hex_len / 2;
112
113 self.bytes.as_bytes()[..common_len]
114 .cmp(&candidate.as_bytes()[..common_len])
115 .then(if self.hex_len % 2 == 1 {
116 let half_byte_idx = self.hex_len / 2;
117 self.bytes.as_bytes()[half_byte_idx].cmp(&(candidate.as_bytes()[half_byte_idx] & 0xf0))
118 } else {
119 Ordering::Equal
120 })
121 }
122
123 pub fn from_hex(value: &str) -> Result<Self, from_hex::Error> {
126 let hex_len = value.len();
127 if hex_len < Self::MIN_HEX_LEN {
128 return Err(from_hex::Error::TooShort { hex_len });
129 }
130 Self::from_hex_nonempty(value)
131 }
132
133 pub fn from_hex_nonempty(value: &str) -> Result<Self, from_hex::Error> {
136 let hex_len = value.len();
137
138 if hex_len > crate::Kind::longest().len_in_hex() {
139 return Err(from_hex::Error::TooLong { hex_len });
140 } else if hex_len == 0 {
141 return Err(from_hex::Error::TooShort { hex_len });
142 }
143
144 let kind = crate::Kind::from_hex_len(hex_len).expect("hex-len is already checked");
145 let mut bytes = ObjectId::null(kind);
146 let dst = &mut bytes.as_mut_slice()[..hex_len.div_ceil(2)];
147 let decode_result = if hex_len % 2 == 0 {
148 faster_hex::hex_decode(value.as_bytes(), dst)
149 } else {
150 let mut hex = crate::Kind::hex_buf();
151 hex[..hex_len].copy_from_slice(value.as_bytes());
152 hex[hex_len] = b'0';
153 faster_hex::hex_decode(&hex[..=hex_len], dst)
154 };
155 decode_result.map_err(|e| match e {
156 faster_hex::Error::InvalidChar | faster_hex::Error::Overflow => from_hex::Error::Invalid,
157 faster_hex::Error::InvalidLength(_) => panic!("This is already checked"),
158 })?;
159
160 Ok(Prefix { bytes, hex_len })
161 }
162
163 pub fn from_reverse_hex(value: &str) -> Result<Self, from_hex::Error> {
165 let hex_len = value.len();
166 if hex_len < Self::MIN_HEX_LEN {
167 return Err(from_hex::Error::TooShort { hex_len });
168 }
169 Self::from_reverse_hex_nonempty(value)
170 }
171
172 pub fn from_reverse_hex_nonempty(value: &str) -> Result<Self, from_hex::Error> {
174 let hex_len = value.len();
175 if hex_len > crate::Kind::longest().len_in_hex() {
176 return Err(from_hex::Error::TooLong { hex_len });
177 } else if hex_len == 0 {
178 return Err(from_hex::Error::TooShort { hex_len });
179 }
180
181 let mut hex = crate::Kind::hex_buf();
182 crate::change_id::reverse_hex_to_hex(value.as_bytes(), &mut hex[..hex_len])
183 .map_err(|()| from_hex::Error::Invalid)?;
184 let hex = std::str::from_utf8(&hex[..hex_len]).expect("translated reverse hex is always ASCII");
185 Self::from_hex_nonempty(hex)
186 }
187
188 pub fn to_reverse_hex(&self) -> ReverseHexDisplay<'_> {
190 ReverseHexDisplay::new(&self.bytes, self.hex_len)
191 }
192}
193
194impl TryFrom<&str> for Prefix {
197 type Error = from_hex::Error;
198
199 fn try_from(value: &str) -> Result<Self, Self::Error> {
200 Prefix::from_hex(value)
201 }
202}
203
204impl std::fmt::Display for Prefix {
205 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
206 self.bytes.to_hex_with_len(self.hex_len).fmt(f)
207 }
208}
209
210impl Prefix {
211 fn eq_str(&self, other: &str) -> bool {
212 self.bytes.to_hex_with_len(self.hex_len).eq_str(other)
213 }
214}
215
216impl_partial_eq_str_one_way!(Prefix);
218
219impl From<ObjectId> for Prefix {
220 fn from(oid: ObjectId) -> Self {
221 Prefix {
222 bytes: oid,
223 hex_len: oid.kind().len_in_hex(),
224 }
225 }
226}
227
228impl From<ChangeId> for Prefix {
229 fn from(change_id: ChangeId) -> Self {
230 ObjectId::from(change_id).into()
231 }
232}