1use std::collections::BTreeMap;
13use std::sync::OnceLock;
14
15use parking_lot::RwLock;
16
17use crate::analyzer::{
18 keyword_analyzer, standard_analyzer, standard_cjk_analyzer, whitespace_analyzer, Analyzer,
19};
20use crate::error::AnalysisError;
21#[cfg(feature = "nori")]
22use crate::nori::nori_analyzer;
23
24pub const DEFAULT_ANALYZER_NAME: &str = "standard";
26
27#[derive(Debug, thiserror::Error)]
28pub enum RegistryError {
29 #[error("cannot overwrite built-in analyzer: {0:?}")]
30 OverwriteBuiltin(String),
31 #[error("cannot drop built-in analyzer: {0:?}")]
32 DropBuiltin(String),
33 #[error("analyzer does not exist: {0:?}")]
34 NotFound(String),
35 #[error("analyzer {name:?} has invalid configuration: {source}")]
36 InvalidConfig {
37 name: String,
38 #[source]
39 source: AnalysisError,
40 },
41}
42
43fn builtins() -> &'static BTreeMap<&'static str, Analyzer> {
44 static B: OnceLock<BTreeMap<&'static str, Analyzer>> = OnceLock::new();
45 B.get_or_init(|| {
46 let mut m = BTreeMap::new();
47 m.insert("whitespace", whitespace_analyzer());
48 m.insert("standard", standard_analyzer("english"));
49 m.insert("standard_cjk", standard_cjk_analyzer("english"));
50 m.insert("keyword", keyword_analyzer());
51 #[cfg(feature = "nori")]
52 m.insert("nori", nori_analyzer());
53 m
54 })
55}
56
57fn custom() -> &'static RwLock<BTreeMap<String, Analyzer>> {
58 static C: OnceLock<RwLock<BTreeMap<String, Analyzer>>> = OnceLock::new();
59 C.get_or_init(|| RwLock::new(BTreeMap::new()))
60}
61
62pub fn register_analyzer(name: impl Into<String>, analyzer: Analyzer) -> Result<(), RegistryError> {
63 let name = name.into();
64 if builtins().contains_key(name.as_str()) {
65 return Err(RegistryError::OverwriteBuiltin(name));
66 }
67 analyzer
68 .validate()
69 .map_err(|source| RegistryError::InvalidConfig {
70 name: name.clone(),
71 source,
72 })?;
73 custom().write().insert(name, analyzer);
74 Ok(())
75}
76
77pub fn is_builtin_analyzer(name: &str) -> bool {
79 builtins().contains_key(name)
80}
81
82pub fn builtin_analyzer_names() -> Vec<String> {
84 builtins().keys().map(|name| (*name).to_owned()).collect()
85}
86
87pub fn get_analyzer(name: &str) -> Result<Analyzer, RegistryError> {
88 if let Some(a) = custom().read().get(name) {
89 return Ok(a.clone());
90 }
91 if let Some(a) = builtins().get(name) {
92 return Ok(a.clone());
93 }
94 Err(RegistryError::NotFound(name.to_string()))
95}
96
97pub fn drop_analyzer(name: &str) -> Result<(), RegistryError> {
98 if builtins().contains_key(name) {
99 return Err(RegistryError::DropBuiltin(name.to_string()));
100 }
101 if custom().write().remove(name).is_none() {
102 return Err(RegistryError::NotFound(name.to_string()));
103 }
104 Ok(())
105}
106
107pub fn list_analyzers() -> Vec<String> {
108 let mut names: Vec<String> = builtins().keys().map(|s| (*s).to_string()).collect();
109 names.extend(custom().read().keys().cloned());
110 names.sort();
111 names.dedup();
112 names
113}
114
115#[cfg(test)]
116mod tests {
117 use super::*;
118
119 #[test]
120 fn builtin_names_resolvable() {
121 for name in ["whitespace", "standard", "standard_cjk", "keyword"] {
122 assert!(get_analyzer(name).is_ok(), "missing builtin: {name}");
123 }
124 }
125
126 #[cfg(feature = "nori")]
127 #[test]
128 fn nori_builtin_is_resolvable_and_reserved() {
129 assert!(get_analyzer("nori").is_ok());
130 assert!(is_builtin_analyzer("nori"));
131 assert!(builtin_analyzer_names().iter().any(|name| name == "nori"));
132 }
133
134 #[test]
135 fn cannot_overwrite_builtin() {
136 let err = register_analyzer("standard", whitespace_analyzer()).unwrap_err();
137 assert!(matches!(err, RegistryError::OverwriteBuiltin(_)));
138 }
139
140 #[test]
141 fn register_get_drop_custom() {
142 register_analyzer("test_custom_alpha", whitespace_analyzer()).unwrap();
143 assert!(get_analyzer("test_custom_alpha").is_ok());
144 drop_analyzer("test_custom_alpha").unwrap();
145 assert!(matches!(
146 get_analyzer("test_custom_alpha"),
147 Err(RegistryError::NotFound(_))
148 ));
149 }
150
151 #[test]
152 fn cannot_drop_builtin() {
153 let err = drop_analyzer("standard").unwrap_err();
154 assert!(matches!(err, RegistryError::DropBuiltin(_)));
155 }
156
157 #[test]
158 fn invalid_custom_analyzer_is_not_published() {
159 let analyzer = Analyzer::new(
160 crate::Tokenizer::NGram {
161 min_gram: 0,
162 max_gram: 2,
163 },
164 Vec::new(),
165 Vec::new(),
166 );
167 let error = register_analyzer("invalid_custom", analyzer).unwrap_err();
168 assert!(matches!(error, RegistryError::InvalidConfig { .. }));
169 assert!(matches!(
170 get_analyzer("invalid_custom"),
171 Err(RegistryError::NotFound(_))
172 ));
173 }
174}