artichoke_backend/convert/
nilable.rs

1//! Converters for nilable primitive Ruby types.
2//!
3//! Excludes collection types Array and Hash.
4
5use crate::Artichoke;
6use crate::core::{Convert, TryConvert, TryConvertMut, Value as _};
7use crate::error::Error;
8use crate::value::Value;
9
10impl Convert<Option<Value>, Value> for Artichoke {
11    fn convert(&self, value: Option<Value>) -> Value {
12        Value::from(value)
13    }
14}
15
16impl Convert<Option<i64>, Value> for Artichoke {
17    fn convert(&self, value: Option<i64>) -> Value {
18        let Some(value) = value else {
19            return Value::nil();
20        };
21        self.convert(value)
22    }
23}
24
25impl TryConvert<Option<usize>, Value> for Artichoke {
26    type Error = Error;
27
28    fn try_convert(&self, value: Option<usize>) -> Result<Value, Self::Error> {
29        let Some(value) = value else {
30            return Ok(Value::nil());
31        };
32        self.try_convert(value)
33    }
34}
35
36impl TryConvertMut<Option<Vec<u8>>, Value> for Artichoke {
37    type Error = Error;
38
39    fn try_convert_mut(&mut self, value: Option<Vec<u8>>) -> Result<Value, Self::Error> {
40        self.try_convert_mut(value.as_deref())
41    }
42}
43
44impl TryConvertMut<Option<&[u8]>, Value> for Artichoke {
45    type Error = Error;
46
47    fn try_convert_mut(&mut self, value: Option<&[u8]>) -> Result<Value, Self::Error> {
48        let Some(value) = value else {
49            return Ok(Value::nil());
50        };
51        self.try_convert_mut(value)
52    }
53}
54
55impl TryConvertMut<Option<String>, Value> for Artichoke {
56    type Error = Error;
57
58    fn try_convert_mut(&mut self, value: Option<String>) -> Result<Value, Self::Error> {
59        self.try_convert_mut(value.as_deref())
60    }
61}
62
63impl TryConvertMut<Option<&str>, Value> for Artichoke {
64    type Error = Error;
65
66    fn try_convert_mut(&mut self, value: Option<&str>) -> Result<Value, Self::Error> {
67        let Some(value) = value else {
68            return Ok(Value::nil());
69        };
70        self.try_convert_mut(value)
71    }
72}
73
74impl Convert<Value, Option<Value>> for Artichoke {
75    fn convert(&self, value: Value) -> Option<Value> {
76        if value.is_nil() { None } else { Some(value) }
77    }
78}
79
80impl TryConvertMut<Value, Option<Vec<u8>>> for Artichoke {
81    type Error = Error;
82
83    fn try_convert_mut(&mut self, value: Value) -> Result<Option<Vec<u8>>, Self::Error> {
84        if value.is_nil() {
85            return Ok(None);
86        }
87        self.try_convert_mut(value).map(Some)
88    }
89}
90
91impl<'a> TryConvertMut<Value, Option<&'a [u8]>> for Artichoke {
92    type Error = Error;
93
94    fn try_convert_mut(&mut self, value: Value) -> Result<Option<&'a [u8]>, Self::Error> {
95        if value.is_nil() {
96            return Ok(None);
97        }
98        self.try_convert_mut(value).map(Some)
99    }
100}
101
102impl TryConvertMut<Value, Option<String>> for Artichoke {
103    type Error = Error;
104
105    fn try_convert_mut(&mut self, value: Value) -> Result<Option<String>, Self::Error> {
106        if value.is_nil() {
107            return Ok(None);
108        }
109        self.try_convert_mut(value).map(Some)
110    }
111}
112
113impl<'a> TryConvertMut<Value, Option<&'a str>> for Artichoke {
114    type Error = Error;
115
116    fn try_convert_mut(&mut self, value: Value) -> Result<Option<&'a str>, Self::Error> {
117        if value.is_nil() {
118            return Ok(None);
119        }
120        self.try_convert_mut(value).map(Some)
121    }
122}
123
124impl TryConvert<Value, Option<i64>> for Artichoke {
125    type Error = Error;
126
127    fn try_convert(&self, value: Value) -> Result<Option<i64>, Self::Error> {
128        if value.is_nil() {
129            return Ok(None);
130        }
131        self.try_convert(value).map(Some)
132    }
133}
134
135#[cfg(test)]
136mod tests {
137    use crate::core::{Convert, TryConvert, TryConvertMut};
138    use crate::test::prelude::*;
139    use crate::value::Value;
140
141    #[test]
142    fn convert_option_value_some_returns_the_inner_value() {
143        let interp = interpreter();
144        let inner = interp.convert(42_i64);
145        let value = interp.convert(Some(inner));
146        // Ensure the converted value is not nil.
147        assert!(!value.is_nil());
148        // Roundtrip: convert back to i64.
149        let num = value.try_convert_into::<i64>(&interp).unwrap();
150        assert_eq!(num, 42);
151    }
152
153    #[test]
154    fn convert_option_value_none_returns_nil() {
155        let interp = interpreter();
156        let value = interp.convert(None::<Value>);
157        assert!(value.is_nil());
158    }
159
160    #[test]
161    fn convert_value_to_option_value_non_nil_returns_some() {
162        let interp = interpreter();
163        let value = interp.convert(789_i64);
164        let opt: Option<Value> = interp.convert(value);
165        assert!(opt.is_some());
166        let num = opt.unwrap().try_convert_into::<i64>(&interp).unwrap();
167        assert_eq!(num, 789);
168    }
169
170    #[test]
171    fn convert_value_to_option_value_nil_returns_none() {
172        let interp = interpreter();
173        let nil_value = Value::nil();
174        let opt: Option<Value> = interp.convert(nil_value);
175        assert!(opt.is_none());
176    }
177
178    // --- Tests for Option<i64> -> Value ---
179
180    #[test]
181    fn convert_option_i64_some_converts_to_fixnum() {
182        let interp = interpreter();
183        let value = interp.convert(Some(123_i64));
184        let num = value.try_convert_into::<i64>(&interp).unwrap();
185        assert_eq!(num, 123);
186    }
187
188    #[test]
189    fn convert_option_i64_none_converts_to_nil() {
190        let interp = interpreter();
191        let value = interp.convert(None::<i64>);
192        assert!(value.is_nil());
193    }
194
195    // --- Tests for Option<usize> -> Value via TryConvert ---
196
197    #[test]
198    fn try_convert_option_usize_some_converts_to_fixnum() {
199        let interp = interpreter();
200        let value = interp.try_convert(Some(456_usize)).unwrap();
201        let num: usize = value.try_convert_into(&interp).unwrap();
202        assert_eq!(num, 456);
203    }
204
205    #[test]
206    fn try_convert_option_usize_none_converts_to_nil() {
207        let interp = interpreter();
208        let value = interp.try_convert(None::<usize>).unwrap();
209        assert!(value.is_nil());
210    }
211
212    // --- Tests for Option<Vec<u8>> -> Value via TryConvertMut ---
213
214    #[test]
215    fn try_convert_mut_option_vec_u8_some_converts_to_string() {
216        let mut interp = interpreter();
217        let input = b"hello".to_vec();
218        let value = interp.try_convert_mut(Some(input.clone())).unwrap();
219        // Convert the resulting Ruby value back to String.
220        let result = value.try_convert_into_mut::<Option<String>>(&mut interp).unwrap();
221        assert_eq!(result, Some(String::from("hello")));
222    }
223
224    #[test]
225    fn try_convert_mut_option_vec_u8_none_converts_to_nil() {
226        let mut interp = interpreter();
227        let value = interp.try_convert_mut(None::<Vec<u8>>).unwrap();
228        assert!(value.is_nil());
229    }
230
231    // --- Tests for Option<&[u8]> -> Value via TryConvertMut ---
232
233    #[test]
234    fn try_convert_mut_option_slice_u8_some_converts_to_string() {
235        let mut interp = interpreter();
236        let input: &[u8] = b"world";
237        let value = interp.try_convert_mut(Some(input)).unwrap();
238        let result = value.try_convert_into_mut::<Option<String>>(&mut interp).unwrap();
239        assert_eq!(result, Some(String::from("world")));
240    }
241
242    #[test]
243    fn try_convert_mut_option_slice_u8_none_converts_to_nil() {
244        let mut interp = interpreter();
245        let value = interp.try_convert_mut(None::<&[u8]>).unwrap();
246        assert!(value.is_nil());
247    }
248
249    #[test]
250    fn try_convert_mut_option_string_some_converts_to_string() {
251        let mut interp = interpreter();
252        let input = String::from("artichoke");
253        let value = interp.try_convert_mut(Some(input.clone())).unwrap();
254        let result = value.try_convert_into_mut::<Option<String>>(&mut interp).unwrap();
255        assert_eq!(result, Some(input));
256    }
257
258    #[test]
259    fn try_convert_mut_option_string_none_converts_to_nil() {
260        let mut interp = interpreter();
261        let value = interp.try_convert_mut(None::<String>).unwrap();
262        assert!(value.is_nil());
263    }
264
265    // --- Tests for Option<&str> -> Value via TryConvertMut ---
266
267    #[test]
268    fn try_convert_mut_option_str_some_converts_to_string() {
269        let mut interp = interpreter();
270        let input = "convert me";
271        let value = interp.try_convert_mut(Some(input)).unwrap();
272        let result = value.try_convert_into_mut::<Option<String>>(&mut interp).unwrap();
273        assert_eq!(result, Some(input.to_owned()));
274    }
275
276    #[test]
277    fn try_convert_mut_option_str_none_converts_to_nil() {
278        let mut interp = interpreter();
279        let value = interp.try_convert_mut(None::<&str>).unwrap();
280        assert!(value.is_nil());
281    }
282
283    // --- Tests for Value -> Option<Vec<u8>> via TryConvertMut ---
284
285    #[test]
286    fn try_convert_mut_value_to_option_vec_u8_non_nil_returns_some() {
287        let mut interp = interpreter();
288        let value = interp.try_convert_mut("hello world").unwrap();
289        let result = value.try_convert_into_mut::<Option<Vec<u8>>>(&mut interp).unwrap();
290        let s = String::from_utf8(result.unwrap()).unwrap();
291        assert_eq!(s, "hello world");
292    }
293
294    #[test]
295    fn try_convert_mut_value_to_option_vec_u8_nil_returns_none() {
296        let mut interp = interpreter();
297        let value = Value::nil();
298        let result = value.try_convert_into_mut::<Option<Vec<u8>>>(&mut interp).unwrap();
299        assert!(result.is_none());
300    }
301
302    // --- Tests for Value -> Option<&[u8]> via TryConvertMut ---
303
304    #[test]
305    fn try_convert_mut_value_to_option_slice_u8_non_nil_returns_some() {
306        let mut interp = interpreter();
307        let value = interp.try_convert_mut("slice-test").unwrap();
308        let result = value.try_convert_into_mut::<Option<&[u8]>>(&mut interp).unwrap();
309        let s = String::from_utf8(result.unwrap().to_vec()).unwrap();
310        assert_eq!(s, "slice-test");
311    }
312
313    #[test]
314    fn try_convert_mut_value_to_option_slice_u8_nil_returns_none() {
315        let mut interp = interpreter();
316        let value = Value::nil();
317        let result = value.try_convert_into_mut::<Option<&[u8]>>(&mut interp).unwrap();
318        assert!(result.is_none());
319    }
320
321    // --- Tests for Value -> Option<String> via TryConvertMut ---
322
323    #[test]
324    fn try_convert_mut_value_to_option_string_non_nil_returns_some() {
325        let mut interp = interpreter();
326        let value = interp.try_convert_mut("string-test").unwrap();
327        let result = value.try_convert_into_mut::<Option<String>>(&mut interp).unwrap();
328        assert_eq!(result, Some(String::from("string-test")));
329    }
330
331    #[test]
332    fn try_convert_mut_value_to_option_string_nil_returns_none() {
333        let mut interp = interpreter();
334        let value = Value::nil();
335        let result = value.try_convert_into_mut::<Option<String>>(&mut interp).unwrap();
336        assert!(result.is_none());
337    }
338
339    #[test]
340    fn try_convert_mut_value_to_option_str_non_nil_returns_some() {
341        let mut interp = interpreter();
342        let value = interp.try_convert_mut("str-test").unwrap();
343        let result = value.try_convert_into_mut::<Option<&str>>(&mut interp).unwrap();
344        assert_eq!(result, Some("str-test"));
345    }
346
347    #[test]
348    fn try_convert_mut_value_to_option_str_nil_returns_none() {
349        let mut interp = interpreter();
350        let value = Value::nil();
351        let result = value.try_convert_into_mut::<Option<&str>>(&mut interp).unwrap();
352        assert!(result.is_none());
353    }
354
355    #[test]
356    fn try_convert_value_to_option_i64_non_nil_returns_some() {
357        let interp = interpreter();
358        let value = interp.convert(555i64);
359        let result = value.try_convert_into::<Option<i64>>(&interp).unwrap();
360        assert_eq!(result, Some(555));
361    }
362
363    #[test]
364    fn try_convert_value_to_option_i64_nil_returns_none() {
365        let interp = interpreter();
366        let value = Value::nil();
367        let result = value.try_convert_into::<Option<i64>>(&interp).unwrap();
368        assert!(result.is_none());
369    }
370}