1use std::collections::{BTreeMap, HashMap};
2use std::fmt;
3use std::hash::BuildHasher;
4
5use geo::Point;
6use rust_decimal::Decimal;
7use surrealdb_types::ToSql;
8
9use crate::expr::Kind;
10use crate::expr::kind::{GeometryKind, HasKind, KindLiteral};
11use crate::val::{
12 Array, Bytes, Closure, Datetime, Duration, File, Geometry, Null, Number, Object, Range,
13 RecordId, Regex, Set, SqlNone, Strand, TableName, Uuid, Value,
14};
15
16#[derive(Clone, Debug, PartialEq)]
18pub enum ElementPosition {
19 Index(usize),
21 Key(String),
23}
24
25#[derive(Clone, Debug)]
26pub enum CoerceError {
27 InvalidKind {
29 from: Value,
30 into: String,
31 },
32 InvalidLength {
33 len: usize,
34 into: String,
35 },
36 ElementOf {
38 inner: Box<CoerceError>,
39 into: String,
40 position: Option<ElementPosition>,
41 },
42}
43impl std::error::Error for CoerceError {}
44impl fmt::Display for CoerceError {
45 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
46 match self {
47 CoerceError::InvalidKind {
48 from,
49 into,
50 } => {
51 write!(f, "Expected `{into}` but found `{from}`", from = from.to_sql())
52 }
53 CoerceError::ElementOf {
54 inner,
55 into,
56 position,
57 } => {
58 inner.fmt(f)?;
59 match position {
60 Some(ElementPosition::Index(i)) => {
61 write!(f, " when coercing element at index {i} of `{into}`")
62 }
63 Some(ElementPosition::Key(k)) => {
64 write!(f, " when coercing value for key '{k}' of `{into}`")
65 }
66 None => write!(f, " when coercing an element of `{into}`"),
67 }
68 }
69 CoerceError::InvalidLength {
70 len,
71 into,
72 } => {
73 write!(f, "Expected `{into}` but found a collection of length `{len}`")
74 }
75 }
76 }
77}
78
79pub trait CoerceErrorExt {
80 fn with_element_of<F>(self, f: F) -> Self
81 where
82 F: Fn() -> String;
83
84 fn with_element_of_at_index<F>(self, index: usize, f: F) -> Self
85 where
86 F: Fn() -> String;
87}
88
89impl<T> CoerceErrorExt for Result<T, CoerceError> {
90 fn with_element_of<F>(self, f: F) -> Self
91 where
92 F: Fn() -> String,
93 {
94 match self {
95 Ok(x) => Ok(x),
96 Err(e) => Err(CoerceError::ElementOf {
97 inner: Box::new(e),
98 into: f(),
99 position: None,
100 }),
101 }
102 }
103
104 fn with_element_of_at_index<F>(self, index: usize, f: F) -> Self
105 where
106 F: Fn() -> String,
107 {
108 match self {
109 Ok(x) => Ok(x),
110 Err(e) => Err(CoerceError::ElementOf {
111 inner: Box::new(e),
112 into: f(),
113 position: Some(ElementPosition::Index(index)),
114 }),
115 }
116 }
117}
118
119pub trait Coerce: Sized {
129 fn can_coerce(v: &Value) -> bool;
133
134 fn coerce(v: Value) -> Result<Self, CoerceError>;
136}
137
138impl Coerce for Value {
139 fn can_coerce(_: &Value) -> bool {
140 true
141 }
142
143 fn coerce(v: Value) -> Result<Self, CoerceError> {
144 Ok(v)
145 }
146}
147
148impl Coerce for SqlNone {
149 fn can_coerce(v: &Value) -> bool {
150 matches!(v, Value::None)
151 }
152
153 fn coerce(v: Value) -> Result<Self, CoerceError> {
154 match v {
155 Value::None => Ok(SqlNone),
156 x => Err(CoerceError::InvalidKind {
157 from: x,
158 into: "none".to_string(),
159 }),
160 }
161 }
162}
163
164impl Coerce for Null {
165 fn can_coerce(v: &Value) -> bool {
166 matches!(v, Value::Null)
167 }
168
169 fn coerce(v: Value) -> Result<Null, CoerceError> {
170 match v {
171 Value::Null => Ok(Null),
173 _ => Err(CoerceError::InvalidKind {
175 from: v,
176 into: "null".into(),
177 }),
178 }
179 }
180}
181
182impl Coerce for i64 {
183 fn can_coerce(v: &Value) -> bool {
188 matches!(v, Value::Number(n) if n.as_int_lossless().is_some())
189 }
190
191 fn coerce(val: Value) -> Result<Self, CoerceError> {
192 match val {
193 Value::Number(Number::Int(v)) => Ok(v),
195 Value::Number(n) => n.as_int_lossless().ok_or_else(|| CoerceError::InvalidKind {
199 from: val,
200 into: "int".into(),
201 }),
202 _ => Err(CoerceError::InvalidKind {
204 from: val,
205 into: "int".into(),
206 }),
207 }
208 }
209}
210
211impl Coerce for f64 {
212 fn can_coerce(v: &Value) -> bool {
213 let Value::Number(n) = v else {
214 return false;
215 };
216 match n {
217 Number::Int(_) | Number::Float(_) => true,
218 Number::Decimal(d) => f64::try_from(*d).is_ok(),
219 }
220 }
221
222 fn coerce(val: Value) -> Result<f64, CoerceError> {
224 match val {
225 Value::Number(Number::Float(v)) => Ok(v),
227 Value::Number(Number::Int(v)) => Ok(v as f64),
229 Value::Number(Number::Decimal(v)) => match v.try_into() {
231 Ok(v) => Ok(v),
233 _ => Err(CoerceError::InvalidKind {
235 from: val,
236 into: "float".into(),
237 }),
238 },
239 _ => Err(CoerceError::InvalidKind {
241 from: val,
242 into: "float".into(),
243 }),
244 }
245 }
246}
247
248impl Coerce for Decimal {
249 fn can_coerce(v: &Value) -> bool {
250 let Value::Number(n) = v else {
251 return false;
252 };
253 match n {
254 Number::Int(_) | Number::Decimal(_) => true,
255 Number::Float(f) => Decimal::try_from(*f).is_ok(),
256 }
257 }
258
259 fn coerce(val: Value) -> Result<Self, CoerceError> {
260 match val {
261 Value::Number(Number::Decimal(x)) => Ok(x),
263 Value::Number(Number::Int(v)) => Ok(Decimal::from(v)),
265 Value::Number(Number::Float(v)) => match Decimal::try_from(v).ok() {
267 Some(v) => Ok(v),
269 None => Err(CoerceError::InvalidKind {
271 from: val,
272 into: "decimal".into(),
273 }),
274 },
275 _ => Err(CoerceError::InvalidKind {
277 from: val,
278 into: "decimal".into(),
279 }),
280 }
281 }
282}
283
284impl Coerce for File {
285 fn can_coerce(v: &Value) -> bool {
286 matches!(v, Value::File(_))
287 }
288
289 fn coerce(v: Value) -> Result<Self, CoerceError> {
290 if let Value::File(x) = v {
291 Ok(x)
292 } else {
293 Err(CoerceError::InvalidKind {
294 from: v,
295 into: "file".to_string(),
296 })
297 }
298 }
299}
300
301impl Coerce for Point<f64> {
302 fn can_coerce(v: &Value) -> bool {
303 matches!(v, Value::Geometry(Geometry::Point(_)))
304 }
305
306 fn coerce(v: Value) -> Result<Self, CoerceError> {
307 if let Value::Geometry(Geometry::Point(x)) = v {
308 Ok(x)
309 } else {
310 Err(CoerceError::InvalidKind {
311 from: v,
312 into: "point".to_string(),
313 })
314 }
315 }
316}
317
318impl<T: Coerce + HasKind> Coerce for Option<T> {
319 fn can_coerce(v: &Value) -> bool {
320 if let Value::None = v {
321 return true;
322 }
323 T::can_coerce(v)
324 }
325
326 fn coerce(v: Value) -> Result<Self, CoerceError> {
327 match v {
328 Value::None => Ok(None),
329 x => Ok(Some(T::coerce(x)?)),
330 }
331 }
332}
333
334impl<T: Coerce + HasKind> Coerce for Vec<T> {
335 fn can_coerce(v: &Value) -> bool {
336 let Value::Array(a) = v else {
337 return false;
338 };
339 a.iter().all(T::can_coerce)
340 }
341
342 fn coerce(v: Value) -> Result<Self, CoerceError> {
343 if !v.is_array() {
344 return Err(CoerceError::InvalidKind {
345 from: v,
346 into: <Self as HasKind>::kind().to_sql(),
347 });
348 }
349 let array = v.into_array().expect("value type checked above");
351
352 let mut res = Vec::with_capacity(array.0.len());
353 for (i, x) in array.0.into_iter().enumerate() {
354 res.push(
355 x.coerce_to::<T>()
356 .with_element_of_at_index(i, || <Self as HasKind>::kind().to_sql())?,
357 );
358 }
359 Ok(res)
360 }
361}
362
363impl<T: Coerce + HasKind> Coerce for BTreeMap<String, T> {
364 fn can_coerce(v: &Value) -> bool {
365 let Value::Object(a) = v else {
366 return false;
367 };
368 a.values().all(T::can_coerce)
369 }
370
371 fn coerce(v: Value) -> Result<Self, CoerceError> {
372 if !v.is_object() {
373 return Err(CoerceError::InvalidKind {
374 from: v,
375 into: Object::kind().to_sql(),
376 });
377 };
378 let obj = v.into_object().expect("value type checked above");
380
381 let mut res = BTreeMap::new();
382 for (k, v) in obj.0 {
383 let key = k.into_string();
385 let value = match v.coerce_to::<T>() {
386 Ok(v) => v,
387 Err(e) => {
388 return Err(CoerceError::ElementOf {
389 inner: Box::new(e),
390 into: format!("object<{}>", <T as HasKind>::kind().to_sql()),
391 position: Some(ElementPosition::Key(key)),
392 });
393 }
394 };
395 res.insert(key, value);
396 }
397 Ok(res)
398 }
399}
400
401impl<T: Coerce + HasKind, S: BuildHasher + Default> Coerce for HashMap<String, T, S> {
402 fn can_coerce(v: &Value) -> bool {
403 let Value::Object(a) = v else {
404 return false;
405 };
406 a.values().all(T::can_coerce)
407 }
408
409 fn coerce(v: Value) -> Result<Self, CoerceError> {
410 if !v.is_object() {
411 return Err(CoerceError::InvalidKind {
412 from: v,
413 into: Kind::of::<Object>().to_sql(),
414 });
415 };
416 let obj = v.into_object().expect("value type checked above");
418
419 let mut res = HashMap::default();
420 for (k, v) in obj.0 {
421 let key = k.into_string();
423 let value = match v.coerce_to::<T>() {
424 Ok(v) => v,
425 Err(e) => {
426 return Err(CoerceError::ElementOf {
427 inner: Box::new(e),
428 into: format!("object<{}>", <T as HasKind>::kind().to_sql()),
429 position: Some(ElementPosition::Key(key)),
430 });
431 }
432 };
433 res.insert(key, value);
434 }
435 Ok(res)
436 }
437}
438
439macro_rules! impl_direct {
440 ($($name:ident => $inner:ty $(= $kind:ident)?),*$(,)?) => {
441 $(
442 impl Coerce for $inner {
443 fn can_coerce(v: &Value) -> bool{
444 matches!(v, Value::$name(_))
445 }
446
447 fn coerce(v: Value) -> Result<Self, CoerceError> {
448 if let Value::$name(x) = v {
449 return Ok(x);
450 } else {
451 return Err(CoerceError::InvalidKind{
452 from: v,
453 into: impl_direct!(@kindof $inner $(= $kind)?),
454 });
455 }
456 }
457 }
458 )*
459 };
460
461 (@kindof $inner:ty = $kind:ident) => {
462 Kind::of::<$kind>().to_sql()
463 };
464
465 (@kindof $inner:ty) => {
466 Kind::of::<$inner>().to_sql()
467 };
468}
469
470impl_direct! {
472 Bool => bool,
473 Number => Number,
474 Uuid => Uuid,
475 Closure => Box<Closure> = Closure,
476 Range => Box<Range> = Range,
477 Datetime => Datetime,
478 Duration => Duration,
479 Bytes => Bytes,
480 Object => Object,
481 Array => Array,
482 RecordId => RecordId,
483 String => Strand = String,
484 Geometry => Geometry,
485 Regex => Regex,
486 Table => TableName,
487}
488
489impl Coerce for Set {
490 fn can_coerce(v: &Value) -> bool {
491 matches!(v, Value::Set(_) | Value::Array(_))
492 }
493
494 fn coerce(v: Value) -> Result<Self, CoerceError> {
495 match v {
496 Value::Set(x) => Ok(x),
497 Value::Array(x) => Ok(Set::from(x.0)),
498 v => Err(CoerceError::InvalidKind {
499 from: v,
500 into: Kind::of::<Set>().to_sql(),
501 }),
502 }
503 }
504}
505
506impl Coerce for String {
509 fn can_coerce(v: &Value) -> bool {
510 matches!(v, Value::String(_))
511 }
512
513 fn coerce(v: Value) -> Result<Self, CoerceError> {
514 match v {
515 Value::String(s) => Ok(s.into_string()),
516 v => Err(CoerceError::InvalidKind {
517 from: v,
518 into: Kind::of::<String>().to_sql(),
519 }),
520 }
521 }
522}
523
524impl Value {
526 pub fn can_coerce_to<T: Coerce>(&self) -> bool {
527 T::can_coerce(self)
528 }
529
530 pub fn can_coerce_to_kind(&self, kind: &Kind) -> bool {
531 match kind {
532 Kind::Any => true,
533 Kind::None => self.can_coerce_to::<SqlNone>(),
534 Kind::Null => self.can_coerce_to::<Null>(),
535 Kind::Bool => self.can_coerce_to::<bool>(),
536 Kind::Int => self.can_coerce_to::<i64>(),
537 Kind::Float => self.can_coerce_to::<f64>(),
538 Kind::Decimal => self.can_coerce_to::<Decimal>(),
539 Kind::Number => self.can_coerce_to::<Number>(),
540 Kind::String => self.can_coerce_to::<String>(),
541 Kind::Datetime => self.can_coerce_to::<Datetime>(),
542 Kind::Duration => self.can_coerce_to::<Duration>(),
543 Kind::Object => self.can_coerce_to::<crate::val::Object>(),
544 Kind::Bytes => self.can_coerce_to::<Bytes>(),
545 Kind::Uuid => self.can_coerce_to::<Uuid>(),
546 Kind::Regex => self.can_coerce_to::<Regex>(),
547 Kind::Range => self.can_coerce_to::<Box<Range>>(),
548 Kind::Function(_, _) => self.can_coerce_to::<Box<Closure>>(),
549 Kind::Set(t, l) => match l {
550 Some(l) => self.can_coerce_to_set_len(t, *l),
551 None => self.can_coerce_to_set(t),
552 },
553 Kind::Array(t, l) => match l {
554 Some(l) => self.can_coerce_to_array_len(t, *l),
555 None => self.can_coerce_to_array(t),
556 },
557 Kind::Table(t) => {
558 if t.is_empty() {
559 self.can_coerce_to::<String>()
560 } else {
561 self.can_coerce_to_table(t)
562 }
563 }
564 Kind::Record(t) => {
565 if t.is_empty() {
566 self.can_coerce_to::<RecordId>()
567 } else {
568 self.can_coerce_to_record(t)
569 }
570 }
571 Kind::Geometry(t) => {
572 if t.is_empty() {
573 self.can_coerce_to::<Geometry>()
574 } else {
575 self.can_coerce_to_geometry(t)
576 }
577 }
578 Kind::Either(k) => k.iter().any(|x| self.can_coerce_to_kind(x)),
579 Kind::Literal(lit) => self.can_coerce_to_literal(lit),
580 Kind::File(buckets) => {
581 if buckets.is_empty() {
582 self.can_coerce_to::<File>()
583 } else {
584 self.can_coerce_to_file_buckets(buckets)
585 }
586 }
587 }
588 }
589
590 fn can_coerce_to_array_len(&self, kind: &Kind, len: u64) -> bool {
591 match self {
592 Value::Array(a) => {
593 a.len() as u64 == len && a.iter().all(|x| x.can_coerce_to_kind(kind))
594 }
595 _ => false,
596 }
597 }
598
599 fn can_coerce_to_array(&self, kind: &Kind) -> bool {
600 match self {
601 Value::Array(a) => a.iter().all(|x| x.can_coerce_to_kind(kind)),
602 _ => false,
603 }
604 }
605
606 fn can_coerce_to_set_len(&self, kind: &Kind, len: u64) -> bool {
607 match self {
608 Value::Set(s) => s.len() as u64 == len && s.iter().all(|x| x.can_coerce_to_kind(kind)),
609 Value::Array(a) => {
610 a.len() as u64 == len && a.iter().all(|x| x.can_coerce_to_kind(kind))
611 }
612 _ => false,
613 }
614 }
615
616 fn can_coerce_to_set(&self, kind: &Kind) -> bool {
617 match self {
618 Value::Set(s) => s.iter().all(|x| x.can_coerce_to_kind(kind)),
619 Value::Array(a) => a.iter().all(|x| x.can_coerce_to_kind(kind)),
620 _ => false,
621 }
622 }
623
624 fn can_coerce_to_table(&self, val: &[TableName]) -> bool {
625 match self {
626 Value::Table(t) => val.is_empty() || val.contains(t),
627 Value::String(s) => {
628 if val.is_empty() {
630 true
631 } else {
632 let s = TableName::from(s.clone());
633 val.contains(&s)
634 }
635 }
636 _ => false,
637 }
638 }
639
640 fn can_coerce_to_record(&self, val: &[TableName]) -> bool {
641 match self {
642 Value::RecordId(t) => val.is_empty() || val.contains(&t.table),
643 _ => false,
644 }
645 }
646
647 fn can_coerce_to_geometry(&self, val: &[GeometryKind]) -> bool {
648 self.is_geometry_type(val)
649 }
650
651 fn can_coerce_to_literal(&self, val: &KindLiteral) -> bool {
652 val.validate_value(self)
653 }
654
655 fn can_coerce_to_file_buckets(&self, buckets: &[String]) -> bool {
656 matches!(self, Value::File(f) if f.is_bucket_type(buckets))
657 }
658
659 pub fn coerce_to<T: Coerce>(self) -> Result<T, CoerceError> {
667 T::coerce(self)
668 }
669
670 pub fn coerce_to_kind(self, kind: &Kind) -> Result<Value, CoerceError> {
672 match kind {
674 Kind::Any => Ok(self),
675 Kind::None => self.coerce_to::<SqlNone>().map(|_| Value::None),
676 Kind::Null => self.coerce_to::<Null>().map(Value::from),
677 Kind::Bool => self.coerce_to::<bool>().map(Value::from),
678 Kind::Int => self.coerce_to::<i64>().map(Value::from),
679 Kind::Float => self.coerce_to::<f64>().map(Value::from),
680 Kind::Decimal => self.coerce_to::<Decimal>().map(Value::from),
681 Kind::Number => self.coerce_to::<Number>().map(Value::from),
682 Kind::String => self.coerce_to::<String>().map(Value::from),
683 Kind::Datetime => self.coerce_to::<Datetime>().map(Value::from),
684 Kind::Duration => self.coerce_to::<Duration>().map(Value::from),
685 Kind::Object => self.coerce_to::<crate::val::Object>().map(Value::from),
686 Kind::Bytes => self.coerce_to::<Bytes>().map(Value::from),
687 Kind::Uuid => self.coerce_to::<Uuid>().map(Value::from),
688 Kind::Regex => self.coerce_to::<Regex>().map(Value::from),
689 Kind::Range => self.coerce_to::<Box<Range>>().map(Value::from),
690 Kind::Function(_, _) => self.coerce_to::<Box<Closure>>().map(Value::from),
691 Kind::Set(t, l) => match l {
692 Some(l) => self.coerce_to_set_kind_len(t, *l).map(Value::from),
693 None => self.coerce_to_set_kind(t).map(Value::from),
694 },
695 Kind::Array(t, l) => match l {
696 Some(l) => self.coerce_to_array_type_len(t, *l).map(Value::from),
697 None => self.coerce_to_array_type(t).map(Value::from),
698 },
699 Kind::Table(t) => {
700 if t.is_empty() {
701 self.coerce_to::<String>()
702 .map(|s| Value::Table(surrealdb_strand::TableName::new(s)))
703 } else {
704 self.coerce_to_table_kind(t).map(Value::from)
705 }
706 }
707 Kind::Record(t) => {
708 if t.is_empty() {
709 self.coerce_to::<RecordId>().map(Value::from)
710 } else {
711 self.coerce_to_record_kind(t).map(Value::from)
712 }
713 }
714 Kind::Geometry(t) => {
715 if t.is_empty() {
716 self.coerce_to::<Geometry>().map(Value::from)
717 } else {
718 self.coerce_to_geometry_kind(t).map(Value::from)
719 }
720 }
721 Kind::Either(k) => {
722 let Some(k) = k.iter().find(|x| self.can_coerce_to_kind(x)) else {
724 return Err(CoerceError::InvalidKind {
725 from: self,
726 into: kind.to_sql(),
727 });
728 };
729
730 Ok(self.coerce_to_kind(k).expect(
731 "If can_coerce_to_kind returns true then coerce_to_kind must not error",
732 ))
733 }
734 Kind::Literal(lit) => self.coerce_to_literal(lit),
735 Kind::File(buckets) => {
736 if buckets.is_empty() {
737 self.coerce_to::<File>().map(Value::from)
738 } else {
739 self.coerce_to_file_buckets(buckets).map(Value::from)
740 }
741 }
742 }
743 }
744
745 pub fn coerce_to_literal(self, literal: &KindLiteral) -> Result<Value, CoerceError> {
748 if literal.validate_value(&self) {
749 Ok(self)
750 } else {
751 Err(CoerceError::InvalidKind {
752 from: self,
753 into: literal.to_sql(),
754 })
755 }
756 }
757
758 pub fn coerce_to_table_kind(
760 self,
761 val: &[TableName],
762 ) -> Result<surrealdb_strand::TableName, CoerceError> {
763 let this = match self {
764 Value::Table(v) => {
766 if val.is_empty() || val.contains(&v) {
767 return Ok(v);
768 } else {
769 Value::Table(v)
770 }
771 }
772 Value::String(s) => {
774 if val.is_empty() {
775 return Ok(surrealdb_strand::TableName::new(s));
776 }
777
778 let t = TableName::from(s);
779 if val.contains(&t) {
780 return Ok(t);
781 }
782
783 Value::String(t.into())
784 }
785 x => x,
786 };
787
788 let mut kind = "table<".to_string();
789 for (idx, t) in val.iter().enumerate() {
790 if idx != 0 {
791 kind.push('|');
792 }
793 kind.push_str(t.as_str())
794 }
795 kind.push('>');
796 Err(CoerceError::InvalidKind {
797 from: this,
798 into: kind,
799 })
800 }
801
802 pub fn coerce_to_record_kind(self, val: &[TableName]) -> Result<RecordId, CoerceError> {
804 let this = match self {
805 Value::RecordId(v) => {
807 if val.is_empty() || val.contains(&v.table) {
808 return Ok(v);
809 } else {
810 Value::RecordId(v)
811 }
812 }
813 x => x,
814 };
815
816 let mut kind = "record<".to_string();
817 for (idx, t) in val.iter().enumerate() {
818 if idx != 0 {
819 kind.push('|');
820 }
821 kind.push_str(t.as_str())
822 }
823 kind.push('>');
824 Err(CoerceError::InvalidKind {
825 from: this,
826 into: kind,
827 })
828 }
829
830 pub fn coerce_to_geometry_kind(self, val: &[GeometryKind]) -> Result<Geometry, CoerceError> {
832 if self.is_geometry_type(val) {
833 let Value::Geometry(x) = self else {
834 unreachable!()
836 };
837 Ok(x)
838 } else {
839 Err(CoerceError::InvalidKind {
840 from: self,
841 into: "geometry".into(),
842 })
843 }
844 }
845
846 pub fn coerce_to_array_type(self, kind: &Kind) -> Result<Array, CoerceError> {
848 self.coerce_to::<Array>()?
849 .into_iter()
850 .enumerate()
851 .map(|(i, value)| {
852 value
853 .coerce_to_kind(kind)
854 .with_element_of_at_index(i, || format!("array<{}>", kind.to_sql()))
855 })
856 .collect::<Result<Array, CoerceError>>()
857 }
858
859 pub fn coerce_to_array_type_len(self, kind: &Kind, len: u64) -> Result<Array, CoerceError> {
861 let array = self.coerce_to::<Array>()?;
862
863 if array.len() as u64 != len {
864 return Err(CoerceError::InvalidLength {
865 len: array.len(),
866 into: format!("array<{},{}>", kind.to_sql(), len),
867 });
868 }
869
870 array
871 .into_iter()
872 .enumerate()
873 .map(|(i, value)| {
874 value
875 .coerce_to_kind(kind)
876 .with_element_of_at_index(i, || format!("array<{}>", kind.to_sql()))
877 })
878 .collect::<Result<Array, CoerceError>>()
879 }
880
881 pub fn coerce_to_set_kind(self, kind: &Kind) -> Result<Set, CoerceError> {
883 self.coerce_to::<Set>()?
884 .into_iter()
885 .enumerate()
886 .map(|(i, value)| {
887 value
888 .coerce_to_kind(kind)
889 .with_element_of_at_index(i, || format!("set<{}>", kind.to_sql()))
890 })
891 .collect::<Result<Set, CoerceError>>()
892 }
893
894 pub fn coerce_to_set_kind_len(self, kind: &Kind, len: u64) -> Result<Set, CoerceError> {
896 let set = self
897 .coerce_to::<Set>()?
898 .into_iter()
899 .enumerate()
900 .map(|(i, value)| {
901 value
902 .coerce_to_kind(kind)
903 .with_element_of_at_index(i, || format!("set<{}>", kind.to_sql()))
904 })
905 .collect::<Result<Set, CoerceError>>()?;
906
907 if set.len() as u64 != len {
908 return Err(CoerceError::InvalidLength {
909 into: format!("set<{},{}>", kind.to_sql(), len),
910 len: set.len(),
911 });
912 }
913
914 Ok(set)
915 }
916
917 pub fn coerce_to_file_buckets(self, buckets: &[String]) -> Result<File, CoerceError> {
918 let v = self.coerce_to::<File>()?;
919
920 if v.is_bucket_type(buckets) {
921 return Ok(v);
922 }
923
924 let mut kind = "file<".to_owned();
925 for (idx, t) in buckets.iter().enumerate() {
926 if idx != 0 {
927 kind.push('|');
928 }
929 kind.push_str(t.as_str())
930 }
931 kind.push('>');
932 Err(CoerceError::InvalidKind {
933 from: v.into(),
934 into: kind,
935 })
936 }
937}
938
939#[cfg(test)]
940mod tests {
941 use surrealdb_strand::Strand;
942
943 use super::*;
944
945 #[test]
946 fn test_coerce_array_element_position_in_error() {
947 let value = Value::Array(Array(vec![
949 Value::Number(1.into()),
950 Value::String(Strand::new_static("bad")),
951 Value::Number(3.into()),
952 ]));
953 let kind = Kind::Int;
954 let err = value.coerce_to_array_type(&kind).unwrap_err();
955 let msg = err.to_string();
956 assert!(msg.contains("index 1"), "error message should mention index 1, got: {msg}");
957 }
958
959 #[test]
960 fn test_coerce_object_key_position_in_error() {
961 use std::collections::BTreeMap;
962
963 let mut map = BTreeMap::new();
964 map.insert(surrealdb_strand::Strand::new_static("valid"), Value::Number(1.into()));
965 map.insert(
966 surrealdb_strand::Strand::new_static("bad_field"),
967 Value::String(Strand::new_static("not_an_int")),
968 );
969 let value = Value::Object(Object(map.into()));
970
971 let err = value.coerce_to::<BTreeMap<String, i64>>().unwrap_err();
972 let msg = err.to_string();
973 assert!(
974 msg.contains("bad_field"),
975 "error message should mention the key 'bad_field', got: {msg}"
976 );
977 }
978
979 #[test]
980 fn test_coerce_vec_element_position_in_error() {
981 let value = Value::Array(Array(vec![
982 Value::Number(1.into()),
983 Value::Number(2.into()),
984 Value::String(Strand::new_static("oops")),
985 ]));
986
987 let err = value.coerce_to::<Vec<i64>>().unwrap_err();
988 let msg = err.to_string();
989 assert!(msg.contains("index 2"), "error message should mention index 2, got: {msg}");
990 }
991
992 #[test]
993 fn test_coerce_to_table_generic() {
994 let value = Value::String(Strand::new_static("users"));
996 let kind = Kind::Table(vec![]);
997 let result = value.coerce_to_kind(&kind);
998 assert!(result.is_ok());
999 if let Ok(Value::Table(table)) = result {
1000 assert_eq!(table.as_str(), "users");
1001 }
1002 }
1003
1004 #[test]
1005 fn test_coerce_to_table_specific() {
1006 let value = Value::String(Strand::new_static("posts"));
1008 let kind = Kind::Table(vec!["users".into()]);
1009 let result = value.coerce_to_kind(&kind);
1010 assert!(result.is_err());
1013 }
1014
1015 #[test]
1016 fn test_coerce_table_to_table() {
1017 let value = Value::Table("users".into());
1019 let kind = Kind::Table(vec!["users".into()]);
1020 let result = value.coerce_to_kind(&kind);
1021 assert!(result.is_ok());
1022
1023 let value = Value::Table("posts".into());
1025 let kind = Kind::Table(vec!["users".into()]);
1026 let result = value.coerce_to_kind(&kind);
1027 assert!(result.is_err());
1028 }
1029
1030 #[test]
1031 fn test_can_coerce_to_table() {
1032 let value = Value::Table("users".into());
1034 let kind = Kind::Table(vec!["users".into()]);
1035 assert!(value.can_coerce_to_kind(&kind));
1036
1037 let value = Value::Table("posts".into());
1039 let kind = Kind::Table(vec!["users".into()]);
1040 assert!(!value.can_coerce_to_kind(&kind));
1041
1042 let value = Value::Number(42.into());
1044 let kind = Kind::Table(vec![]);
1045 assert!(!value.can_coerce_to_kind(&kind));
1046 }
1047
1048 #[test]
1049 fn test_coerce_table_empty_tables_list() {
1050 let value = Value::Table("anything".into());
1052 let kind = Kind::Table(vec![]);
1053 let result = value.coerce_to_kind(&kind);
1054 assert!(result.is_err()); }
1056
1057 fn float(f: f64) -> Value {
1058 Value::Number(Number::Float(f))
1059 }
1060
1061 fn decimal(mantissa: i128, scale: u32) -> Value {
1062 Value::Number(Number::Decimal(Decimal::from_i128_with_scale(mantissa, scale)))
1063 }
1064
1065 #[test]
1066 fn test_coerce_float_to_int_accepts_whole_floats_in_range() {
1067 assert_eq!(float(5.0).coerce_to::<i64>().unwrap(), 5);
1068 assert_eq!(float(-5.0).coerce_to::<i64>().unwrap(), -5);
1069 assert_eq!(float(-0.0).coerce_to::<i64>().unwrap(), 0);
1071 assert_eq!(
1073 float(9_007_199_254_740_992.0).coerce_to::<i64>().unwrap(),
1074 9_007_199_254_740_992
1075 );
1076 assert_eq!(float(i64::MIN as f64).coerce_to::<i64>().unwrap(), i64::MIN);
1078 assert_eq!(float(2f64.powi(63) - 1024.0).coerce_to::<i64>().unwrap(), i64::MAX - 1023);
1080 }
1081
1082 #[test]
1083 fn test_coerce_float_to_int_refuses_what_is_not_one_i64() {
1084 for f in [
1085 2f64.powi(63),
1087 i64::MAX as f64,
1088 -2f64.powi(63) - 2048.0,
1090 1e30,
1091 -1e30,
1092 f64::MAX,
1093 f64::MIN,
1094 f64::NAN,
1095 f64::INFINITY,
1096 f64::NEG_INFINITY,
1097 0.5,
1098 -0.5,
1099 ] {
1100 let err = float(f).coerce_to::<i64>().unwrap_err();
1101 assert!(
1102 matches!(&err, CoerceError::InvalidKind { into, .. } if into == "int"),
1103 "{f} should be refused as an int, got: {err}"
1104 );
1105 assert!(!float(f).can_coerce_to::<i64>(), "{f} must not report as coercible to int");
1106 }
1107 }
1108
1109 #[test]
1110 fn test_coerce_float_out_of_range_refused_like_decimal() {
1111 let float_err = float(1e30).coerce_to::<i64>().unwrap_err();
1113 let decimal_err = decimal(10_000_000_000_000_000_000, 0).coerce_to::<i64>().unwrap_err();
1114 assert!(matches!(float_err, CoerceError::InvalidKind { ref into, .. } if into == "int"));
1115 assert!(matches!(decimal_err, CoerceError::InvalidKind { ref into, .. } if into == "int"));
1116 }
1117
1118 fn number_battery() -> Vec<Value> {
1121 let mut out = vec![
1122 Value::Number(Number::Int(0)),
1123 Value::Number(Number::Int(i64::MIN)),
1124 Value::Number(Number::Int(i64::MAX)),
1125 decimal(0, 0),
1126 decimal(15, 1),
1127 decimal(-15, 1),
1128 decimal(50, 1),
1130 decimal(i64::MAX as i128, 0),
1131 decimal(i64::MAX as i128 + 1, 0),
1132 decimal(i64::MIN as i128, 0),
1133 decimal(i64::MIN as i128 - 1, 0),
1134 decimal(10_000_000_000_000_000_000, 0),
1135 decimal(100_000_000_000_000_000_005, 1),
1137 Value::Number(Number::Decimal(Decimal::MAX)),
1138 Value::Number(Number::Decimal(Decimal::MIN)),
1139 ];
1140 for f in [
1141 0.0,
1142 -0.0,
1143 5.0,
1144 0.5,
1145 -0.5,
1146 1e30,
1147 -1e30,
1148 i64::MAX as f64,
1149 i64::MIN as f64,
1150 f64::MAX,
1151 f64::MIN,
1152 f64::MIN_POSITIVE,
1153 f64::NAN,
1154 f64::INFINITY,
1155 f64::NEG_INFINITY,
1156 ] {
1157 out.push(float(f));
1158 }
1159 out
1160 }
1161
1162 #[test]
1163 fn test_can_coerce_agrees_with_coerce_for_numbers() {
1164 for value in number_battery() {
1168 for kind in [Kind::Int, Kind::Float, Kind::Decimal, Kind::Number] {
1169 assert_eq!(
1170 value.can_coerce_to_kind(&kind),
1171 value.clone().coerce_to_kind(&kind).is_ok(),
1172 "can_coerce and coerce disagree for {value:?} into {}",
1173 kind.to_sql()
1174 );
1175 }
1176 }
1177 }
1178
1179 #[test]
1180 fn test_coerce_union_with_int_never_claims_a_value_it_cannot_coerce() {
1181 let fractional = decimal(15, 1);
1182 let huge = float(1e30);
1183 let unions = [
1184 Kind::Either(vec![Kind::Int, Kind::String]),
1185 Kind::Either(vec![Kind::None, Kind::Int]),
1186 Kind::Array(Box::new(Kind::Either(vec![Kind::Int, Kind::String])), None),
1187 ];
1188 for kind in &unions {
1189 let wrap = |v: Value| match kind {
1190 Kind::Array(..) => Value::Array(Array(vec![v])),
1191 _ => v,
1192 };
1193 assert!(wrap(fractional.clone()).coerce_to_kind(kind).is_err());
1194 assert!(wrap(huge.clone()).coerce_to_kind(kind).is_err());
1195 }
1196 let either = Kind::Either(vec![Kind::Int, Kind::Float]);
1198 assert!(matches!(
1199 huge.coerce_to_kind(&either).unwrap(),
1200 Value::Number(Number::Float(f)) if f == 1e30
1201 ));
1202 assert!(matches!(
1203 fractional.coerce_to_kind(&either).unwrap(),
1204 Value::Number(Number::Float(f)) if f == 1.5
1205 ));
1206 assert!(matches!(
1208 float(5.0).coerce_to_kind(&either).unwrap(),
1209 Value::Number(Number::Int(5))
1210 ));
1211 }
1212}