Skip to main content

swh_mosaic/
lib.rs

1// Copyright (C) 2026  The Software Heritage developers
2// See the AUTHORS file at the top-level directory of this distribution
3// License: GNU General Public License version 3, or any later version
4// See top-level LICENSE file for more information
5
6#![doc = include_str!("../README.md")]
7
8#[cfg(test)]
9#[macro_use]
10extern crate assert_matches;
11
12use anyhow::{bail, Error, Result};
13use clap::ValueEnum;
14use std::cmp::Ordering;
15use std::fmt;
16use std::ops::{Add, AddAssign, Sub, SubAssign};
17pub mod backends;
18pub mod commands;
19pub mod creator;
20pub mod ebml;
21pub mod reader;
22pub mod updater;
23pub mod writer;
24
25#[derive(Clone, Copy, Debug, Eq, PartialEq, PartialOrd)]
26/// New type wrapping `u64` and meant to represent the size of various elements in a MOSAIC.
27///
28/// Interesting properties it has are the following:
29/// - Additions and subtractions are checked, so there is no wrapping when overflowing.
30///   - `Size` - `Size` = `Size`
31///   - `Size` + `Size` = `Size`
32/// - Comparisons between `Size` and `u64` are possible.
33/// - Equality checks between `Size` and `u64` are possible.
34pub struct Size(pub u64);
35
36impl From<u64> for Size {
37    fn from(val: u64) -> Self {
38        Size(val)
39    }
40}
41
42impl TryInto<Size> for usize {
43    type Error = Error;
44
45    fn try_into(self) -> Result<Size, Error> {
46        Ok(Size(self.try_into()?))
47    }
48}
49
50impl fmt::Display for Size {
51    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
52        self.0.fmt(f)
53    }
54}
55
56impl Add for Size {
57    type Output = Size;
58
59    fn add(self, other: Self) -> Self {
60        let res = self
61            .0
62            .checked_add(other.0)
63            .unwrap_or_else(|| panic!("Failed to add {self} and {other}"));
64        Size(res)
65    }
66}
67
68impl AddAssign for Size {
69    fn add_assign(&mut self, other: Self) {
70        self.0 = self
71            .0
72            .checked_add(other.0)
73            .unwrap_or_else(|| panic!("Failed to add {self} and {other}"));
74    }
75}
76
77impl Sub for Size {
78    type Output = Size;
79
80    fn sub(self, other: Self) -> Self {
81        let res = self
82            .0
83            .checked_sub(other.0)
84            .unwrap_or_else(|| panic!("Failed to subtract {self} and {other}"));
85        Size(res)
86    }
87}
88
89impl SubAssign for Size {
90    fn sub_assign(&mut self, other: Self) {
91        self.0 = self
92            .0
93            .checked_sub(other.0)
94            .unwrap_or_else(|| panic!("Failed to subtract {self} and {other}"));
95    }
96}
97
98impl PartialEq<u64> for Size {
99    fn eq(&self, other: &u64) -> bool {
100        self.0 == *other
101    }
102}
103
104impl PartialEq<Size> for u64 {
105    fn eq(&self, other: &Size) -> bool {
106        *self == other.0
107    }
108}
109
110impl PartialOrd<u64> for Size {
111    fn partial_cmp(&self, other: &u64) -> Option<Ordering> {
112        self.0.partial_cmp(other)
113    }
114}
115
116#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, PartialOrd)]
117/// New type wrapping `usize` and meant to represent a position or an offset in a MOSAIC.
118///
119/// Interesting properties it has are the following:
120/// - Additions and subtractions are checked, so there is no wrapping when overflowing.
121///   - `Position` - `Position` = `Size`
122///   - `Position` + `Size` = `Position`
123///   - `Position` + `Position` is not possible, as it would make no sense
124/// - Comparisons between `Position` and `usize` are possible.
125/// - Equality checks between `Position` and `usize` are possible.
126pub struct Position(pub usize);
127
128impl From<usize> for Position {
129    fn from(val: usize) -> Self {
130        Position(val)
131    }
132}
133
134impl fmt::Display for Position {
135    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
136        self.0.fmt(f)
137    }
138}
139
140impl Add<Size> for Position {
141    type Output = Position;
142
143    fn add(self, other: Size) -> Self {
144        Position(
145            self.0
146                .checked_add(other.0 as usize)
147                .unwrap_or_else(|| panic!("Failed to add {self} and {other}")),
148        )
149    }
150}
151
152impl AddAssign<Size> for Position {
153    fn add_assign(&mut self, other: Size) {
154        self.0 = self
155            .0
156            .checked_add(other.0 as usize)
157            .unwrap_or_else(|| panic!("Failed to add {self} and {other}"))
158    }
159}
160
161impl Sub<Size> for Position {
162    type Output = Position;
163
164    fn sub(self, other: Size) -> Self {
165        Position(
166            self.0
167                .checked_sub(other.0 as usize)
168                .unwrap_or_else(|| panic!("Failed to subtract {self} to {other}")),
169        )
170    }
171}
172
173impl SubAssign<Size> for Position {
174    fn sub_assign(&mut self, other: Size) {
175        self.0 = self
176            .0
177            .checked_sub(other.0 as usize)
178            .unwrap_or_else(|| panic!("Failed to subtract {self} and {other}"));
179    }
180}
181
182impl Sub for Position {
183    type Output = Size;
184
185    fn sub(self, other: Position) -> Size {
186        let res = u64::try_from(
187            self.0
188                .checked_sub(other.0)
189                .unwrap_or_else(|| panic!("Failed to subtract {self} and {other}")),
190        )
191        .unwrap_or_else(|_| panic!("Failed to convert ({self} - {other}) to u64"));
192        Size(res)
193    }
194}
195
196impl SubAssign for Position {
197    fn sub_assign(&mut self, other: Position) {
198        self.0 = self
199            .0
200            .checked_sub(other.0)
201            .unwrap_or_else(|| panic!("Failed to subtract {self} and {other}"))
202    }
203}
204
205impl PartialEq<usize> for Position {
206    fn eq(&self, other: &usize) -> bool {
207        self.0 == *other
208    }
209}
210
211impl PartialEq<Position> for usize {
212    fn eq(&self, other: &Position) -> bool {
213        *self == other.0
214    }
215}
216
217impl PartialOrd<usize> for Position {
218    fn partial_cmp(&self, other: &usize) -> Option<Ordering> {
219        self.0.partial_cmp(other)
220    }
221}
222
223/// Values allowed in the `IdxDescription` element, describing the key and key map used
224/// by each index.
225#[derive(Clone, Copy, Hash, ValueEnum, Debug, PartialEq)]
226pub enum IdxDescription {
227    /// Key is object's SHA1 (20-bytes array), Map is an [FMPHGO MPH](https://docs.rs/ph/0.10.0/ph/fmph/struct.GOFunction.html)
228    Sha1Fmphgo,
229
230    /// Key is the SHA1 of the object prefixed as in git (20-bytes array), Map is an [FMPHGO MPH](https://docs.rs/ph/0.10.0/ph/fmph/struct.GOFunction.html)
231    Sha1gitFmphgo,
232
233    /// Key is object's SHA256(32-bytes array), Map is an [FMPHGO MPH](https://docs.rs/ph/0.10.0/ph/fmph/struct.GOFunction.html)
234    Sha256Fmphgo,
235
236    /// Key is object's blake2s256 checksum (32-bytes array), Map is an [FMPHGO MPH](https://docs.rs/ph/0.10.0/ph/fmph/struct.GOFunction.html)
237    Blake2Fmphgo,
238}
239
240impl IdxDescription {
241    /// Return the key's size, in bytes
242    pub const fn key_len(self) -> Size {
243        Size(match self {
244            IdxDescription::Sha1Fmphgo | IdxDescription::Sha1gitFmphgo => 20,
245            IdxDescription::Sha256Fmphgo | IdxDescription::Blake2Fmphgo => 32,
246        })
247    }
248
249    /// Return the value that should be written in the `IdxDescription` element.
250    pub const fn description(self) -> &'static str {
251        match self {
252            IdxDescription::Sha1Fmphgo => "key:sha1 pkg:cargo/ph@0.10.0 swh:1:dir:795095368f036f42adebcb75782a61494940e9b8 ph::fmph::GOFunction",
253            IdxDescription::Sha1gitFmphgo => "key:sha1_git pkg:cargo/ph@0.10.0 swh:1:dir:795095368f036f42adebcb75782a61494940e9b8 ph::fmph::GOFunction",
254            IdxDescription::Sha256Fmphgo => "key:sha256 pkg:cargo/ph@0.10.0 swh:1:dir:795095368f036f42adebcb75782a61494940e9b8 ph::fmph::GOFunction",
255            IdxDescription::Blake2Fmphgo => "key:blake2s256 pkg:cargo/ph@0.10.0 swh:1:dir:795095368f036f42adebcb75782a61494940e9b8 ph::fmph::GOFunction",
256        }
257    }
258
259    /// Match an index description to its corresponding `IdxDescription` enum member
260    pub fn from_description(description: &str) -> Result<IdxDescription> {
261        match description {
262        "key:sha1 pkg:cargo/ph@0.10.0 swh:1:dir:795095368f036f42adebcb75782a61494940e9b8 ph::fmph::GOFunction" => Ok(IdxDescription::Sha1Fmphgo),
263        "key:sha1_git pkg:cargo/ph@0.10.0 swh:1:dir:795095368f036f42adebcb75782a61494940e9b8 ph::fmph::GOFunction" => Ok(IdxDescription::Sha1gitFmphgo),
264        "key:sha256 pkg:cargo/ph@0.10.0 swh:1:dir:795095368f036f42adebcb75782a61494940e9b8 ph::fmph::GOFunction" => Ok(IdxDescription::Sha256Fmphgo),
265        "key:blake2s256 pkg:cargo/ph@0.10.0 swh:1:dir:795095368f036f42adebcb75782a61494940e9b8 ph::fmph::GOFunction" => Ok(IdxDescription::Blake2Fmphgo),
266        _ => Err(reader::MosaicReaderError::UnknownIdxDescription{ description: description.to_string() }.into())
267    }
268    }
269}
270
271impl fmt::Display for IdxDescription {
272    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
273        match self {
274            IdxDescription::Sha1Fmphgo => write!(f, "SHA1"),
275            IdxDescription::Sha1gitFmphgo => write!(f, "SHA1Git"),
276            IdxDescription::Sha256Fmphgo => write!(f, "SHA256"),
277            IdxDescription::Blake2Fmphgo => write!(f, "BLAKE2"),
278        }
279    }
280}
281
282/// Enumeration of objects' compression methods, as can be declared in `CompressionMethod` elements
283#[derive(PartialEq, Debug, Clone, Copy)]
284pub enum CompressionMethod {
285    None,
286    Zstd,
287    ZstdDict,
288}
289
290impl fmt::Display for CompressionMethod {
291    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
292        f.write_str(String::from(*self).as_str())
293    }
294}
295
296impl From<CompressionMethod> for String {
297    fn from(val: CompressionMethod) -> Self {
298        match val {
299            CompressionMethod::None => String::from("none"),
300            CompressionMethod::Zstd => String::from("zstd"),
301            CompressionMethod::ZstdDict => String::from("zstd.dict"),
302        }
303    }
304}
305
306impl TryFrom<String> for CompressionMethod {
307    type Error = Error;
308    fn try_from(value: String) -> Result<Self, Self::Error> {
309        match value.as_str() {
310            "none" => Ok(CompressionMethod::None),
311            "zstd" => Ok(CompressionMethod::Zstd),
312            "zstd.dict" => Ok(CompressionMethod::ZstdDict),
313            _ => bail!("Unknown compression method: {}", value),
314        }
315    }
316}