use std::fmt;
use std::ops::Bound;
use std::str::FromStr as _;
use geo::Point;
use rust_decimal::Decimal;
use surrealdb_types::ToSql;
use super::coerce::ElementPosition;
use crate::cnf::GENERATION_ALLOCATION_LIMIT;
use crate::expr::Kind;
use crate::expr::kind::{GeometryKind, HasKind, KindLiteral};
use crate::syn;
use crate::val::{
Array, Bytes, Closure, Datetime, DecimalExt, Duration, File, Geometry, Null, Number, Object,
Range, RecordId, Regex, Set, SqlNone, TableName, Uuid, Value,
};
#[derive(Clone, Debug)]
pub enum CastError {
InvalidKind {
from: Value,
into: String,
},
InvalidLength {
len: usize,
into: String,
},
ElementOf {
inner: Box<CastError>,
into: String,
position: Option<ElementPosition>,
},
RangeSizeLimit {
value: Box<Range>,
},
}
impl std::error::Error for CastError {}
impl fmt::Display for CastError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
CastError::InvalidKind {
from,
into,
} => {
write!(f, "Could not cast into `{into}` using input `{from}`", from = from.to_sql())
}
CastError::ElementOf {
inner,
into,
position,
} => {
inner.fmt(f)?;
match position {
Some(ElementPosition::Index(i)) => {
write!(f, " when casting element at index {i} of `{into}`")
}
Some(ElementPosition::Key(k)) => {
write!(f, " when casting value for key '{k}' of `{into}`")
}
None => write!(f, " when casting an element of `{into}`"),
}
}
CastError::InvalidLength {
len,
into,
} => {
write!(f, "Expected `{into}` but found a collection of length `{len}`")
}
CastError::RangeSizeLimit {
value,
} => {
write!(
f,
"Casting range `{value}` to an array would create an array larger then the max allocation limit.",
value = value.to_sql()
)
}
}
}
}
pub trait CastErrorExt {
fn with_element_of_at_index<F>(self, index: usize, f: F) -> Self
where
F: Fn() -> String;
}
impl<T> CastErrorExt for Result<T, CastError> {
fn with_element_of_at_index<F>(self, index: usize, f: F) -> Self
where
F: Fn() -> String,
{
match self {
Ok(x) => Ok(x),
Err(e) => Err(CastError::ElementOf {
inner: Box::new(e),
into: f(),
position: Some(ElementPosition::Index(index)),
}),
}
}
}
pub trait Cast: Sized {
fn can_cast(v: &Value) -> bool;
fn cast(v: Value) -> Result<Self, CastError>;
}
impl Cast for Value {
fn can_cast(_: &Value) -> bool {
true
}
fn cast(v: Value) -> Result<Self, CastError> {
Ok(v)
}
}
impl Cast for SqlNone {
fn can_cast(v: &Value) -> bool {
matches!(v, Value::None)
}
fn cast(v: Value) -> Result<Self, CastError> {
match v {
Value::None => Ok(SqlNone),
x => Err(CastError::InvalidKind {
from: x,
into: "none".to_string(),
}),
}
}
}
impl Cast for Null {
fn can_cast(v: &Value) -> bool {
matches!(v, Value::Null)
}
fn cast(v: Value) -> Result<Self, CastError> {
match v {
Value::Null => Ok(Null),
x => Err(CastError::InvalidKind {
from: x,
into: "null".to_string(),
}),
}
}
}
impl Cast for bool {
fn can_cast(v: &Value) -> bool {
match v {
Value::Bool(_) => true,
Value::String(x) => matches!(x.as_str(), "true" | "false"),
_ => false,
}
}
fn cast(v: Value) -> Result<Self, CastError> {
match v {
Value::Bool(b) => Ok(b),
Value::String(x) => match x.as_str() {
"true" => Ok(true),
"false" => Ok(false),
_ => Err(CastError::InvalidKind {
from: Value::String(x),
into: "bool".to_string(),
}),
},
x => Err(CastError::InvalidKind {
from: x,
into: "bool".to_string(),
}),
}
}
}
impl Cast for i64 {
fn can_cast(v: &Value) -> bool {
match v {
Value::Number(Number::Int(_)) => true,
Value::Number(Number::Float(v)) => v.fract() == 0.0,
Value::Number(Number::Decimal(v)) => v.is_integer() || i64::try_from(*v).is_ok(),
Value::String(v) => v.parse::<i64>().is_ok(),
_ => false,
}
}
fn cast(v: Value) -> Result<Self, CastError> {
match v {
Value::Number(Number::Int(x)) => Ok(x),
Value::Number(Number::Float(v)) if v.fract() == 0.0 => Ok(v as i64),
Value::Number(Number::Decimal(d)) if d.is_integer() => match d.try_into() {
Ok(v) => Ok(v),
_ => Err(CastError::InvalidKind {
from: v,
into: "int".into(),
}),
},
Value::String(ref s) => match s.parse::<i64>() {
Ok(v) => Ok(v),
_ => Err(CastError::InvalidKind {
from: v,
into: "int".into(),
}),
},
_ => Err(CastError::InvalidKind {
from: v,
into: "int".into(),
}),
}
}
}
impl Cast for f64 {
fn can_cast(v: &Value) -> bool {
match v {
Value::Number(Number::Int(_) | Number::Float(_)) => true,
Value::Number(Number::Decimal(v)) => v.is_integer() || i64::try_from(*v).is_ok(),
Value::String(v) => v.parse::<f64>().is_ok(),
_ => false,
}
}
fn cast(v: Value) -> Result<Self, CastError> {
match v {
Value::Number(Number::Float(i)) => Ok(i),
Value::Number(Number::Int(f)) => Ok(f as f64),
Value::Number(Number::Decimal(d)) => match d.try_into() {
Ok(v) => Ok(v),
_ => Err(CastError::InvalidKind {
from: v,
into: "float".into(),
}),
},
Value::String(ref s) => match s.parse::<f64>() {
Ok(v) => Ok(v),
_ => Err(CastError::InvalidKind {
from: v,
into: "float".into(),
}),
},
_ => Err(CastError::InvalidKind {
from: v,
into: "float".into(),
}),
}
}
}
impl Cast for Decimal {
fn can_cast(v: &Value) -> bool {
match v {
Value::Number(_) => true,
Value::String(v) => v.parse::<f64>().is_ok(),
_ => false,
}
}
fn cast(v: Value) -> Result<Self, CastError> {
match v {
Value::Number(Number::Decimal(d)) => Ok(d),
Value::Number(Number::Int(ref i)) => Ok(Decimal::from(*i)),
Value::Number(Number::Float(ref f)) => match Decimal::try_from(*f) {
Ok(d) => Ok(d),
_ => Err(CastError::InvalidKind {
from: v,
into: "decimal".into(),
}),
},
Value::String(ref s) => match Decimal::from_str_normalized(s) {
Ok(v) => Ok(v),
_ => Err(CastError::InvalidKind {
from: v,
into: "decimal".into(),
}),
},
_ => Err(CastError::InvalidKind {
from: v,
into: "decimal".into(),
}),
}
}
}
impl Cast for Number {
fn can_cast(v: &Value) -> bool {
match v {
Value::Number(_) => true,
Value::String(s) => Number::from_str(s).is_ok(),
_ => false,
}
}
fn cast(v: Value) -> Result<Self, CastError> {
match v {
Value::Number(v) => Ok(v),
Value::String(ref s) => Number::from_str(s).map_err(|_| CastError::InvalidKind {
from: v,
into: "number".into(),
}),
_ => Err(CastError::InvalidKind {
from: v,
into: "number".into(),
}),
}
}
}
impl Cast for String {
fn can_cast(v: &Value) -> bool {
match v {
Value::None | Value::Null => false,
Value::Bytes(b) => std::str::from_utf8(b).is_ok(),
_ => true,
}
}
fn cast(v: Value) -> Result<Self, CastError> {
match v {
Value::Bytes(b) => match String::from_utf8(b.0.to_vec()) {
Ok(x) => Ok(x),
Err(e) => Err(CastError::InvalidKind {
from: Value::Bytes(Bytes::from(e.into_bytes())),
into: "string".to_owned(),
}),
},
Value::Null => Ok("NULL".into()),
Value::None => Ok("NONE".into()),
Value::String(x) => Ok(x.into_string()),
Value::Uuid(x) => Ok(x.to_string()),
Value::Datetime(x) => Ok(x.to_string()),
Value::Number(Number::Decimal(x)) => Ok(x.to_string()),
x => Ok(x.to_sql()),
}
}
}
impl Cast for Uuid {
fn can_cast(v: &Value) -> bool {
match v {
Value::Uuid(_) => true,
Value::String(s) => Uuid::from_str(s).is_ok(),
_ => false,
}
}
fn cast(v: Value) -> Result<Self, CastError> {
match v {
Value::Uuid(u) => Ok(u),
Value::String(ref s) => Uuid::from_str(s).map_err(|_| CastError::InvalidKind {
from: v,
into: "uuid".into(),
}),
_ => Err(CastError::InvalidKind {
from: v,
into: "uuid".into(),
}),
}
}
}
impl Cast for Datetime {
fn can_cast(v: &Value) -> bool {
match v {
Value::Datetime(_) => true,
Value::String(s) => Datetime::from_str(s).is_ok(),
_ => false,
}
}
fn cast(v: Value) -> Result<Self, CastError> {
match v {
Value::Datetime(v) => Ok(v),
Value::String(ref s) => Datetime::from_str(s).map_err(|_| CastError::InvalidKind {
from: v,
into: "datetime".into(),
}),
_ => Err(CastError::InvalidKind {
from: v,
into: "datetime".into(),
}),
}
}
}
impl Cast for Duration {
fn can_cast(v: &Value) -> bool {
match v {
Value::Duration(_) => true,
Value::String(s) => Duration::from_str(s).is_ok(),
_ => false,
}
}
fn cast(v: Value) -> Result<Self, CastError> {
match v {
Value::Duration(v) => Ok(v),
Value::String(ref s) => Duration::from_str(s).map_err(|_| CastError::InvalidKind {
from: v,
into: "duration".into(),
}),
_ => Err(CastError::InvalidKind {
from: v,
into: "duration".into(),
}),
}
}
}
impl Cast for Bytes {
fn can_cast(v: &Value) -> bool {
match v {
Value::Bytes(_) | Value::String(_) => true,
Value::Array(x) => x.iter().all(|v| value_to_byte(v).is_some()),
_ => false,
}
}
fn cast(v: Value) -> Result<Self, CastError> {
match v {
Value::Bytes(b) => Ok(b),
Value::String(s) => Ok(Bytes::from(s.into_string().into_bytes())),
Value::Array(x) => match x.0.iter().map(value_to_byte).collect::<Option<Vec<u8>>>() {
Some(bytes) => Ok(Bytes::from(bytes)),
None => Err(CastError::InvalidKind {
from: Value::Array(x),
into: "bytes".to_owned(),
}),
},
_ => Err(CastError::InvalidKind {
from: v,
into: "bytes".into(),
}),
}
}
}
fn value_to_byte(v: &Value) -> Option<u8> {
match v {
Value::Number(Number::Int(x)) => u8::try_from(*x).ok(),
Value::Number(Number::Float(f)) if f.fract() == 0.0 => u8::try_from(*f as i64).ok(),
Value::Number(Number::Decimal(d)) if d.is_integer() => {
u8::try_from(i64::try_from(*d).ok()?).ok()
}
Value::String(s) => s.parse::<u8>().ok(),
_ => None,
}
}
impl Cast for Array {
fn can_cast(v: &Value) -> bool {
match v {
Value::Array(_) | Value::Bytes(_) | Value::Set(_) => true,
Value::Range(r) => r.can_coerce_to_typed::<i64>(),
_ => false,
}
}
fn cast(v: Value) -> Result<Self, CastError> {
match v {
Value::Array(x) => Ok(x),
Value::Set(s) => Ok(s.into_iter().collect()),
Value::Range(range) => {
if !range.can_coerce_to_typed::<i64>() {
return Err(CastError::InvalidKind {
from: Value::Range(range),
into: "array".to_string(),
});
}
let range = range.coerce_to_typed::<i64>().expect("range type checked above");
if range.len().is_none_or(|n| n > *GENERATION_ALLOCATION_LIMIT) {
return Err(CastError::RangeSizeLimit {
value: Box::new(Range::from(range)),
});
}
Ok(range.cast_to_array())
}
Value::Bytes(x) => Ok(Array(x.0.into_iter().map(|x| Value::from(x as i64)).collect())),
_ => Err(CastError::InvalidKind {
from: v,
into: "array".into(),
}),
}
}
}
impl Cast for Set {
fn can_cast(v: &Value) -> bool {
matches!(v, Value::Set(_) | Value::Array(_))
}
fn cast(v: Value) -> Result<Self, CastError> {
match v {
Value::Set(x) => Ok(x),
Value::Array(x) => {
Ok(Set::from(x.0))
}
_ => Err(CastError::InvalidKind {
from: v,
into: "set".into(),
}),
}
}
}
impl Cast for Regex {
fn can_cast(v: &Value) -> bool {
match v {
Value::Regex(_) => true,
Value::String(x) => Regex::from_str(x).is_ok(),
_ => false,
}
}
fn cast(v: Value) -> Result<Self, CastError> {
match v {
Value::Regex(x) => Ok(x),
Value::String(x) => match Regex::from_str(&x) {
Ok(x) => Ok(x),
Err(_) => Err(CastError::InvalidKind {
from: Value::String(x),
into: "regex".to_string(),
}),
},
x => Err(CastError::InvalidKind {
from: x,
into: "regex".to_string(),
}),
}
}
}
impl Cast for Box<Range> {
fn can_cast(v: &Value) -> bool {
match v {
Value::Range(_) => true,
Value::Array(x) => x.len() == 2,
_ => false,
}
}
fn cast(v: Value) -> Result<Self, CastError> {
match v {
Value::Range(x) => Ok(x),
Value::Array(x) => {
if x.len() != 2 {
return Err(CastError::InvalidKind {
from: Value::Array(x),
into: "range".to_string(),
});
}
let mut iter = x.into_iter();
let beg = iter.next().expect("array length checked above");
let end = iter.next().expect("array length checked above");
Ok(Box::new(Range {
start: Bound::Included(beg),
end: Bound::Excluded(end),
}))
}
_ => Err(CastError::InvalidKind {
from: v,
into: "range".into(),
}),
}
}
}
impl Cast for Point<f64> {
fn can_cast(v: &Value) -> bool {
match v {
Value::Geometry(Geometry::Point(_)) => true,
Value::Array(x) => x.len() == 2,
_ => false,
}
}
fn cast(v: Value) -> Result<Self, CastError> {
match v {
Value::Geometry(Geometry::Point(v)) => Ok(v),
Value::Array(x) => {
if x.len() != 2 {
return Err(CastError::InvalidKind {
from: Value::Array(x),
into: "point".to_string(),
});
}
if !x[0].can_coerce_to::<f64>() || !x[1].can_coerce_to::<f64>() {
return Err(CastError::InvalidKind {
from: Value::Array(x),
into: "point".to_string(),
});
}
let mut iter = x.into_iter();
let x = iter
.next()
.expect("array length checked above")
.cast_to::<f64>()
.expect("value type checked above");
let y = iter
.next()
.expect("array length checked above")
.cast_to::<f64>()
.expect("value type checked above");
Ok(Point::new(x, y))
}
_ => Err(CastError::InvalidKind {
from: v,
into: "point".into(),
}),
}
}
}
impl Cast for RecordId {
fn can_cast(v: &Value) -> bool {
match v {
Value::RecordId(_) => true,
Value::String(x) => syn::record_id(x).is_ok(),
_ => false,
}
}
fn cast(v: Value) -> Result<Self, CastError> {
match v {
Value::RecordId(x) => Ok(x),
Value::String(x) => match syn::record_id(&x) {
Ok(x) => Ok(x.into()),
Err(_) => Err(CastError::InvalidKind {
from: Value::String(x),
into: "record".to_string(),
}),
},
from => Err(CastError::InvalidKind {
from,
into: "record".to_string(),
}),
}
}
}
impl Cast for crate::val::TableName {
fn can_cast(v: &Value) -> bool {
matches!(v, Value::Table(_) | Value::String(_))
}
fn cast(v: Value) -> Result<Self, CastError> {
match v {
Value::Table(x) => Ok(x),
Value::String(x) => Ok(crate::val::TableName::new(x)),
from => Err(CastError::InvalidKind {
from,
into: "table".to_string(),
}),
}
}
}
impl<T: Cast> Cast for Option<T> {
fn can_cast(v: &Value) -> bool {
if let Value::None = v {
return true;
}
T::can_cast(v)
}
fn cast(v: Value) -> Result<Self, CastError> {
match v {
Value::None => Ok(None),
x => T::cast(x).map(Some),
}
}
}
macro_rules! impl_direct {
($($name:ident => $inner:ty $(= $kind:ident)?),*$(,)?) => {
$(
impl Cast for $inner {
fn can_cast(v: &Value) -> bool{
matches!(v, Value::$name(_))
}
fn cast(v: Value) -> Result<Self, CastError> {
if let Value::$name(x) = v {
return Ok(x);
} else {
return Err(CastError::InvalidKind{
from: v,
into: impl_direct!(@kindof $inner $(= $kind)?),
});
}
}
}
)*
};
(@kindof $inner:ty = $kind:ident) => {
<$kind as HasKind>::kind().to_sql()
};
(@kindof $inner:ty) => {
<$inner as HasKind>::kind().to_sql()
};
}
impl_direct! {
Closure => Box<Closure> = Closure,
Object => Object,
Geometry => Geometry,
File => File,
}
impl Value {
pub fn can_cast_to<T: Cast>(&self) -> bool {
T::can_cast(self)
}
pub fn can_cast_to_kind(&self, kind: &Kind) -> bool {
match kind {
Kind::Any => true,
Kind::None => self.can_cast_to::<SqlNone>(),
Kind::Null => self.can_cast_to::<Null>(),
Kind::Bool => self.can_cast_to::<bool>(),
Kind::Int => self.can_cast_to::<i64>(),
Kind::Float => self.can_cast_to::<f64>(),
Kind::Decimal => self.can_cast_to::<Decimal>(),
Kind::Number => self.can_cast_to::<Number>(),
Kind::String => self.can_cast_to::<String>(),
Kind::Datetime => self.can_cast_to::<Datetime>(),
Kind::Duration => self.can_cast_to::<Duration>(),
Kind::Object => self.can_cast_to::<crate::val::Object>(),
Kind::Bytes => self.can_cast_to::<Bytes>(),
Kind::Uuid => self.can_cast_to::<Uuid>(),
Kind::Regex => self.can_cast_to::<Regex>(),
Kind::Range => self.can_cast_to::<Box<Range>>(),
Kind::Function(_, _) => self.can_cast_to::<Box<Closure>>(),
Kind::Set(t, l) => match l {
Some(l) => self.can_cast_to_set_len(t, *l),
None => self.can_cast_to_set(t),
},
Kind::Array(t, l) => match l {
Some(l) => self.can_cast_to_array_len(t, *l),
None => self.can_cast_to_array(t),
},
Kind::Table(t) => {
if t.is_empty() {
self.can_cast_to::<String>()
} else {
self.can_cast_to_table(t)
}
}
Kind::Record(t) => {
if t.is_empty() {
self.can_cast_to::<RecordId>()
} else {
self.can_cast_to_record(t)
}
}
Kind::Geometry(t) => {
if t.is_empty() {
self.can_cast_to::<Geometry>()
} else {
self.can_cast_to_geometry(t)
}
}
Kind::Either(k) => k.iter().any(|x| self.can_cast_to_kind(x)),
Kind::Literal(lit) => self.can_cast_to_literal(lit),
Kind::File(buckets) => {
if buckets.is_empty() {
self.can_cast_to::<File>()
} else {
self.can_cast_to_file_buckets(buckets)
}
}
}
}
fn can_cast_to_array_len(&self, kind: &Kind, len: u64) -> bool {
match self {
Value::Array(a) => a.len() as u64 == len && a.iter().all(|x| x.can_cast_to_kind(kind)),
_ => false,
}
}
fn can_cast_to_array(&self, kind: &Kind) -> bool {
match self {
Value::Array(a) => a.iter().all(|x| x.can_cast_to_kind(kind)),
_ => false,
}
}
fn can_cast_to_set_len(&self, kind: &Kind, len: u64) -> bool {
match self {
Value::Set(s) => s.len() as u64 == len && s.iter().all(|x| x.can_cast_to_kind(kind)),
_ => false,
}
}
fn can_cast_to_set(&self, kind: &Kind) -> bool {
match self {
Value::Set(s) => s.iter().all(|x| x.can_cast_to_kind(kind)),
_ => false,
}
}
fn can_cast_to_table(&self, val: &[TableName]) -> bool {
match self {
Value::Table(t) => t.is_table_type(val),
Value::String(_) => true, _ => false,
}
}
fn can_cast_to_record(&self, val: &[TableName]) -> bool {
match self {
Value::RecordId(t) => t.is_table_type(val),
_ => false,
}
}
fn can_cast_to_geometry(&self, val: &[GeometryKind]) -> bool {
self.is_geometry_type(val)
}
fn can_cast_to_literal(&self, val: &KindLiteral) -> bool {
val.validate_value(self)
}
fn can_cast_to_file_buckets(&self, buckets: &[String]) -> bool {
matches!(self, Value::File(f) if f.is_bucket_type(buckets))
}
pub fn cast_to<T: Cast>(self) -> Result<T, CastError> {
T::cast(self)
}
pub fn cast_to_kind(self, kind: &Kind) -> Result<Value, CastError> {
match kind {
Kind::Any => Ok(self),
Kind::None => self.cast_to::<SqlNone>().map(|_| Value::None),
Kind::Null => self.cast_to::<Null>().map(|_| Value::Null),
Kind::Bool => self.cast_to::<bool>().map(Value::from),
Kind::Int => self.cast_to::<i64>().map(Value::from),
Kind::Float => self.cast_to::<f64>().map(Value::from),
Kind::Decimal => self.cast_to::<Decimal>().map(Value::from),
Kind::Number => self.cast_to::<Number>().map(Value::from),
Kind::String => self.cast_to::<String>().map(Value::from),
Kind::Datetime => self.cast_to::<Datetime>().map(Value::from),
Kind::Duration => self.cast_to::<Duration>().map(Value::from),
Kind::Object => self.cast_to::<crate::val::Object>().map(Value::from),
Kind::Bytes => self.cast_to::<Bytes>().map(Value::from),
Kind::Uuid => self.cast_to::<Uuid>().map(Value::from),
Kind::Regex => self.cast_to::<Regex>().map(Value::from),
Kind::Range => self.cast_to::<Box<Range>>().map(Value::from),
Kind::Function(_, _) => self.cast_to::<Box<Closure>>().map(Value::from),
Kind::Set(t, l) => match l {
Some(l) => self.cast_to_set_type_len(t, *l).map(Value::from),
None => self.cast_to_set_type(t).map(Value::from),
},
Kind::Array(t, l) => match l {
Some(l) => self.cast_to_array_len(t, *l).map(Value::from),
None => self.cast_to_array(t).map(Value::from),
},
Kind::Table(t) => match t.is_empty() {
true => {
self.cast_to::<String>().map(|s| Value::Table(crate::val::TableName::new(s)))
}
false => self.cast_to_table(t).map(Value::from),
},
Kind::Record(t) => match t.is_empty() {
true => self.cast_to::<RecordId>().map(Value::from),
false => self.cast_to_record(t).map(Value::from),
},
Kind::Geometry(t) => match t.is_empty() {
true => self.cast_to::<Geometry>().map(Value::from),
false => self.cast_to_geometry(t).map(Value::from),
},
Kind::Either(k) => {
let Some(k) = k.iter().find(|x| self.can_cast_to_kind(x)) else {
return Err(CastError::InvalidKind {
from: self,
into: kind.to_sql(),
});
};
Ok(self.cast_to_kind(k).expect(
"If can_coerce_to_kind returns true then coerce_to_kind must not error",
))
}
Kind::Literal(lit) => self.cast_to_literal(lit),
Kind::File(buckets) => {
if buckets.is_empty() {
self.cast_to::<File>().map(Value::from)
} else {
self.cast_to_file_buckets(buckets).map(Value::from)
}
}
}
}
pub(crate) fn cast_to_literal(self, literal: &KindLiteral) -> Result<Value, CastError> {
if literal.validate_value(&self) {
Ok(self)
} else {
Err(CastError::InvalidKind {
from: self,
into: literal.to_sql(),
})
}
}
fn cast_to_table(self, val: &[TableName]) -> Result<crate::val::TableName, CastError> {
match self {
Value::Table(v) if v.is_table_type(val) => Ok(v),
Value::String(v) => {
let table = crate::val::TableName::new(v.clone());
if table.is_table_type(val) {
Ok(table)
} else {
let mut kind = "table<".to_string();
for (idx, t) in val.iter().enumerate() {
if idx != 0 {
kind.push('|');
}
kind.push_str(t.as_str())
}
kind.push('>');
Err(CastError::InvalidKind {
from: Value::String(v),
into: kind,
})
}
}
x => {
let mut kind = "table<".to_string();
for (idx, t) in val.iter().enumerate() {
if idx != 0 {
kind.push('|');
}
kind.push_str(t.as_str())
}
kind.push('>');
Err(CastError::InvalidKind {
from: x,
into: kind,
})
}
}
}
fn cast_to_record(self, val: &[TableName]) -> Result<RecordId, CastError> {
let expected_kind = || -> String {
let mut kind = "record<".to_string();
for (idx, t) in val.iter().enumerate() {
if idx != 0 {
kind.push('|');
}
kind.push_str(t.as_str())
}
kind.push('>');
kind
};
match self {
Value::RecordId(v) if v.is_table_type(val) => Ok(v),
Value::String(v) => {
let record_id = match syn::record_id(v.as_str()) {
Ok(x) => RecordId::from(x),
Err(_) => {
return Err(CastError::InvalidKind {
from: Value::String(v),
into: expected_kind(),
});
}
};
if !record_id.is_table_type(val) {
return Err(CastError::InvalidKind {
from: Value::String(v),
into: expected_kind(),
});
}
Ok(record_id)
}
x => Err(CastError::InvalidKind {
from: x,
into: expected_kind(),
}),
}
}
fn cast_to_geometry(self, val: &[GeometryKind]) -> Result<Geometry, CastError> {
match self {
Value::Geometry(v) if self.is_geometry_type(val) => Ok(v),
Value::Array(_) => {
if val.contains(&GeometryKind::Point)
&& let Some(p) = Geometry::array_to_point(&self)
{
Ok(Geometry::Point(p))
} else {
Err(CastError::InvalidKind {
from: self,
into: "geometry".into(),
})
}
}
_ => Err(CastError::InvalidKind {
from: self,
into: "geometry".into(),
}),
}
}
fn cast_to_array(self, kind: &Kind) -> Result<Array, CastError> {
self.cast_to::<Array>()?
.into_iter()
.enumerate()
.map(|(i, value)| {
value
.cast_to_kind(kind)
.with_element_of_at_index(i, || format!("array<{}>", kind.to_sql()))
})
.collect::<Result<Array, CastError>>()
}
fn cast_to_array_len(self, kind: &Kind, len: u64) -> Result<Array, CastError> {
let array = self.cast_to::<Array>()?;
if (array.len() as u64) != len {
return Err(CastError::InvalidLength {
len: array.len(),
into: format!("array<{},{}>", kind.to_sql(), len),
});
}
array
.into_iter()
.enumerate()
.map(|(i, value)| {
value
.cast_to_kind(kind)
.with_element_of_at_index(i, || format!("array<{}>", kind.to_sql()))
})
.collect::<Result<Array, CastError>>()
}
pub(crate) fn cast_to_set_type(self, kind: &Kind) -> Result<Set, CastError> {
self.cast_to::<Array>()?
.into_iter()
.enumerate()
.map(|(i, value)| {
value
.cast_to_kind(kind)
.with_element_of_at_index(i, || format!("set<{}>", kind.to_sql()))
})
.collect::<Result<Set, CastError>>()
}
pub(crate) fn cast_to_set_type_len(self, kind: &Kind, len: u64) -> Result<Set, CastError> {
let set = self
.cast_to::<Array>()?
.into_iter()
.enumerate()
.map(|(i, value)| {
value
.cast_to_kind(kind)
.with_element_of_at_index(i, || format!("set<{}>", kind.to_sql()))
})
.collect::<Result<Set, CastError>>()?;
if (set.len() as u64) != len {
return Err(CastError::InvalidLength {
len: set.len(),
into: format!("set<{},{}>", kind.to_sql(), len),
});
}
Ok(set)
}
pub(crate) fn cast_to_file_buckets(self, buckets: &[String]) -> Result<File, CastError> {
let v = self.cast_to::<File>()?;
if v.is_bucket_type(buckets) {
return Ok(v);
}
let mut kind = "file<".to_owned();
for (idx, t) in buckets.iter().enumerate() {
if idx != 0 {
kind.push('|');
}
kind.push_str(t.as_str())
}
kind.push('>');
Err(CastError::InvalidKind {
from: v.into(),
into: kind,
})
}
}
#[cfg(test)]
mod tests {
use surrealdb_strand::Strand;
use super::*;
#[test]
fn test_cast_to_table_generic() {
let value = Value::String(Strand::new_static("users"));
let kind = Kind::Table(vec![]);
let result = value.cast_to_kind(&kind);
assert!(result.is_ok());
if let Ok(Value::Table(table)) = result {
assert_eq!(table.as_str(), "users");
} else {
panic!("Expected Value::Table");
}
}
#[test]
fn test_cast_to_table_specific() {
let value = Value::String(Strand::new_static("users"));
let kind = Kind::Table(vec!["users".into()]);
let result = value.cast_to_kind(&kind);
assert!(result.is_ok());
if let Ok(Value::Table(table)) = result {
assert_eq!(table.as_str(), "users");
}
let value = Value::String(Strand::new_static("posts"));
let kind = Kind::Table(vec!["users".into()]);
let result = value.cast_to_kind(&kind);
assert!(result.is_err());
}
#[test]
fn test_cast_to_table_union() {
let value = Value::String(Strand::new_static("posts"));
let kind = Kind::Table(vec!["users".into(), "posts".into()]);
let result = value.cast_to_kind(&kind);
assert!(result.is_ok());
if let Ok(Value::Table(table)) = result {
assert_eq!(table.as_str(), "posts");
}
let value = Value::String(Strand::new_static("comments"));
let kind = Kind::Table(vec!["users".into(), "posts".into()]);
let result = value.cast_to_kind(&kind);
assert!(result.is_err());
}
#[test]
fn test_cast_table_to_table() {
let value = Value::Table(crate::val::TableName::new("users".to_string()));
let kind = Kind::Table(vec!["users".into()]);
let result = value.cast_to_kind(&kind);
assert!(result.is_ok());
}
#[test]
fn test_can_cast_to_table() {
let value = Value::String(Strand::new_static("users"));
let kind = Kind::Table(vec![]);
assert!(value.can_cast_to_kind(&kind));
let value = Value::Table(crate::val::TableName::new("users".to_string()));
let kind = Kind::Table(vec!["users".into()]);
assert!(value.can_cast_to_kind(&kind));
let value = Value::Table(crate::val::TableName::new("posts".to_string()));
let kind = Kind::Table(vec!["users".into()]);
assert!(!value.can_cast_to_kind(&kind));
}
}