1use crate::{
4 internal::gitoid_from_buffer, util::stream_len::stream_len, Error, HashAlgorithm, ObjectType,
5 Result,
6};
7use core::{
8 cmp::Ordering,
9 fmt::{Debug, Formatter, Result as FmtResult},
10 hash::{Hash, Hasher},
11 marker::PhantomData,
12};
13use digest::OutputSizeUser;
14
15#[cfg(feature = "async")]
16use {
17 crate::{internal::gitoid_from_async_reader, util::stream_len::async_stream_len},
18 tokio::io::{AsyncRead, AsyncSeek},
19};
20
21#[cfg(feature = "std")]
22use {
23 crate::{gitoid_url_parser::GitOidUrlParser, internal::gitoid_from_reader},
24 serde::{
25 de::{Deserializer, Error as DeserializeError, Visitor},
26 Deserialize, Serialize, Serializer,
27 },
28 std::{
29 fmt::Display,
30 io::{Read, Seek},
31 result::Result as StdResult,
32 str::FromStr,
33 },
34 url::Url,
35};
36
37#[repr(C)]
41pub struct GitOid<H, O>
42where
43 H: HashAlgorithm,
44 O: ObjectType,
45{
46 #[doc(hidden)]
47 pub(crate) _phantom: PhantomData<O>,
48
49 #[doc(hidden)]
50 pub(crate) value: H::Array,
51}
52
53#[cfg(feature = "std")]
54pub(crate) const GITOID_URL_SCHEME: &str = "gitoid";
55
56impl<H, O> GitOid<H, O>
57where
58 H: HashAlgorithm,
59 O: ObjectType,
60{
61 pub fn id_bytes<B: AsRef<[u8]>>(content: B) -> GitOid<H, O> {
63 fn inner<H, O>(content: &[u8]) -> GitOid<H, O>
64 where
65 H: HashAlgorithm,
66 O: ObjectType,
67 {
68 gitoid_from_buffer(H::new(), content, content.len()).unwrap()
70 }
71
72 inner(content.as_ref())
73 }
74
75 pub fn id_str<S: AsRef<str>>(s: S) -> GitOid<H, O> {
77 fn inner<H, O>(s: &str) -> GitOid<H, O>
78 where
79 H: HashAlgorithm,
80 O: ObjectType,
81 {
82 GitOid::id_bytes(s.as_bytes())
83 }
84
85 inner(s.as_ref())
86 }
87
88 #[cfg(feature = "std")]
89 pub fn id_reader<R: Read + Seek>(mut reader: R) -> Result<GitOid<H, O>> {
91 let expected_length = stream_len(&mut reader)? as usize;
92 GitOid::id_reader_with_length(reader, expected_length)
93 }
94
95 #[cfg(feature = "std")]
96 pub fn id_reader_with_length<R>(reader: R, expected_length: usize) -> Result<GitOid<H, O>>
98 where
99 R: Read + Seek,
100 {
101 gitoid_from_reader(H::new(), reader, expected_length)
102 }
103
104 #[cfg(feature = "async")]
105 pub async fn id_async_reader<R: AsyncRead + AsyncSeek + Unpin>(
107 mut reader: R,
108 ) -> Result<GitOid<H, O>> {
109 let expected_length = async_stream_len(&mut reader).await? as usize;
110 GitOid::id_async_reader_with_length(reader, expected_length).await
111 }
112
113 #[cfg(feature = "async")]
114 pub async fn id_async_reader_with_length<R: AsyncRead + AsyncSeek + Unpin>(
116 reader: R,
117 expected_length: usize,
118 ) -> Result<GitOid<H, O>> {
119 gitoid_from_async_reader(H::new(), reader, expected_length).await
120 }
121
122 #[cfg(feature = "std")]
123 pub fn try_from_url(url: Url) -> Result<GitOid<H, O>> {
125 GitOid::try_from(url)
126 }
127
128 #[cfg(feature = "std")]
129 pub fn url(&self) -> Url {
131 Url::parse(&self.to_string()).unwrap()
134 }
135
136 pub fn as_bytes(&self) -> &[u8] {
138 &self.value[..]
139 }
140
141 #[cfg(feature = "std")]
142 pub fn as_hex(&self) -> String {
144 hex::encode(self.as_bytes())
145 }
146
147 pub const fn hash_algorithm(&self) -> &'static str {
149 H::NAME
150 }
151
152 pub const fn object_type(&self) -> &'static str {
154 O::NAME
155 }
156
157 pub fn hash_len(&self) -> usize {
159 <H::Alg as OutputSizeUser>::output_size()
160 }
161}
162
163#[cfg(feature = "std")]
164impl<H, O> FromStr for GitOid<H, O>
165where
166 H: HashAlgorithm,
167 O: ObjectType,
168{
169 type Err = Error;
170
171 fn from_str(s: &str) -> Result<GitOid<H, O>> {
172 let url = Url::parse(s)?;
173 GitOid::try_from_url(url)
174 }
175}
176
177impl<H, O> Clone for GitOid<H, O>
178where
179 H: HashAlgorithm,
180 O: ObjectType,
181{
182 fn clone(&self) -> Self {
183 *self
184 }
185}
186
187impl<H, O> Copy for GitOid<H, O>
188where
189 H: HashAlgorithm,
190 O: ObjectType,
191{
192}
193
194impl<H, O> PartialEq<GitOid<H, O>> for GitOid<H, O>
195where
196 H: HashAlgorithm,
197 O: ObjectType,
198{
199 fn eq(&self, other: &GitOid<H, O>) -> bool {
200 self.value == other.value
201 }
202}
203
204impl<H, O> Eq for GitOid<H, O>
205where
206 H: HashAlgorithm,
207 O: ObjectType,
208{
209}
210
211impl<H, O> PartialOrd<GitOid<H, O>> for GitOid<H, O>
212where
213 H: HashAlgorithm,
214 O: ObjectType,
215{
216 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
217 Some(self.cmp(other))
218 }
219}
220
221impl<H, O> Ord for GitOid<H, O>
222where
223 H: HashAlgorithm,
224 O: ObjectType,
225{
226 fn cmp(&self, other: &Self) -> Ordering {
227 self.value.cmp(&other.value)
228 }
229}
230
231impl<H, O> Hash for GitOid<H, O>
232where
233 H: HashAlgorithm,
234 O: ObjectType,
235{
236 fn hash<H2>(&self, state: &mut H2)
237 where
238 H2: Hasher,
239 {
240 self.value.hash(state);
241 }
242}
243
244impl<H, O> Debug for GitOid<H, O>
245where
246 H: HashAlgorithm,
247 O: ObjectType,
248{
249 fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
250 f.debug_struct("GitOid")
251 .field("object_type", &O::NAME)
252 .field("hash_algorithm", &H::NAME)
253 .field("value", &self.value)
254 .finish()
255 }
256}
257
258#[cfg(feature = "std")]
259impl<H, O> Display for GitOid<H, O>
260where
261 H: HashAlgorithm,
262 O: ObjectType,
263{
264 fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
265 write!(
266 f,
267 "{}:{}:{}:{}",
268 GITOID_URL_SCHEME,
269 O::NAME,
270 H::NAME,
271 self.as_hex()
272 )
273 }
274}
275
276#[cfg(feature = "std")]
277impl<H, O> Serialize for GitOid<H, O>
278where
279 H: HashAlgorithm,
280 O: ObjectType,
281{
282 fn serialize<S>(&self, serializer: S) -> StdResult<S::Ok, S::Error>
283 where
284 S: Serializer,
285 {
286 let self_as_url_str = self.url().to_string();
288 serializer.serialize_str(&self_as_url_str)
289 }
290}
291
292#[cfg(feature = "std")]
293impl<'de, H, O> Deserialize<'de> for GitOid<H, O>
294where
295 H: HashAlgorithm,
296 O: ObjectType,
297{
298 fn deserialize<D>(deserializer: D) -> StdResult<Self, D::Error>
299 where
300 D: Deserializer<'de>,
301 {
302 struct GitOidVisitor<H: HashAlgorithm, O: ObjectType>(PhantomData<H>, PhantomData<O>);
304
305 impl<H: HashAlgorithm, O: ObjectType> Visitor<'_> for GitOidVisitor<H, O> {
306 type Value = GitOid<H, O>;
307
308 fn expecting(&self, formatter: &mut Formatter<'_>) -> FmtResult {
309 formatter.write_str("a gitoid-scheme URL")
310 }
311
312 fn visit_str<E>(self, value: &str) -> StdResult<Self::Value, E>
313 where
314 E: DeserializeError,
315 {
316 let url = Url::parse(value).map_err(E::custom)?;
317 let id = GitOid::try_from(url).map_err(E::custom)?;
318 Ok(id)
319 }
320 }
321
322 deserializer.deserialize_str(GitOidVisitor(PhantomData, PhantomData))
323 }
324}
325
326#[cfg(feature = "std")]
327impl<H, O> TryFrom<Url> for GitOid<H, O>
328where
329 H: HashAlgorithm,
330 O: ObjectType,
331{
332 type Error = Error;
333
334 fn try_from(url: Url) -> Result<GitOid<H, O>> {
335 GitOidUrlParser::new(&url).parse()
336 }
337}