Skip to main content

reifydb_value/value/
try_from.rs

1// SPDX-License-Identifier: Apache-2.0
2// Copyright (c) 2026 ReifyDB
3
4use std::{
5	error,
6	fmt::{self, Display, Formatter},
7};
8
9use crate::{
10	fragment::Fragment,
11	value::{
12		Value,
13		blob::Blob,
14		date::Date,
15		datetime::DateTime,
16		decimal::Decimal,
17		duration::Duration,
18		identity::IdentityId,
19		int::Int,
20		ordered_f32::OrderedF32,
21		ordered_f64::OrderedF64,
22		temporal::parse::{
23			date::parse_date, datetime::parse_datetime, duration::parse_duration, time::parse_time,
24		},
25		time::Time,
26		uint::Uint,
27		uuid::{Uuid4, Uuid7},
28		value_type::ValueType,
29	},
30};
31
32#[derive(Debug, Clone, PartialEq)]
33pub enum FromValueError {
34	TypeMismatch {
35		expected: ValueType,
36		found: ValueType,
37	},
38
39	OutOfRange {
40		value: String,
41		target_type: &'static str,
42	},
43}
44
45impl Display for FromValueError {
46	fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
47		match self {
48			FromValueError::TypeMismatch {
49				expected,
50				found,
51			} => {
52				write!(f, "type mismatch: expected {:?}, found {:?}", expected, found)
53			}
54			FromValueError::OutOfRange {
55				value,
56				target_type,
57			} => {
58				write!(f, "value {} out of range for type {}", value, target_type)
59			}
60		}
61	}
62}
63
64impl error::Error for FromValueError {}
65
66pub trait TryFromValue: Sized {
67	fn try_from_value(value: &Value) -> Result<Self, FromValueError>;
68
69	fn from_value(value: &Value) -> Option<Self> {
70		match value {
71			Value::None {
72				..
73			} => None,
74			v => Self::try_from_value(v).ok(),
75		}
76	}
77}
78
79pub trait TryFromValueCoerce: Sized {
80	fn try_from_value_coerce(value: &Value) -> Result<Self, FromValueError>;
81
82	fn from_value_coerce(value: &Value) -> Option<Self> {
83		match value {
84			Value::None {
85				..
86			} => None,
87			v => Self::try_from_value_coerce(v).ok(),
88		}
89	}
90}
91
92impl TryFromValue for Value {
93	fn try_from_value(value: &Value) -> Result<Self, FromValueError> {
94		Ok(value.clone())
95	}
96}
97
98impl TryFromValue for bool {
99	fn try_from_value(value: &Value) -> Result<Self, FromValueError> {
100		match value {
101			Value::Boolean(v) => Ok(*v),
102			_ => Err(FromValueError::TypeMismatch {
103				expected: ValueType::Boolean,
104				found: value.get_type(),
105			}),
106		}
107	}
108}
109
110impl TryFromValue for i8 {
111	fn try_from_value(value: &Value) -> Result<Self, FromValueError> {
112		match value {
113			Value::Int1(v) => Ok(*v),
114			_ => Err(FromValueError::TypeMismatch {
115				expected: ValueType::Int1,
116				found: value.get_type(),
117			}),
118		}
119	}
120}
121
122impl TryFromValue for i16 {
123	fn try_from_value(value: &Value) -> Result<Self, FromValueError> {
124		match value {
125			Value::Int2(v) => Ok(*v),
126			_ => Err(FromValueError::TypeMismatch {
127				expected: ValueType::Int2,
128				found: value.get_type(),
129			}),
130		}
131	}
132}
133
134impl TryFromValue for i32 {
135	fn try_from_value(value: &Value) -> Result<Self, FromValueError> {
136		match value {
137			Value::Int4(v) => Ok(*v),
138			_ => Err(FromValueError::TypeMismatch {
139				expected: ValueType::Int4,
140				found: value.get_type(),
141			}),
142		}
143	}
144}
145
146impl TryFromValue for i64 {
147	fn try_from_value(value: &Value) -> Result<Self, FromValueError> {
148		match value {
149			Value::Int8(v) => Ok(*v),
150			_ => Err(FromValueError::TypeMismatch {
151				expected: ValueType::Int8,
152				found: value.get_type(),
153			}),
154		}
155	}
156}
157
158impl TryFromValue for i128 {
159	fn try_from_value(value: &Value) -> Result<Self, FromValueError> {
160		match value {
161			Value::Int16(v) => Ok(*v),
162			_ => Err(FromValueError::TypeMismatch {
163				expected: ValueType::Int16,
164				found: value.get_type(),
165			}),
166		}
167	}
168}
169
170impl TryFromValue for u8 {
171	fn try_from_value(value: &Value) -> Result<Self, FromValueError> {
172		match value {
173			Value::Uint1(v) => Ok(*v),
174			_ => Err(FromValueError::TypeMismatch {
175				expected: ValueType::Uint1,
176				found: value.get_type(),
177			}),
178		}
179	}
180}
181
182impl TryFromValue for u16 {
183	fn try_from_value(value: &Value) -> Result<Self, FromValueError> {
184		match value {
185			Value::Uint2(v) => Ok(*v),
186			_ => Err(FromValueError::TypeMismatch {
187				expected: ValueType::Uint2,
188				found: value.get_type(),
189			}),
190		}
191	}
192}
193
194impl TryFromValue for u32 {
195	fn try_from_value(value: &Value) -> Result<Self, FromValueError> {
196		match value {
197			Value::Uint4(v) => Ok(*v),
198			_ => Err(FromValueError::TypeMismatch {
199				expected: ValueType::Uint4,
200				found: value.get_type(),
201			}),
202		}
203	}
204}
205
206impl TryFromValue for u64 {
207	fn try_from_value(value: &Value) -> Result<Self, FromValueError> {
208		match value {
209			Value::Uint8(v) => Ok(*v),
210			_ => Err(FromValueError::TypeMismatch {
211				expected: ValueType::Uint8,
212				found: value.get_type(),
213			}),
214		}
215	}
216}
217
218impl TryFromValue for u128 {
219	fn try_from_value(value: &Value) -> Result<Self, FromValueError> {
220		match value {
221			Value::Uint16(v) => Ok(*v),
222			_ => Err(FromValueError::TypeMismatch {
223				expected: ValueType::Uint16,
224				found: value.get_type(),
225			}),
226		}
227	}
228}
229
230impl TryFromValue for f32 {
231	fn try_from_value(value: &Value) -> Result<Self, FromValueError> {
232		match value {
233			Value::Float4(v) => Ok(v.value()),
234			_ => Err(FromValueError::TypeMismatch {
235				expected: ValueType::Float4,
236				found: value.get_type(),
237			}),
238		}
239	}
240}
241
242impl TryFromValue for f64 {
243	fn try_from_value(value: &Value) -> Result<Self, FromValueError> {
244		match value {
245			Value::Float8(v) => Ok(v.value()),
246			_ => Err(FromValueError::TypeMismatch {
247				expected: ValueType::Float8,
248				found: value.get_type(),
249			}),
250		}
251	}
252}
253
254impl TryFromValue for String {
255	fn try_from_value(value: &Value) -> Result<Self, FromValueError> {
256		match value {
257			Value::Utf8(v) => Ok(v.clone()),
258			_ => Err(FromValueError::TypeMismatch {
259				expected: ValueType::Utf8,
260				found: value.get_type(),
261			}),
262		}
263	}
264}
265
266impl TryFromValue for OrderedF32 {
267	fn try_from_value(value: &Value) -> Result<Self, FromValueError> {
268		match value {
269			Value::Float4(v) => Ok(*v),
270			_ => Err(FromValueError::TypeMismatch {
271				expected: ValueType::Float4,
272				found: value.get_type(),
273			}),
274		}
275	}
276}
277
278impl TryFromValue for OrderedF64 {
279	fn try_from_value(value: &Value) -> Result<Self, FromValueError> {
280		match value {
281			Value::Float8(v) => Ok(*v),
282			_ => Err(FromValueError::TypeMismatch {
283				expected: ValueType::Float8,
284				found: value.get_type(),
285			}),
286		}
287	}
288}
289
290impl TryFromValue for Blob {
291	fn try_from_value(value: &Value) -> Result<Self, FromValueError> {
292		match value {
293			Value::Blob(v) => Ok(v.clone()),
294			_ => Err(FromValueError::TypeMismatch {
295				expected: ValueType::Blob,
296				found: value.get_type(),
297			}),
298		}
299	}
300}
301
302impl TryFromValue for Uuid4 {
303	fn try_from_value(value: &Value) -> Result<Self, FromValueError> {
304		match value {
305			Value::Uuid4(v) => Ok(*v),
306			_ => Err(FromValueError::TypeMismatch {
307				expected: ValueType::Uuid4,
308				found: value.get_type(),
309			}),
310		}
311	}
312}
313
314impl TryFromValue for Uuid7 {
315	fn try_from_value(value: &Value) -> Result<Self, FromValueError> {
316		match value {
317			Value::Uuid7(v) => Ok(*v),
318			_ => Err(FromValueError::TypeMismatch {
319				expected: ValueType::Uuid7,
320				found: value.get_type(),
321			}),
322		}
323	}
324}
325
326impl TryFromValue for Date {
327	fn try_from_value(value: &Value) -> Result<Self, FromValueError> {
328		match value {
329			Value::Date(v) => Ok(*v),
330			_ => Err(FromValueError::TypeMismatch {
331				expected: ValueType::Date,
332				found: value.get_type(),
333			}),
334		}
335	}
336}
337
338impl TryFromValue for DateTime {
339	fn try_from_value(value: &Value) -> Result<Self, FromValueError> {
340		match value {
341			Value::DateTime(v) => Ok(*v),
342			_ => Err(FromValueError::TypeMismatch {
343				expected: ValueType::DateTime,
344				found: value.get_type(),
345			}),
346		}
347	}
348}
349
350impl TryFromValue for Time {
351	fn try_from_value(value: &Value) -> Result<Self, FromValueError> {
352		match value {
353			Value::Time(v) => Ok(*v),
354			_ => Err(FromValueError::TypeMismatch {
355				expected: ValueType::Time,
356				found: value.get_type(),
357			}),
358		}
359	}
360}
361
362impl TryFromValue for Duration {
363	fn try_from_value(value: &Value) -> Result<Self, FromValueError> {
364		match value {
365			Value::Duration(v) => Ok(*v),
366			_ => Err(FromValueError::TypeMismatch {
367				expected: ValueType::Duration,
368				found: value.get_type(),
369			}),
370		}
371	}
372}
373
374impl TryFromValue for IdentityId {
375	fn try_from_value(value: &Value) -> Result<Self, FromValueError> {
376		match value {
377			Value::IdentityId(v) => Ok(*v),
378			_ => Err(FromValueError::TypeMismatch {
379				expected: ValueType::IdentityId,
380				found: value.get_type(),
381			}),
382		}
383	}
384}
385
386impl TryFromValue for Int {
387	fn try_from_value(value: &Value) -> Result<Self, FromValueError> {
388		match value {
389			Value::Int(v) => Ok(v.clone()),
390			_ => Err(FromValueError::TypeMismatch {
391				expected: ValueType::Int,
392				found: value.get_type(),
393			}),
394		}
395	}
396}
397
398impl TryFromValue for Uint {
399	fn try_from_value(value: &Value) -> Result<Self, FromValueError> {
400		match value {
401			Value::Uint(v) => Ok(v.clone()),
402			_ => Err(FromValueError::TypeMismatch {
403				expected: ValueType::Uint,
404				found: value.get_type(),
405			}),
406		}
407	}
408}
409
410impl TryFromValue for Decimal {
411	fn try_from_value(value: &Value) -> Result<Self, FromValueError> {
412		match value {
413			Value::Decimal(v) => Ok(v.clone()),
414			_ => Err(FromValueError::TypeMismatch {
415				expected: ValueType::Decimal,
416				found: value.get_type(),
417			}),
418		}
419	}
420}
421
422macro_rules! coerce_temporal {
423	($t:ty, $variant:ident, $parse:path, $expected:expr) => {
424		impl TryFromValueCoerce for $t {
425			fn try_from_value_coerce(value: &Value) -> Result<Self, FromValueError> {
426				match value {
427					Value::$variant(v) => Ok(*v),
428					Value::Utf8(s) => $parse(Fragment::internal(s)).map_err(|_| {
429						FromValueError::TypeMismatch {
430							expected: $expected,
431							found: ValueType::Utf8,
432						}
433					}),
434					_ => Err(FromValueError::TypeMismatch {
435						expected: $expected,
436						found: value.get_type(),
437					}),
438				}
439			}
440		}
441	};
442}
443
444coerce_temporal!(Date, Date, parse_date, ValueType::Date);
445coerce_temporal!(DateTime, DateTime, parse_datetime, ValueType::DateTime);
446coerce_temporal!(Time, Time, parse_time, ValueType::Time);
447coerce_temporal!(Duration, Duration, parse_duration, ValueType::Duration);
448
449macro_rules! coerce_int {
450	($t:ty, $expected:expr, $name:literal) => {
451		impl TryFromValueCoerce for $t {
452			fn try_from_value_coerce(value: &Value) -> Result<Self, FromValueError> {
453				let out_of_range = |repr: String| FromValueError::OutOfRange {
454					value: repr,
455					target_type: $name,
456				};
457				match value {
458					Value::Int1(v) => <$t>::try_from(*v).map_err(|_| out_of_range(v.to_string())),
459					Value::Int2(v) => <$t>::try_from(*v).map_err(|_| out_of_range(v.to_string())),
460					Value::Int4(v) => <$t>::try_from(*v).map_err(|_| out_of_range(v.to_string())),
461					Value::Int8(v) => <$t>::try_from(*v).map_err(|_| out_of_range(v.to_string())),
462					Value::Int16(v) => <$t>::try_from(*v).map_err(|_| out_of_range(v.to_string())),
463					Value::Uint1(v) => <$t>::try_from(*v).map_err(|_| out_of_range(v.to_string())),
464					Value::Uint2(v) => <$t>::try_from(*v).map_err(|_| out_of_range(v.to_string())),
465					Value::Uint4(v) => <$t>::try_from(*v).map_err(|_| out_of_range(v.to_string())),
466					Value::Uint8(v) => <$t>::try_from(*v).map_err(|_| out_of_range(v.to_string())),
467					Value::Uint16(v) => <$t>::try_from(*v).map_err(|_| out_of_range(v.to_string())),
468					_ => Err(FromValueError::TypeMismatch {
469						expected: $expected,
470						found: value.get_type(),
471					}),
472				}
473			}
474		}
475	};
476}
477
478coerce_int!(i8, ValueType::Int1, "i8");
479coerce_int!(i16, ValueType::Int2, "i16");
480coerce_int!(i32, ValueType::Int4, "i32");
481coerce_int!(i64, ValueType::Int8, "i64");
482coerce_int!(i128, ValueType::Int16, "i128");
483coerce_int!(u8, ValueType::Uint1, "u8");
484coerce_int!(u16, ValueType::Uint2, "u16");
485coerce_int!(u32, ValueType::Uint4, "u32");
486coerce_int!(u64, ValueType::Uint8, "u64");
487coerce_int!(u128, ValueType::Uint16, "u128");
488coerce_int!(usize, ValueType::Uint8, "usize");
489
490impl TryFromValueCoerce for f32 {
491	fn try_from_value_coerce(value: &Value) -> Result<Self, FromValueError> {
492		match value {
493			Value::Float4(v) => Ok(v.value()),
494			Value::Float8(v) => Ok(v.value() as f32),
495
496			Value::Int1(v) => Ok(*v as f32),
497			Value::Int2(v) => Ok(*v as f32),
498			Value::Int4(v) => Ok(*v as f32),
499			Value::Int8(v) => Ok(*v as f32),
500			Value::Int16(v) => Ok(*v as f32),
501			Value::Uint1(v) => Ok(*v as f32),
502			Value::Uint2(v) => Ok(*v as f32),
503			Value::Uint4(v) => Ok(*v as f32),
504			Value::Uint8(v) => Ok(*v as f32),
505			Value::Uint16(v) => Ok(*v as f32),
506			_ => Err(FromValueError::TypeMismatch {
507				expected: ValueType::Float4,
508				found: value.get_type(),
509			}),
510		}
511	}
512}
513
514impl TryFromValueCoerce for f64 {
515	fn try_from_value_coerce(value: &Value) -> Result<Self, FromValueError> {
516		match value {
517			Value::Float4(v) => Ok(v.value() as f64),
518			Value::Float8(v) => Ok(v.value()),
519
520			Value::Int1(v) => Ok(*v as f64),
521			Value::Int2(v) => Ok(*v as f64),
522			Value::Int4(v) => Ok(*v as f64),
523			Value::Int8(v) => Ok(*v as f64),
524			Value::Int16(v) => Ok(*v as f64),
525			Value::Uint1(v) => Ok(*v as f64),
526			Value::Uint2(v) => Ok(*v as f64),
527			Value::Uint4(v) => Ok(*v as f64),
528			Value::Uint8(v) => Ok(*v as f64),
529			Value::Uint16(v) => Ok(*v as f64),
530			_ => Err(FromValueError::TypeMismatch {
531				expected: ValueType::Float8,
532				found: value.get_type(),
533			}),
534		}
535	}
536}
537
538#[cfg(test)]
539#[allow(clippy::approx_constant)]
540pub mod tests {
541	use super::*;
542	use crate::value::{ordered_f32::OrderedF32, ordered_f64::OrderedF64};
543
544	#[test]
545	fn test_try_from_value_primitives() {
546		// Test boolean
547		assert_eq!(bool::try_from_value(&Value::Boolean(true)), Ok(true));
548		assert_eq!(bool::try_from_value(&Value::Boolean(false)), Ok(false));
549		assert!(bool::try_from_value(&Value::Int4(42)).is_err());
550
551		// Test integers
552		assert_eq!(i8::try_from_value(&Value::Int1(42)), Ok(42i8));
553		assert_eq!(i16::try_from_value(&Value::Int2(1234)), Ok(1234i16));
554		assert_eq!(i32::try_from_value(&Value::Int4(123456)), Ok(123456i32));
555		assert_eq!(i64::try_from_value(&Value::Int8(1234567890)), Ok(1234567890i64));
556
557		// Test unsigned integers
558		assert_eq!(u8::try_from_value(&Value::Uint1(42)), Ok(42u8));
559		assert_eq!(u16::try_from_value(&Value::Uint2(1234)), Ok(1234u16));
560		assert_eq!(u32::try_from_value(&Value::Uint4(123456)), Ok(123456u32));
561		assert_eq!(u64::try_from_value(&Value::Uint8(1234567890)), Ok(1234567890u64));
562
563		// Test string
564		assert_eq!(String::try_from_value(&Value::Utf8("hello".to_string())), Ok("hello".to_string()));
565	}
566
567	#[test]
568	fn test_from_value_undefined() {
569		// from_value should return None for Undefined
570		assert_eq!(bool::from_value(&Value::none()), None);
571		assert_eq!(i32::from_value(&Value::none()), None);
572		assert_eq!(String::from_value(&Value::none()), None);
573
574		// from_value should return None for type mismatch
575		assert_eq!(bool::from_value(&Value::Int4(42)), None);
576		assert_eq!(i32::from_value(&Value::Boolean(true)), None);
577	}
578
579	#[test]
580	fn test_try_from_value_coerce_i64() {
581		// Accepts every integer width whose value fits in i64, regardless of signedness
582		assert_eq!(i64::try_from_value_coerce(&Value::Int1(42)), Ok(42i64));
583		assert_eq!(i64::try_from_value_coerce(&Value::Int2(1234)), Ok(1234i64));
584		assert_eq!(i64::try_from_value_coerce(&Value::Int4(123456)), Ok(123456i64));
585		assert_eq!(i64::try_from_value_coerce(&Value::Int8(1234567890)), Ok(1234567890i64));
586		assert_eq!(i64::try_from_value_coerce(&Value::Uint4(42)), Ok(42i64));
587
588		// Rejects values that overflow i64 (range-checked) and non-integer types
589		assert!(i64::try_from_value_coerce(&Value::Uint8(u64::MAX)).is_err());
590		assert!(i64::try_from_value_coerce(&Value::Boolean(true)).is_err());
591	}
592
593	#[test]
594	fn test_try_from_value_coerce_u64() {
595		// Should accept all unsigned integer types
596		assert_eq!(u64::try_from_value_coerce(&Value::Uint1(42)), Ok(42u64));
597		assert_eq!(u64::try_from_value_coerce(&Value::Uint2(1234)), Ok(1234u64));
598		assert_eq!(u64::try_from_value_coerce(&Value::Uint4(123456)), Ok(123456u64));
599		assert_eq!(u64::try_from_value_coerce(&Value::Uint8(1234567890)), Ok(1234567890u64));
600
601		// Should accept non-negative signed integers
602		assert_eq!(u64::try_from_value_coerce(&Value::Int4(42)), Ok(42u64));
603
604		// Should reject negative signed integers
605		assert!(u64::try_from_value_coerce(&Value::Int4(-42)).is_err());
606	}
607
608	#[test]
609	fn test_try_from_value_coerce_f64() {
610		// Should accept float types
611		let f4 = OrderedF32::try_from(3.14f32).unwrap();
612		let f8 = OrderedF64::try_from(3.14159f64).unwrap();
613		assert!((f64::try_from_value_coerce(&Value::Float4(f4)).unwrap() - 3.14).abs() < 0.01);
614		assert!((f64::try_from_value_coerce(&Value::Float8(f8)).unwrap() - 3.14159).abs() < 0.00001);
615
616		// Should accept integer types
617		assert_eq!(f64::try_from_value_coerce(&Value::Int4(42)), Ok(42.0f64));
618		assert_eq!(f64::try_from_value_coerce(&Value::Uint4(42)), Ok(42.0f64));
619	}
620
621	#[test]
622	fn test_from_value_coerce_undefined() {
623		// from_value_coerce should return None for Undefined
624		assert_eq!(i64::from_value_coerce(&Value::none()), None);
625		assert_eq!(u64::from_value_coerce(&Value::none()), None);
626		assert_eq!(f64::from_value_coerce(&Value::none()), None);
627	}
628
629	#[test]
630	fn test_try_from_value_coerce_temporal_parses_strings() {
631		// A duration literal string coerces; sub-minute must survive intact.
632		assert_eq!(
633			Duration::try_from_value_coerce(&Value::Utf8("1s".to_string())),
634			Ok(Duration::from_seconds(1).unwrap())
635		);
636		assert_eq!(
637			Duration::try_from_value_coerce(&Value::Utf8("5m".to_string())),
638			Ok(Duration::from_minutes(5).unwrap())
639		);
640		assert_eq!(
641			Duration::try_from_value_coerce(&Value::Utf8("PT1M".to_string())),
642			Ok(Duration::from_minutes(1).unwrap())
643		);
644
645		// The native variant passes straight through.
646		let d = Duration::from_seconds(60).unwrap();
647		assert_eq!(Duration::try_from_value_coerce(&Value::Duration(d)), Ok(d));
648
649		// Date / DateTime / Time literals coerce as well.
650		assert_eq!(
651			Date::try_from_value_coerce(&Value::Utf8("2024-01-15".to_string())),
652			Ok(Date::new(2024, 1, 15).unwrap())
653		);
654		assert_eq!(
655			DateTime::try_from_value_coerce(&Value::Utf8("2024-01-15T10:30:00".to_string())),
656			Ok(DateTime::from_ymd_hms(2024, 1, 15, 10, 30, 0).unwrap())
657		);
658		assert_eq!(
659			Time::try_from_value_coerce(&Value::Utf8("10:30:00".to_string())),
660			Ok(Time::new(10, 30, 0, 0).unwrap())
661		);
662	}
663
664	#[test]
665	fn test_try_from_value_coerce_temporal_rejects_non_literals() {
666		// A bare integer must NOT be silently treated as a duration (no implied unit).
667		assert!(Duration::try_from_value_coerce(&Value::Uint8(60)).is_err());
668		assert!(Duration::try_from_value_coerce(&Value::Int4(1)).is_err());
669
670		// An unparseable string and a foreign temporal variant are rejected.
671		assert!(Duration::try_from_value_coerce(&Value::Utf8("notaduration".to_string())).is_err());
672		assert!(Duration::try_from_value_coerce(&Value::Time(Time::new(1, 0, 0, 0).unwrap())).is_err());
673	}
674}