use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Statement {
#[serde(rename = "_type")]
pub type_: String,
pub subject: Vec<Subject>,
pub predicate_type: String,
pub predicate: serde_json::Value,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct Subject {
pub name: String,
pub digest: Digest,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct Digest {
#[serde(skip_serializing_if = "Option::is_none")]
pub sha256: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub sha512: Option<String>,
}
impl Statement {
pub fn matches_sha256(&self, hash_hex: &str) -> bool {
self.subject.iter().any(|subject| {
subject
.digest
.sha256
.as_ref()
.is_some_and(|h| h == hash_hex)
})
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_statement_deserialization() {
let json = r#"{
"_type": "https://in-toto.io/Statement/v1",
"subject": [
{
"name": "example.txt",
"digest": {
"sha256": "abc123"
}
}
],
"predicateType": "https://slsa.dev/provenance/v1",
"predicate": {}
}"#;
let statement: Statement = serde_json::from_str(json).unwrap();
assert_eq!(statement.type_, "https://in-toto.io/Statement/v1");
assert_eq!(statement.subject.len(), 1);
assert_eq!(statement.subject[0].name, "example.txt");
assert_eq!(
statement.subject[0].digest.sha256,
Some("abc123".to_string())
);
}
#[test]
fn test_matches_sha256() {
let statement = Statement {
type_: "https://in-toto.io/Statement/v1".to_string(),
subject: vec![
Subject {
name: "file1.txt".to_string(),
digest: Digest {
sha256: Some("hash1".to_string()),
sha512: None,
},
},
Subject {
name: "file2.txt".to_string(),
digest: Digest {
sha256: Some("hash2".to_string()),
sha512: None,
},
},
],
predicate_type: "https://slsa.dev/provenance/v1".to_string(),
predicate: serde_json::json!({}),
};
assert!(statement.matches_sha256("hash1"));
assert!(statement.matches_sha256("hash2"));
assert!(!statement.matches_sha256("hash3"));
}
}