Skip to main content

gaze_recognizers/
lib.rs

1#![cfg_attr(docsrs, feature(doc_cfg))]
2//! Built-in recognizer backends for the Gaze pseudonymization pipeline.
3//!
4//! ## Feature flags
5//!
6//! | Feature | Default | What it enables |
7//! |---------|---------|-----------------|
8//! | `phone-parser` | yes | Parser-backed E.164 and national phone validation via `phonenumber` |
9//! | `safety-net` | no | `NerSafetyNet` observer pass |
10//! | `safety-net-openai` | no | OpenAI-filter safety net subprocess; also enables `safety-net` |
11//! | `test-support` | no | Fixture helpers for safety-net tests; not for production use |
12//!
13//! With `phone-parser` disabled, parser-backed phone validators fail closed. There is
14//! no silent regex-only fallback, so phone spans that require parser validation are not
15//! detected.
16
17mod anchored_match;
18mod dictionary;
19mod error;
20mod locale_aware;
21mod ner;
22mod regex;
23#[cfg(feature = "safety-net")]
24pub mod safety_net;
25pub mod validators;
26
27pub use anchored_match::{
28    is_person_name_candidate, AnchoredBoundary, AnchoredMatchRecognizer, CuePosition, NameShape,
29};
30pub use dictionary::DictionaryRecognizer;
31pub use error::{RecognizerError, Result};
32#[cfg(feature = "phone-parser")]
33pub use gaze_types::Region;
34pub use gaze_types::{SafetyTier, ValidatorKind};
35pub use locale_aware::{
36    LocaleAwareModel, LocaleAwareModelRegistry, ModelError, ModelHints, ModelInput, ModelSpan,
37    ModelStage,
38};
39pub use ner::{
40    LabelMap, NerBackendKind, NerDetector, NerLoadError, NerOptions, NerRecognizer,
41    VerifiedArtifacts,
42};
43pub use regex::{NormalizerKind, RegexDetector};
44
45pub fn embedded(name: &str) -> Option<&'static str> {
46    match name {
47        "core" | "core-extended" => Some(include_str!("../embedded/core.toml")),
48        "locale-de" => Some(include_str!("../embedded/locale-de.toml")),
49        "locale-en" => Some(include_str!("../embedded/locale-en.toml")),
50        "locale-br" => Some(include_str!("../embedded/locale-br.toml")),
51        "locale-fr" => Some(include_str!("../embedded/locale-fr.toml")),
52        "locale-in" => Some(include_str!("../embedded/locale-in.toml")),
53        "locale-nl" => Some(include_str!("../embedded/locale-nl.toml")),
54        "locale-uk" => Some(include_str!("../embedded/locale-uk.toml")),
55        _ => None,
56    }
57}
58
59#[cfg(test)]
60mod tests {
61    use super::embedded;
62    use gaze::{RawMatch, Rulepack, RulepackSource};
63
64    #[test]
65    fn embedded_core_rulepack_parses_and_contains_unified_recognizers() {
66        let core = embedded("core").expect("core rulepack");
67        let rulepack = Rulepack::load(RulepackSource::Embedded(core)).expect("valid core");
68
69        assert_eq!(rulepack.recognizers.len(), 29);
70        assert_eq!(rulepack.recognizers[0].id, "email.global");
71        assert_eq!(rulepack.recognizers[1].id, "email.header.name");
72        assert_eq!(rulepack.recognizers[2].id, "email.header.name.paren");
73        assert_eq!(rulepack.recognizers[3].id, "name.forward_marker");
74        assert_eq!(rulepack.recognizers[4].id, "name.agent_recipient");
75        assert_eq!(rulepack.recognizers[5].id, "name.auto_footer");
76        assert!(matches!(
77            rulepack.recognizers[0].matcher,
78            RawMatch::Regex { .. }
79        ));
80        assert!(matches!(
81            rulepack.recognizers[1].matcher,
82            RawMatch::Regex { .. }
83        ));
84        assert!(matches!(
85            rulepack.recognizers[2].matcher,
86            RawMatch::Regex { .. }
87        ));
88        assert!(rulepack.recognizers[3..]
89            .iter()
90            .take(3)
91            .all(|recognizer| matches!(recognizer.matcher, RawMatch::AnchoredMatch { .. })));
92        assert!(rulepack.recognizers[6..]
93            .iter()
94            .all(|recognizer| matches!(recognizer.matcher, RawMatch::Regex { .. })));
95    }
96
97    #[test]
98    fn embedded_core_extended_aliases_core() {
99        assert_eq!(embedded("core-extended"), embedded("core"));
100        let core_extended = embedded("core-extended").expect("core-extended rulepack");
101        let rulepack =
102            Rulepack::load(RulepackSource::Embedded(core_extended)).expect("valid core-extended");
103
104        assert_eq!(rulepack.recognizers.len(), 29);
105        assert!(rulepack
106            .recognizers
107            .iter()
108            .any(|recognizer| recognizer.id == "phone.structural"));
109        assert!(rulepack
110            .recognizers
111            .iter()
112            .any(|recognizer| recognizer.id == "phone.e164.spaced"));
113        assert!(rulepack
114            .recognizers
115            .iter()
116            .any(|recognizer| recognizer.id == "vat.de"));
117        assert!(rulepack
118            .recognizers
119            .iter()
120            .any(|recognizer| recognizer.id == "vat.es"));
121    }
122}