artichoke_backend/convert/
hash.rs

1use std::collections::HashMap;
2
3use crate::Artichoke;
4use crate::convert::{BoxUnboxVmValue, UnboxRubyError};
5use crate::core::{TryConvertMut, Value as _};
6use crate::error::Error;
7use crate::extn::core::array::Array;
8use crate::sys;
9use crate::types::{Ruby, Rust};
10use crate::value::Value;
11
12impl TryConvertMut<Vec<(Value, Value)>, Value> for Artichoke {
13    type Error = Error;
14
15    fn try_convert_mut(&mut self, value: Vec<(Value, Value)>) -> Result<Value, Self::Error> {
16        let capa = sys::mrb_int::try_from(value.len()).unwrap_or_default();
17
18        let hash = unsafe { self.with_ffi_boundary(|mrb| sys::mrb_hash_new_capa(mrb, capa))? };
19        let hash = self.protect(Value::from(hash));
20
21        for (key, val) in value {
22            let key = key.inner();
23            let val = val.inner();
24            unsafe {
25                self.with_ffi_boundary(|mrb| sys::mrb_hash_set(mrb, hash.inner(), key, val))?;
26            }
27        }
28        Ok(hash)
29    }
30}
31
32impl TryConvertMut<Vec<(Vec<u8>, Vec<i64>)>, Value> for Artichoke {
33    type Error = Error;
34
35    fn try_convert_mut(&mut self, value: Vec<(Vec<u8>, Vec<i64>)>) -> Result<Value, Self::Error> {
36        let capa = sys::mrb_int::try_from(value.len()).unwrap_or_default();
37
38        let hash = unsafe { self.with_ffi_boundary(|mrb| sys::mrb_hash_new_capa(mrb, capa))? };
39        let hash = self.protect(Value::from(hash));
40
41        for (key, val) in value {
42            let key = self.try_convert_mut(key)?;
43            let val = self.try_convert_mut(val)?;
44            unsafe {
45                self.with_ffi_boundary(|mrb| sys::mrb_hash_set(mrb, hash.inner(), key.inner(), val.inner()))?;
46            }
47        }
48        Ok(hash)
49    }
50}
51
52impl TryConvertMut<HashMap<Vec<u8>, Vec<u8>>, Value> for Artichoke {
53    type Error = Error;
54
55    fn try_convert_mut(&mut self, value: HashMap<Vec<u8>, Vec<u8>>) -> Result<Value, Self::Error> {
56        let capa = sys::mrb_int::try_from(value.len()).unwrap_or_default();
57
58        let hash = unsafe { self.with_ffi_boundary(|mrb| sys::mrb_hash_new_capa(mrb, capa))? };
59        let hash = self.protect(Value::from(hash));
60
61        for (key, val) in value {
62            let key = self.try_convert_mut(key)?;
63            let val = self.try_convert_mut(val)?;
64            unsafe {
65                self.with_ffi_boundary(|mrb| sys::mrb_hash_set(mrb, hash.inner(), key.inner(), val.inner()))?;
66            }
67        }
68        Ok(hash)
69    }
70}
71
72impl TryConvertMut<Option<HashMap<Vec<u8>, Option<Vec<u8>>>>, Value> for Artichoke {
73    type Error = Error;
74
75    fn try_convert_mut(&mut self, value: Option<HashMap<Vec<u8>, Option<Vec<u8>>>>) -> Result<Value, Self::Error> {
76        let Some(value) = value else {
77            return Ok(Value::nil());
78        };
79        let capa = sys::mrb_int::try_from(value.len()).unwrap_or_default();
80
81        let hash = unsafe { self.with_ffi_boundary(|mrb| sys::mrb_hash_new_capa(mrb, capa))? };
82        let hash = self.protect(Value::from(hash));
83
84        for (key, val) in value {
85            let key = self.try_convert_mut(key)?;
86            let val = self.try_convert_mut(val)?;
87            unsafe {
88                self.with_ffi_boundary(|mrb| sys::mrb_hash_set(mrb, hash.inner(), key.inner(), val.inner()))?;
89            }
90        }
91        Ok(hash)
92    }
93}
94
95impl TryConvertMut<Value, Vec<(Value, Value)>> for Artichoke {
96    type Error = Error;
97
98    fn try_convert_mut(&mut self, value: Value) -> Result<Vec<(Value, Value)>, Self::Error> {
99        let Ruby::Hash = value.ruby_type() else {
100            return Err(UnboxRubyError::new(&value, Rust::Map).into());
101        };
102        let hash = value.inner();
103        let keys = unsafe { self.with_ffi_boundary(|mrb| sys::mrb_hash_keys(mrb, hash))? };
104
105        let mut keys = self.protect(Value::from(keys));
106        let array = unsafe { Array::unbox_from_value(&mut keys, self) }?;
107
108        let mut pairs = Vec::with_capacity(array.len());
109        for key in &*array {
110            let value = unsafe { self.with_ffi_boundary(|mrb| sys::mrb_hash_get(mrb, hash, key.inner()))? };
111            pairs.push((key, self.protect(Value::from(value))));
112        }
113        Ok(pairs)
114    }
115}
116
117#[cfg(test)]
118mod tests {
119    use std::collections::HashMap;
120
121    use crate::test::prelude::*;
122
123    #[test]
124    fn prop_roundtrip_kv() {
125        let mut interp = interpreter();
126        run_arbitrary::<HashMap<Vec<u8>, Vec<u8>>>(|hash| {
127            let value = interp.try_convert_mut(hash.clone()).unwrap();
128            let len = value.funcall(&mut interp, "length", &[], None).unwrap();
129            let len = len.try_convert_into::<usize>(&interp).unwrap();
130            assert_eq!(len, hash.len());
131
132            let recovered = value.try_convert_into_mut::<Vec<(Value, Value)>>(&mut interp).unwrap();
133            assert_eq!(recovered.len(), hash.len());
134
135            for (key, val) in recovered {
136                let key = key.try_convert_into_mut::<Vec<u8>>(&mut interp).unwrap();
137                let val = val.try_convert_into_mut::<Vec<u8>>(&mut interp).unwrap();
138                assert_eq!(hash.get(&key), Some(&val));
139            }
140        });
141    }
142}