use crate::event::EventSourced;
use std::any::TypeId;
use std::hash::{Hash, Hasher};
pub(crate) fn projection_cache_key<T>(entity: &str) -> Vec<u8>
where
T: EventSourced + 'static,
{
let schema_v = T::schema_version();
let type_disc = {
let mut h = std::collections::hash_map::DefaultHasher::new();
TypeId::of::<T>().hash(&mut h);
h.finish()
};
let kinds_disc = relevant_kinds_hash::<T>();
let mut cache_key = Vec::with_capacity(entity.len() + 1 + 8 + 8 + 8);
cache_key.extend_from_slice(entity.as_bytes());
cache_key.push(0);
cache_key.extend_from_slice(&type_disc.to_le_bytes());
cache_key.extend_from_slice(&schema_v.to_le_bytes());
cache_key.extend_from_slice(&kinds_disc.to_le_bytes());
cache_key
}
fn relevant_kinds_hash<T>() -> u64
where
T: EventSourced + 'static,
{
let mut kinds: Vec<u16> = T::relevant_event_kinds()
.iter()
.map(|k| k.as_raw_u16())
.collect();
kinds.sort_unstable();
let mut h = std::collections::hash_map::DefaultHasher::new();
for k in &kinds {
k.hash(&mut h);
}
kinds.len().hash(&mut h);
h.finish()
}
#[cfg(test)]
mod relevant_kinds_hash_tests {
use super::*;
use crate::event::{Event, EventKind};
struct OneKind;
impl EventSourced for OneKind {
type Input = crate::event::JsonValueInput;
fn apply_event(&mut self, _event: &Event<serde_json::Value>) {}
fn from_events(events: &[Event<serde_json::Value>]) -> Option<Self> {
(!events.is_empty()).then_some(Self)
}
fn relevant_event_kinds() -> &'static [EventKind] {
static KINDS: [EventKind; 1] = [EventKind::DATA];
&KINDS
}
}
struct TwoKinds;
impl EventSourced for TwoKinds {
type Input = crate::event::JsonValueInput;
fn apply_event(&mut self, _event: &Event<serde_json::Value>) {}
fn from_events(events: &[Event<serde_json::Value>]) -> Option<Self> {
(!events.is_empty()).then_some(Self)
}
fn relevant_event_kinds() -> &'static [EventKind] {
static KINDS: [EventKind; 2] = [EventKind::DATA, EventKind::EFFECT_ERROR];
&KINDS
}
}
#[test]
fn distinct_kind_sets_produce_distinct_hashes() {
assert_ne!(
relevant_kinds_hash::<OneKind>(),
relevant_kinds_hash::<TwoKinds>(),
"different relevant_event_kinds must hash differently"
);
}
#[test]
fn cache_key_kinds_component_tracks_relevant_kinds() {
let one = projection_cache_key::<OneKind>("entity");
let two = projection_cache_key::<TwoKinds>("entity");
assert_ne!(one[one.len() - 8..], two[two.len() - 8..]);
}
#[test]
fn projection_cache_key_type_discriminant_is_type_id_fail_closed_portability() {
let key = projection_cache_key::<OneKind>("entity");
let key_repeat = projection_cache_key::<OneKind>("entity");
assert_eq!(
key, key_repeat,
"PROPERTY: TypeId discriminant is stable within one toolchain build"
);
assert_ne!(
projection_cache_key::<OneKind>("entity"),
projection_cache_key::<TwoKinds>("entity"),
"PROPERTY: different EventSourced types must not share a cache key"
);
assert!(
key.starts_with(b"entity\0"),
"PROPERTY: cache key layout is entity\\0type_id... (entity bytes, then a NUL separator)"
);
}
}