Skip to main content

reifydb_value/value/
try_from.rs

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