Skip to main content

gaze/
dictionaries.rs

1use crate::context::Context;
2pub use gaze_types::{
3    DictionaryBundle, DictionaryEntry, DictionaryLoadError, DictionarySource, DictionaryStats,
4    RulepackDict,
5};
6
7pub trait DictionaryBundleExt {
8    fn from_context(ctx: &Context) -> Self;
9}
10
11impl DictionaryBundleExt for DictionaryBundle {
12    fn from_context(ctx: &Context) -> Self {
13        dictionary_bundle_from_context(ctx)
14    }
15}
16
17pub fn dictionary_bundle_from_context(ctx: &Context) -> DictionaryBundle {
18    let entries = ctx.dictionaries.iter().map(|(name, dictionary)| {
19        (
20            name.clone(),
21            DictionaryEntry::new(
22                name,
23                dictionary.terms.clone(),
24                dictionary.case_sensitive,
25                DictionarySource::Cli,
26            )
27            .expect("Context validates dictionary terms before bundle construction"),
28        )
29    });
30    DictionaryBundle::from_entries(entries)
31}
32
33#[cfg(test)]
34mod tests {
35    use super::*;
36    use crate::{ContextDictionary, PiiClass};
37    use serde_json::Map;
38    use std::collections::HashMap;
39
40    #[test]
41    fn context_bundle_builds_automata_per_request() {
42        let ctx = Context {
43            dictionaries: HashMap::from([(
44                "dict_alpha".to_string(),
45                ContextDictionary {
46                    terms: vec!["AAA-12345".to_string()],
47                    case_sensitive: true,
48                },
49            )]),
50            class_map: HashMap::from([(
51                "dict_alpha".to_string(),
52                PiiClass::Custom("class_alpha".to_string()),
53            )]),
54            fields: Map::new(),
55        };
56
57        let bundle = dictionary_bundle_from_context(&ctx);
58        let entry = bundle.get("dict_alpha").expect("entry");
59        assert_eq!(entry.terms(), &["AAA-12345".to_string()]);
60        assert!(entry.case_sensitive());
61    }
62
63    #[test]
64    fn merge_prefers_second_bundle_for_same_name() {
65        let a = DictionaryBundle::from_rulepack_terms(&[RulepackDict::new(
66            "songs",
67            vec!["Song A".to_string()],
68            true,
69        )]);
70        let b = DictionaryBundle::from_rulepack_terms(&[RulepackDict::new(
71            "songs",
72            vec!["Song B".to_string()],
73            true,
74        )]);
75
76        let merged = DictionaryBundle::merge(a, b);
77        let entry = merged.get("songs").expect("entry");
78        assert_eq!(entry.terms(), &["Song B".to_string()]);
79    }
80
81    #[test]
82    fn extension_trait_restores_from_context_constructor() {
83        let ctx = Context {
84            dictionaries: HashMap::from([(
85                "dict_alpha".to_string(),
86                ContextDictionary {
87                    terms: vec!["AAA-12345".to_string()],
88                    case_sensitive: true,
89                },
90            )]),
91            class_map: HashMap::from([(
92                "dict_alpha".to_string(),
93                PiiClass::Custom("class_alpha".to_string()),
94            )]),
95            fields: Map::new(),
96        };
97
98        let bundle = DictionaryBundle::from_context(&ctx);
99        assert!(bundle.get("dict_alpha").is_some());
100    }
101}