use crate::types::{GqlType, TypeError, TypeResult};
#[derive(Debug)]
pub struct TypeCaster;
impl TypeCaster {
#[allow(dead_code)] pub fn can_cast(from: &GqlType, to: &GqlType) -> bool {
if from == to {
return true;
}
match (from, to) {
(GqlType::Boolean, GqlType::String { .. }) => true,
(GqlType::Boolean, GqlType::Integer | GqlType::SmallInt | GqlType::BigInt) => true,
(GqlType::String { .. }, GqlType::Boolean) => true,
(GqlType::Integer | GqlType::SmallInt | GqlType::BigInt, GqlType::Boolean) => true,
(from_t, to_t) if from_t.is_numeric() && to_t.is_numeric() => true,
(GqlType::String { .. }, to_t) if to_t.is_numeric() => true,
(from_t, to_t) if from_t.is_temporal() && to_t.is_temporal() => {
Self::can_cast_temporal(from_t, to_t)
}
(GqlType::String { .. }, to_t) if to_t.is_temporal() => true,
(GqlType::String { .. }, GqlType::Duration { .. }) => true,
(_, GqlType::String { .. }) => {
!matches!(from, GqlType::Graph { .. } | GqlType::BindingTable)
}
(
GqlType::List {
element_type: from_elem,
..
},
GqlType::List {
element_type: to_elem,
..
},
) => Self::can_cast(from_elem, to_elem),
_ => false,
}
}
fn can_cast_temporal(from: &GqlType, to: &GqlType) -> bool {
match (from, to) {
_ if std::mem::discriminant(from) == std::mem::discriminant(to) => true,
(
GqlType::Date,
GqlType::Timestamp { .. }
| GqlType::ZonedDateTime { .. }
| GqlType::LocalDateTime { .. },
) => true,
(GqlType::Time { .. }, GqlType::ZonedTime { .. } | GqlType::LocalTime { .. }) => true,
(GqlType::ZonedTime { .. }, GqlType::Time { .. } | GqlType::LocalTime { .. }) => true,
(GqlType::LocalTime { .. }, GqlType::Time { .. }) => true,
(
GqlType::Timestamp { .. },
GqlType::ZonedDateTime { .. } | GqlType::LocalDateTime { .. } | GqlType::Date,
) => true,
(GqlType::ZonedDateTime { .. }, GqlType::LocalDateTime { .. } | GqlType::Date) => true,
(GqlType::LocalDateTime { .. }, GqlType::Date) => true,
_ => false,
}
}
#[allow(dead_code)] pub fn cast(from: &GqlType, to: &GqlType) -> TypeResult<CastOperation> {
if !Self::can_cast(from, to) {
return Err(TypeError::InvalidCast(
format!("{:?}", from),
format!("{:?}", to),
));
}
if from == to {
return Ok(CastOperation::Identity);
}
match (from, to) {
(GqlType::Boolean, GqlType::String { .. }) => Ok(CastOperation::BooleanToString),
(GqlType::Boolean, to_t) if to_t.is_numeric() => Ok(CastOperation::BooleanToNumeric),
(GqlType::String { .. }, GqlType::Boolean) => Ok(CastOperation::StringToBoolean),
(from_t, GqlType::Boolean) if from_t.is_numeric() => {
Ok(CastOperation::NumericToBoolean)
}
(from_t, to_t) if from_t.is_numeric() && to_t.is_numeric() => {
Ok(CastOperation::NumericCast)
}
(from_t, GqlType::String { .. }) if from_t.is_numeric() => {
Ok(CastOperation::NumericToString)
}
(GqlType::String { .. }, to_t) if to_t.is_numeric() => {
Ok(CastOperation::StringToNumeric)
}
(from_t, to_t) if from_t.is_temporal() && to_t.is_temporal() => {
Ok(CastOperation::TemporalCast)
}
(GqlType::String { .. }, to_t) if to_t.is_temporal() => {
Ok(CastOperation::StringToTemporal)
}
(from_t, GqlType::String { .. }) if from_t.is_temporal() => {
Ok(CastOperation::TemporalToString)
}
_ => Ok(CastOperation::Custom),
}
}
#[allow(dead_code)] pub fn is_typed(value_type: &GqlType, check_type: &GqlType) -> bool {
match (value_type, check_type) {
_ if value_type == check_type => true,
(GqlType::Integer, GqlType::SmallInt) => true,
(GqlType::BigInt, GqlType::SmallInt | GqlType::Integer) => true,
(GqlType::Int128, GqlType::SmallInt | GqlType::Integer | GqlType::BigInt) => true,
(
GqlType::Int256,
GqlType::SmallInt | GqlType::Integer | GqlType::BigInt | GqlType::Int128,
) => true,
(GqlType::Double, GqlType::Float { .. }) => true,
_ => false,
}
}
}
#[derive(Debug, Clone)]
#[allow(dead_code)] pub enum CastOperation {
Identity,
BooleanToString,
BooleanToNumeric,
StringToBoolean,
NumericToBoolean,
NumericCast,
NumericToString,
StringToNumeric,
TemporalCast,
StringToTemporal,
TemporalToString,
Custom,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_can_cast() {
assert!(TypeCaster::can_cast(
&GqlType::Boolean,
&GqlType::String { max_length: None }
));
assert!(TypeCaster::can_cast(
&GqlType::String { max_length: None },
&GqlType::Boolean
));
let int = GqlType::Integer;
let double = GqlType::Double;
assert!(TypeCaster::can_cast(&int, &double));
assert!(TypeCaster::can_cast(&double, &int));
let date = GqlType::Date;
let timestamp = GqlType::Timestamp {
precision: None,
with_timezone: false,
};
assert!(TypeCaster::can_cast(&date, ×tamp));
}
#[test]
fn test_is_typed() {
let small = GqlType::SmallInt;
let int = GqlType::Integer;
let big = GqlType::BigInt;
assert!(TypeCaster::is_typed(&int, &int));
assert!(!TypeCaster::is_typed(&small, &int));
assert!(TypeCaster::is_typed(&int, &small));
assert!(TypeCaster::is_typed(&big, &int));
}
#[test]
fn test_cast_operation() {
let bool_type = GqlType::Boolean;
let string_type = GqlType::String { max_length: None };
let cast_op = TypeCaster::cast(&bool_type, &string_type).unwrap();
assert!(matches!(cast_op, CastOperation::BooleanToString));
let int_type = GqlType::Integer;
let double_type = GqlType::Double;
let cast_op = TypeCaster::cast(&int_type, &double_type).unwrap();
assert!(matches!(cast_op, CastOperation::NumericCast));
}
}