Skip to main content

graphrecords_query/capabilities/
conversion.rs

1use crate::{
2    AttributeName, Failure, IndexValue, QueryResult, Scalar, ValueDomain,
3    cast::{
4        Bool as BoolTarget, CastTarget, DateTime as DateTimeTarget, Duration as DurationTarget,
5        Float as FloatTarget, Int as IntTarget, String as StringTarget,
6    },
7    error::conversion::InvalidCast,
8};
9use chrono::{DateTime, NaiveDateTime, TimeDelta};
10use graphrecords_core::graphrecord::{
11    GraphRecordAttribute, GraphRecordValue, NodeIndex, datatypes::DataType,
12};
13use std::{
14    fmt::{Debug, Display},
15    time::Duration as StandardDuration,
16};
17
18pub trait ValueCast<T: CastTarget>: ValueDomain {
19    fn cast<'a>(
20        label: &'static str,
21        value: Self::Value<'a>,
22        target: &T,
23    ) -> QueryResult<Self::Value<'a>>;
24}
25
26fn invalid_cast<T, U>(label: &'static str, value: T, target: DataType) -> QueryResult<U>
27where
28    T: Debug + Display + Send + Sync + 'static,
29{
30    Err(Failure::new(label, InvalidCast::new(value, target)))
31}
32
33fn duration_from_parts(seconds: u64, nanoseconds: u32, negative: bool) -> Option<TimeDelta> {
34    let duration = TimeDelta::from_std(StandardDuration::new(seconds, nanoseconds)).ok()?;
35
36    Some(if negative { -duration } else { duration })
37}
38
39fn duration_from_milliseconds(milliseconds: i64) -> Option<TimeDelta> {
40    let magnitude = milliseconds.unsigned_abs();
41
42    duration_from_parts(
43        magnitude / 1_000,
44        ((magnitude % 1_000) * 1_000_000) as u32,
45        milliseconds.is_negative(),
46    )
47}
48
49fn duration_from_fractional_milliseconds(milliseconds: f64) -> Option<TimeDelta> {
50    if !milliseconds.is_finite() {
51        return None;
52    }
53
54    let duration = StandardDuration::try_from_secs_f64(milliseconds.abs() / 1_000.0).ok()?;
55    let duration = TimeDelta::from_std(duration).ok()?;
56
57    Some(if milliseconds.is_sign_negative() {
58        -duration
59    } else {
60        duration
61    })
62}
63
64const fn datetime_from_duration(duration: TimeDelta) -> Option<NaiveDateTime> {
65    DateTime::UNIX_EPOCH
66        .naive_utc()
67        .checked_add_signed(duration)
68}
69
70fn parse_duration(value: &str) -> Option<TimeDelta> {
71    if value == "P0D" {
72        return duration_from_parts(0, 0, false);
73    }
74
75    let (negative, value) = match value.strip_prefix('-') {
76        Some(value) => (true, value),
77        None => (false, value),
78    };
79    let value = value.strip_prefix("PT")?.strip_suffix('S')?;
80    let (seconds, nanoseconds) = match value.split_once('.') {
81        Some((seconds, fraction)) => {
82            if fraction.is_empty()
83                || fraction.len() > 9
84                || !fraction.bytes().all(|character| character.is_ascii_digit())
85            {
86                return None;
87            }
88
89            let nanoseconds = fraction.parse::<u32>().ok()? * 10_u32.pow(9 - fraction.len() as u32);
90
91            (seconds, nanoseconds)
92        }
93        None => (value, 0),
94    };
95
96    if seconds.is_empty() || !seconds.bytes().all(|character| character.is_ascii_digit()) {
97        return None;
98    }
99
100    duration_from_parts(seconds.parse().ok()?, nanoseconds, negative)
101}
102
103fn cast_value_to_bool(
104    label: &'static str,
105    value: GraphRecordValue,
106) -> QueryResult<GraphRecordValue> {
107    match value {
108        GraphRecordValue::String(value) => match value.parse() {
109            Ok(value) => Ok(GraphRecordValue::Bool(value)),
110            Err(_) => invalid_cast(label, GraphRecordValue::String(value), DataType::Bool),
111        },
112        GraphRecordValue::Int(value) => Ok(GraphRecordValue::Bool(value != 0)),
113        GraphRecordValue::Float(value) => Ok(GraphRecordValue::Bool(value != 0.0)),
114        GraphRecordValue::Bool(_) => Ok(value),
115        value @ (GraphRecordValue::DateTime(_)
116        | GraphRecordValue::Duration(_)
117        | GraphRecordValue::Null) => invalid_cast(label, value, DataType::Bool),
118    }
119}
120
121fn cast_value_to_datetime(
122    label: &'static str,
123    value: GraphRecordValue,
124) -> QueryResult<GraphRecordValue> {
125    match value {
126        GraphRecordValue::String(value) => match value.parse().map(GraphRecordValue::DateTime) {
127            Ok(value) => Ok(value),
128            Err(_) => invalid_cast(label, GraphRecordValue::String(value), DataType::DateTime),
129        },
130        GraphRecordValue::Int(value) => duration_from_milliseconds(value)
131            .and_then(datetime_from_duration)
132            .map(GraphRecordValue::DateTime)
133            .ok_or_else(|| {
134                Failure::new(
135                    label,
136                    InvalidCast::new(GraphRecordValue::Int(value), DataType::DateTime),
137                )
138            }),
139        GraphRecordValue::Float(value) => duration_from_fractional_milliseconds(value)
140            .and_then(datetime_from_duration)
141            .map(GraphRecordValue::DateTime)
142            .ok_or_else(|| {
143                Failure::new(
144                    label,
145                    InvalidCast::new(GraphRecordValue::Float(value), DataType::DateTime),
146                )
147            }),
148        GraphRecordValue::DateTime(_) => Ok(value),
149        value @ (GraphRecordValue::Bool(_)
150        | GraphRecordValue::Duration(_)
151        | GraphRecordValue::Null) => invalid_cast(label, value, DataType::DateTime),
152    }
153}
154
155fn cast_value_to_duration(
156    label: &'static str,
157    value: GraphRecordValue,
158) -> QueryResult<GraphRecordValue> {
159    match value {
160        GraphRecordValue::String(value) => parse_duration(&value)
161            .map(GraphRecordValue::Duration)
162            .ok_or_else(|| {
163                Failure::new(
164                    label,
165                    InvalidCast::new(GraphRecordValue::String(value), DataType::Duration),
166                )
167            }),
168        GraphRecordValue::Int(value) => duration_from_milliseconds(value)
169            .map(GraphRecordValue::Duration)
170            .ok_or_else(|| {
171                Failure::new(
172                    label,
173                    InvalidCast::new(GraphRecordValue::Int(value), DataType::Duration),
174                )
175            }),
176        GraphRecordValue::Float(value) => duration_from_fractional_milliseconds(value)
177            .map(GraphRecordValue::Duration)
178            .ok_or_else(|| {
179                Failure::new(
180                    label,
181                    InvalidCast::new(GraphRecordValue::Float(value), DataType::Duration),
182                )
183            }),
184        GraphRecordValue::Duration(_) => Ok(value),
185        value @ (GraphRecordValue::Bool(_)
186        | GraphRecordValue::DateTime(_)
187        | GraphRecordValue::Null) => invalid_cast(label, value, DataType::Duration),
188    }
189}
190
191fn cast_value_to_float(
192    label: &'static str,
193    value: GraphRecordValue,
194) -> QueryResult<GraphRecordValue> {
195    match value {
196        GraphRecordValue::String(value) => match value.parse() {
197            Ok(value) => Ok(GraphRecordValue::Float(value)),
198            Err(_) => invalid_cast(label, GraphRecordValue::String(value), DataType::Float),
199        },
200        GraphRecordValue::Int(value) => Ok(GraphRecordValue::Float(value as f64)),
201        GraphRecordValue::Float(_) => Ok(value),
202        GraphRecordValue::Bool(value) => Ok(GraphRecordValue::Float(if value { 1.0 } else { 0.0 })),
203        GraphRecordValue::DateTime(value) => {
204            let datetime = value.and_utc();
205            let milliseconds = datetime.timestamp_millis() as f64
206                + f64::from(datetime.timestamp_subsec_nanos() % 1_000_000) / 1_000_000.0;
207
208            Ok(GraphRecordValue::Float(milliseconds))
209        }
210        GraphRecordValue::Duration(value) => {
211            Ok(GraphRecordValue::Float(value.as_seconds_f64() * 1_000.0))
212        }
213        GraphRecordValue::Null => invalid_cast(label, GraphRecordValue::Null, DataType::Float),
214    }
215}
216
217fn cast_value_to_int(
218    label: &'static str,
219    value: GraphRecordValue,
220) -> QueryResult<GraphRecordValue> {
221    match value {
222        GraphRecordValue::String(value) => match value.parse() {
223            Ok(value) => Ok(GraphRecordValue::Int(value)),
224            Err(_) => invalid_cast(label, GraphRecordValue::String(value), DataType::Int),
225        },
226        GraphRecordValue::Int(_) => Ok(value),
227        GraphRecordValue::Float(value)
228            if value.is_finite() && value >= i64::MIN as f64 && value < -(i64::MIN as f64) =>
229        {
230            Ok(GraphRecordValue::Int(value as i64))
231        }
232        GraphRecordValue::Float(value) => {
233            invalid_cast(label, GraphRecordValue::Float(value), DataType::Int)
234        }
235        GraphRecordValue::Bool(value) => Ok(GraphRecordValue::Int(i64::from(value))),
236        GraphRecordValue::DateTime(value) => {
237            Ok(GraphRecordValue::Int(value.and_utc().timestamp_millis()))
238        }
239        GraphRecordValue::Duration(value) => Ok(GraphRecordValue::Int(value.num_milliseconds())),
240        GraphRecordValue::Null => invalid_cast(label, GraphRecordValue::Null, DataType::Int),
241    }
242}
243
244fn cast_value_to_string(value: GraphRecordValue) -> GraphRecordValue {
245    GraphRecordValue::String(match value {
246        GraphRecordValue::String(value) => value,
247        GraphRecordValue::Int(value) => value.to_string(),
248        GraphRecordValue::Float(value) => value.to_string(),
249        GraphRecordValue::Bool(value) => value.to_string(),
250        GraphRecordValue::DateTime(value) => value.format("%Y-%m-%dT%H:%M:%S%.f").to_string(),
251        GraphRecordValue::Duration(value) => value.to_string(),
252        GraphRecordValue::Null => "Null".to_string(),
253    })
254}
255
256fn cast_attribute_to_int(
257    label: &'static str,
258    value: GraphRecordAttribute,
259) -> QueryResult<GraphRecordAttribute> {
260    match value {
261        GraphRecordAttribute::String(value) => match value.parse() {
262            Ok(value) => Ok(GraphRecordAttribute::Int(value)),
263            Err(_) => invalid_cast(label, GraphRecordAttribute::String(value), DataType::Int),
264        },
265        GraphRecordAttribute::Int(_) => Ok(value),
266    }
267}
268
269fn cast_attribute_to_string(value: GraphRecordAttribute) -> GraphRecordAttribute {
270    GraphRecordAttribute::String(match value {
271        GraphRecordAttribute::String(value) => value,
272        GraphRecordAttribute::Int(value) => value.to_string(),
273    })
274}
275
276impl ValueCast<BoolTarget> for Scalar {
277    fn cast<'a>(
278        label: &'static str,
279        value: Self::Value<'a>,
280        _target: &BoolTarget,
281    ) -> QueryResult<Self::Value<'a>> {
282        cast_value_to_bool(label, value)
283    }
284}
285
286impl ValueCast<DateTimeTarget> for Scalar {
287    fn cast<'a>(
288        label: &'static str,
289        value: Self::Value<'a>,
290        _target: &DateTimeTarget,
291    ) -> QueryResult<Self::Value<'a>> {
292        cast_value_to_datetime(label, value)
293    }
294}
295
296impl ValueCast<DurationTarget> for Scalar {
297    fn cast<'a>(
298        label: &'static str,
299        value: Self::Value<'a>,
300        _target: &DurationTarget,
301    ) -> QueryResult<Self::Value<'a>> {
302        cast_value_to_duration(label, value)
303    }
304}
305
306impl ValueCast<FloatTarget> for Scalar {
307    fn cast<'a>(
308        label: &'static str,
309        value: Self::Value<'a>,
310        _target: &FloatTarget,
311    ) -> QueryResult<Self::Value<'a>> {
312        cast_value_to_float(label, value)
313    }
314}
315
316impl ValueCast<IntTarget> for Scalar {
317    fn cast<'a>(
318        label: &'static str,
319        value: Self::Value<'a>,
320        _target: &IntTarget,
321    ) -> QueryResult<Self::Value<'a>> {
322        cast_value_to_int(label, value)
323    }
324}
325
326impl ValueCast<StringTarget> for Scalar {
327    fn cast<'a>(
328        _label: &'static str,
329        value: Self::Value<'a>,
330        _target: &StringTarget,
331    ) -> QueryResult<Self::Value<'a>> {
332        Ok(cast_value_to_string(value))
333    }
334}
335
336impl ValueCast<IntTarget> for AttributeName {
337    fn cast<'a>(
338        label: &'static str,
339        value: Self::Value<'a>,
340        _target: &IntTarget,
341    ) -> QueryResult<Self::Value<'a>> {
342        cast_attribute_to_int(label, value)
343    }
344}
345
346impl ValueCast<StringTarget> for AttributeName {
347    fn cast<'a>(
348        _label: &'static str,
349        value: Self::Value<'a>,
350        _target: &StringTarget,
351    ) -> QueryResult<Self::Value<'a>> {
352        Ok(cast_attribute_to_string(value))
353    }
354}
355
356impl ValueCast<BoolTarget> for IndexValue<GraphRecordValue> {
357    fn cast<'a>(
358        label: &'static str,
359        value: Self::Value<'a>,
360        _target: &BoolTarget,
361    ) -> QueryResult<Self::Value<'a>> {
362        cast_value_to_bool(label, value)
363    }
364}
365
366impl ValueCast<DateTimeTarget> for IndexValue<GraphRecordValue> {
367    fn cast<'a>(
368        label: &'static str,
369        value: Self::Value<'a>,
370        _target: &DateTimeTarget,
371    ) -> QueryResult<Self::Value<'a>> {
372        cast_value_to_datetime(label, value)
373    }
374}
375
376impl ValueCast<DurationTarget> for IndexValue<GraphRecordValue> {
377    fn cast<'a>(
378        label: &'static str,
379        value: Self::Value<'a>,
380        _target: &DurationTarget,
381    ) -> QueryResult<Self::Value<'a>> {
382        cast_value_to_duration(label, value)
383    }
384}
385
386impl ValueCast<FloatTarget> for IndexValue<GraphRecordValue> {
387    fn cast<'a>(
388        label: &'static str,
389        value: Self::Value<'a>,
390        _target: &FloatTarget,
391    ) -> QueryResult<Self::Value<'a>> {
392        cast_value_to_float(label, value)
393    }
394}
395
396impl ValueCast<IntTarget> for IndexValue<GraphRecordValue> {
397    fn cast<'a>(
398        label: &'static str,
399        value: Self::Value<'a>,
400        _target: &IntTarget,
401    ) -> QueryResult<Self::Value<'a>> {
402        cast_value_to_int(label, value)
403    }
404}
405
406impl ValueCast<StringTarget> for IndexValue<GraphRecordValue> {
407    fn cast<'a>(
408        _label: &'static str,
409        value: Self::Value<'a>,
410        _target: &StringTarget,
411    ) -> QueryResult<Self::Value<'a>> {
412        Ok(cast_value_to_string(value))
413    }
414}
415
416impl ValueCast<IntTarget> for IndexValue<NodeIndex> {
417    fn cast<'a>(
418        label: &'static str,
419        value: Self::Value<'a>,
420        _target: &IntTarget,
421    ) -> QueryResult<Self::Value<'a>> {
422        cast_attribute_to_int(label, value)
423    }
424}
425
426impl ValueCast<StringTarget> for IndexValue<NodeIndex> {
427    fn cast<'a>(
428        _label: &'static str,
429        value: Self::Value<'a>,
430        _target: &StringTarget,
431    ) -> QueryResult<Self::Value<'a>> {
432        Ok(cast_attribute_to_string(value))
433    }
434}
435
436impl ValueCast<IntTarget> for IndexValue<AttributeName> {
437    fn cast<'a>(
438        label: &'static str,
439        value: Self::Value<'a>,
440        _target: &IntTarget,
441    ) -> QueryResult<Self::Value<'a>> {
442        cast_attribute_to_int(label, value)
443    }
444}
445
446impl ValueCast<StringTarget> for IndexValue<AttributeName> {
447    fn cast<'a>(
448        _label: &'static str,
449        value: Self::Value<'a>,
450        _target: &StringTarget,
451    ) -> QueryResult<Self::Value<'a>> {
452        Ok(cast_attribute_to_string(value))
453    }
454}