1use crate::facets::Facets;
4use foyer::HybridCache;
5use journal_index::{FieldName, FileIndex};
6use journal_registry::File;
7use serde::{Deserialize, Serialize};
8
9const CACHE_VERSION: u32 = 3;
17
18#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
24pub struct FileIndexKey {
25 version: u32,
26 #[serde(default)]
27 namespace: String,
28 pub file: File,
29 pub(crate) facets: Facets,
30 pub(crate) source_timestamp_field: Option<FieldName>,
31}
32
33impl FileIndexKey {
34 pub fn new(file: &File, facets: &Facets, source_timestamp_field: Option<FieldName>) -> Self {
35 Self::new_with_namespace(file, facets, source_timestamp_field, "")
36 }
37
38 pub fn new_with_namespace(
44 file: &File,
45 facets: &Facets,
46 source_timestamp_field: Option<FieldName>,
47 namespace: impl Into<String>,
48 ) -> Self {
49 Self {
50 version: CACHE_VERSION,
51 namespace: namespace.into(),
52 file: file.clone(),
53 facets: facets.clone(),
54 source_timestamp_field,
55 }
56 }
57}
58
59pub type FileIndexCache = HybridCache<FileIndexKey, FileIndex>;
61
62#[cfg(test)]
63mod tests {
64 use super::*;
65 use std::collections::HashMap;
66 use std::path::Path;
67
68 #[test]
69 fn cache_version_bump_prevents_old_key_reuse() {
70 let file = test_file();
71 let facets = Facets::new(&[]);
72 let current = FileIndexKey::new(&file, &facets, None);
73 let old = FileIndexKey {
74 version: CACHE_VERSION - 1,
75 namespace: String::new(),
76 file,
77 facets,
78 source_timestamp_field: None,
79 };
80
81 let mut cache = HashMap::new();
82 cache.insert(old, "old-index");
83
84 assert_eq!(cache.get(¤t), None);
85 }
86
87 #[test]
88 fn cache_namespace_keys_do_not_collide() {
89 let file = test_file();
90 let facets = Facets::new(&[]);
91
92 let default = FileIndexKey::new(&file, &facets, None);
93 let explicit_default = FileIndexKey::new_with_namespace(&file, &facets, None, "");
94 let named = FileIndexKey::new_with_namespace(&file, &facets, None, "consumer-v2");
95
96 assert_eq!(default, explicit_default);
97 assert_ne!(default, named);
98 }
99
100 #[test]
101 fn old_serialized_cache_key_without_namespace_decodes_as_old_key() {
102 let file = test_file();
103 let facets = Facets::new(&[]);
104 let old = FileIndexKey {
105 version: CACHE_VERSION - 1,
106 namespace: "old-cache".to_string(),
107 file: file.clone(),
108 facets: facets.clone(),
109 source_timestamp_field: None,
110 };
111 let mut old_shape = serde_json::to_value(&old).expect("serialize old key");
112 old_shape
113 .as_object_mut()
114 .expect("key serializes as object")
115 .remove("namespace");
116
117 let decoded: FileIndexKey =
118 serde_json::from_value(old_shape).expect("decode old key without namespace");
119
120 assert_eq!(decoded.namespace, "");
121 assert_eq!(decoded.version, CACHE_VERSION - 1);
122 assert_ne!(decoded, FileIndexKey::new(&file, &facets, None));
123 }
124
125 fn test_file() -> File {
126 File::from_path(Path::new(
127 "/var/log/journal/00112233445566778899aabbccddeeff/system.journal",
128 ))
129 .expect("valid journal file path")
130 }
131}