Skip to main content

ic_dbms_api/dbms/
value.rs

1use std::str::FromStr;
2
3use candid::CandidType;
4use serde::{Deserialize, Serialize};
5
6use super::types;
7
8/// A generic wrapper enum to hold any DBMS value.
9#[derive(
10    Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, CandidType, Serialize, Deserialize,
11)]
12pub enum Value {
13    Blob(types::Blob),
14    Boolean(types::Boolean),
15    Date(types::Date),
16    DateTime(types::DateTime),
17    Decimal(types::Decimal),
18    Int8(types::Int8),
19    Int16(types::Int16),
20    Int32(types::Int32),
21    Int64(types::Int64),
22    Json(types::Json),
23    Null,
24    Principal(types::Principal),
25    Text(types::Text),
26    Uint8(types::Uint8),
27    Uint16(types::Uint16),
28    Uint32(types::Uint32),
29    Uint64(types::Uint64),
30    Uuid(types::Uuid),
31}
32
33impl FromStr for Value {
34    type Err = ();
35
36    fn from_str(s: &str) -> Result<Self, Self::Err> {
37        Ok(Self::Text(s.into()))
38    }
39}
40
41// macro rules for implementing From trait for Value enum variants
42macro_rules! impl_conv_for_value {
43    ($variant:ident, $ty:ty, $name:ident, $test_name:ident) => {
44        impl From<$ty> for Value {
45            fn from(value: $ty) -> Self {
46                Value::$variant(value)
47            }
48        }
49
50        impl Value {
51            /// Attempts to extract a reference to the inner value if it matches the variant.
52            pub fn $name(&self) -> Option<&$ty> {
53                if let Value::$variant(v) = self {
54                    Some(v)
55                } else {
56                    None
57                }
58            }
59        }
60
61        #[cfg(test)]
62        mod $test_name {
63            use super::*;
64
65            #[test]
66            fn test_value_conversion() {
67                let value_instance: $ty = Default::default();
68                let value: Value = value_instance.clone().into();
69                assert_eq!(value.$name(), Some(&value_instance));
70            }
71        }
72    };
73}
74
75macro_rules! value_from_primitive {
76    ($variant:ident, $primitive:ty, $test_name:ident) => {
77        value_from_primitive!($variant, $primitive, $test_name, Default::default());
78    };
79
80    ($variant:ident, $primitive:ty, $test_name:ident, $default_value:expr) => {
81        impl From<$primitive> for Value {
82            fn from(value: $primitive) -> Self {
83                Value::$variant($crate::prelude::$variant(value.into()))
84            }
85        }
86
87        #[cfg(test)]
88        mod $test_name {
89            use super::*;
90
91            #[test]
92            fn test_value_from_primitive() {
93                let primitive_value: $primitive = $default_value;
94                if let Value::$variant(inner_value) = Value::from(primitive_value.clone()) {
95                    assert_eq!(inner_value.0, primitive_value);
96                } else {
97                    panic!("Value variant does not match");
98                }
99            }
100        }
101    };
102}
103
104// implement conversions for all Value variants
105impl_conv_for_value!(Blob, types::Blob, as_blob, tests_blob);
106impl_conv_for_value!(Boolean, types::Boolean, as_boolean, tests_boolean);
107impl_conv_for_value!(Date, types::Date, as_date, tests_date);
108impl_conv_for_value!(DateTime, types::DateTime, as_datetime, tests_datetime);
109impl_conv_for_value!(Decimal, types::Decimal, as_decimal, tests_decimal);
110impl_conv_for_value!(Int8, types::Int8, as_int8, tests_int8);
111impl_conv_for_value!(Int16, types::Int16, as_int16, tests_int16);
112impl_conv_for_value!(Int32, types::Int32, as_int32, tests_int32);
113impl_conv_for_value!(Int64, types::Int64, as_int64, tests_int64);
114impl_conv_for_value!(Json, types::Json, as_json, tests_json);
115impl_conv_for_value!(Principal, types::Principal, as_principal, tests_principal);
116impl_conv_for_value!(Text, types::Text, as_text, tests_text);
117impl_conv_for_value!(Uint8, types::Uint8, as_uint8, tests_uint8);
118impl_conv_for_value!(Uint16, types::Uint16, as_uint16, tests_uint16);
119impl_conv_for_value!(Uint32, types::Uint32, as_uint32, tests_uint32);
120impl_conv_for_value!(Uint64, types::Uint64, as_uint64, tests_uint64);
121impl_conv_for_value!(Uuid, types::Uuid, as_uuid, tests_uuid);
122
123// from inner values of types
124value_from_primitive!(Blob, &[u8], tests_blob_primitive_slice);
125value_from_primitive!(Blob, Vec<u8>, tests_blob_primitive);
126value_from_primitive!(Boolean, bool, tests_boolean_primitive);
127value_from_primitive!(Decimal, rust_decimal::Decimal, tests_decimal_primitive);
128value_from_primitive!(Int8, i8, tests_int8_primitive);
129value_from_primitive!(Int16, i16, tests_int16_primitive);
130value_from_primitive!(Int32, i32, tests_int32_primitive);
131value_from_primitive!(Int64, i64, tests_int64_primitive);
132value_from_primitive!(Uint8, u8, tests_uint8_primitive);
133value_from_primitive!(Uint16, u16, tests_uint16_primitive);
134value_from_primitive!(Uint32, u32, tests_uint32_primitive);
135value_from_primitive!(Uint64, u64, tests_uint64_primitive);
136value_from_primitive!(
137    Principal,
138    candid::Principal,
139    tests_principal_primitive,
140    candid::Principal::anonymous()
141);
142value_from_primitive!(Text, String, tests_text_primitive_string);
143value_from_primitive!(Text, &str, tests_text_primitive_str);
144value_from_primitive!(Uuid, uuid::Uuid, tests_uuid_primitive);
145
146impl Value {
147    /// Checks if the value is [`Value::Null`].
148    pub fn is_null(&self) -> bool {
149        matches!(self, Value::Null)
150    }
151
152    /// Returns the type name of the value as a string.
153    pub fn type_name(&self) -> &'static str {
154        match self {
155            Value::Blob(_) => "Blob",
156            Value::Boolean(_) => "Boolean",
157            Value::Date(_) => "Date",
158            Value::DateTime(_) => "DateTime",
159            Value::Decimal(_) => "Decimal",
160            Value::Int8(_) => "Int8",
161            Value::Int16(_) => "Int16",
162            Value::Int32(_) => "Int32",
163            Value::Int64(_) => "Int64",
164            Value::Json(_) => "Json",
165            Value::Null => "Null",
166            Value::Principal(_) => "Principal",
167            Value::Text(_) => "Text",
168            Value::Uint8(_) => "Uint8",
169            Value::Uint16(_) => "Uint16",
170            Value::Uint32(_) => "Uint32",
171            Value::Uint64(_) => "Uint64",
172            Value::Uuid(_) => "Uuid",
173        }
174    }
175}
176
177#[cfg(test)]
178mod tests {
179
180    use uuid::Uuid;
181
182    use super::*;
183
184    #[test]
185    fn test_null() {
186        let int_value: Value = types::Int32(42).into();
187        assert!(!int_value.is_null());
188
189        let null_value = Value::Null;
190        assert!(null_value.is_null());
191    }
192
193    #[test]
194    fn test_value_conversion_blob() {
195        let blob = types::Blob(vec![1, 2, 3]);
196        let value: Value = blob.clone().into();
197        assert_eq!(value.as_blob(), Some(&blob));
198    }
199
200    #[test]
201    fn test_value_conversion_boolean() {
202        let boolean = types::Boolean(true);
203        let value: Value = boolean.into();
204        assert_eq!(value.as_boolean(), Some(&boolean));
205    }
206
207    #[test]
208    fn test_value_conversion_date() {
209        let date = types::Date {
210            year: 2023,
211            month: 3,
212            day: 15,
213        }; // Example date
214        let value: Value = date.into();
215        assert_eq!(value.as_date(), Some(&date));
216    }
217
218    #[test]
219    fn test_value_conversion_datetime() {
220        let datetime = types::DateTime {
221            year: 2023,
222            month: 3,
223            day: 15,
224            hour: 12,
225            minute: 30,
226            second: 45,
227            microsecond: 123456,
228            timezone_offset_minutes: 0,
229        }; // Example datetime
230        let value: Value = datetime.into();
231        assert_eq!(value.as_datetime(), Some(&datetime));
232    }
233
234    #[test]
235    fn test_value_conversion_decimal() {
236        let decimal = types::Decimal(rust_decimal::Decimal::new(12345, 2)); // 123.45
237        let value: Value = decimal.into();
238        assert_eq!(value.as_decimal(), Some(&decimal));
239    }
240
241    #[test]
242    fn test_value_conversion_int32() {
243        let int32 = types::Int32(1234567890);
244        let value: Value = int32.into();
245        assert_eq!(value.as_int32(), Some(&int32));
246    }
247
248    #[test]
249    fn test_value_conversion_int64() {
250        let int64 = types::Int64(1234567890);
251        let value: Value = int64.into();
252        assert_eq!(value.as_int64(), Some(&int64));
253    }
254
255    #[test]
256    fn test_value_conversion_principal() {
257        let principal = types::Principal(candid::Principal::from_text("aaaaa-aa").unwrap());
258        let value: Value = principal.clone().into();
259        assert_eq!(value.as_principal(), Some(&principal));
260    }
261
262    #[test]
263    fn test_value_conversion_text() {
264        let text = types::Text("Hello, World!".to_string());
265        let value: Value = text.clone().into();
266        assert_eq!(value.as_text(), Some(&text));
267    }
268
269    #[test]
270    fn test_value_conversion_uint32() {
271        let uint32 = types::Uint32(123456);
272        let value: Value = uint32.into();
273        assert_eq!(value.as_uint32(), Some(&uint32));
274    }
275
276    #[test]
277    fn test_value_conversion_uint64() {
278        let uint64 = types::Uint64(12345678901234);
279        let value: Value = uint64.into();
280        assert_eq!(value.as_uint64(), Some(&uint64));
281    }
282
283    #[test]
284    fn test_value_conversion_uuid() {
285        let uuid = types::Uuid(
286            Uuid::parse_str("550e8400-e29b-41d4-a716-446655440000").expect("failed to parse uuid"),
287        );
288        let value: Value = uuid.clone().into();
289        assert_eq!(value.as_uuid(), Some(&uuid));
290    }
291
292    #[test]
293    fn test_value_type_name() {
294        let int_value: Value = types::Int32(42).into();
295        assert_eq!(int_value.type_name(), "Int32");
296
297        let text_value: Value = types::Text("Hello".to_string()).into();
298        assert_eq!(text_value.type_name(), "Text");
299
300        let null_value = Value::Null;
301        assert_eq!(null_value.type_name(), "Null");
302    }
303
304    #[test]
305    fn test_value_from_str() {
306        let str_value = "Hello, DBMS!";
307
308        let value = Value::from_str(str_value).unwrap();
309        assert_eq!(value.as_text().unwrap().0, str_value);
310    }
311}