use thiserror::Error;
use crate::NodeType;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[repr(C)]
pub struct SWHID {
pub namespace_version: u8,
pub node_type: NodeType,
pub hash: [u8; 20],
}
impl SWHID {
pub const BYTES_SIZE: usize = 22;
}
impl core::fmt::Display for SWHID {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"swh:{}:{}:",
self.namespace_version,
self.node_type.to_str(),
)?;
for byte in self.hash.iter() {
write!(f, "{:02x}", byte)?;
}
Ok(())
}
}
#[derive(Error, Debug)]
pub enum BinSWHIDDeserializationError {
#[error("Unsupported SWHID version: {0}")]
Version(u8),
#[error("Invalid SWHID type: {0}")]
Type(u8),
}
impl TryFrom<[u8; SWHID::BYTES_SIZE]> for SWHID {
type Error = BinSWHIDDeserializationError;
fn try_from(value: [u8; SWHID::BYTES_SIZE]) -> std::result::Result<Self, Self::Error> {
use BinSWHIDDeserializationError::*;
let namespace_version = value[0];
if namespace_version != 1 {
return Err(Version(namespace_version));
}
let node_type = NodeType::try_from(value[1]).map_err(Type)?;
let mut hash = [0; 20];
hash.copy_from_slice(&value[2..]);
Ok(Self {
namespace_version,
node_type,
hash,
})
}
}
#[derive(Error, Debug, PartialEq, Eq, Hash)]
pub enum StrSWHIDDeserializationError {
#[error("Invalid syntax: {0}")]
Syntax(&'static str),
#[error("Unsupported SWHID namespace: {0}")]
Namespace(String),
#[error("Unsupported SWHID version: {0}")]
Version(String),
#[error("Expected hash length to be {expected}, got {got}")]
HashLength { expected: usize, got: usize },
#[error("Invalid SWHID type: {0}")]
Type(String),
#[error("SWHID hash is not hexadecimal: {0}")]
HashAlphabet(String),
}
impl TryFrom<&str> for SWHID {
type Error = StrSWHIDDeserializationError;
fn try_from(value: &str) -> std::result::Result<Self, Self::Error> {
use StrSWHIDDeserializationError::*;
let mut tokens = value.splitn(4, ':');
let Some(namespace) = tokens.next() else {
return Err(Syntax("SWHID is empty"));
};
if namespace != "swh" {
return Err(Namespace(namespace.to_string()));
}
let Some(namespace_version) = tokens.next() else {
return Err(Syntax("SWHID is too short (no namespace version)"));
};
if namespace_version != "1" {
return Err(Version(namespace_version.to_string()));
}
let Some(node_type) = tokens.next() else {
return Err(Syntax("SWHID is too short (no object type)"));
};
let Some(hex_hash) = tokens.next() else {
return Err(Syntax("SWHID is too short (no object hash)"));
};
if hex_hash.len() != 40 {
return Err(HashLength {
expected: 40,
got: hex_hash.len(),
});
}
let node_type = node_type
.parse::<NodeType>()
.map_err(|e| Type(e.to_string()))?;
let mut hash = [0u8; 20];
faster_hex::hex_decode(hex_hash.as_bytes(), &mut hash)
.map_err(|_| HashAlphabet(hex_hash.to_string()))?;
Ok(Self {
namespace_version: 1,
node_type,
hash,
})
}
}
impl From<SWHID> for [u8; SWHID::BYTES_SIZE] {
fn from(value: SWHID) -> Self {
let mut result = [0; SWHID::BYTES_SIZE];
result[0] = value.namespace_version;
result[1] = value.node_type as u8;
result[2..].copy_from_slice(&value.hash);
result
}
}
#[cfg(feature = "serde")]
impl serde::Serialize for SWHID {
fn serialize<S: serde::Serializer>(
&self,
serializer: S,
) -> std::result::Result<S::Ok, S::Error> {
serializer.collect_str(self)
}
}
#[cfg(feature = "serde")]
impl<'de> serde::Deserialize<'de> for SWHID {
fn deserialize<D: serde::Deserializer<'de>>(
deserializer: D,
) -> std::result::Result<Self, D::Error> {
use serde::de::Error;
<&str>::deserialize(deserializer).and_then(|s| s.try_into().map_err(D::Error::custom))
}
}
#[doc(hidden)]
#[cfg(feature = "macros")]
pub const fn __parse_swhid(node_type: crate::NodeType, hash: &'static str) -> SWHID {
use const_panic::unwrap_ok;
unwrap_ok!(match const_hex::const_decode_to_array(hash.as_bytes()) {
Ok(hash) => Ok(SWHID {
namespace_version: 1,
node_type,
hash
}),
Err(_) => Err("invalid SWHID hash"),
})
}
#[cfg(feature = "macros")]
#[macro_export]
macro_rules! swhid {
(swh:1:cnt:$hash:literal) => {{
const swhid: ::swh_graph::SWHID = {
let hash: &str = stringify!($hash);
::swh_graph::__parse_swhid(::swh_graph::NodeType::Content, hash)
};
swhid
}};
(swh:1:dir:$hash:literal) => {{
const swhid: ::swh_graph::SWHID = {
let hash: &str = stringify!($hash);
::swh_graph::__parse_swhid(::swh_graph::NodeType::Directory, hash)
};
swhid
}};
(swh:1:rev:$hash:literal) => {{
const swhid: ::swh_graph::SWHID = {
let hash: &str = stringify!($hash);
::swh_graph::__parse_swhid(::swh_graph::NodeType::Revision, hash)
};
swhid
}};
(swh:1:rel:$hash:literal) => {{
const swhid: ::swh_graph::SWHID = {
let hash: &str = stringify!($hash);
::swh_graph::__parse_swhid(::swh_graph::NodeType::Release, hash)
};
swhid
}};
(swh:1:snp:$hash:literal) => {{
const swhid: ::swh_graph::SWHID = {
let hash: &str = stringify!($hash);
::swh_graph::__parse_swhid(::swh_graph::NodeType::Snapshot, hash)
};
swhid
}};
(swh:1:ori:$hash:literal) => {{
const swhid: ::swh_graph::SWHID = {
let hash: &str = stringify!($hash);
::swh_graph::__parse_swhid(::swh_graph::NodeType::Origin, hash)
};
swhid
}};
}