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