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