Skip to main content

aorist_extendr_api/robj/
from_robj.rs

1use super::*;
2
3/// Trait used for incomming parameter conversion.
4pub trait FromRobj<'a>: Sized {
5    // Convert an incomming Robj from R into a value or an error.
6    fn from_robj(_robj: &'a Robj) -> std::result::Result<Self, &'static str> {
7        Err("unable to convert value from R object")
8    }
9}
10
11macro_rules! impl_prim_from_robj {
12    ($t: ty) => {
13        impl<'a> FromRobj<'a> for $t {
14            fn from_robj(robj: &'a Robj) -> std::result::Result<Self, &'static str> {
15                if let Some(v) = robj.as_integer_slice() {
16                    match v.len() {
17                        0 => Err("Input must be of length 1. Vector of length zero given."),
18                        1 => {
19                            if !v[0].is_na() {
20                                Ok(v[0] as Self)
21                            } else {
22                                Err("Input must not be NA.")
23                            }
24                        }
25                        _ => Err("Input must be of length 1. Vector of length >1 given."),
26                    }
27                } else if let Some(v) = robj.as_real_slice() {
28                    match v.len() {
29                        0 => Err("Input must be of length 1. Vector of length zero given."),
30                        1 => {
31                            if !v[0].is_na() {
32                                Ok(v[0] as Self)
33                            } else {
34                                Err("Input must not be NA.")
35                            }
36                        }
37                        _ => Err("Input must be of length 1. Vector of length >1 given."),
38                    }
39                } else {
40                    Err("unable to convert R object to primitive")
41                }
42            }
43        }
44    };
45}
46
47impl_prim_from_robj!(u8);
48impl_prim_from_robj!(u16);
49impl_prim_from_robj!(u32);
50impl_prim_from_robj!(u64);
51impl_prim_from_robj!(i8);
52impl_prim_from_robj!(i16);
53impl_prim_from_robj!(i32);
54impl_prim_from_robj!(i64);
55impl_prim_from_robj!(f32);
56impl_prim_from_robj!(f64);
57
58impl<'a> FromRobj<'a> for bool {
59    fn from_robj(robj: &'a Robj) -> std::result::Result<Self, &'static str> {
60        if let Some(v) = robj.as_logical_slice() {
61            match v.len() {
62                0 => Err("Input must be of length 1. Vector of length zero given."),
63                1 => {
64                    if !v[0].is_na() {
65                        Ok(v[0].to_bool())
66                    } else {
67                        Err("Input must not be NA.")
68                    }
69                }
70                _ => Err("Input must be of length 1. Vector of length >1 given."),
71            }
72        } else {
73            Err("Not a logical object.")
74        }
75    }
76}
77
78impl<'a> FromRobj<'a> for &'a str {
79    fn from_robj(robj: &'a Robj) -> std::result::Result<Self, &'static str> {
80        if robj.is_na() {
81            Err("Input must not be NA.")
82        } else if let Some(s) = robj.as_str() {
83            Ok(s)
84        } else {
85            Err("Not a string object.")
86        }
87    }
88}
89
90impl<'a> FromRobj<'a> for String {
91    fn from_robj(robj: &'a Robj) -> std::result::Result<Self, &'static str> {
92        if robj.is_na() {
93            Err("Input must not be NA.")
94        } else if let Some(s) = robj.as_str() {
95            Ok(s.to_string())
96        } else {
97            Err("not a string object")
98        }
99    }
100}
101
102impl<'a> FromRobj<'a> for Vec<i32> {
103    fn from_robj(robj: &'a Robj) -> std::result::Result<Self, &'static str> {
104        if let Some(v) = robj.as_integer_slice() {
105            Ok(Vec::from(v))
106        } else {
107            Err("not an integer or logical vector")
108        }
109    }
110}
111
112impl<'a> FromRobj<'a> for Vec<f64> {
113    fn from_robj(robj: &'a Robj) -> std::result::Result<Self, &'static str> {
114        if let Some(v) = robj.as_real_slice() {
115            Ok(Vec::from(v))
116        } else {
117            Err("not a floating point vector")
118        }
119    }
120}
121
122impl<'a> FromRobj<'a> for Vec<String> {
123    fn from_robj(robj: &'a Robj) -> std::result::Result<Self, &'static str> {
124        if robj.is_na() {
125            Err("Input must be a character vector. Got 'NA'.")
126        } else if let Some(v) = robj.as_string_vector() {
127            let str_vec = v.to_vec();
128            // check for NA's in the string vector
129            if let Some(_str) = str_vec.iter().find(|&s| *s == na_str()) {
130                Err("Input vector cannot contain NA's.")
131            } else {
132                Ok(str_vec)
133            }
134        } else {
135            Err("Input must be a character vector.")
136        }
137    }
138}
139
140macro_rules! impl_iter_from_robj {
141    ($t: ty, $iter_fn: ident, $msg: expr) => {
142        impl<'a> FromRobj<'a> for $t {
143            fn from_robj(robj: &'a Robj) -> std::result::Result<Self, &'static str> {
144                if let Some(v) = robj.$iter_fn() {
145                    Ok(v)
146                } else {
147                    Err($msg)
148                }
149            }
150        }
151    };
152}
153
154impl_iter_from_robj!(StrIter, as_str_iter, "Not a character vector.");
155impl_iter_from_robj!(Int, as_integer_iter, "Not an integer vector.");
156impl_iter_from_robj!(Real, as_real_iter, "Not a real vector.");
157impl_iter_from_robj!(Logical, as_logical_iter, "Not a logical vector.");
158
159/// Pass-through Robj conversion, essentially a clone.
160impl<'a> FromRobj<'a> for Robj {
161    fn from_robj(robj: &'a Robj) -> std::result::Result<Self, &'static str> {
162        Ok(unsafe { new_owned(robj.get()) })
163    }
164}
165
166impl<'a> FromRobj<'a> for HashMap<String, Robj> {
167    fn from_robj(robj: &'a Robj) -> std::result::Result<Self, &'static str> {
168        if let Some(iter) = robj.as_list().map(|l| l.iter()) {
169            Ok(iter
170                .map(|(k, v)| (k.to_string(), v))
171                .collect::<HashMap<String, Robj>>())
172        } else {
173            Err("expected a list")
174        }
175    }
176}
177
178impl<'a> FromRobj<'a> for HashMap<&str, Robj> {
179    fn from_robj(robj: &'a Robj) -> std::result::Result<Self, &'static str> {
180        if let Some(iter) = robj.as_list().map(|l| l.iter()) {
181            Ok(iter.map(|(k, v)| (k, v)).collect::<HashMap<&str, Robj>>())
182        } else {
183            Err("expected a list")
184        }
185    }
186}
187
188// NA-sensitive integer input handling
189impl<'a> FromRobj<'a> for Option<i32> {
190    fn from_robj(robj: &'a Robj) -> std::result::Result<Self, &'static str> {
191        if robj.is_na() {
192            Ok(None)
193        } else if let Some(val) = robj.as_integer() {
194            Ok(Some(val))
195        } else {
196            Err("expected an integer scalar")
197        }
198    }
199}
200
201// NA-sensitive logical input handling
202impl<'a> FromRobj<'a> for Option<bool> {
203    fn from_robj(robj: &'a Robj) -> std::result::Result<Self, &'static str> {
204        if let Some(val) = robj.as_logical() {
205            if val.is_na() {
206                Ok(None)
207            } else {
208                Ok(Some(val.is_true()))
209            }
210        } else {
211            Err("expected a logical scalar")
212        }
213    }
214}
215
216// NA-sensitive real input handling
217impl<'a> FromRobj<'a> for Option<f64> {
218    fn from_robj(robj: &'a Robj) -> std::result::Result<Self, &'static str> {
219        if robj.is_na() {
220            Ok(None)
221        } else if let Some(val) = robj.as_real() {
222            Ok(Some(val))
223        } else {
224            Err("expected a real scalar")
225        }
226    }
227}
228
229// NA-sensitive string input handling
230impl<'a> FromRobj<'a> for Option<&'a str> {
231    fn from_robj(robj: &'a Robj) -> std::result::Result<Self, &'static str> {
232        if robj.is_na() {
233            Ok(None)
234        } else if let Some(val) = robj.as_str() {
235            Ok(Some(val))
236        } else {
237            Err("expected a character scalar")
238        }
239    }
240}
241
242// NA-sensitive string input handling
243impl<'a> FromRobj<'a> for Option<String> {
244    fn from_robj(robj: &'a Robj) -> std::result::Result<Self, &'static str> {
245        if robj.is_na() {
246            Ok(None)
247        } else if let Some(val) = robj.as_str() {
248            Ok(Some(val.to_string()))
249        } else {
250            Err("expected a character scalar")
251        }
252    }
253}
254
255impl<'a, T> FromRobj<'a> for &'a [T]
256where
257    Robj: AsTypedSlice<'a, T>,
258{
259    fn from_robj(robj: &'a Robj) -> std::result::Result<Self, &'static str> {
260        if let Some(slice) = robj.as_typed_slice() {
261            Ok(slice)
262        } else {
263            Err("Expected a vector type.")
264        }
265    }
266}
267
268// Matrix input parameters.
269impl<'a, T: 'a> FromRobj<'a> for RArray<T, [usize; 2]>
270where
271    Robj: AsTypedSlice<'a, T>,
272{
273    fn from_robj(robj: &'a Robj) -> std::result::Result<Self, &'static str> {
274        match robj.as_matrix() {
275            Some(x) => Ok(x),
276            _ => Err("Expected a matrix."),
277        }
278    }
279}
280
281// Matrix input parameters.
282impl<'a, T: 'a> FromRobj<'a> for RMatrix3D<T>
283where
284    Robj: AsTypedSlice<'a, T>,
285{
286    fn from_robj(robj: &'a Robj) -> std::result::Result<Self, &'static str> {
287        match robj.as_matrix3d() {
288            Some(x) => Ok(x),
289            _ => Err("Expected a matrix."),
290        }
291    }
292}