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