Skip to main content

rustic_core/
id.rs

1//! The `Id` type and related functions
2
3use std::{fmt, io::Read, ops::Deref, path::Path, str::FromStr};
4
5use binrw::{BinRead, BinWrite};
6use derive_more::{Constructor, Display};
7use rand::{RngCore, rng};
8use serde_derive::{Deserialize, Serialize};
9
10use crate::{
11    crypto::hasher::hash_reader,
12    error::{ErrorKind, RusticError, RusticResult},
13};
14
15pub(crate) mod constants {
16    /// The length of the hash in bytes
17    pub(super) const LEN: usize = 32;
18    /// The length of the hash in hexadecimal characters
19    pub(crate) const HEX_LEN: usize = LEN * 2;
20}
21
22#[macro_export]
23/// Generate newtypes for `Id`s identifying Repository files
24macro_rules! define_new_id_struct {
25    ($a:ident, $b: expr) => {
26        #[doc = concat!("An Id identifying a ", stringify!($b))]
27        #[derive(
28            Debug,
29            Clone,
30            Copy,
31            Default,
32            PartialEq,
33            Eq,
34            PartialOrd,
35            Ord,
36            Hash,
37            derive_more::Deref,
38            derive_more::Display,
39            derive_more::From,
40            derive_more::FromStr,
41            serde::Serialize,
42            serde::Deserialize,
43        )]
44        #[serde(transparent)]
45        pub struct $a($crate::Id);
46
47        impl $a {
48            /// impl `into_inner`
49            #[must_use]
50            pub fn into_inner(self) -> $crate::Id {
51                self.0
52            }
53        }
54    };
55}
56
57/// `Id` is the hash id of an object.
58///
59/// It is being used to identify blobs or files saved in the repository.
60#[derive(
61    Serialize,
62    Deserialize,
63    Clone,
64    Copy,
65    Default,
66    PartialEq,
67    Eq,
68    Hash,
69    PartialOrd,
70    Ord,
71    Constructor,
72    BinWrite,
73    BinRead,
74    Display,
75)]
76#[display("{}", &self.to_hex()[0..8])]
77pub struct Id(
78    /// The actual hash
79    #[serde(serialize_with = "hex::serde::serialize")]
80    #[serde(deserialize_with = "hex::serde::deserialize")]
81    [u8; constants::LEN],
82);
83
84impl FromStr for Id {
85    type Err = Box<RusticError>;
86    fn from_str(s: &str) -> Result<Self, Self::Err> {
87        let mut id = Self::default();
88        hex::decode_to_slice(s, &mut id.0).map_err(|err| {
89            RusticError::with_source(
90                ErrorKind::InvalidInput,
91                "Failed to decode hex string `{value}` into Id. The value must be a valid hexadecimal string.",
92                err
93            )
94            .attach_context("value", s)
95        })?;
96
97        Ok(id)
98    }
99}
100
101impl Id {
102    /// Parse an `Id` from a hexadecimal string
103    ///
104    /// # Arguments
105    ///
106    /// * `s` - The hexadecimal string to parse
107    ///
108    /// # Errors
109    ///
110    /// * If the string is not a valid hexadecimal string
111    ///
112    /// # Examples
113    ///
114    /// ```
115    /// # use rustic_core::Id;
116    /// let id = Id::from_hex("0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef").unwrap();
117    /// # assert_eq!(id.to_hex().as_str(), "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef");
118    /// ```
119    #[deprecated(note = "use FromStr::from_str instead")]
120    pub fn from_hex(s: &str) -> RusticResult<Self> {
121        s.parse()
122    }
123
124    /// Generate a random `Id`.
125    #[must_use]
126    pub fn random_from_rng(rng: &mut impl RngCore) -> Self {
127        let mut id = Self::default();
128        rng.fill_bytes(&mut id.0);
129        id
130    }
131
132    /// Generate a random `Id`.
133    #[must_use]
134    pub fn random() -> Self {
135        Self::random_from_rng(&mut rng())
136    }
137
138    /// Convert to [`HexId`].
139    ///
140    /// # Examples
141    ///
142    /// ```
143    /// use rustic_core::Id;
144    ///
145    /// let id = Id::from_hex("0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef").unwrap();
146    ///
147    /// assert_eq!(id.to_hex().as_str(), "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef");
148    /// ```
149    ///
150    /// # Panics
151    ///
152    /// * Panics if the `hex` crate fails to encode the hash
153    // TODO! - remove the panic
154    #[must_use]
155    pub fn to_hex(self) -> HexId {
156        let mut hex_id = HexId::EMPTY;
157        // HexId's len is LEN * 2
158        hex::encode_to_slice(self.0, &mut hex_id.0).unwrap();
159        hex_id
160    }
161
162    /// Checks if the [`Id`] is zero
163    ///
164    /// # Examples
165    ///
166    /// ```
167    /// use rustic_core::Id;
168    ///
169    /// let id = Id::from_hex("0000000000000000000000000000000000000000000000000000000000000000").unwrap();
170    ///
171    /// assert!(id.is_null());
172    /// ```
173    #[must_use]
174    pub fn is_null(&self) -> bool {
175        self == &Self::default()
176    }
177
178    /// Checks if this [`Id`] matches the content of a reader
179    ///
180    /// # Arguments
181    ///
182    /// * `length` - The length of the blob
183    /// * `r` - The reader to check
184    ///
185    /// # Returns
186    ///
187    /// `true` if the SHA256 matches, `false` otherwise
188    pub fn blob_matches_reader(&self, length: u64, r: &mut impl Read) -> bool {
189        // check if SHA256 matches
190        let r = r.take(length);
191        hash_reader(r).is_ok_and(|id| self == &id)
192    }
193
194    /// returns the first 4 bytes as u32 (interpreted as little endian)
195    #[must_use]
196    pub fn as_u32(&self) -> u32 {
197        u32::from_le_bytes([self.0[0], self.0[1], self.0[2], self.0[3]])
198    }
199
200    /// Finds the [`Id`]s starting with the given strings from an iterator over [`Id`]s.
201    /// # Type Parameters
202    ///
203    /// * `T` - The type of the strings.
204    /// * `I` - The iterator used to produce [`Id`]s.
205    ///
206    /// # Arguments
207    ///
208    /// * `vec` - The strings to search for.
209    ///
210    /// # Errors
211    ///
212    /// * If no id could be found for any request.
213    /// * If the id is not unique for any request.
214    pub fn find_starts_with_from_iter<T: AsRef<str>, I: IntoIterator<Item = Self>>(
215        vec: &[T],
216        iter: I,
217    ) -> RusticResult<Vec<Self>> {
218        iter.into_iter()
219            .find_unique_multiple(|id: &Self, v: &T| id.to_hex().starts_with(v.as_ref()), vec)
220            .assert_found(vec)
221    }
222}
223
224impl fmt::Debug for Id {
225    /// Format the `Id` as a hexadecimal string
226    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
227        write!(f, "{}", &*self.to_hex())
228    }
229}
230
231/// An `Id` in hexadecimal format
232#[derive(Copy, Clone, Debug)]
233pub struct HexId([u8; constants::HEX_LEN]);
234
235impl HexId {
236    /// An empty [`HexId`]
237    const EMPTY: Self = Self([b'0'; constants::HEX_LEN]);
238
239    /// Get the string representation of a [`HexId`]
240    ///
241    /// # Panics
242    ///
243    /// * If the [`HexId`] is not a valid UTF-8 string
244    #[must_use]
245    pub fn as_str(&self) -> &str {
246        // This is only ever filled with hex chars, which are ascii
247        std::str::from_utf8(&self.0).unwrap()
248    }
249}
250
251impl Deref for HexId {
252    type Target = str;
253
254    fn deref(&self) -> &Self::Target {
255        self.as_str()
256    }
257}
258
259impl AsRef<Path> for HexId {
260    fn as_ref(&self) -> &Path {
261        self.as_str().as_ref()
262    }
263}
264
265/// Possible results when searching for a criterion in an iterator
266#[derive(Debug, Clone, Copy, PartialEq, Eq)]
267pub enum FindResult<T> {
268    NotFound,
269    Found(T),
270    NonUnique,
271}
272
273#[derive(Debug)]
274/// results when searching for multiple criterion in an iterator
275pub struct FindUniqueResults<T>(Vec<FindResult<T>>);
276
277impl<T: Clone> FindUniqueResults<T> {
278    pub fn new<S>(vec: &[S]) -> Self {
279        Self(vec![FindResult::NotFound; vec.len()])
280    }
281    pub fn add_item<S, F: FnMut(&T, &S) -> bool>(&mut self, item: T, mut cmp: F, with: &[S]) {
282        for (i, v) in with.iter().enumerate() {
283            if cmp(&item, v) {
284                if matches!(self.0[i], FindResult::NotFound) {
285                    self.0[i] = FindResult::Found(item.clone());
286                } else {
287                    self.0[i] = FindResult::NonUnique;
288                }
289            }
290        }
291    }
292    pub fn assert_found<S: AsRef<str>>(self, vec: &[S]) -> RusticResult<Vec<T>> {
293        self.0
294            .into_iter()
295            .enumerate()
296            .map(|(i, id)| match id {
297                FindResult::Found(id) => Ok(id),
298                FindResult::NotFound => Err(RusticError::new(
299                    ErrorKind::Backend,
300                    "No suitable id found for `{id}`.",
301                )
302                .attach_context("id", vec[i].as_ref().to_string())),
303                FindResult::NonUnique => Err(RusticError::new(
304                    ErrorKind::Backend,
305                    "Id not unique: `{id}`.",
306                )
307                .attach_context("id", vec[i].as_ref().to_string())),
308            })
309            .collect()
310    }
311}
312
313pub trait FindUniqueMultiple: Iterator + Sized
314where
315    Self::Item: Clone,
316{
317    fn find_unique_multiple<T, F: FnMut(&Self::Item, &T) -> bool>(
318        self,
319        mut cmp: F,
320        with: &[T],
321    ) -> FindUniqueResults<Self::Item> {
322        let mut results = FindUniqueResults::new(with);
323        for item in self {
324            results.add_item(item, &mut cmp, with);
325        }
326        results
327    }
328}
329
330impl<I: Iterator> FindUniqueMultiple for I where I::Item: Clone {}