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}