1use lindera::dictionary::{FieldDefinition, FieldType, Schema};
8use lindera_binding_core::{CoreFieldDefinition, CoreFieldType, CoreSchema};
9
10use crate::error::to_napi_error;
11
12#[napi(string_enum)]
16pub enum JsFieldType {
17 Surface,
19 LeftContextId,
21 RightContextId,
23 Cost,
25 Custom,
27}
28
29impl From<CoreFieldType> for JsFieldType {
30 fn from(field_type: CoreFieldType) -> Self {
31 match field_type {
32 CoreFieldType::Surface => JsFieldType::Surface,
33 CoreFieldType::LeftContextId => JsFieldType::LeftContextId,
34 CoreFieldType::RightContextId => JsFieldType::RightContextId,
35 CoreFieldType::Cost => JsFieldType::Cost,
36 CoreFieldType::Custom => JsFieldType::Custom,
37 }
38 }
39}
40
41impl From<JsFieldType> for CoreFieldType {
42 fn from(field_type: JsFieldType) -> Self {
43 match field_type {
44 JsFieldType::Surface => CoreFieldType::Surface,
45 JsFieldType::LeftContextId => CoreFieldType::LeftContextId,
46 JsFieldType::RightContextId => CoreFieldType::RightContextId,
47 JsFieldType::Cost => CoreFieldType::Cost,
48 JsFieldType::Custom => CoreFieldType::Custom,
49 }
50 }
51}
52
53impl From<FieldType> for JsFieldType {
54 fn from(field_type: FieldType) -> Self {
55 JsFieldType::from(CoreFieldType::from(field_type))
56 }
57}
58
59impl From<JsFieldType> for FieldType {
60 fn from(field_type: JsFieldType) -> Self {
61 FieldType::from(CoreFieldType::from(field_type))
62 }
63}
64
65#[napi(object)]
69pub struct JsFieldDefinition {
70 pub index: u32,
72 pub name: String,
74 pub field_type: JsFieldType,
76 pub description: Option<String>,
78}
79
80impl From<CoreFieldDefinition> for JsFieldDefinition {
81 fn from(field_def: CoreFieldDefinition) -> Self {
82 JsFieldDefinition {
83 index: field_def.index as u32,
84 name: field_def.name,
85 field_type: field_def.field_type.into(),
86 description: field_def.description,
87 }
88 }
89}
90
91impl From<JsFieldDefinition> for CoreFieldDefinition {
92 fn from(field_def: JsFieldDefinition) -> Self {
93 CoreFieldDefinition {
94 index: field_def.index as usize,
95 name: field_def.name,
96 field_type: field_def.field_type.into(),
97 description: field_def.description,
98 }
99 }
100}
101
102impl From<FieldDefinition> for JsFieldDefinition {
103 fn from(field_def: FieldDefinition) -> Self {
104 JsFieldDefinition::from(CoreFieldDefinition::from(field_def))
105 }
106}
107
108impl From<JsFieldDefinition> for FieldDefinition {
109 fn from(field_def: JsFieldDefinition) -> Self {
110 FieldDefinition::from(CoreFieldDefinition::from(field_def))
111 }
112}
113
114#[napi(js_name = "Schema")]
119pub struct JsSchema {
120 inner: CoreSchema,
122}
123
124#[napi]
125impl JsSchema {
126 #[napi(constructor)]
132 pub fn new(fields: Vec<String>) -> Self {
133 Self {
134 inner: CoreSchema::new(fields),
135 }
136 }
137
138 #[napi(factory)]
144 pub fn create_default() -> Self {
145 Self {
146 inner: CoreSchema::create_default(),
147 }
148 }
149
150 #[napi(getter)]
152 pub fn fields(&self) -> Vec<String> {
153 self.inner.fields().to_vec()
154 }
155
156 #[napi]
166 pub fn get_field_index(&self, field_name: String) -> Option<u32> {
167 self.inner.get_field_index(&field_name).map(|i| i as u32)
168 }
169
170 #[napi]
172 pub fn field_count(&self) -> u32 {
173 self.inner.field_count() as u32
174 }
175
176 #[napi]
186 pub fn get_field_name(&self, index: u32) -> Option<String> {
187 self.inner
188 .get_field_name(index as usize)
189 .map(str::to_string)
190 }
191
192 #[napi]
198 pub fn get_custom_fields(&self) -> Vec<String> {
199 self.inner.get_custom_fields().to_vec()
200 }
201
202 #[napi]
208 pub fn get_all_fields(&self) -> Vec<String> {
209 self.inner.fields().to_vec()
210 }
211
212 #[napi]
222 pub fn get_field_by_name(&self, name: String) -> Option<JsFieldDefinition> {
223 self.inner
224 .get_field_by_name(&name)
225 .map(JsFieldDefinition::from)
226 }
227
228 #[napi]
234 pub fn validate_record(&self, record: Vec<String>) -> napi::Result<()> {
235 self.inner.validate_record(&record).map_err(to_napi_error)
236 }
237}
238
239impl From<CoreSchema> for JsSchema {
240 fn from(schema: CoreSchema) -> Self {
241 JsSchema { inner: schema }
242 }
243}
244
245impl From<JsSchema> for CoreSchema {
246 fn from(schema: JsSchema) -> Self {
247 schema.inner
248 }
249}
250
251impl From<JsSchema> for Schema {
252 fn from(schema: JsSchema) -> Self {
253 schema.inner.into()
254 }
255}
256
257impl From<Schema> for JsSchema {
258 fn from(schema: Schema) -> Self {
259 JsSchema {
260 inner: CoreSchema::from(schema),
261 }
262 }
263}
264
265#[cfg(test)]
266mod tests {
267 use super::*;
268
269 #[test]
270 fn test_js_field_type_to_field_type_all_variants() {
271 assert!(matches!(
272 FieldType::from(JsFieldType::Surface),
273 FieldType::Surface
274 ));
275 assert!(matches!(
276 FieldType::from(JsFieldType::Custom),
277 FieldType::Custom
278 ));
279 }
280
281 #[test]
282 fn test_field_type_to_js_field_type_all_variants() {
283 assert!(matches!(
284 JsFieldType::from(FieldType::Surface),
285 JsFieldType::Surface
286 ));
287 assert!(matches!(
288 JsFieldType::from(FieldType::Custom),
289 JsFieldType::Custom
290 ));
291 }
292
293 #[test]
294 fn test_js_schema_new_builds_index_map() {
295 let schema = JsSchema::new(vec!["a".to_string(), "b".to_string(), "c".to_string()]);
296 assert_eq!(schema.get_field_index("a".to_string()), Some(0));
297 assert_eq!(schema.get_field_index("b".to_string()), Some(1));
298 assert_eq!(schema.get_field_index("c".to_string()), Some(2));
299 }
300
301 #[test]
302 fn test_js_schema_get_field_index_not_found() {
303 let schema = JsSchema::new(vec!["x".to_string()]);
304 assert_eq!(schema.get_field_index("y".to_string()), None);
305 }
306
307 #[test]
308 fn test_js_schema_field_count() {
309 let schema = JsSchema::new(vec!["a".to_string(), "b".to_string(), "c".to_string()]);
310 assert_eq!(schema.field_count(), 3);
311 }
312
313 #[test]
314 fn test_js_schema_get_field_name() {
315 let schema = JsSchema::new(vec!["a".to_string(), "b".to_string()]);
316 assert_eq!(schema.get_field_name(0), Some("a".to_string()));
317 assert_eq!(schema.get_field_name(9), None);
318 }
319
320 #[test]
321 fn test_js_schema_get_custom_fields() {
322 let schema = JsSchema::new(vec![
323 "surface".to_string(),
324 "left_context_id".to_string(),
325 "right_context_id".to_string(),
326 "cost".to_string(),
327 "pos1".to_string(),
328 "pos2".to_string(),
329 ]);
330 let custom = schema.get_custom_fields();
331 assert_eq!(custom, vec!["pos1".to_string(), "pos2".to_string()]);
332 }
333
334 #[test]
335 fn test_js_schema_get_custom_fields_no_custom() {
336 let schema = JsSchema::new(vec![
337 "surface".to_string(),
338 "left_context_id".to_string(),
339 "right_context_id".to_string(),
340 "cost".to_string(),
341 ]);
342 assert!(schema.get_custom_fields().is_empty());
343 }
344
345 #[test]
346 fn test_js_schema_create_default() {
347 let schema = JsSchema::create_default();
348 assert_eq!(schema.field_count(), 13);
349 assert_eq!(schema.get_field_index("surface".to_string()), Some(0));
350 assert_eq!(schema.fields()[5], "pos_detail_1");
351 assert_eq!(
352 schema.get_field_index("pronunciation".to_string()),
353 Some(12)
354 );
355 }
356
357 #[test]
358 fn test_js_schema_get_field_by_name() {
359 let schema = JsSchema::create_default();
360 let surface = schema.get_field_by_name("surface".to_string()).unwrap();
361 assert_eq!(surface.index, 0);
362 assert!(matches!(surface.field_type, JsFieldType::Surface));
363
364 let custom = schema
365 .get_field_by_name("pos_detail_1".to_string())
366 .unwrap();
367 assert_eq!(custom.index, 5);
368 assert!(matches!(custom.field_type, JsFieldType::Custom));
369
370 assert!(schema.get_field_by_name("nope".to_string()).is_none());
371 }
372
373 #[test]
374 fn test_js_schema_to_lindera_schema_roundtrip() {
375 let fields = vec![
376 "surface".to_string(),
377 "left_context_id".to_string(),
378 "right_context_id".to_string(),
379 "cost".to_string(),
380 "pos".to_string(),
381 ];
382 let js_schema = JsSchema::new(fields.clone());
383 let lindera_schema: Schema = js_schema.into();
384 let roundtripped: JsSchema = lindera_schema.into();
385 assert_eq!(roundtripped.field_count(), 5);
386 assert_eq!(roundtripped.get_field_index("pos".to_string()), Some(4));
387 }
388
389 #[test]
390 fn test_lindera_schema_to_js_schema() {
391 let lindera_schema = Schema::new(vec!["a".to_string(), "b".to_string()]);
392 let js_schema: JsSchema = lindera_schema.into();
393 assert_eq!(js_schema.field_count(), 2);
394 assert_eq!(js_schema.get_field_index("a".to_string()), Some(0));
395 }
396}