Skip to main content

surrealdb_expr/val/value/convert/
coerce.rs

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/// Identifies which element of a collection caused a coercion failure.
17#[derive(Clone, Debug, PartialEq)]
18pub enum ElementPosition {
19	/// Zero-based index into an array or set.
20	Index(usize),
21	/// Key of an object field.
22	Key(String),
23}
24
25#[derive(Clone, Debug)]
26pub enum CoerceError {
27	// Coercion error at the end.
28	InvalidKind {
29		from: Value,
30		into: String,
31	},
32	InvalidLength {
33		len: usize,
34		into: String,
35	},
36	// Coerce failed because element of type didn't match.
37	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
119/// Trait for converting the value using coercion rules.
120///
121/// Coercion rules are applied whenever a Value needs to be of a specific
122/// [`Kind`]. This happens when a value is applied to a place with a type like
123/// table fields and function parameters.
124///
125/// Coercion rules are more strict then casting rules.
126/// Calling this method will succeed if the value can be unified with the kind
127/// of the target
128pub trait Coerce: Sized {
129	/// Returns if calling coerce on the value will succeed or not.
130	///
131	/// If `T::can_coerce(&v)` returns `false` then `T::coerce(v) should not
132	fn can_coerce(v: &Value) -> bool;
133
134	/// Coerce a value.
135	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			// Allow any null value
172			Value::Null => Ok(Null),
173			// Anything else raises an error
174			_ => Err(CoerceError::InvalidKind {
175				from: v,
176				into: "null".into(),
177			}),
178		}
179	}
180}
181
182impl Coerce for i64 {
183	/// A number coerces exactly when it denotes one `i64`, which is what
184	/// `coerce` checks too: the two must agree, because a union kind coerces
185	/// to the first member `can_coerce` accepts and then requires `coerce` to
186	/// succeed.
187	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			// Allow any int number
194			Value::Number(Number::Int(v)) => Ok(v),
195			// A float or decimal coerces only when it is a whole number within
196			// the `i64` range. Anything else is refused, never rounded or
197			// clamped to the nearest bound.
198			Value::Number(n) => n.as_int_lossless().ok_or_else(|| CoerceError::InvalidKind {
199				from: val,
200				into: "int".into(),
201			}),
202			// Anything else raises an error
203			_ => 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	/// Try to coerce this value to an `f64`
223	fn coerce(val: Value) -> Result<f64, CoerceError> {
224		match val {
225			// Allow any float number
226			Value::Number(Number::Float(v)) => Ok(v),
227			// Attempt to convert an int number
228			Value::Number(Number::Int(v)) => Ok(v as f64),
229			// Attempt to convert a decimal number
230			Value::Number(Number::Decimal(v)) => match v.try_into() {
231				// The Decimal can be represented as a f64
232				Ok(v) => Ok(v),
233				// This Decimal loses precision
234				_ => Err(CoerceError::InvalidKind {
235					from: val,
236					into: "float".into(),
237				}),
238			},
239			// Anything else raises an error
240			_ => 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			// Allow any decimal number
262			Value::Number(Number::Decimal(x)) => Ok(x),
263			// Attempt to convert an int number
264			Value::Number(Number::Int(v)) => Ok(Decimal::from(v)),
265			// Attempt to convert an float number
266			Value::Number(Number::Float(v)) => match Decimal::try_from(v).ok() {
267				// The Float can be represented as a Decimal
268				Some(v) => Ok(v),
269				// This Float does not convert to a Decimal
270				None => Err(CoerceError::InvalidKind {
271					from: val,
272					into: "decimal".into(),
273				}),
274			},
275			// Anything else raises an error
276			_ => 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		// checked above
350		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		// checked above
379		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			// object<T> kinds don't actually exist in surql.
384			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		// checked above
417		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			// object<T> kinds don't actually exist in surql.
422			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
470// Types which directly match one enum variant or fail
471impl_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
506/// `String` coercion delegates to `Strand` and converts on success
507/// so existing `coerce_to::<String>()` call sites keep working.
508impl 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
524// Coerce to runtime value implementations
525impl 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				// Allow strings to be coerced to tables
629				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	/// Convert the value using coercion rules.
660	///
661	/// Coercion rules are more strict then coverting rules.
662	/// Calling this method will succeed if the value can by unified with the
663	/// kind of the target
664	///
665	/// This method is a shorthand for `T::coerce(self)`
666	pub fn coerce_to<T: Coerce>(self) -> Result<T, CoerceError> {
667		T::coerce(self)
668	}
669
670	/// Try to coerce this value to the specified `Kind`
671	pub fn coerce_to_kind(self, kind: &Kind) -> Result<Value, CoerceError> {
672		// Attempt to convert to the desired type
673		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				// Check first for valid kind, then convert to not consume the value
723				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	/// Try to coerce this value to a Literal, returns a `Value` with the
746	/// coerced value
747	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	/// Try to coerce this value to a Table of a certain type
759	pub fn coerce_to_table_kind(
760		self,
761		val: &[TableName],
762	) -> Result<surrealdb_strand::TableName, CoerceError> {
763		let this = match self {
764			// Tables are allowed if correct type
765			Value::Table(v) => {
766				if val.is_empty() || val.contains(&v) {
767					return Ok(v);
768				} else {
769					Value::Table(v)
770				}
771			}
772			// Allow strings to be coerced to tables
773			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	/// Try to coerce this value to a Record of a certain type
803	pub fn coerce_to_record_kind(self, val: &[TableName]) -> Result<RecordId, CoerceError> {
804		let this = match self {
805			// Records are allowed if correct type
806			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	/// Try to coerce this value to a `Geometry` of a certain type
831	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				// Checked above in is_geometry_type
835				unreachable!()
836			};
837			Ok(x)
838		} else {
839			Err(CoerceError::InvalidKind {
840				from: self,
841				into: "geometry".into(),
842			})
843		}
844	}
845
846	/// Try to coerce this value to an `Array` of a certain type
847	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	/// Try to coerce this value to an `Array` of a certain type, and length
860	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	/// Try to coerce this value to a `Set` of a certain type
882	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	/// Try to coerce this value to a `Set` of a certain type and length
895	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		// An array where the second element (index 1) is the wrong type.
948		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		// Test coercing string to generic table type
995		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		// Coercion should fail for wrong table name (more strict than cast)
1007		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		// Coercion from string to specific table type should fail because
1011		// coercion is stricter and only allows exact matches
1012		assert!(result.is_err());
1013	}
1014
1015	#[test]
1016	fn test_coerce_table_to_table() {
1017		// Test coercing table value to matching table type
1018		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		// Test coercing table value to non-matching table type
1024		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		// Test can_coerce_to_kind for tables
1033		let value = Value::Table("users".into());
1034		let kind = Kind::Table(vec!["users".into()]);
1035		assert!(value.can_coerce_to_kind(&kind));
1036
1037		// Wrong table name
1038		let value = Value::Table("posts".into());
1039		let kind = Kind::Table(vec!["users".into()]);
1040		assert!(!value.can_coerce_to_kind(&kind));
1041
1042		// Wrong type
1043		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		// Test with empty tables list (should accept any table)
1051		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()); // Coercion from string is strict
1055	}
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		// Negative zero is the integer zero
1070		assert_eq!(float(-0.0).coerce_to::<i64>().unwrap(), 0);
1071		// 2^53: every smaller integer is also an f64
1072		assert_eq!(
1073			float(9_007_199_254_740_992.0).coerce_to::<i64>().unwrap(),
1074			9_007_199_254_740_992
1075		);
1076		// -2^63 is `i64::MIN`
1077		assert_eq!(float(i64::MIN as f64).coerce_to::<i64>().unwrap(), i64::MIN);
1078		// The largest f64 below 2^63
1079		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			// 2^63: one past `i64::MAX`, which `i64::MAX as f64` also rounds up to
1086			2f64.powi(63),
1087			i64::MAX as f64,
1088			// The f64 below -2^63
1089			-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		// A float and a decimal out of the `i64` range fail the same way
1112		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	/// Numbers of every shape that matter to an `int`: the bounds, the first
1119	/// value past each bound, fractions, and the non-finite floats.
1120	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			// A whole decimal carrying a trailing scale
1129			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			// Fractional and out of range at once
1136			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		// A union kind coerces to the first member `can_coerce_to_kind`
1165		// accepts and treats a failure there as unreachable, so the two must
1166		// never disagree.
1167		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		// An alternative that can hold the value is still reached
1197		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		// An in-range whole float is the first member's int
1207		assert!(matches!(
1208			float(5.0).coerce_to_kind(&either).unwrap(),
1209			Value::Number(Number::Int(5))
1210		));
1211	}
1212}