Skip to main content

lindera_ruby/
util.rs

1//! Utility functions for Ruby-Rust data conversion.
2//!
3//! This module provides helper functions for converting between Ruby objects
4//! and Rust data structures, particularly for working with JSON-like data.
5
6use magnus::prelude::*;
7use magnus::{Error, RArray, RHash, Ruby, TryConvert, Value};
8use serde_json::json;
9
10/// Converts a Ruby value to a serde_json::Value.
11///
12/// # Arguments
13///
14/// * `ruby` - Ruby runtime handle.
15/// * `value` - Ruby value to convert.
16///
17/// # Returns
18///
19/// A `serde_json::Value` representing the Ruby value.
20///
21/// # Errors
22///
23/// Returns an error if the Ruby value type is not supported.
24pub fn rb_value_to_json(ruby: &Ruby, value: Value) -> Result<serde_json::Value, Error> {
25    if value.is_nil() {
26        Ok(serde_json::Value::Null)
27    } else if value.is_kind_of(ruby.class_true_class())
28        || value.is_kind_of(ruby.class_false_class())
29    {
30        let b: bool = TryConvert::try_convert(value).map_err(|e| {
31            Error::new(
32                ruby.exception_type_error(),
33                format!("Failed to convert boolean: {e}"),
34            )
35        })?;
36        Ok(serde_json::Value::Bool(b))
37    } else if value.is_kind_of(ruby.class_integer()) {
38        let i: i64 = TryConvert::try_convert(value).map_err(|e| {
39            Error::new(
40                ruby.exception_type_error(),
41                format!("Failed to convert integer: {e}"),
42            )
43        })?;
44        Ok(serde_json::Value::from(i))
45    } else if value.is_kind_of(ruby.class_float()) {
46        let f: f64 = TryConvert::try_convert(value).map_err(|e| {
47            Error::new(
48                ruby.exception_type_error(),
49                format!("Failed to convert float: {e}"),
50            )
51        })?;
52        Ok(json!(f))
53    } else if value.is_kind_of(ruby.class_string()) || value.is_kind_of(ruby.class_symbol()) {
54        let s: String = TryConvert::try_convert(value).map_err(|e| {
55            Error::new(
56                ruby.exception_type_error(),
57                format!("Failed to convert string: {e}"),
58            )
59        })?;
60        Ok(serde_json::Value::String(s))
61    } else if let Ok(arr) = RArray::try_convert(value) {
62        rb_array_to_json(ruby, arr)
63    } else if let Ok(hash) = RHash::try_convert(value) {
64        rb_hash_to_json(ruby, hash)
65    } else {
66        Err(Error::new(
67            ruby.exception_type_error(),
68            format!("Unsupported Ruby object type: {}", unsafe {
69                value.classname()
70            }),
71        ))
72    }
73}
74
75/// Converts a Ruby array to a serde_json::Value::Array.
76///
77/// # Arguments
78///
79/// * `ruby` - Ruby runtime handle.
80/// * `array` - Ruby array to convert.
81///
82/// # Returns
83///
84/// A `serde_json::Value` representing the array.
85fn rb_array_to_json(ruby: &Ruby, array: RArray) -> Result<serde_json::Value, Error> {
86    let mut vec = Vec::new();
87    for item in array.into_iter() {
88        vec.push(rb_value_to_json(ruby, item)?);
89    }
90    Ok(serde_json::Value::Array(vec))
91}
92
93/// Converts a Ruby hash to a serde_json::Value::Object.
94///
95/// # Arguments
96///
97/// * `ruby` - Ruby runtime handle.
98/// * `hash` - Ruby hash to convert.
99///
100/// # Returns
101///
102/// A `serde_json::Value` representing the hash.
103pub fn rb_hash_to_json(ruby: &Ruby, hash: RHash) -> Result<serde_json::Value, Error> {
104    let mut map = serde_json::Map::new();
105    hash.foreach(|key: String, value: Value| {
106        let json_value = rb_value_to_json(ruby, value)?;
107        map.insert(key, json_value);
108        Ok(magnus::r_hash::ForEach::Continue)
109    })?;
110    Ok(serde_json::Value::Object(map))
111}
112
113/// Converts a serde_json::Value to a Ruby value.
114///
115/// # Arguments
116///
117/// * `ruby` - Ruby runtime handle.
118/// * `value` - JSON value to convert.
119///
120/// # Returns
121///
122/// A Ruby `Value` representing the JSON value.
123pub fn json_to_rb_value(ruby: &Ruby, value: &serde_json::Value) -> Result<Value, Error> {
124    match value {
125        serde_json::Value::Null => Ok(ruby.qnil().as_value()),
126        serde_json::Value::Bool(b) => Ok(if *b {
127            ruby.qtrue().as_value()
128        } else {
129            ruby.qfalse().as_value()
130        }),
131        serde_json::Value::Number(n) => {
132            if let Some(i) = n.as_i64() {
133                Ok(ruby.integer_from_i64(i).as_value())
134            } else if let Some(f) = n.as_f64() {
135                Ok(ruby.float_from_f64(f).as_value())
136            } else {
137                Err(Error::new(
138                    ruby.exception_type_error(),
139                    "Unsupported number type",
140                ))
141            }
142        }
143        serde_json::Value::String(s) => Ok(ruby.str_new(s).as_value()),
144        serde_json::Value::Array(arr) => {
145            let rb_arr = ruby.ary_new_capa(arr.len());
146            for item in arr {
147                rb_arr.push(json_to_rb_value(ruby, item)?)?;
148            }
149            Ok(rb_arr.as_value())
150        }
151        serde_json::Value::Object(obj) => {
152            let rb_hash = ruby.hash_new();
153            for (key, val) in obj {
154                rb_hash.aset(ruby.str_new(key), json_to_rb_value(ruby, val)?)?;
155            }
156            Ok(rb_hash.as_value())
157        }
158    }
159}