1use magnus::prelude::*;
8use magnus::{Error, RArray, Ruby, function, method};
9
10use lindera::dictionary::{FieldDefinition, FieldType, Schema};
11use lindera_binding_core::{CoreFieldDefinition, CoreFieldType, CoreSchema};
12
13#[magnus::wrap(class = "Lindera::FieldType", free_immediately, size)]
17#[derive(Debug, Clone)]
18pub struct RbFieldType {
19 inner: RbFieldTypeKind,
21}
22
23#[derive(Debug, Clone)]
25enum RbFieldTypeKind {
26 Surface,
28 LeftContextId,
30 RightContextId,
32 Cost,
34 Custom,
36}
37
38impl RbFieldType {
39 fn to_s(&self) -> &str {
41 match self.inner {
42 RbFieldTypeKind::Surface => "surface",
43 RbFieldTypeKind::LeftContextId => "left_context_id",
44 RbFieldTypeKind::RightContextId => "right_context_id",
45 RbFieldTypeKind::Cost => "cost",
46 RbFieldTypeKind::Custom => "custom",
47 }
48 }
49
50 fn inspect(&self) -> String {
52 format!("#<Lindera::FieldType: {}>", self.to_s())
53 }
54}
55
56impl From<CoreFieldType> for RbFieldType {
57 fn from(field_type: CoreFieldType) -> Self {
58 let inner = match field_type {
59 CoreFieldType::Surface => RbFieldTypeKind::Surface,
60 CoreFieldType::LeftContextId => RbFieldTypeKind::LeftContextId,
61 CoreFieldType::RightContextId => RbFieldTypeKind::RightContextId,
62 CoreFieldType::Cost => RbFieldTypeKind::Cost,
63 CoreFieldType::Custom => RbFieldTypeKind::Custom,
64 };
65 RbFieldType { inner }
66 }
67}
68
69impl From<RbFieldType> for CoreFieldType {
70 fn from(field_type: RbFieldType) -> Self {
71 match field_type.inner {
72 RbFieldTypeKind::Surface => CoreFieldType::Surface,
73 RbFieldTypeKind::LeftContextId => CoreFieldType::LeftContextId,
74 RbFieldTypeKind::RightContextId => CoreFieldType::RightContextId,
75 RbFieldTypeKind::Cost => CoreFieldType::Cost,
76 RbFieldTypeKind::Custom => CoreFieldType::Custom,
77 }
78 }
79}
80
81impl From<FieldType> for RbFieldType {
82 fn from(field_type: FieldType) -> Self {
83 RbFieldType::from(CoreFieldType::from(field_type))
84 }
85}
86
87impl From<RbFieldType> for FieldType {
88 fn from(field_type: RbFieldType) -> Self {
89 FieldType::from(CoreFieldType::from(field_type))
90 }
91}
92
93#[magnus::wrap(class = "Lindera::FieldDefinition", free_immediately, size)]
97#[derive(Debug, Clone)]
98pub struct RbFieldDefinition {
99 pub index: usize,
101 pub name: String,
103 pub field_type: RbFieldType,
105 pub description: Option<String>,
107}
108
109impl RbFieldDefinition {
110 fn index(&self) -> usize {
112 self.index
113 }
114
115 fn name(&self) -> String {
117 self.name.clone()
118 }
119
120 fn field_type(&self) -> RbFieldType {
122 self.field_type.clone()
123 }
124
125 fn description(&self) -> Option<String> {
127 self.description.clone()
128 }
129
130 fn to_s(&self) -> String {
132 format!("FieldDefinition(index={}, name={})", self.index, self.name)
133 }
134
135 fn inspect(&self) -> String {
137 format!(
138 "#<Lindera::FieldDefinition: index={}, name='{}', field_type={:?}, description={:?}>",
139 self.index, self.name, self.field_type.inner, self.description
140 )
141 }
142}
143
144impl From<CoreFieldDefinition> for RbFieldDefinition {
145 fn from(field_def: CoreFieldDefinition) -> Self {
146 RbFieldDefinition {
147 index: field_def.index,
148 name: field_def.name,
149 field_type: field_def.field_type.into(),
150 description: field_def.description,
151 }
152 }
153}
154
155impl From<RbFieldDefinition> for CoreFieldDefinition {
156 fn from(field_def: RbFieldDefinition) -> Self {
157 CoreFieldDefinition {
158 index: field_def.index,
159 name: field_def.name,
160 field_type: field_def.field_type.into(),
161 description: field_def.description,
162 }
163 }
164}
165
166impl From<FieldDefinition> for RbFieldDefinition {
167 fn from(field_def: FieldDefinition) -> Self {
168 RbFieldDefinition::from(CoreFieldDefinition::from(field_def))
169 }
170}
171
172impl From<RbFieldDefinition> for FieldDefinition {
173 fn from(field_def: RbFieldDefinition) -> Self {
174 FieldDefinition::from(CoreFieldDefinition::from(field_def))
175 }
176}
177
178#[magnus::wrap(class = "Lindera::Schema", free_immediately, size)]
183#[derive(Debug, Clone)]
184pub struct RbSchema {
185 inner: CoreSchema,
187}
188
189impl RbSchema {
190 fn new(fields: Vec<String>) -> Self {
200 Self {
201 inner: CoreSchema::new(fields),
202 }
203 }
204
205 fn create_default() -> Self {
211 Self {
212 inner: CoreSchema::create_default(),
213 }
214 }
215
216 fn fields(&self) -> Vec<String> {
218 self.inner.fields().to_vec()
219 }
220
221 fn get_field_index(&self, field_name: String) -> Option<usize> {
231 self.inner.get_field_index(&field_name)
232 }
233
234 fn field_count(&self) -> usize {
240 self.inner.field_count()
241 }
242
243 fn get_field_name(&self, index: usize) -> Option<String> {
253 self.inner.get_field_name(index).map(str::to_string)
254 }
255
256 fn get_custom_fields(&self) -> Vec<String> {
262 self.inner.get_custom_fields().to_vec()
263 }
264
265 fn get_all_fields(&self) -> Vec<String> {
271 self.inner.fields().to_vec()
272 }
273
274 fn get_field_by_name(&self, name: String) -> Option<RbFieldDefinition> {
284 self.inner
285 .get_field_by_name(&name)
286 .map(RbFieldDefinition::from)
287 }
288
289 fn validate_record(&self, record: RArray) -> Result<(), Error> {
299 let ruby = Ruby::get().expect("Ruby runtime not initialized");
300 let values: Vec<String> = record.to_vec()?;
301
302 self.inner
303 .validate_record(&values)
304 .map_err(|err| Error::new(ruby.exception_arg_error(), err.to_string()))
305 }
306
307 fn to_s(&self) -> String {
309 format!("Schema(fields={})", self.inner.field_count())
310 }
311
312 fn inspect(&self) -> String {
314 format!("#<Lindera::Schema: fields={:?}>", self.inner.fields())
315 }
316}
317
318impl RbSchema {
319 pub fn new_internal(fields: Vec<String>) -> Self {
321 Self::new(fields)
322 }
323
324 pub fn create_default_internal() -> Self {
326 Self::create_default()
327 }
328}
329
330impl From<CoreSchema> for RbSchema {
331 fn from(schema: CoreSchema) -> Self {
332 RbSchema { inner: schema }
333 }
334}
335
336impl From<RbSchema> for CoreSchema {
337 fn from(schema: RbSchema) -> Self {
338 schema.inner
339 }
340}
341
342impl From<RbSchema> for Schema {
343 fn from(schema: RbSchema) -> Self {
344 schema.inner.into()
345 }
346}
347
348impl From<Schema> for RbSchema {
349 fn from(schema: Schema) -> Self {
350 RbSchema {
351 inner: CoreSchema::from(schema),
352 }
353 }
354}
355
356pub fn define(ruby: &Ruby, module: &magnus::RModule) -> Result<(), Error> {
367 let field_type_class = module.define_class("FieldType", ruby.class_object())?;
368 field_type_class.define_method("to_s", method!(RbFieldType::to_s, 0))?;
369 field_type_class.define_method("inspect", method!(RbFieldType::inspect, 0))?;
370
371 let field_def_class = module.define_class("FieldDefinition", ruby.class_object())?;
372 field_def_class.define_method("index", method!(RbFieldDefinition::index, 0))?;
373 field_def_class.define_method("name", method!(RbFieldDefinition::name, 0))?;
374 field_def_class.define_method("field_type", method!(RbFieldDefinition::field_type, 0))?;
375 field_def_class.define_method("description", method!(RbFieldDefinition::description, 0))?;
376 field_def_class.define_method("to_s", method!(RbFieldDefinition::to_s, 0))?;
377 field_def_class.define_method("inspect", method!(RbFieldDefinition::inspect, 0))?;
378
379 let schema_class = module.define_class("Schema", ruby.class_object())?;
380 schema_class.define_singleton_method("new", function!(RbSchema::new, 1))?;
381 schema_class
382 .define_singleton_method("create_default", function!(RbSchema::create_default, 0))?;
383 schema_class.define_method("fields", method!(RbSchema::fields, 0))?;
384 schema_class.define_method("get_field_index", method!(RbSchema::get_field_index, 1))?;
385 schema_class.define_method("field_count", method!(RbSchema::field_count, 0))?;
386 schema_class.define_method("get_field_name", method!(RbSchema::get_field_name, 1))?;
387 schema_class.define_method("get_custom_fields", method!(RbSchema::get_custom_fields, 0))?;
388 schema_class.define_method("get_all_fields", method!(RbSchema::get_all_fields, 0))?;
389 schema_class.define_method("get_field_by_name", method!(RbSchema::get_field_by_name, 1))?;
390 schema_class.define_method("validate_record", method!(RbSchema::validate_record, 1))?;
391 schema_class.define_method("to_s", method!(RbSchema::to_s, 0))?;
392 schema_class.define_method("inspect", method!(RbSchema::inspect, 0))?;
393
394 Ok(())
395}
396
397#[cfg(test)]
398mod tests {
399 use super::*;
400
401 #[test]
402 fn test_rb_field_type_surface_to_lindera() {
403 let rb = RbFieldType {
404 inner: RbFieldTypeKind::Surface,
405 };
406 let lindera: FieldType = rb.into();
407 assert!(matches!(lindera, FieldType::Surface));
408 }
409
410 #[test]
411 fn test_rb_field_type_custom_to_lindera() {
412 let rb = RbFieldType {
413 inner: RbFieldTypeKind::Custom,
414 };
415 let lindera: FieldType = rb.into();
416 assert!(matches!(lindera, FieldType::Custom));
417 }
418
419 #[test]
420 fn test_lindera_field_type_surface_to_rb() {
421 let rb: RbFieldType = FieldType::Surface.into();
422 assert!(matches!(rb.inner, RbFieldTypeKind::Surface));
423 }
424
425 #[test]
426 fn test_lindera_field_type_custom_to_rb() {
427 let rb: RbFieldType = FieldType::Custom.into();
428 assert!(matches!(rb.inner, RbFieldTypeKind::Custom));
429 }
430
431 #[test]
432 fn test_rb_schema_new_builds_index_map() {
433 let fields = vec!["a".to_string(), "b".to_string(), "c".to_string()];
434 let schema = RbSchema::new_internal(fields);
435 assert_eq!(schema.get_field_index("a".to_string()), Some(0));
436 assert_eq!(schema.get_field_index("b".to_string()), Some(1));
437 assert_eq!(schema.get_field_index("c".to_string()), Some(2));
438 assert_eq!(schema.get_field_index("d".to_string()), None);
439 }
440
441 #[test]
442 fn test_rb_schema_field_count() {
443 let fields = vec!["x".to_string(), "y".to_string()];
444 let schema = RbSchema::new_internal(fields);
445 assert_eq!(schema.field_count(), 2);
446 }
447
448 #[test]
449 fn test_rb_schema_get_custom_fields_with_more_than_4() {
450 let fields = vec![
451 "surface".to_string(),
452 "left_context_id".to_string(),
453 "right_context_id".to_string(),
454 "cost".to_string(),
455 "major_pos".to_string(),
456 "reading".to_string(),
457 ];
458 let schema = RbSchema::new_internal(fields);
459 let custom = schema.get_custom_fields();
460 assert_eq!(custom, vec!["major_pos", "reading"]);
461 }
462
463 #[test]
464 fn test_rb_schema_get_custom_fields_with_4_or_fewer() {
465 let fields = vec![
466 "surface".to_string(),
467 "left_context_id".to_string(),
468 "right_context_id".to_string(),
469 "cost".to_string(),
470 ];
471 let schema = RbSchema::new_internal(fields);
472 assert!(schema.get_custom_fields().is_empty());
473 }
474
475 #[test]
476 fn test_rb_schema_create_default() {
477 let schema = RbSchema::create_default_internal();
478 assert_eq!(schema.field_count(), 13);
479 assert_eq!(schema.fields()[0], "surface");
480 assert_eq!(schema.fields()[3], "cost");
481 assert_eq!(schema.fields()[5], "pos_detail_1");
482 }
483
484 #[test]
485 fn test_rb_schema_get_field_by_name() {
486 let schema = RbSchema::create_default_internal();
487 let surface = schema.get_field_by_name("surface".to_string()).unwrap();
488 assert_eq!(surface.index, 0);
489 assert!(matches!(surface.field_type.inner, RbFieldTypeKind::Surface));
490
491 let custom = schema
492 .get_field_by_name("pos_detail_1".to_string())
493 .unwrap();
494 assert_eq!(custom.index, 5);
495 assert!(matches!(custom.field_type.inner, RbFieldTypeKind::Custom));
496
497 assert!(schema.get_field_by_name("nope".to_string()).is_none());
498 }
499
500 #[test]
501 fn test_rb_schema_to_lindera_schema() {
502 let fields = vec!["a".to_string(), "b".to_string(), "c".to_string()];
503 let rb_schema = RbSchema::new_internal(fields.clone());
504 let lindera_schema: Schema = rb_schema.into();
505 assert_eq!(lindera_schema.get_all_fields(), &fields);
506 }
507
508 #[test]
509 fn test_lindera_schema_to_rb_schema() {
510 let fields = vec!["x".to_string(), "y".to_string(), "z".to_string()];
511 let lindera_schema = Schema::new(fields.clone());
512 let rb_schema: RbSchema = lindera_schema.into();
513 assert_eq!(rb_schema.fields(), fields);
514 assert_eq!(rb_schema.get_field_index("x".to_string()), Some(0));
515 assert_eq!(rb_schema.get_field_index("z".to_string()), Some(2));
516 }
517
518 #[test]
519 fn test_rb_schema_roundtrip() {
520 let fields = vec![
521 "surface".to_string(),
522 "left_context_id".to_string(),
523 "right_context_id".to_string(),
524 "cost".to_string(),
525 "reading".to_string(),
526 ];
527 let rb_schema = RbSchema::new_internal(fields.clone());
528 let lindera_schema: Schema = rb_schema.into();
529 let back: RbSchema = lindera_schema.into();
530 assert_eq!(back.fields(), fields);
531 assert_eq!(back.field_count(), 5);
532 }
533
534 #[test]
535 fn test_rb_field_definition_to_lindera() {
536 let rb_def = RbFieldDefinition {
537 index: 2,
538 name: "right_context_id".to_string(),
539 field_type: RbFieldType {
540 inner: RbFieldTypeKind::RightContextId,
541 },
542 description: Some("Right context ID".to_string()),
543 };
544 let lindera_def: FieldDefinition = rb_def.into();
545 assert_eq!(lindera_def.index, 2);
546 assert_eq!(lindera_def.name, "right_context_id");
547 assert!(matches!(lindera_def.field_type, FieldType::RightContextId));
548 assert_eq!(
549 lindera_def.description,
550 Some("Right context ID".to_string())
551 );
552 }
553
554 #[test]
555 fn test_lindera_field_definition_to_rb() {
556 let lindera_def = FieldDefinition {
557 index: 4,
558 name: "major_pos".to_string(),
559 field_type: FieldType::Custom,
560 description: None,
561 };
562 let rb_def: RbFieldDefinition = lindera_def.into();
563 assert_eq!(rb_def.index, 4);
564 assert_eq!(rb_def.name, "major_pos");
565 assert!(matches!(rb_def.field_type.inner, RbFieldTypeKind::Custom));
566 assert!(rb_def.description.is_none());
567 }
568}