use std::fmt;
use std::str::FromStr;
use camino::Utf8PathBuf;
use serde::{Deserialize, Deserializer, Serialize, Serializer};
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct Sha256(String);
impl Sha256 {
#[must_use]
pub fn of(bytes: &[u8]) -> Self {
use sha2::Digest;
Self(hex::encode(sha2::Sha256::digest(bytes)))
}
#[must_use]
pub fn as_str(&self) -> &str {
&self.0
}
}
impl fmt::Display for Sha256 {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(&self.0)
}
}
#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
#[error("'{0}' is not a sha256 digest")]
pub struct Sha256Error(String);
impl FromStr for Sha256 {
type Err = Sha256Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
if s.len() == 64
&& s.bytes()
.all(|b| b.is_ascii_digit() || (b'a'..=b'f').contains(&b))
{
Ok(Self(s.to_string()))
} else {
Err(Sha256Error(s.to_string()))
}
}
}
impl Serialize for Sha256 {
fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
serializer.serialize_str(&self.0)
}
}
impl<'de> Deserialize<'de> for Sha256 {
fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
let s = String::deserialize(deserializer)?;
s.parse().map_err(serde::de::Error::custom)
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct ManagedEntry {
pub source: Utf8PathBuf,
pub destination: Utf8PathBuf,
pub sha256: Sha256,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct AdoptedEntry {
pub source: Utf8PathBuf,
pub destination: Utf8PathBuf,
pub sha256: Sha256,
pub baseline_sha256: Sha256,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct IntegrationBlock {
pub path: Utf8PathBuf,
pub marker_hash: Sha256,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn digests_deterministically() {
let digest = Sha256::of(b"payload\n");
assert_eq!(digest.as_str().len(), 64);
assert_eq!(digest, Sha256::of(b"payload\n"));
assert_ne!(digest, Sha256::of(b"payload"));
}
#[test]
fn rejects_malformed_digests() {
for bad in ["", "abc", &"A".repeat(64), &"g".repeat(64)] {
assert!(bad.parse::<Sha256>().is_err(), "accepted {bad:?}");
}
assert!("0123456789abcdef".repeat(4).parse::<Sha256>().is_ok());
}
}