use crate::facets::Facets;
use foyer::HybridCache;
use journal_index::{FieldName, FileIndex};
use journal_registry::File;
use serde::{Deserialize, Serialize};
const CACHE_VERSION: u32 = 3;
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct FileIndexKey {
version: u32,
#[serde(default)]
namespace: String,
pub file: File,
pub(crate) facets: Facets,
pub(crate) source_timestamp_field: Option<FieldName>,
}
impl FileIndexKey {
pub fn new(file: &File, facets: &Facets, source_timestamp_field: Option<FieldName>) -> Self {
Self::new_with_namespace(file, facets, source_timestamp_field, "")
}
pub fn new_with_namespace(
file: &File,
facets: &Facets,
source_timestamp_field: Option<FieldName>,
namespace: impl Into<String>,
) -> Self {
Self {
version: CACHE_VERSION,
namespace: namespace.into(),
file: file.clone(),
facets: facets.clone(),
source_timestamp_field,
}
}
}
pub type FileIndexCache = HybridCache<FileIndexKey, FileIndex>;
#[cfg(test)]
mod tests {
use super::*;
use std::collections::HashMap;
use std::path::Path;
#[test]
fn cache_version_bump_prevents_old_key_reuse() {
let file = test_file();
let facets = Facets::new(&[]);
let current = FileIndexKey::new(&file, &facets, None);
let old = FileIndexKey {
version: CACHE_VERSION - 1,
namespace: String::new(),
file,
facets,
source_timestamp_field: None,
};
let mut cache = HashMap::new();
cache.insert(old, "old-index");
assert_eq!(cache.get(¤t), None);
}
#[test]
fn cache_namespace_keys_do_not_collide() {
let file = test_file();
let facets = Facets::new(&[]);
let default = FileIndexKey::new(&file, &facets, None);
let explicit_default = FileIndexKey::new_with_namespace(&file, &facets, None, "");
let named = FileIndexKey::new_with_namespace(&file, &facets, None, "consumer-v2");
assert_eq!(default, explicit_default);
assert_ne!(default, named);
}
#[test]
fn old_serialized_cache_key_without_namespace_decodes_as_old_key() {
let file = test_file();
let facets = Facets::new(&[]);
let old = FileIndexKey {
version: CACHE_VERSION - 1,
namespace: "old-cache".to_string(),
file: file.clone(),
facets: facets.clone(),
source_timestamp_field: None,
};
let mut old_shape = serde_json::to_value(&old).expect("serialize old key");
old_shape
.as_object_mut()
.expect("key serializes as object")
.remove("namespace");
let decoded: FileIndexKey =
serde_json::from_value(old_shape).expect("decode old key without namespace");
assert_eq!(decoded.namespace, "");
assert_eq!(decoded.version, CACHE_VERSION - 1);
assert_ne!(decoded, FileIndexKey::new(&file, &facets, None));
}
fn test_file() -> File {
File::from_path(Path::new(
"/var/log/journal/00112233445566778899aabbccddeeff/system.journal",
))
.expect("valid journal file path")
}
}