bolt_proto/value/
conversions.rs

1use std::{collections::HashMap, hash::BuildHasher};
2
3use chrono::{DateTime, FixedOffset, NaiveDate, NaiveDateTime, NaiveTime, Offset, TimeZone};
4use chrono_tz::Tz;
5
6use crate::value::*;
7
8// ------------------------- Into Value -------------------------
9
10#[doc(hidden)]
11macro_rules! impl_from {
12    ($T:ty, $V:ident) => {
13        impl ::std::convert::From<$T> for $crate::Value {
14            fn from(value: $T) -> Self {
15                Value::$V(value)
16            }
17        }
18    };
19}
20
21impl_from!(bool, Boolean);
22
23macro_rules! impl_from_int {
24    ($($T:ty),+) => {
25        $(
26            impl ::std::convert::From<$T> for $crate::Value {
27                fn from(value: $T) -> Self {
28                    Value::Integer(value as i64)
29                }
30            }
31        )*
32    };
33}
34impl_from_int!(i8, i16, i32, i64);
35
36impl_from!(f64, Float);
37
38impl From<&[u8]> for Value {
39    fn from(value: &[u8]) -> Self {
40        Value::Bytes(value.to_vec())
41    }
42}
43
44impl_from!(Vec<u8>, Bytes);
45
46impl<T> From<Vec<T>> for Value
47where
48    T: Into<Value>,
49{
50    fn from(value: Vec<T>) -> Self {
51        Value::List(value.into_iter().map(T::into).collect())
52    }
53}
54
55impl<K, V, S> From<HashMap<K, V, S>> for Value
56where
57    K: Into<std::string::String>,
58    V: Into<Value>,
59    S: BuildHasher,
60{
61    fn from(value: HashMap<K, V, S>) -> Self {
62        Value::Map(
63            value
64                .into_iter()
65                .map(|(k, v)| (K::into(k), V::into(v)))
66                .collect(),
67        )
68    }
69}
70
71impl From<&str> for Value {
72    fn from(value: &str) -> Self {
73        Value::String(String::from(value))
74    }
75}
76
77impl_from!(String, String);
78
79impl_from!(Node, Node);
80
81impl_from!(Relationship, Relationship);
82
83impl_from!(Path, Path);
84
85impl_from!(UnboundRelationship, UnboundRelationship);
86
87impl_from!(NaiveDate, Date);
88
89// No timezone-aware time in chrono, so provide a separate conversion
90impl<O: Offset> From<(NaiveTime, O)> for Value {
91    fn from(pair: (NaiveTime, O)) -> Self {
92        Value::Time(pair.0, pair.1.fix())
93    }
94}
95
96impl<T: TimeZone> From<DateTime<T>> for Value {
97    fn from(value: DateTime<T>) -> Self {
98        Value::DateTimeOffset(DateTime::from_utc(value.naive_utc(), value.offset().fix()))
99    }
100}
101
102// Can't decide between Offset or Zoned variant at runtime if using a T: TimeZone, so
103// provide a separate conversion
104impl From<(NaiveDateTime, chrono_tz::Tz)> for Value {
105    fn from(pair: (NaiveDateTime, chrono_tz::Tz)) -> Self {
106        Value::DateTimeZoned(pair.1.from_utc_datetime(&pair.0))
107    }
108}
109
110impl_from!(NaiveTime, LocalTime);
111
112impl_from!(NaiveDateTime, LocalDateTime);
113
114impl_from!(Duration, Duration);
115
116impl From<std::time::Duration> for Value {
117    fn from(value: std::time::Duration) -> Self {
118        Value::Duration(Duration::from(value))
119    }
120}
121
122impl_from!(Point2D, Point2D);
123
124impl_from!(Point3D, Point3D);
125
126impl<T: Into<Value>> From<Option<T>> for Value {
127    fn from(option: Option<T>) -> Self {
128        match option {
129            Some(v) => v.into(),
130            None => Value::Null,
131        }
132    }
133}
134
135// ------------------------- From Value -------------------------
136
137#[doc(hidden)]
138macro_rules! impl_try_from_value {
139    ($T:ty, $V:ident) => {
140        impl ::std::convert::TryFrom<$crate::Value> for $T {
141            type Error = $crate::error::ConversionError;
142
143            fn try_from(value: $crate::Value) -> $crate::error::ConversionResult<Self> {
144                match value {
145                    $crate::Value::$V(inner) => Ok(inner),
146                    _ => Err($crate::error::ConversionError::FromValue(value)),
147                }
148            }
149        }
150    };
151}
152
153impl_try_from_value!(bool, Boolean);
154
155macro_rules! impl_try_from_value_for_ints {
156    ($($T:ty),+) => {
157        $(
158            impl TryFrom<$crate::Value> for $T {
159                type Error = $crate::error::ConversionError;
160
161                fn try_from(value: $crate::Value) -> $crate::error::ConversionResult<Self> {
162                    use ::std::convert::TryInto;
163
164                    match value {
165                        $crate::Value::Integer(integer) => Ok(integer.try_into()?),
166                        _ => Err($crate::error::ConversionError::FromValue(value)),
167                    }
168                }
169            }
170        )*
171    };
172}
173impl_try_from_value_for_ints!(i8, i16, i32, i64);
174
175impl_try_from_value!(f64, Float);
176
177impl_try_from_value!(Vec<u8>, Bytes);
178
179impl<T> TryFrom<Value> for Vec<T>
180where
181    T: TryFrom<Value, Error = ConversionError>,
182{
183    type Error = ConversionError;
184
185    fn try_from(value: Value) -> ConversionResult<Self> {
186        match value {
187            Value::List(list) => list.into_iter().map(T::try_from).collect(),
188            _ => Err(ConversionError::FromValue(value)),
189        }
190    }
191}
192
193impl_try_from_value!(Vec<Value>, List);
194
195impl<V, S> TryFrom<Value> for HashMap<std::string::String, V, S>
196where
197    V: TryFrom<Value, Error = ConversionError>,
198    S: BuildHasher + Default,
199{
200    type Error = ConversionError;
201
202    fn try_from(value: Value) -> ConversionResult<Self> {
203        match value {
204            Value::Map(map) => {
205                let mut new_map = HashMap::with_capacity_and_hasher(map.len(), Default::default());
206                for (k, v) in map {
207                    new_map.insert(k, V::try_from(v)?);
208                }
209                Ok(new_map)
210            }
211            _ => Err(ConversionError::FromValue(value)),
212        }
213    }
214}
215
216impl<S> TryFrom<Value> for HashMap<std::string::String, Value, S>
217where
218    S: BuildHasher + Default,
219{
220    type Error = ConversionError;
221
222    fn try_from(value: Value) -> ConversionResult<Self> {
223        match value {
224            Value::Map(map) => {
225                let mut new_map = HashMap::with_capacity_and_hasher(map.len(), Default::default());
226                for (k, v) in map {
227                    new_map.insert(k, v);
228                }
229                Ok(new_map)
230            }
231            _ => Err(ConversionError::FromValue(value)),
232        }
233    }
234}
235
236impl_try_from_value!(String, String);
237
238impl_try_from_value!(Node, Node);
239
240impl_try_from_value!(Relationship, Relationship);
241
242impl_try_from_value!(Path, Path);
243
244impl_try_from_value!(UnboundRelationship, UnboundRelationship);
245
246impl_try_from_value!(NaiveDate, Date);
247
248impl TryFrom<Value> for DateTime<FixedOffset> {
249    type Error = ConversionError;
250
251    fn try_from(value: Value) -> ConversionResult<Self> {
252        match value {
253            Value::DateTimeOffset(date_time_offset) => Ok(date_time_offset),
254            Value::DateTimeZoned(date_time_zoned) => {
255                Ok(date_time_zoned.with_timezone(&date_time_zoned.offset().fix()))
256            }
257            _ => Err(ConversionError::FromValue(value)),
258        }
259    }
260}
261
262impl_try_from_value!(DateTime<Tz>, DateTimeZoned);
263
264impl_try_from_value!(NaiveTime, LocalTime);
265
266impl_try_from_value!(NaiveDateTime, LocalDateTime);
267
268// We cannot convert to std::time::Duration, since months are not well-defined in terms of
269// seconds, and our Duration can hold quantities that are impossible to hold in a
270// std::time::Duration (like negative durations).
271impl_try_from_value!(Duration, Duration);
272
273impl_try_from_value!(Point2D, Point2D);
274
275impl_try_from_value!(Point3D, Point3D);