artichoke_backend/convert/
string.rs

1use std::borrow::Cow;
2use std::str;
3
4use crate::Artichoke;
5use crate::convert::UnboxRubyError;
6use crate::core::TryConvertMut;
7use crate::error::Error;
8use crate::types::Rust;
9use crate::value::Value;
10
11impl TryConvertMut<String, Value> for Artichoke {
12    type Error = Error;
13
14    fn try_convert_mut(&mut self, value: String) -> Result<Value, Self::Error> {
15        // Ruby `String`s are just bytes, so get a pointer to the underlying
16        // `&[u8]` infallibly and convert that to a `Value`.
17        self.try_convert_mut(value.into_bytes())
18    }
19}
20
21impl TryConvertMut<&str, Value> for Artichoke {
22    type Error = Error;
23
24    fn try_convert_mut(&mut self, value: &str) -> Result<Value, Self::Error> {
25        // Ruby `String`s are just bytes, so get a pointer to the underlying
26        // `&[u8]` infallibly and convert that to a `Value`.
27        self.try_convert_mut(value.as_bytes())
28    }
29}
30
31impl<'a> TryConvertMut<Cow<'a, str>, Value> for Artichoke {
32    type Error = Error;
33
34    fn try_convert_mut(&mut self, value: Cow<'a, str>) -> Result<Value, Self::Error> {
35        match value {
36            Cow::Borrowed(string) => self.try_convert_mut(string),
37            Cow::Owned(string) => self.try_convert_mut(string),
38        }
39    }
40}
41
42impl TryConvertMut<Value, String> for Artichoke {
43    type Error = Error;
44
45    fn try_convert_mut(&mut self, value: Value) -> Result<String, Self::Error> {
46        let bytes = self.try_convert_mut(value)?;
47        // This converter requires that the bytes be valid UTF-8 data. If the
48        // `Value` contains binary data, use the `Vec<u8>` or `&[u8]` converter.
49        let string = String::from_utf8(bytes).map_err(|_| UnboxRubyError::new(&value, Rust::String))?;
50        Ok(string)
51    }
52}
53
54impl<'a> TryConvertMut<Value, &'a str> for Artichoke {
55    type Error = Error;
56
57    fn try_convert_mut(&mut self, value: Value) -> Result<&'a str, Self::Error> {
58        let bytes = self.try_convert_mut(value)?;
59        // This converter requires that the bytes be valid UTF-8 data. If the
60        // `Value` contains binary data, use the `Vec<u8>` or `&[u8]` converter.
61        let string = str::from_utf8(bytes).map_err(|_| UnboxRubyError::new(&value, Rust::String))?;
62        Ok(string)
63    }
64}
65
66#[cfg(test)]
67mod tests {
68    use crate::test::prelude::*;
69
70    #[test]
71    fn fail_convert() {
72        let mut interp = interpreter();
73        // get a mrb_value that can't be converted to a primitive type.
74        let value = interp.eval(b"Object.new").unwrap();
75        let result = value.try_convert_into_mut::<String>(&mut interp);
76        assert!(result.is_err());
77    }
78
79    #[test]
80    fn prop_convert_to_string() {
81        let mut interp = interpreter();
82        run_arbitrary::<String>(|s| {
83            let value = interp.try_convert_mut(s.clone()).unwrap();
84            let string: Vec<u8> = interp.try_convert_mut(value).unwrap();
85            assert_eq!(string, s.as_bytes());
86        });
87    }
88
89    #[test]
90    fn prop_string_with_value() {
91        let mut interp = interpreter();
92        run_arbitrary::<String>(|s| {
93            let value = interp.try_convert_mut(s.clone()).unwrap();
94            assert_eq!(value.to_s(&mut interp), s.as_bytes());
95        });
96    }
97
98    #[test]
99    #[cfg(feature = "core-regexp")]
100    fn prop_utf8string_borrowed() {
101        let mut interp = interpreter();
102        run_arbitrary::<String>(|s| {
103            // Borrowed converter
104            let value = interp.try_convert_mut(s.as_str()).unwrap();
105            let len = value
106                .funcall(&mut interp, "length", &[], None)
107                .and_then(|value| value.try_convert_into::<usize>(&interp))
108                .unwrap();
109            assert_eq!(len, s.chars().count());
110
111            let zero = interp.convert(0);
112            let first = value
113                .funcall(&mut interp, "[]", &[zero], None)
114                .and_then(|value| value.try_convert_into_mut::<Option<String>>(&mut interp))
115                .unwrap();
116            let mut iter = s.chars();
117            if let Some(ch) = iter.next() {
118                assert_eq!(first, Some(ch.to_string()));
119            } else {
120                assert!(first.is_none());
121            }
122
123            let recovered: String = interp.try_convert_mut(value).unwrap();
124            assert_eq!(recovered, s);
125        });
126    }
127
128    #[test]
129    #[cfg(feature = "core-regexp")]
130    fn prop_utf8string_owned() {
131        let mut interp = interpreter();
132        run_arbitrary::<String>(|s| {
133            // Owned converter
134            let value = interp.try_convert_mut(s.clone()).unwrap();
135            let len = value
136                .funcall(&mut interp, "length", &[], None)
137                .and_then(|value| value.try_convert_into::<usize>(&interp))
138                .unwrap();
139            assert_eq!(len, s.chars().count());
140
141            let zero = interp.convert(0);
142            let first = value
143                .funcall(&mut interp, "[]", &[zero], None)
144                .and_then(|value| value.try_convert_into_mut::<Option<String>>(&mut interp))
145                .unwrap();
146            let mut iter = s.chars();
147            if let Some(ch) = iter.next() {
148                assert_eq!(first, Some(ch.to_string()));
149            } else {
150                assert!(first.is_none());
151            }
152
153            let recovered: String = interp.try_convert_mut(value).unwrap();
154            assert_eq!(recovered, s);
155        });
156    }
157
158    #[test]
159    fn prop_borrowed_roundtrip() {
160        let mut interp = interpreter();
161        run_arbitrary::<String>(|s| {
162            let value = interp.try_convert_mut(s.as_str()).unwrap();
163            let roundtrip = value.try_convert_into_mut::<String>(&mut interp).unwrap();
164            assert_eq!(roundtrip, s);
165        });
166    }
167
168    #[test]
169    fn prop_owned_roundtrip() {
170        let mut interp = interpreter();
171        run_arbitrary::<String>(|s| {
172            let value = interp.try_convert_mut(s.clone()).unwrap();
173            let roundtrip = value.try_convert_into_mut::<String>(&mut interp).unwrap();
174            assert_eq!(roundtrip, s);
175        });
176    }
177
178    #[test]
179    fn prop_roundtrip_err() {
180        let mut interp = interpreter();
181        for b in [true, false] {
182            let value = interp.convert(b);
183            let result = value.try_convert_into_mut::<String>(&mut interp);
184            assert!(result.is_err());
185        }
186    }
187}