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