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}