use crate::hash::{hex_bytes, sha256_hex};
use serde::{Deserialize, Serialize};
use thiserror::Error;
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
#[serde(try_from = "ChecksumFields")]
pub struct ArtifactChecksumRecord {
algorithm: String,
hash: String,
}
#[derive(Deserialize)]
#[serde(deny_unknown_fields)]
struct ChecksumFields {
algorithm: String,
hash: String,
}
impl TryFrom<ChecksumFields> for ArtifactChecksumRecord {
type Error = ChecksumError;
fn try_from(value: ChecksumFields) -> Result<Self, Self::Error> {
if value.algorithm != "sha256" {
return Err(ChecksumError::UnsupportedAlgorithm(value.algorithm));
}
Self::from_hash(&value.hash)
}
}
impl ArtifactChecksumRecord {
#[must_use]
pub fn from_bytes(bytes: &[u8]) -> Self {
Self {
algorithm: "sha256".to_owned(),
hash: sha256_hex(bytes),
}
}
pub fn from_hash(hash: &str) -> Result<Self, ChecksumError> {
validate_hash(hash)?;
Ok(Self {
algorithm: "sha256".to_owned(),
hash: hash.to_ascii_lowercase(),
})
}
pub(crate) fn from_digest(digest: [u8; 32]) -> Self {
Self {
algorithm: "sha256".to_owned(),
hash: hex_bytes(digest),
}
}
#[must_use]
pub fn algorithm(&self) -> &str {
&self.algorithm
}
#[must_use]
pub fn hash(&self) -> &str {
&self.hash
}
pub fn verify(&self, expected_hash: &str) -> Result<(), ChecksumError> {
validate_hash(expected_hash)?;
if self.hash.eq_ignore_ascii_case(expected_hash) {
Ok(())
} else {
Err(ChecksumError::ChecksumMismatch {
expected: expected_hash.to_ascii_lowercase(),
actual: self.hash.clone(),
})
}
}
}
fn validate_hash(hash: &str) -> Result<(), ChecksumError> {
if hash.len() != 64 || !hash.bytes().all(|byte| byte.is_ascii_hexdigit()) {
return Err(ChecksumError::InvalidHash(hash.to_owned()));
}
Ok(())
}
#[derive(Debug, Error)]
pub enum ChecksumError {
#[error("checksum mismatch: expected {expected}, actual {actual}")]
ChecksumMismatch {
expected: String,
actual: String,
},
#[error("invalid SHA-256 checksum: {0}")]
InvalidHash(String),
#[error("unsupported checksum algorithm {0}")]
UnsupportedAlgorithm(String),
}
#[cfg(test)]
mod tests;