1use std::{
2 borrow::Borrow,
3 hash::{Hash, Hasher},
4 ops::Deref,
5};
6
7use crate::{Kind, borrowed::oid};
8
9#[cfg(feature = "sha1")]
10use crate::{EMPTY_BLOB_SHA1, EMPTY_TREE_SHA1, SIZE_OF_SHA1_DIGEST};
11
12#[cfg(feature = "sha256")]
13use crate::{EMPTY_BLOB_SHA256, EMPTY_TREE_SHA256, SIZE_OF_SHA256_DIGEST};
14
15#[derive(PartialEq, Eq, Ord, PartialOrd, Clone, Copy)]
17#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
18#[non_exhaustive]
19pub enum ObjectId {
20 #[cfg(feature = "sha1")]
22 Sha1([u8; SIZE_OF_SHA1_DIGEST]),
23 #[cfg(feature = "sha256")]
25 Sha256([u8; SIZE_OF_SHA256_DIGEST]),
26}
27
28impl Hash for ObjectId {
35 fn hash<H: Hasher>(&self, state: &mut H) {
36 state.write(self.as_slice());
37 }
38}
39
40#[expect(missing_docs)]
41pub mod decode {
42 use std::str::FromStr;
43
44 use crate::object_id::ObjectId;
45
46 #[cfg(feature = "sha1")]
47 use crate::{SIZE_OF_SHA1_DIGEST, SIZE_OF_SHA1_HEX_DIGEST};
48
49 #[cfg(feature = "sha256")]
50 use crate::{SIZE_OF_SHA256_DIGEST, SIZE_OF_SHA256_HEX_DIGEST};
51
52 #[derive(Debug, thiserror::Error)]
54 #[expect(missing_docs)]
55 pub enum Error {
56 #[error("A hash sized {0} hexadecimal characters is invalid")]
57 InvalidHexEncodingLength(usize),
58 #[error("Invalid character encountered")]
59 Invalid,
60 }
61
62 impl ObjectId {
64 pub fn from_hex(buffer: &[u8]) -> Result<ObjectId, Error> {
70 match buffer.len() {
71 #[cfg(feature = "sha1")]
72 SIZE_OF_SHA1_HEX_DIGEST => Ok({
73 ObjectId::Sha1({
74 let mut buf = [0; SIZE_OF_SHA1_DIGEST];
75 faster_hex::hex_decode(buffer, &mut buf).map_err(|err| match err {
76 faster_hex::Error::InvalidChar | faster_hex::Error::Overflow => Error::Invalid,
77 faster_hex::Error::InvalidLength(_) => {
78 unreachable!("BUG: This is already checked")
79 }
80 })?;
81 buf
82 })
83 }),
84 #[cfg(feature = "sha256")]
85 SIZE_OF_SHA256_HEX_DIGEST => Ok({
86 ObjectId::Sha256({
87 let mut buf = [0; SIZE_OF_SHA256_DIGEST];
88 faster_hex::hex_decode(buffer, &mut buf).map_err(|err| match err {
89 faster_hex::Error::InvalidChar | faster_hex::Error::Overflow => Error::Invalid,
90 faster_hex::Error::InvalidLength(_) => {
91 unreachable!("BUG: This is already checked")
92 }
93 })?;
94 buf
95 })
96 }),
97 len => Err(Error::InvalidHexEncodingLength(len)),
98 }
99 }
100 }
101
102 impl FromStr for ObjectId {
103 type Err = Error;
104
105 fn from_str(s: &str) -> Result<Self, Self::Err> {
106 Self::from_hex(s.as_bytes())
107 }
108 }
109}
110
111impl ObjectId {
113 #[inline]
115 pub fn kind(&self) -> Kind {
116 match self {
117 #[cfg(feature = "sha1")]
118 ObjectId::Sha1(_) => Kind::Sha1,
119 #[cfg(feature = "sha256")]
120 ObjectId::Sha256(_) => Kind::Sha256,
121 }
122 }
123 #[inline]
125 pub fn as_slice(&self) -> &[u8] {
126 match self {
127 #[cfg(feature = "sha1")]
128 Self::Sha1(b) => b.as_ref(),
129 #[cfg(feature = "sha256")]
130 Self::Sha256(b) => b.as_ref(),
131 }
132 }
133 #[inline]
135 pub fn as_mut_slice(&mut self) -> &mut [u8] {
136 match self {
137 #[cfg(feature = "sha1")]
138 Self::Sha1(b) => b.as_mut(),
139 #[cfg(feature = "sha256")]
140 Self::Sha256(b) => b.as_mut(),
141 }
142 }
143
144 #[inline]
146 pub const fn empty_blob(hash: Kind) -> ObjectId {
147 match hash {
148 #[cfg(feature = "sha1")]
149 Kind::Sha1 => ObjectId::Sha1(*EMPTY_BLOB_SHA1),
150 #[cfg(feature = "sha256")]
151 Kind::Sha256 => ObjectId::Sha256(*EMPTY_BLOB_SHA256),
152 }
153 }
154
155 #[inline]
157 pub const fn empty_tree(hash: Kind) -> ObjectId {
158 match hash {
159 #[cfg(feature = "sha1")]
160 Kind::Sha1 => ObjectId::Sha1(*EMPTY_TREE_SHA1),
161 #[cfg(feature = "sha256")]
162 Kind::Sha256 => ObjectId::Sha256(*EMPTY_TREE_SHA256),
163 }
164 }
165
166 #[inline]
168 #[doc(alias = "zero", alias = "git2")]
169 pub const fn null(kind: Kind) -> ObjectId {
170 match kind {
171 #[cfg(feature = "sha1")]
172 Kind::Sha1 => Self::null_sha1(),
173 #[cfg(feature = "sha256")]
174 Kind::Sha256 => Self::null_sha256(),
175 }
176 }
177
178 #[inline]
180 #[doc(alias = "is_zero", alias = "git2")]
181 pub fn is_null(&self) -> bool {
182 match self {
183 #[cfg(feature = "sha1")]
184 ObjectId::Sha1(digest) => &digest[..] == oid::null_sha1().as_bytes(),
185 #[cfg(feature = "sha256")]
186 ObjectId::Sha256(digest) => &digest[..] == oid::null_sha256().as_bytes(),
187 }
188 }
189
190 #[inline]
192 pub fn is_empty_blob(&self) -> bool {
193 self == &Self::empty_blob(self.kind())
194 }
195
196 #[inline]
198 pub fn is_empty_tree(&self) -> bool {
199 self == &Self::empty_tree(self.kind())
200 }
201}
202
203impl ObjectId {
205 pub fn from_bytes_or_panic(bytes: &[u8]) -> Self {
209 match bytes.len() {
210 #[cfg(feature = "sha1")]
211 SIZE_OF_SHA1_DIGEST => Self::Sha1(bytes.try_into().expect("prior length validation")),
212 #[cfg(feature = "sha256")]
213 SIZE_OF_SHA256_DIGEST => Self::Sha256(bytes.try_into().expect("prior length validation")),
214 other => panic!("BUG: unsupported hash len: {other}"),
215 }
216 }
217}
218
219impl ObjectId {
221 #[inline]
223 #[cfg(feature = "sha1")]
224 fn new_sha1(id: [u8; SIZE_OF_SHA1_DIGEST]) -> Self {
225 ObjectId::Sha1(id)
226 }
227
228 #[inline]
230 #[cfg(feature = "sha256")]
231 fn new_sha256(id: [u8; SIZE_OF_SHA256_DIGEST]) -> Self {
232 ObjectId::Sha256(id)
233 }
234
235 #[inline]
239 #[cfg(feature = "sha1")]
240 pub(crate) fn from_20_bytes(b: &[u8]) -> ObjectId {
241 let mut id = [0; SIZE_OF_SHA1_DIGEST];
242 id.copy_from_slice(b);
243 ObjectId::Sha1(id)
244 }
245
246 #[inline]
250 #[cfg(feature = "sha256")]
251 pub(crate) fn from_32_bytes(b: &[u8]) -> ObjectId {
252 let mut id = [0; SIZE_OF_SHA256_DIGEST];
253 id.copy_from_slice(b);
254 ObjectId::Sha256(id)
255 }
256
257 #[inline]
259 #[cfg(feature = "sha1")]
260 pub(crate) const fn null_sha1() -> ObjectId {
261 ObjectId::Sha1([0u8; SIZE_OF_SHA1_DIGEST])
262 }
263
264 #[inline]
266 #[cfg(feature = "sha256")]
267 pub(crate) const fn null_sha256() -> ObjectId {
268 ObjectId::Sha256([0u8; SIZE_OF_SHA256_DIGEST])
269 }
270}
271
272impl std::fmt::Debug for ObjectId {
273 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
274 match self {
275 #[cfg(feature = "sha1")]
276 ObjectId::Sha1(_hash) => f.write_str("Sha1(")?,
277 #[cfg(feature = "sha256")]
278 ObjectId::Sha256(_) => f.write_str("Sha256(")?,
279 }
280 for b in self.as_bytes() {
281 write!(f, "{b:02x}")?;
282 }
283 f.write_str(")")
284 }
285}
286
287#[cfg(feature = "sha1")]
288impl From<[u8; SIZE_OF_SHA1_DIGEST]> for ObjectId {
289 fn from(v: [u8; SIZE_OF_SHA1_DIGEST]) -> Self {
290 Self::new_sha1(v)
291 }
292}
293
294#[cfg(feature = "sha256")]
295impl From<[u8; SIZE_OF_SHA256_DIGEST]> for ObjectId {
296 fn from(v: [u8; SIZE_OF_SHA256_DIGEST]) -> Self {
297 Self::new_sha256(v)
298 }
299}
300
301impl From<&oid> for ObjectId {
302 fn from(v: &oid) -> Self {
303 match v.kind() {
304 #[cfg(feature = "sha1")]
305 Kind::Sha1 => ObjectId::from_20_bytes(v.as_bytes()),
306 #[cfg(feature = "sha256")]
307 Kind::Sha256 => ObjectId::from_32_bytes(v.as_bytes()),
308 }
309 }
310}
311
312impl TryFrom<&[u8]> for ObjectId {
313 type Error = crate::Error;
314
315 fn try_from(bytes: &[u8]) -> Result<Self, Self::Error> {
316 Ok(oid::try_from_bytes(bytes)?.into())
317 }
318}
319
320impl Deref for ObjectId {
321 type Target = oid;
322
323 fn deref(&self) -> &Self::Target {
324 self.as_ref()
325 }
326}
327
328impl AsRef<oid> for ObjectId {
329 fn as_ref(&self) -> &oid {
330 oid::from_bytes_unchecked(self.as_slice())
331 }
332}
333
334impl Borrow<oid> for ObjectId {
335 fn borrow(&self) -> &oid {
336 self.as_ref()
337 }
338}
339
340impl std::fmt::Display for ObjectId {
341 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
342 write!(f, "{}", self.to_hex())
343 }
344}
345
346impl PartialEq<&oid> for ObjectId {
347 fn eq(&self, other: &&oid) -> bool {
348 self.as_ref() == *other
349 }
350}