1use crate::Result;
4use crate::error::{Error, TypeError};
5use crate::value::Value;
6use std::collections::HashMap;
7use std::sync::Arc;
8
9#[derive(Debug, Clone)]
14pub struct ColumnInfo {
15 names: Vec<String>,
17 name_to_index: HashMap<String, usize>,
19}
20
21impl ColumnInfo {
22 pub fn new(names: Vec<String>) -> Self {
24 let name_to_index = names
25 .iter()
26 .enumerate()
27 .map(|(i, name)| (name.clone(), i))
28 .collect();
29 Self {
30 names,
31 name_to_index,
32 }
33 }
34
35 pub fn len(&self) -> usize {
37 self.names.len()
38 }
39
40 pub fn is_empty(&self) -> bool {
42 self.names.is_empty()
43 }
44
45 pub fn index_of(&self, name: &str) -> Option<usize> {
47 self.name_to_index.get(name).copied()
48 }
49
50 pub fn name_at(&self, index: usize) -> Option<&str> {
52 self.names.get(index).map(String::as_str)
53 }
54
55 pub fn contains(&self, name: &str) -> bool {
57 self.name_to_index.contains_key(name)
58 }
59
60 pub fn names(&self) -> &[String] {
62 &self.names
63 }
64}
65
66#[derive(Debug, Clone)]
71pub struct Row {
72 values: Vec<Value>,
74 columns: Arc<ColumnInfo>,
76}
77
78impl Row {
79 pub fn new(column_names: Vec<String>, values: Vec<Value>) -> Self {
84 let columns = Arc::new(ColumnInfo::new(column_names));
85 Self { values, columns }
86 }
87
88 #[must_use]
105 pub fn subset_by_prefix(&self, prefix: &str) -> Self {
106 let prefix_with_sep = format!("{}__", prefix);
107 let mut names = Vec::new();
108 let mut values = Vec::new();
109
110 for (name, value) in self.iter() {
111 if let Some(stripped) = name.strip_prefix(&prefix_with_sep) {
112 names.push(stripped.to_string());
113 values.push(value.clone());
114 }
115 }
116
117 Self::new(names, values)
118 }
119
120 #[must_use]
124 pub fn has_prefix(&self, prefix: &str) -> bool {
125 let prefix_with_sep = format!("{}__", prefix);
126 self.column_names()
127 .any(|name| name.starts_with(&prefix_with_sep))
128 }
129
130 #[must_use]
134 pub fn prefix_is_all_null(&self, prefix: &str) -> bool {
135 let prefix_with_sep = format!("{}__", prefix);
136 for (name, value) in self.iter() {
137 if name.starts_with(&prefix_with_sep) && !value.is_null() {
138 return false;
139 }
140 }
141
142 true
144 }
145
146 pub fn with_columns(columns: Arc<ColumnInfo>, values: Vec<Value>) -> Self {
150 Self { values, columns }
151 }
152
153 pub fn column_info(&self) -> Arc<ColumnInfo> {
157 Arc::clone(&self.columns)
158 }
159
160 pub fn len(&self) -> usize {
162 self.values.len()
163 }
164
165 pub fn is_empty(&self) -> bool {
167 self.values.is_empty()
168 }
169
170 pub fn get(&self, index: usize) -> Option<&Value> {
172 self.values.get(index)
173 }
174
175 pub fn get_by_name(&self, name: &str) -> Option<&Value> {
177 self.columns.index_of(name).and_then(|i| self.values.get(i))
178 }
179
180 pub fn contains_column(&self, name: &str) -> bool {
182 self.columns.contains(name)
183 }
184
185 #[allow(clippy::result_large_err)]
187 pub fn get_as<T: FromValue>(&self, index: usize) -> Result<T> {
188 let value = self.get(index).ok_or_else(|| {
189 Error::Type(TypeError {
190 expected: std::any::type_name::<T>(),
191 actual: format!(
192 "index {} out of bounds (row has {} columns)",
193 index,
194 self.len()
195 ),
196 column: None,
197 rust_type: None,
198 })
199 })?;
200 T::from_value(value)
201 }
202
203 #[allow(clippy::result_large_err)]
205 pub fn get_named<T: FromValue>(&self, name: &str) -> Result<T> {
206 let value = self.get_by_name(name).ok_or_else(|| {
207 Error::Type(TypeError {
208 expected: std::any::type_name::<T>(),
209 actual: format!("column '{}' not found", name),
210 column: Some(name.to_string()),
211 rust_type: None,
212 })
213 })?;
214 T::from_value(value).map_err(|e| match e {
215 Error::Type(mut te) => {
216 te.column = Some(name.to_string());
217 Error::Type(te)
218 }
219 e => e,
220 })
221 }
222
223 pub fn column_names(&self) -> impl Iterator<Item = &str> {
225 self.columns.names().iter().map(String::as_str)
226 }
227
228 pub fn values(&self) -> impl Iterator<Item = &Value> {
230 self.values.iter()
231 }
232
233 pub fn iter(&self) -> impl Iterator<Item = (&str, &Value)> {
235 self.columns
236 .names()
237 .iter()
238 .map(String::as_str)
239 .zip(self.values.iter())
240 }
241}
242
243pub trait FromValue: Sized {
245 #[allow(clippy::result_large_err)]
247 fn from_value(value: &Value) -> Result<Self>;
248}
249
250impl FromValue for bool {
251 fn from_value(value: &Value) -> Result<Self> {
252 value.as_bool().ok_or_else(|| {
253 Error::Type(TypeError {
254 expected: "bool",
255 actual: value.type_name().to_string(),
256 column: None,
257 rust_type: None,
258 })
259 })
260 }
261}
262
263impl FromValue for i8 {
264 fn from_value(value: &Value) -> Result<Self> {
265 match value {
266 Value::TinyInt(v) => Ok(*v),
267 Value::Bool(v) => Ok(if *v { 1 } else { 0 }),
268 _ => Err(Error::Type(TypeError {
269 expected: "i8",
270 actual: value.type_name().to_string(),
271 column: None,
272 rust_type: None,
273 })),
274 }
275 }
276}
277
278impl FromValue for i16 {
279 fn from_value(value: &Value) -> Result<Self> {
280 match value {
281 Value::TinyInt(v) => Ok(i16::from(*v)),
282 Value::SmallInt(v) => Ok(*v),
283 Value::Bool(v) => Ok(if *v { 1 } else { 0 }),
284 _ => Err(Error::Type(TypeError {
285 expected: "i16",
286 actual: value.type_name().to_string(),
287 column: None,
288 rust_type: None,
289 })),
290 }
291 }
292}
293
294impl FromValue for i32 {
295 fn from_value(value: &Value) -> Result<Self> {
296 match value {
297 Value::TinyInt(v) => Ok(i32::from(*v)),
298 Value::SmallInt(v) => Ok(i32::from(*v)),
299 Value::Int(v) => Ok(*v),
300 Value::Bool(v) => Ok(if *v { 1 } else { 0 }),
301 _ => Err(Error::Type(TypeError {
302 expected: "i32",
303 actual: value.type_name().to_string(),
304 column: None,
305 rust_type: None,
306 })),
307 }
308 }
309}
310
311impl FromValue for i64 {
312 fn from_value(value: &Value) -> Result<Self> {
313 value.as_i64().ok_or_else(|| {
314 Error::Type(TypeError {
315 expected: "i64",
316 actual: value.type_name().to_string(),
317 column: None,
318 rust_type: None,
319 })
320 })
321 }
322}
323
324impl FromValue for u8 {
325 fn from_value(value: &Value) -> Result<Self> {
326 let v = value.as_i64().ok_or_else(|| {
327 Error::Type(TypeError {
328 expected: "u8",
329 actual: value.type_name().to_string(),
330 column: None,
331 rust_type: None,
332 })
333 })?;
334 u8::try_from(v).map_err(|_| {
335 Error::Type(TypeError {
336 expected: "u8",
337 actual: format!("value {} out of range", v),
338 column: None,
339 rust_type: None,
340 })
341 })
342 }
343}
344
345impl FromValue for u16 {
346 fn from_value(value: &Value) -> Result<Self> {
347 let v = value.as_i64().ok_or_else(|| {
348 Error::Type(TypeError {
349 expected: "u16",
350 actual: value.type_name().to_string(),
351 column: None,
352 rust_type: None,
353 })
354 })?;
355 u16::try_from(v).map_err(|_| {
356 Error::Type(TypeError {
357 expected: "u16",
358 actual: format!("value {} out of range", v),
359 column: None,
360 rust_type: None,
361 })
362 })
363 }
364}
365
366impl FromValue for u32 {
367 fn from_value(value: &Value) -> Result<Self> {
368 let v = value.as_i64().ok_or_else(|| {
369 Error::Type(TypeError {
370 expected: "u32",
371 actual: value.type_name().to_string(),
372 column: None,
373 rust_type: None,
374 })
375 })?;
376 u32::try_from(v).map_err(|_| {
377 Error::Type(TypeError {
378 expected: "u32",
379 actual: format!("value {} out of range", v),
380 column: None,
381 rust_type: None,
382 })
383 })
384 }
385}
386
387impl FromValue for u64 {
388 fn from_value(value: &Value) -> Result<Self> {
389 let v = value.as_i64().ok_or_else(|| {
390 Error::Type(TypeError {
391 expected: "u64",
392 actual: value.type_name().to_string(),
393 column: None,
394 rust_type: None,
395 })
396 })?;
397 u64::try_from(v).map_err(|_| {
398 Error::Type(TypeError {
399 expected: "u64",
400 actual: format!("value {} out of range", v),
401 column: None,
402 rust_type: None,
403 })
404 })
405 }
406}
407
408const F32_MAX_EXACT_INT: i64 = 1 << 24;
410
411impl FromValue for f32 {
412 fn from_value(value: &Value) -> Result<Self> {
414 match value {
415 Value::Float(v) => Ok(*v),
416 #[allow(clippy::cast_possible_truncation)]
417 Value::Double(v) => {
418 let converted = *v as f32;
419 if (f64::from(converted) - *v).abs() > f64::EPSILON * v.abs().max(1.0) {
421 return Err(Error::Type(TypeError {
422 expected: "f32-representable f64",
423 actual: format!("f64 value {} loses precision as f32", v),
424 column: None,
425 rust_type: Some("f32"),
426 }));
427 }
428 Ok(converted)
429 }
430 Value::TinyInt(v) => Ok(f32::from(*v)),
431 Value::SmallInt(v) => Ok(f32::from(*v)),
432 #[allow(clippy::cast_possible_truncation)]
433 Value::Int(v) => {
434 if i64::from(*v).abs() > F32_MAX_EXACT_INT {
435 return Err(Error::Type(TypeError {
436 expected: "f32-representable i32",
437 actual: format!(
438 "i32 value {} exceeds f32 exact integer range (±{})",
439 v, F32_MAX_EXACT_INT
440 ),
441 column: None,
442 rust_type: Some("f32"),
443 }));
444 }
445 Ok(*v as f32)
446 }
447 #[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)]
448 Value::BigInt(v) => {
449 if v.unsigned_abs() > F32_MAX_EXACT_INT as u64 {
451 return Err(Error::Type(TypeError {
452 expected: "f32-representable i64",
453 actual: format!(
454 "i64 value {} exceeds f32 exact integer range (±{})",
455 v, F32_MAX_EXACT_INT
456 ),
457 column: None,
458 rust_type: Some("f32"),
459 }));
460 }
461 Ok(*v as f32)
462 }
463 Value::Bool(v) => Ok(if *v { 1.0 } else { 0.0 }),
465 _ => Err(Error::Type(TypeError {
466 expected: "f32",
467 actual: value.type_name().to_string(),
468 column: None,
469 rust_type: None,
470 })),
471 }
472 }
473}
474
475const F64_MAX_EXACT_INT: i64 = 1 << 53;
477
478impl FromValue for f64 {
479 fn from_value(value: &Value) -> Result<Self> {
481 match value {
482 Value::Float(v) => Ok(f64::from(*v)),
483 Value::Double(v) => Ok(*v),
484 Value::TinyInt(v) => Ok(f64::from(*v)),
485 Value::SmallInt(v) => Ok(f64::from(*v)),
486 Value::Int(v) => Ok(f64::from(*v)),
487 #[allow(clippy::cast_precision_loss, clippy::cast_sign_loss)]
488 Value::BigInt(v) => {
489 if v.unsigned_abs() > F64_MAX_EXACT_INT as u64 {
491 return Err(Error::Type(TypeError {
492 expected: "f64-representable i64",
493 actual: format!(
494 "i64 value {} exceeds f64 exact integer range (±{})",
495 v, F64_MAX_EXACT_INT
496 ),
497 column: None,
498 rust_type: Some("f64"),
499 }));
500 }
501 Ok(*v as f64)
502 }
503 Value::Bool(v) => Ok(if *v { 1.0 } else { 0.0 }),
504 _ => Err(Error::Type(TypeError {
505 expected: "f64",
506 actual: value.type_name().to_string(),
507 column: None,
508 rust_type: None,
509 })),
510 }
511 }
512}
513
514impl FromValue for String {
515 fn from_value(value: &Value) -> Result<Self> {
516 match value {
517 Value::Text(s) => Ok(s.clone()),
518 Value::Decimal(s) => Ok(s.clone()),
519 _ => Err(Error::Type(TypeError {
520 expected: "String",
521 actual: value.type_name().to_string(),
522 column: None,
523 rust_type: None,
524 })),
525 }
526 }
527}
528
529impl FromValue for Vec<u8> {
530 fn from_value(value: &Value) -> Result<Self> {
531 match value {
532 Value::Bytes(b) => Ok(b.clone()),
533 Value::Text(s) => Ok(s.as_bytes().to_vec()),
534 _ => Err(Error::Type(TypeError {
535 expected: "Vec<u8>",
536 actual: value.type_name().to_string(),
537 column: None,
538 rust_type: None,
539 })),
540 }
541 }
542}
543
544impl<T: FromValue> FromValue for Option<T> {
545 fn from_value(value: &Value) -> Result<Self> {
546 if value.is_null() {
547 Ok(None)
548 } else {
549 T::from_value(value).map(Some)
550 }
551 }
552}
553
554impl FromValue for Value {
555 fn from_value(value: &Value) -> Result<Self> {
556 Ok(value.clone())
557 }
558}
559
560impl FromValue for serde_json::Value {
561 fn from_value(value: &Value) -> Result<Self> {
562 match value {
563 Value::Json(v) => Ok(v.clone()),
564 Value::Text(s) => serde_json::from_str(s).map_err(|e| {
565 Error::Type(TypeError {
566 expected: "valid JSON",
567 actual: format!("invalid JSON: {}", e),
568 column: None,
569 rust_type: None,
570 })
571 }),
572 _ => Err(Error::Type(TypeError {
573 expected: "JSON",
574 actual: value.type_name().to_string(),
575 column: None,
576 rust_type: None,
577 })),
578 }
579 }
580}
581
582impl FromValue for [u8; 16] {
583 fn from_value(value: &Value) -> Result<Self> {
584 match value {
585 Value::Uuid(v) => Ok(*v),
586 Value::Bytes(v) if v.len() == 16 => {
587 let mut arr = [0u8; 16];
588 arr.copy_from_slice(v);
589 Ok(arr)
590 }
591 _ => Err(Error::Type(TypeError {
592 expected: "UUID (16 bytes)",
593 actual: value.type_name().to_string(),
594 column: None,
595 rust_type: None,
596 })),
597 }
598 }
599}
600
601#[cfg(test)]
602mod tests {
603 use super::*;
604
605 #[test]
606 fn test_row_basic_access() {
607 let row = Row::new(
608 vec!["id".to_string(), "name".to_string(), "age".to_string()],
609 vec![
610 Value::Int(1),
611 Value::Text("Alice".to_string()),
612 Value::Int(30),
613 ],
614 );
615
616 assert_eq!(row.len(), 3);
617 assert!(!row.is_empty());
618
619 assert_eq!(row.get(0), Some(&Value::Int(1)));
621 assert_eq!(row.get(1), Some(&Value::Text("Alice".to_string())));
622 assert_eq!(row.get(3), None);
623
624 assert_eq!(row.get_by_name("id"), Some(&Value::Int(1)));
626 assert_eq!(
627 row.get_by_name("name"),
628 Some(&Value::Text("Alice".to_string()))
629 );
630 assert_eq!(row.get_by_name("missing"), None);
631 }
632
633 #[test]
634 fn test_row_typed_access() {
635 let row = Row::new(
636 vec!["id".to_string(), "name".to_string()],
637 vec![Value::Int(42), Value::Text("Bob".to_string())],
638 );
639
640 assert_eq!(row.get_as::<i32>(0).unwrap(), 42);
642 assert_eq!(row.get_as::<i64>(0).unwrap(), 42);
643 assert_eq!(row.get_as::<String>(1).unwrap(), "Bob");
644
645 assert_eq!(row.get_named::<i32>("id").unwrap(), 42);
647 assert_eq!(row.get_named::<String>("name").unwrap(), "Bob");
648 }
649
650 #[test]
651 fn test_row_type_errors() {
652 let row = Row::new(
653 vec!["id".to_string()],
654 vec![Value::Text("not a number".to_string())],
655 );
656
657 assert!(row.get_named::<i32>("id").is_err());
659
660 assert!(row.get_named::<i32>("missing").is_err());
662
663 assert!(row.get_as::<i32>(99).is_err());
665 }
666
667 #[test]
668 fn test_row_null_handling() {
669 let row = Row::new(vec!["nullable".to_string()], vec![Value::Null]);
670
671 assert_eq!(row.get_named::<Option<i32>>("nullable").unwrap(), None);
673
674 assert!(row.get_named::<i32>("nullable").is_err());
676 }
677
678 #[test]
679 fn test_row_iterators() {
680 let row = Row::new(
681 vec!["a".to_string(), "b".to_string()],
682 vec![Value::Int(1), Value::Int(2)],
683 );
684
685 let names: Vec<_> = row.column_names().collect();
687 assert_eq!(names, vec!["a", "b"]);
688
689 let values: Vec<_> = row.values().collect();
691 assert_eq!(values, vec![&Value::Int(1), &Value::Int(2)]);
692
693 let pairs: Vec<_> = row.iter().collect();
695 assert_eq!(pairs, vec![("a", &Value::Int(1)), ("b", &Value::Int(2))]);
696 }
697
698 #[test]
699 fn test_row_shared_columns() {
700 let columns = Arc::new(ColumnInfo::new(vec!["id".to_string(), "name".to_string()]));
701
702 let row1 = Row::with_columns(
703 Arc::clone(&columns),
704 vec![Value::Int(1), Value::Text("Alice".to_string())],
705 );
706 let row2 = Row::with_columns(
707 Arc::clone(&columns),
708 vec![Value::Int(2), Value::Text("Bob".to_string())],
709 );
710
711 assert!(Arc::ptr_eq(&row1.column_info(), &row2.column_info()));
713
714 assert_eq!(row1.get_named::<i32>("id").unwrap(), 1);
716 assert_eq!(row2.get_named::<i32>("id").unwrap(), 2);
717 }
718
719 #[test]
720 fn test_row_contains_column() {
721 let row = Row::new(vec!["exists".to_string()], vec![Value::Int(1)]);
722
723 assert!(row.contains_column("exists"));
724 assert!(!row.contains_column("missing"));
725 }
726
727 #[test]
728 fn test_column_info() {
729 let info = ColumnInfo::new(vec![
730 "id".to_string(),
731 "name".to_string(),
732 "age".to_string(),
733 ]);
734
735 assert_eq!(info.len(), 3);
736 assert!(!info.is_empty());
737
738 assert_eq!(info.index_of("id"), Some(0));
739 assert_eq!(info.index_of("name"), Some(1));
740 assert_eq!(info.index_of("missing"), None);
741
742 assert_eq!(info.name_at(0), Some("id"));
743 assert_eq!(info.name_at(1), Some("name"));
744 assert_eq!(info.name_at(99), None);
745
746 assert!(info.contains("id"));
747 assert!(!info.contains("missing"));
748 }
749
750 #[test]
751 fn test_from_value_all_types() {
752 assert!(bool::from_value(&Value::Bool(true)).unwrap());
754 assert!(bool::from_value(&Value::Int(1)).unwrap());
755 assert!(!bool::from_value(&Value::Int(0)).unwrap());
756
757 assert_eq!(i8::from_value(&Value::TinyInt(42)).unwrap(), 42);
759
760 assert_eq!(i16::from_value(&Value::SmallInt(100)).unwrap(), 100);
762 assert_eq!(i16::from_value(&Value::TinyInt(10)).unwrap(), 10);
763
764 assert_eq!(i32::from_value(&Value::Int(1000)).unwrap(), 1000);
766
767 assert_eq!(i64::from_value(&Value::BigInt(10000)).unwrap(), 10000);
769
770 let pi_f32 = std::f32::consts::PI;
772 let from_float = f32::from_value(&Value::Float(pi_f32)).unwrap();
773 assert!((from_float - pi_f32).abs() < 1e-6);
774
775 let pi_f64 = std::f64::consts::PI;
777 let from_double = f64::from_value(&Value::Double(pi_f64)).unwrap();
778 assert!((from_double - pi_f64).abs() < 1e-12);
779
780 assert_eq!(
782 String::from_value(&Value::Text("hello".to_string())).unwrap(),
783 "hello"
784 );
785
786 assert_eq!(
788 Vec::<u8>::from_value(&Value::Bytes(vec![1, 2, 3])).unwrap(),
789 vec![1, 2, 3]
790 );
791 }
792
793 #[test]
794 fn test_empty_row() {
795 let row = Row::new(vec![], vec![]);
796 assert!(row.is_empty());
797 assert_eq!(row.len(), 0);
798 assert_eq!(row.get(0), None);
799 assert!(row.get_as::<i32>(0).is_err());
800 }
801
802 #[test]
803 fn test_large_row() {
804 let n = 100;
806 let names: Vec<_> = (0..n).map(|i| format!("col_{}", i)).collect();
807 let values: Vec<_> = (0..n).map(Value::Int).collect();
808 let row = Row::new(names, values);
809
810 assert_eq!(row.len(), n as usize);
811 assert_eq!(row.get_named::<i32>("col_0").unwrap(), 0);
812 assert_eq!(row.get_named::<i32>("col_50").unwrap(), 50);
813 assert_eq!(row.get_named::<i32>("col_99").unwrap(), 99);
814 }
815
816 #[test]
817 fn test_subset_by_prefix() {
818 let row = Row::new(
819 vec![
820 "heroes__id".to_string(),
821 "heroes__name".to_string(),
822 "teams__id".to_string(),
823 "teams__name".to_string(),
824 ],
825 vec![
826 Value::Int(1),
827 Value::Text("Batman".to_string()),
828 Value::Int(10),
829 Value::Text("Justice League".to_string()),
830 ],
831 );
832
833 let heroes_row = row.subset_by_prefix("heroes");
835 assert_eq!(heroes_row.len(), 2);
836 assert_eq!(heroes_row.get_named::<i32>("id").unwrap(), 1);
837 assert_eq!(heroes_row.get_named::<String>("name").unwrap(), "Batman");
838
839 let teams_row = row.subset_by_prefix("teams");
841 assert_eq!(teams_row.len(), 2);
842 assert_eq!(teams_row.get_named::<i32>("id").unwrap(), 10);
843 assert_eq!(
844 teams_row.get_named::<String>("name").unwrap(),
845 "Justice League"
846 );
847
848 let empty_row = row.subset_by_prefix("powers");
850 assert!(empty_row.is_empty());
851 }
852
853 #[test]
854 fn test_has_prefix() {
855 let row = Row::new(
856 vec!["heroes__id".to_string(), "teams__id".to_string()],
857 vec![Value::Int(1), Value::Int(10)],
858 );
859
860 assert!(row.has_prefix("heroes"));
861 assert!(row.has_prefix("teams"));
862 assert!(!row.has_prefix("powers"));
863 }
864
865 #[test]
866 fn test_prefix_is_all_null() {
867 let row = Row::new(
868 vec![
869 "heroes__id".to_string(),
870 "heroes__name".to_string(),
871 "teams__id".to_string(),
872 "teams__name".to_string(),
873 ],
874 vec![
875 Value::Int(1),
876 Value::Text("Batman".to_string()),
877 Value::Null,
878 Value::Null,
879 ],
880 );
881
882 assert!(!row.prefix_is_all_null("heroes"));
884
885 assert!(row.prefix_is_all_null("teams"));
887
888 assert!(row.prefix_is_all_null("powers"));
890 }
891
892 #[test]
895 fn test_from_value_f32_precision_checks() {
896 let v = f32::from_value(&Value::Double(1.5)).unwrap();
898 assert!((v - 1.5).abs() < f32::EPSILON);
899
900 let result = f32::from_value(&Value::Double(1e20_f64));
902 assert!(result.is_err());
903
904 let v = f32::from_value(&Value::Int(1000)).unwrap();
906 assert!((v - 1000.0).abs() < f32::EPSILON);
907
908 let result = f32::from_value(&Value::BigInt(i64::MAX));
910 assert!(result.is_err());
911 }
912
913 #[test]
914 fn test_from_value_f64_precision_checks() {
915 let v = f64::from_value(&Value::BigInt(42)).unwrap();
917 assert!((v - 42.0).abs() < f64::EPSILON);
918
919 let result = f64::from_value(&Value::BigInt(i64::MAX));
921 assert!(result.is_err());
922
923 let boundary = 1i64 << 53;
925 let v = f64::from_value(&Value::BigInt(boundary)).unwrap();
926 assert!((v - boundary as f64).abs() < 1.0);
927
928 let result = f64::from_value(&Value::BigInt(boundary + 1));
930 assert!(result.is_err());
931 }
932
933 #[test]
934 fn test_from_value_f32_int_boundary() {
935 const F32_MAX_EXACT: i64 = 1 << 24; #[allow(clippy::cast_possible_truncation)]
939 let boundary = F32_MAX_EXACT as i32;
940 let v = f32::from_value(&Value::Int(boundary)).unwrap();
941 assert!((v - boundary as f32).abs() < 1.0);
942
943 #[allow(clippy::cast_possible_truncation)]
945 let over = (F32_MAX_EXACT + 1) as i32;
946 let result = f32::from_value(&Value::Int(over));
947 assert!(result.is_err());
948 }
949
950 #[test]
951 fn test_from_value_bool_to_f64() {
952 assert!((f64::from_value(&Value::Bool(true)).unwrap() - 1.0).abs() < f64::EPSILON);
954 assert!((f64::from_value(&Value::Bool(false)).unwrap() - 0.0).abs() < f64::EPSILON);
955 }
956}