1use std::collections::HashMap;
9
10use lindera::dictionary::Metadata;
11use lindera_binding_core::CoreMetadata;
12
13#[napi(object)]
17pub struct MetadataOptions {
18 pub name: Option<String>,
20 pub encoding: Option<String>,
22 pub default_word_cost: Option<i32>,
24 pub default_left_context_id: Option<u32>,
26 pub default_right_context_id: Option<u32>,
28 pub default_field_value: Option<String>,
30 pub flexible_csv: Option<bool>,
32 pub skip_invalid_cost_or_id: Option<bool>,
34 pub normalize_details: Option<bool>,
36}
37
38#[napi(js_name = "Metadata")]
43pub struct JsMetadata {
44 inner: CoreMetadata,
46}
47
48#[napi]
49impl JsMetadata {
50 #[napi(constructor)]
56 pub fn new(options: Option<MetadataOptions>) -> Self {
57 let opts = options.unwrap_or(MetadataOptions {
58 name: None,
59 encoding: None,
60 default_word_cost: None,
61 default_left_context_id: None,
62 default_right_context_id: None,
63 default_field_value: None,
64 flexible_csv: None,
65 skip_invalid_cost_or_id: None,
66 normalize_details: None,
67 });
68
69 JsMetadata {
70 inner: CoreMetadata::new(
71 opts.name,
72 opts.encoding,
73 opts.default_word_cost.map(|c| c as i16),
74 opts.default_left_context_id.map(|id| id as u16),
75 opts.default_right_context_id.map(|id| id as u16),
76 opts.default_field_value,
77 opts.flexible_csv,
78 opts.skip_invalid_cost_or_id,
79 opts.normalize_details,
80 None,
81 None,
82 ),
83 }
84 }
85
86 #[napi(factory)]
92 pub fn create_default() -> Self {
93 JsMetadata::new(None)
94 }
95
96 #[napi(factory)]
106 pub fn from_json_file(path: String) -> napi::Result<Self> {
107 let json_str = std::fs::read_to_string(&path).map_err(|e| {
108 napi::Error::new(
109 napi::Status::GenericFailure,
110 format!("Failed to read file: {e}"),
111 )
112 })?;
113
114 let metadata: Metadata = serde_json::from_str(&json_str).map_err(|e| {
115 napi::Error::new(
116 napi::Status::GenericFailure,
117 format!("Failed to parse JSON: {e}"),
118 )
119 })?;
120
121 Ok(metadata.into())
122 }
123
124 #[napi(getter)]
126 pub fn name(&self) -> String {
127 self.inner.name.clone()
128 }
129
130 #[napi(setter)]
132 pub fn set_name(&mut self, name: String) {
133 self.inner.name = name;
134 }
135
136 #[napi(getter)]
138 pub fn encoding(&self) -> String {
139 self.inner.encoding.clone()
140 }
141
142 #[napi(setter)]
144 pub fn set_encoding(&mut self, encoding: String) {
145 self.inner.encoding = encoding;
146 }
147
148 #[napi(getter)]
150 pub fn default_word_cost(&self) -> i32 {
151 self.inner.default_word_cost as i32
152 }
153
154 #[napi(setter)]
156 pub fn set_default_word_cost(&mut self, cost: i32) {
157 self.inner.default_word_cost = cost as i16;
158 }
159
160 #[napi(getter)]
162 pub fn default_left_context_id(&self) -> u32 {
163 self.inner.default_left_context_id as u32
164 }
165
166 #[napi(setter)]
168 pub fn set_default_left_context_id(&mut self, id: u32) {
169 self.inner.default_left_context_id = id as u16;
170 }
171
172 #[napi(getter)]
174 pub fn default_right_context_id(&self) -> u32 {
175 self.inner.default_right_context_id as u32
176 }
177
178 #[napi(setter)]
180 pub fn set_default_right_context_id(&mut self, id: u32) {
181 self.inner.default_right_context_id = id as u16;
182 }
183
184 #[napi(getter)]
186 pub fn default_field_value(&self) -> String {
187 self.inner.default_field_value.clone()
188 }
189
190 #[napi(setter)]
192 pub fn set_default_field_value(&mut self, value: String) {
193 self.inner.default_field_value = value;
194 }
195
196 #[napi(getter)]
198 pub fn flexible_csv(&self) -> bool {
199 self.inner.flexible_csv
200 }
201
202 #[napi(setter)]
204 pub fn set_flexible_csv(&mut self, value: bool) {
205 self.inner.flexible_csv = value;
206 }
207
208 #[napi(getter)]
210 pub fn skip_invalid_cost_or_id(&self) -> bool {
211 self.inner.skip_invalid_cost_or_id
212 }
213
214 #[napi(setter)]
216 pub fn set_skip_invalid_cost_or_id(&mut self, value: bool) {
217 self.inner.skip_invalid_cost_or_id = value;
218 }
219
220 #[napi(getter)]
222 pub fn normalize_details(&self) -> bool {
223 self.inner.normalize_details
224 }
225
226 #[napi(setter)]
228 pub fn set_normalize_details(&mut self, value: bool) {
229 self.inner.normalize_details = value;
230 }
231
232 #[napi]
238 pub fn to_object(&self) -> HashMap<String, String> {
239 let mut dict = HashMap::new();
240 dict.insert("name".to_string(), self.inner.name.clone());
241 dict.insert("encoding".to_string(), self.inner.encoding.clone());
242 dict.insert(
243 "defaultWordCost".to_string(),
244 self.inner.default_word_cost.to_string(),
245 );
246 dict.insert(
247 "defaultLeftContextId".to_string(),
248 self.inner.default_left_context_id.to_string(),
249 );
250 dict.insert(
251 "defaultRightContextId".to_string(),
252 self.inner.default_right_context_id.to_string(),
253 );
254 dict.insert(
255 "defaultFieldValue".to_string(),
256 self.inner.default_field_value.clone(),
257 );
258 dict.insert(
259 "flexibleCsv".to_string(),
260 self.inner.flexible_csv.to_string(),
261 );
262 dict.insert(
263 "skipInvalidCostOrId".to_string(),
264 self.inner.skip_invalid_cost_or_id.to_string(),
265 );
266 dict.insert(
267 "normalizeDetails".to_string(),
268 self.inner.normalize_details.to_string(),
269 );
270 dict
271 }
272}
273
274impl JsMetadata {
275 pub fn to_lindera_metadata(metadata: &JsMetadata) -> Metadata {
285 metadata.inner.clone().into()
286 }
287}
288
289impl From<JsMetadata> for Metadata {
290 fn from(metadata: JsMetadata) -> Self {
291 metadata.inner.into()
292 }
293}
294
295impl From<Metadata> for JsMetadata {
296 fn from(metadata: Metadata) -> Self {
297 JsMetadata {
298 inner: CoreMetadata::from(metadata),
299 }
300 }
301}
302
303#[cfg(test)]
304mod tests {
305 use super::*;
306
307 #[test]
308 fn test_js_metadata_to_lindera_metadata() {
309 let js_metadata = JsMetadata::new(None);
310 let lindera_metadata: Metadata = js_metadata.into();
311 assert_eq!(lindera_metadata.name, "default");
312 assert_eq!(lindera_metadata.encoding, "UTF-8");
313 assert_eq!(lindera_metadata.default_word_cost, -10000);
314 assert_eq!(lindera_metadata.default_left_context_id, 1288);
315 assert_eq!(lindera_metadata.default_right_context_id, 1288);
316 assert_eq!(lindera_metadata.default_field_value, "*");
317 assert!(!lindera_metadata.flexible_csv);
318 assert!(!lindera_metadata.skip_invalid_cost_or_id);
319 assert!(!lindera_metadata.normalize_details);
320 }
321
322 #[test]
323 fn test_lindera_metadata_to_js_metadata() {
324 let lindera_metadata = Metadata::new(
325 "test".to_string(),
326 "EUC-JP".to_string(),
327 -5000,
328 100,
329 200,
330 "-".to_string(),
331 true,
332 true,
333 true,
334 lindera::dictionary::Schema::default(),
335 lindera::dictionary::Schema::default(),
336 );
337 let js_metadata: JsMetadata = lindera_metadata.into();
338 assert_eq!(js_metadata.name(), "test");
339 assert_eq!(js_metadata.encoding(), "EUC-JP");
340 assert_eq!(js_metadata.default_word_cost(), -5000);
341 assert_eq!(js_metadata.default_left_context_id(), 100);
342 assert_eq!(js_metadata.default_right_context_id(), 200);
343 assert_eq!(js_metadata.default_field_value(), "-");
344 assert!(js_metadata.flexible_csv());
345 assert!(js_metadata.skip_invalid_cost_or_id());
346 assert!(js_metadata.normalize_details());
347 }
348
349 #[test]
350 fn test_js_metadata_with_custom_options() {
351 let opts = MetadataOptions {
352 name: Some("custom".to_string()),
353 encoding: Some("Shift_JIS".to_string()),
354 default_word_cost: Some(-5000),
355 default_left_context_id: Some(100),
356 default_right_context_id: Some(200),
357 default_field_value: Some("-".to_string()),
358 flexible_csv: Some(true),
359 skip_invalid_cost_or_id: Some(true),
360 normalize_details: Some(true),
361 };
362 let js_metadata = JsMetadata::new(Some(opts));
363 assert_eq!(js_metadata.name(), "custom");
364 assert_eq!(js_metadata.encoding(), "Shift_JIS");
365 assert_eq!(js_metadata.default_word_cost(), -5000);
366 assert_eq!(js_metadata.default_left_context_id(), 100);
367 assert_eq!(js_metadata.default_right_context_id(), 200);
368 assert_eq!(js_metadata.default_field_value(), "-");
369 assert!(js_metadata.flexible_csv());
370 assert!(js_metadata.skip_invalid_cost_or_id());
371 assert!(js_metadata.normalize_details());
372 }
373
374 #[test]
375 fn test_js_metadata_roundtrip() {
376 let original = JsMetadata::new(None);
377 let lindera: Metadata = original.into();
378 let roundtripped: JsMetadata = lindera.into();
379 assert_eq!(roundtripped.name(), "default");
380 assert_eq!(roundtripped.encoding(), "UTF-8");
381 assert_eq!(roundtripped.default_word_cost(), -10000);
382 assert_eq!(roundtripped.default_left_context_id(), 1288);
383 assert_eq!(roundtripped.default_right_context_id(), 1288);
384 }
385}