swh-mosaic 0.3.1

MOdular Storage of Archived and Indexed Contents from Software Heritage
Documentation
// Copyright (C) 2026  The Software Heritage developers
// See the AUTHORS file at the top-level directory of this distribution
// License: GNU General Public License version 3, or any later version
// See top-level LICENSE file for more information

#![doc = include_str!("../README.md")]

#[cfg(test)]
#[macro_use]
extern crate assert_matches;

use anyhow::{bail, Error, Result};
use clap::ValueEnum;
use std::cmp::Ordering;
use std::fmt;
use std::ops::{Add, AddAssign, Sub, SubAssign};
pub mod backends;
pub mod commands;
pub mod creator;
pub mod ebml;
pub mod reader;
pub mod updater;
pub mod writer;

#[derive(Clone, Copy, Debug, Eq, PartialEq, PartialOrd)]
/// New type wrapping `u64` and meant to represent the size of various elements in a MOSAIC.
///
/// Interesting properties it has are the following:
/// - Additions and subtractions are checked, so there is no wrapping when overflowing.
///   - `Size` - `Size` = `Size`
///   - `Size` + `Size` = `Size`
/// - Comparisons between `Size` and `u64` are possible.
/// - Equality checks between `Size` and `u64` are possible.
pub struct Size(pub u64);

impl From<u64> for Size {
    fn from(val: u64) -> Self {
        Size(val)
    }
}

impl TryInto<Size> for usize {
    type Error = Error;

    fn try_into(self) -> Result<Size, Error> {
        Ok(Size(self.try_into()?))
    }
}

impl fmt::Display for Size {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        self.0.fmt(f)
    }
}

impl Add for Size {
    type Output = Size;

    fn add(self, other: Self) -> Self {
        let res = self
            .0
            .checked_add(other.0)
            .unwrap_or_else(|| panic!("Failed to add {self} and {other}"));
        Size(res)
    }
}

impl AddAssign for Size {
    fn add_assign(&mut self, other: Self) {
        self.0 = self
            .0
            .checked_add(other.0)
            .unwrap_or_else(|| panic!("Failed to add {self} and {other}"));
    }
}

impl Sub for Size {
    type Output = Size;

    fn sub(self, other: Self) -> Self {
        let res = self
            .0
            .checked_sub(other.0)
            .unwrap_or_else(|| panic!("Failed to subtract {self} and {other}"));
        Size(res)
    }
}

impl SubAssign for Size {
    fn sub_assign(&mut self, other: Self) {
        self.0 = self
            .0
            .checked_sub(other.0)
            .unwrap_or_else(|| panic!("Failed to subtract {self} and {other}"));
    }
}

impl PartialEq<u64> for Size {
    fn eq(&self, other: &u64) -> bool {
        self.0 == *other
    }
}

impl PartialEq<Size> for u64 {
    fn eq(&self, other: &Size) -> bool {
        *self == other.0
    }
}

impl PartialOrd<u64> for Size {
    fn partial_cmp(&self, other: &u64) -> Option<Ordering> {
        self.0.partial_cmp(other)
    }
}

#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, PartialOrd)]
/// New type wrapping `usize` and meant to represent a position or an offset in a MOSAIC.
///
/// Interesting properties it has are the following:
/// - Additions and subtractions are checked, so there is no wrapping when overflowing.
///   - `Position` - `Position` = `Size`
///   - `Position` + `Size` = `Position`
///   - `Position` + `Position` is not possible, as it would make no sense
/// - Comparisons between `Position` and `usize` are possible.
/// - Equality checks between `Position` and `usize` are possible.
pub struct Position(pub usize);

impl From<usize> for Position {
    fn from(val: usize) -> Self {
        Position(val)
    }
}

impl fmt::Display for Position {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        self.0.fmt(f)
    }
}

impl Add<Size> for Position {
    type Output = Position;

    fn add(self, other: Size) -> Self {
        Position(
            self.0
                .checked_add(other.0 as usize)
                .unwrap_or_else(|| panic!("Failed to add {self} and {other}")),
        )
    }
}

impl AddAssign<Size> for Position {
    fn add_assign(&mut self, other: Size) {
        self.0 = self
            .0
            .checked_add(other.0 as usize)
            .unwrap_or_else(|| panic!("Failed to add {self} and {other}"))
    }
}

impl Sub<Size> for Position {
    type Output = Position;

    fn sub(self, other: Size) -> Self {
        Position(
            self.0
                .checked_sub(other.0 as usize)
                .unwrap_or_else(|| panic!("Failed to subtract {self} to {other}")),
        )
    }
}

impl SubAssign<Size> for Position {
    fn sub_assign(&mut self, other: Size) {
        self.0 = self
            .0
            .checked_sub(other.0 as usize)
            .unwrap_or_else(|| panic!("Failed to subtract {self} and {other}"));
    }
}

impl Sub for Position {
    type Output = Size;

    fn sub(self, other: Position) -> Size {
        let res = u64::try_from(
            self.0
                .checked_sub(other.0)
                .unwrap_or_else(|| panic!("Failed to subtract {self} and {other}")),
        )
        .unwrap_or_else(|_| panic!("Failed to convert ({self} - {other}) to u64"));
        Size(res)
    }
}

impl SubAssign for Position {
    fn sub_assign(&mut self, other: Position) {
        self.0 = self
            .0
            .checked_sub(other.0)
            .unwrap_or_else(|| panic!("Failed to subtract {self} and {other}"))
    }
}

impl PartialEq<usize> for Position {
    fn eq(&self, other: &usize) -> bool {
        self.0 == *other
    }
}

impl PartialEq<Position> for usize {
    fn eq(&self, other: &Position) -> bool {
        *self == other.0
    }
}

impl PartialOrd<usize> for Position {
    fn partial_cmp(&self, other: &usize) -> Option<Ordering> {
        self.0.partial_cmp(other)
    }
}

/// Values allowed in the `IdxDescription` element, describing the key and key map used
/// by each index.
#[derive(Clone, Copy, Hash, ValueEnum, Debug, PartialEq)]
pub enum IdxDescription {
    /// 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)
    Sha1Fmphgo,

    /// 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)
    Sha1gitFmphgo,

    /// 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)
    Sha256Fmphgo,

    /// 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)
    Blake2Fmphgo,
}

impl IdxDescription {
    /// Return the key's size, in bytes
    pub const fn key_len(self) -> Size {
        Size(match self {
            IdxDescription::Sha1Fmphgo | IdxDescription::Sha1gitFmphgo => 20,
            IdxDescription::Sha256Fmphgo | IdxDescription::Blake2Fmphgo => 32,
        })
    }

    /// Return the value that should be written in the `IdxDescription` element.
    pub const fn description(self) -> &'static str {
        match self {
            IdxDescription::Sha1Fmphgo => "key:sha1 pkg:cargo/ph@0.10.0 swh:1:dir:795095368f036f42adebcb75782a61494940e9b8 ph::fmph::GOFunction",
            IdxDescription::Sha1gitFmphgo => "key:sha1_git pkg:cargo/ph@0.10.0 swh:1:dir:795095368f036f42adebcb75782a61494940e9b8 ph::fmph::GOFunction",
            IdxDescription::Sha256Fmphgo => "key:sha256 pkg:cargo/ph@0.10.0 swh:1:dir:795095368f036f42adebcb75782a61494940e9b8 ph::fmph::GOFunction",
            IdxDescription::Blake2Fmphgo => "key:blake2s256 pkg:cargo/ph@0.10.0 swh:1:dir:795095368f036f42adebcb75782a61494940e9b8 ph::fmph::GOFunction",
        }
    }

    /// Match an index description to its corresponding `IdxDescription` enum member
    pub fn from_description(description: &str) -> Result<IdxDescription> {
        match description {
        "key:sha1 pkg:cargo/ph@0.10.0 swh:1:dir:795095368f036f42adebcb75782a61494940e9b8 ph::fmph::GOFunction" => Ok(IdxDescription::Sha1Fmphgo),
        "key:sha1_git pkg:cargo/ph@0.10.0 swh:1:dir:795095368f036f42adebcb75782a61494940e9b8 ph::fmph::GOFunction" => Ok(IdxDescription::Sha1gitFmphgo),
        "key:sha256 pkg:cargo/ph@0.10.0 swh:1:dir:795095368f036f42adebcb75782a61494940e9b8 ph::fmph::GOFunction" => Ok(IdxDescription::Sha256Fmphgo),
        "key:blake2s256 pkg:cargo/ph@0.10.0 swh:1:dir:795095368f036f42adebcb75782a61494940e9b8 ph::fmph::GOFunction" => Ok(IdxDescription::Blake2Fmphgo),
        _ => Err(reader::MosaicReaderError::UnknownIdxDescription{ description: description.to_string() }.into())
    }
    }
}

impl fmt::Display for IdxDescription {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            IdxDescription::Sha1Fmphgo => write!(f, "SHA1"),
            IdxDescription::Sha1gitFmphgo => write!(f, "SHA1Git"),
            IdxDescription::Sha256Fmphgo => write!(f, "SHA256"),
            IdxDescription::Blake2Fmphgo => write!(f, "BLAKE2"),
        }
    }
}

/// Enumeration of objects' compression methods, as can be declared in `CompressionMethod` elements
#[derive(PartialEq, Debug, Clone, Copy)]
pub enum CompressionMethod {
    None,
    Zstd,
    ZstdDict,
}

impl fmt::Display for CompressionMethod {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        f.write_str(String::from(*self).as_str())
    }
}

impl From<CompressionMethod> for String {
    fn from(val: CompressionMethod) -> Self {
        match val {
            CompressionMethod::None => String::from("none"),
            CompressionMethod::Zstd => String::from("zstd"),
            CompressionMethod::ZstdDict => String::from("zstd.dict"),
        }
    }
}

impl TryFrom<String> for CompressionMethod {
    type Error = Error;
    fn try_from(value: String) -> Result<Self, Self::Error> {
        match value.as_str() {
            "none" => Ok(CompressionMethod::None),
            "zstd" => Ok(CompressionMethod::Zstd),
            "zstd.dict" => Ok(CompressionMethod::ZstdDict),
            _ => bail!("Unknown compression method: {}", value),
        }
    }
}