use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use sha2::{Digest, Sha256};
use ternary_signal::Signal;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Delta {
pub id: String,
pub delta_type: DeltaType,
pub key: String,
pub value: Vec<Signal>,
pub metadata: DeltaMetadata,
pub prev_hash: Option<String>,
pub hash: String,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum DeltaType {
Create,
Update,
Delete,
Merge,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DeltaMetadata {
pub timestamp: DateTime<Utc>,
pub source: String,
pub strength: Signal,
pub observation_count: Option<usize>,
#[serde(default)]
pub custom: Option<Vec<u8>>,
}
impl Delta {
pub fn new(
delta_type: DeltaType,
key: String,
value: Vec<Signal>,
source: String,
strength: Signal,
prev_hash: Option<String>,
) -> Self {
let metadata = DeltaMetadata {
timestamp: Utc::now(),
source,
strength,
observation_count: None,
custom: None,
};
let mut delta = Self {
id: uuid::Uuid::new_v4().to_string(),
delta_type,
key,
value,
metadata,
prev_hash,
hash: String::new(), };
delta.hash = delta.compute_hash();
delta
}
pub fn create(key: impl Into<String>, value: Vec<Signal>, source: impl Into<String>) -> Self {
Self::new(
DeltaType::Create,
key.into(),
value,
source.into(),
Signal::MAX_POSITIVE,
None,
)
}
pub fn update(
key: impl Into<String>,
value: Vec<Signal>,
source: impl Into<String>,
strength: Signal,
prev_hash: Option<String>,
) -> Self {
Self::new(
DeltaType::Update,
key.into(),
value,
source.into(),
strength,
prev_hash,
)
}
pub fn delete(key: impl Into<String>, source: impl Into<String>, prev_hash: Option<String>) -> Self {
Self::new(
DeltaType::Delete,
key.into(),
vec![],
source.into(),
Signal::MAX_POSITIVE,
prev_hash,
)
}
pub fn merge(
key: impl Into<String>,
value: Vec<Signal>,
source: impl Into<String>,
strength: Signal,
prev_hash: Option<String>,
) -> Self {
Self::new(
DeltaType::Merge,
key.into(),
value,
source.into(),
strength,
prev_hash,
)
}
pub fn compute_hash(&self) -> String {
let mut hasher = Sha256::new();
hasher.update(self.id.as_bytes());
hasher.update(&[self.delta_type as u8]);
hasher.update(self.key.as_bytes());
for signal in &self.value {
hasher.update(&[signal.polarity as u8, signal.magnitude]);
}
hasher.update(self.metadata.timestamp.to_rfc3339().as_bytes());
hasher.update(self.metadata.source.as_bytes());
hasher.update(&[self.metadata.strength.polarity as u8, self.metadata.strength.magnitude]);
if let Some(ref prev) = self.prev_hash {
hasher.update(prev.as_bytes());
}
format!("{:x}", hasher.finalize())
}
pub fn verify_hash(&self) -> bool {
self.hash == self.compute_hash()
}
pub fn verify_chain(&self, prev: &Delta) -> bool {
match &self.prev_hash {
Some(prev_hash) => prev_hash == &prev.hash,
None => false,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_delta_creation() {
let value = vec![Signal::positive(128), Signal::negative(64)];
let delta = Delta::create("test_key", value.clone(), "test_source");
assert_eq!(delta.delta_type, DeltaType::Create);
assert_eq!(delta.key, "test_key");
assert_eq!(delta.value, value);
assert!(delta.verify_hash());
}
#[test]
fn test_delta_chain() {
let delta1 = Delta::create("key", vec![Signal::positive(100)], "source");
let delta2 = Delta::update(
"key",
vec![Signal::positive(200)],
"source",
Signal::positive(204), Some(delta1.hash.clone()),
);
assert!(delta2.verify_chain(&delta1));
}
#[test]
fn test_hash_stability() {
let delta = Delta::create("key", vec![Signal::positive(100)], "source");
let hash1 = delta.hash.clone();
let hash2 = delta.compute_hash();
assert_eq!(hash1, hash2);
}
}