extendr_api/wrapper/
nullable.rs

1use super::*;
2use crate as extendr_api;
3
4/// Wrapper for handling potentially NULL values.
5/// ```
6/// use extendr_api::prelude::*;
7/// test! {
8///     use extendr_api::wrapper::Nullable::*;
9///
10///     // Plain integer.
11///     let s1 = r!(1);
12///     let n1 = <Nullable<i32>>::try_from(&s1)?;
13///     assert_eq!(n1, NotNull(1));
14///
15///     // NA integer - error.
16///     let sna = r!(NA_INTEGER);
17///     assert_eq!(<Nullable<i32>>::try_from(&sna).is_err(), true);
18///
19///     // NA integer - option gives none.
20///     assert_eq!(<Nullable<Option<i32>>>::try_from(&sna)?, NotNull(None));
21///
22///     // NULL object.
23///     let snull = r!(NULL);
24///     let nnull = <Nullable<i32>>::try_from(&snull)?;
25///     assert_eq!(nnull, Null);
26///
27///     assert_eq!(r!(Nullable::<i32>::Null), r!(NULL));
28///     assert_eq!(r!(Nullable::<i32>::NotNull(1)), r!(1));
29/// }
30/// ```
31#[derive(Debug, PartialEq, Clone)]
32pub enum Nullable<T> {
33    NotNull(T),
34    Null,
35}
36
37impl TryFrom<Robj> for Nullable<()> {
38    type Error = Error;
39
40    fn try_from(value: Robj) -> Result<Self> {
41        Self::try_from(&value)
42    }
43}
44
45impl TryFrom<&Robj> for Nullable<()> {
46    type Error = Error;
47
48    fn try_from(value: &Robj) -> Result<Self> {
49        if value.is_null() {
50            Ok(Self::Null)
51        } else {
52            Err(Error::ExpectedNull(value.clone()))
53        }
54    }
55}
56
57impl<T> TryFrom<Robj> for Nullable<T>
58where
59    T: TryFrom<Robj, Error = Error>,
60{
61    type Error = Error;
62
63    /// Convert an object that may be null to a rust type.
64    /// ```
65    /// use extendr_api::prelude::*;
66    /// test! {
67    ///     let s1 = r!(1);
68    ///     let n1 = <Nullable<i32>>::try_from(s1)?;
69    ///     assert_eq!(n1, Nullable::NotNull(1));
70    ///     let snull = r!(NULL);
71    ///     let nnull = <Nullable<i32>>::try_from(snull)?;
72    ///     assert_eq!(nnull, Nullable::Null);
73    /// }
74    /// ```
75    fn try_from(robj: Robj) -> Result<Self> {
76        if robj.is_null() {
77            Ok(Nullable::Null)
78        } else {
79            Ok(Nullable::NotNull(robj.try_into()?))
80        }
81    }
82}
83
84impl<'a, T> TryFrom<&'a Robj> for Nullable<T>
85where
86    T: TryFrom<&'a Robj, Error = Error>,
87{
88    type Error = Error;
89
90    /// Convert an object that may be null to a rust type.
91    /// ```
92    /// use extendr_api::prelude::*;
93    /// test! {
94    ///     let s1 = r!(1);
95    ///     let n1 = <Nullable<i32>>::try_from(&s1)?;
96    ///     assert_eq!(n1, Nullable::NotNull(1));
97    ///     let snull = r!(NULL);
98    ///     let nnull = <Nullable<i32>>::try_from(&snull)?;
99    ///     assert_eq!(nnull, Nullable::Null);
100    /// }
101    /// ```
102    fn try_from(robj: &'a Robj) -> Result<Self> {
103        if robj.is_null() {
104            Ok(Nullable::Null)
105        } else {
106            Ok(Nullable::NotNull(robj.try_into()?))
107        }
108    }
109}
110
111impl<T> From<Nullable<T>> for Robj
112where
113    T: Into<Robj>,
114{
115    /// Convert a rust object to NULL or another type.
116    /// ```
117    /// use extendr_api::prelude::*;
118    /// test! {
119    ///     assert_eq!(r!(Nullable::<i32>::Null), r!(NULL));
120    ///     assert_eq!(r!(Nullable::<i32>::NotNull(1)), r!(1));
121    /// }
122    /// ```
123    fn from(val: Nullable<T>) -> Self {
124        match val {
125            Nullable::NotNull(t) => t.into(),
126            Nullable::Null => r!(NULL),
127        }
128    }
129}
130
131impl<T> From<Nullable<T>> for Option<T>
132where
133    T: TryFrom<Robj, Error = Error>,
134{
135    /// Convert a Nullable type into Option
136    /// ```
137    /// use extendr_api::prelude::*;
138    /// test! {
139    ///     assert_eq!(<Option<i32>>::from(Nullable::Null), None);
140    ///     assert_eq!(<Option<i32>>::from(Nullable::NotNull(42)), Some(42));
141    /// }
142    /// ```
143    fn from(value: Nullable<T>) -> Self {
144        match value {
145            Nullable::NotNull(value) => Some(value),
146            _ => None,
147        }
148    }
149}
150
151impl<'a, T> From<&'a Nullable<T>> for Option<&'a T>
152where
153    T: TryFrom<Robj, Error = Error>,
154{
155    /// Convert a Nullable reference type into Option containing reference
156    /// ```
157    /// use extendr_api::prelude::*;
158    /// test! {
159    ///     assert_eq!(<Option<&i32>>::from(&Nullable::Null), None);
160    ///     assert_eq!(<Option<&i32>>::from(&Nullable::NotNull(42)), Some(&42));
161    /// }
162    /// ```
163    fn from(value: &'a Nullable<T>) -> Self {
164        match value {
165            Nullable::NotNull(value) => Some(value),
166            _ => None,
167        }
168    }
169}
170
171impl<T> From<Option<T>> for Nullable<T>
172where
173    T: Into<Robj>,
174{
175    /// Convert an Option into Nullable type
176    /// ```
177    /// use extendr_api::prelude::*;
178    /// test! {
179    ///     let x : Nullable<_> = From::<Option<i32>>::from(None);
180    ///     assert_eq!(x, Nullable::<i32>::Null);
181    ///     let x : Nullable<_> = From::<Option<i32>>::from(Some(42));
182    ///     assert_eq!(x, Nullable::<i32>::NotNull(42));
183    /// }
184    /// ```
185    fn from(value: Option<T>) -> Self {
186        match value {
187            Some(value) => Nullable::NotNull(value),
188            _ => Nullable::Null,
189        }
190    }
191}
192
193impl<T> Nullable<T>
194where
195    T: TryFrom<Robj, Error = Error>,
196{
197    /// Convert Nullable R object into `Option`
198    /// ```
199    /// use extendr_api::prelude::*;
200    /// test! {
201    ///     assert_eq!(Nullable::<Rint>::Null.into_option(), None);
202    ///     assert_eq!(Nullable::<Rint>::NotNull(Rint::from(42)).into_option(), Some(Rint::from(42)));
203    /// }
204    /// ```
205    pub fn into_option(self) -> Option<T> {
206        self.into()
207    }
208}
209impl<T> Nullable<T> {
210    /// Map `Nullable<T>` into `Nullable<U>`
211    ///
212    /// ```
213    /// use extendr_api::prelude::*;
214    /// test! {
215    ///     assert_eq!(Nullable::<Rfloat>::Null.map(|x| x.abs()), Nullable::<Rfloat>::Null);
216    ///     assert_eq!(Nullable::<Rfloat>::NotNull(Rfloat::from(42.0)).map(|x| x.abs()), Nullable::<Rfloat>::NotNull(Rfloat::from(42.0)));
217    /// }
218    /// ```
219    pub fn map<F, U>(self, f: F) -> Nullable<U>
220    where
221        F: FnOnce(T) -> U,
222    {
223        match self {
224            Nullable::NotNull(value) => Nullable::NotNull(f(value)),
225            _ => Nullable::Null,
226        }
227    }
228}