use crate::types::{GqlType, TypeError, TypeResult};
#[derive(Debug)]
pub struct TypeCoercion;
impl TypeCoercion {
#[allow(dead_code)] pub fn coerce(from: &GqlType, to: &GqlType) -> TypeResult<CoercionStrategy> {
if from == to {
return Ok(CoercionStrategy::None);
}
match (from, to) {
(
GqlType::SmallInt,
GqlType::Integer | GqlType::BigInt | GqlType::Int128 | GqlType::Int256,
) => Ok(CoercionStrategy::IntegerWidening),
(GqlType::Integer, GqlType::BigInt | GqlType::Int128 | GqlType::Int256) => {
Ok(CoercionStrategy::IntegerWidening)
}
(GqlType::BigInt, GqlType::Int128 | GqlType::Int256) => {
Ok(CoercionStrategy::IntegerWidening)
}
(GqlType::Int128, GqlType::Int256) => Ok(CoercionStrategy::IntegerWidening),
(
GqlType::SmallInt
| GqlType::Integer
| GqlType::BigInt
| GqlType::Int128
| GqlType::Int256,
GqlType::Decimal { .. },
) => Ok(CoercionStrategy::IntegerToDecimal),
(
GqlType::SmallInt | GqlType::Integer,
GqlType::Float { .. } | GqlType::Real | GqlType::Double,
) => Ok(CoercionStrategy::IntegerToFloat),
(GqlType::Float { .. } | GqlType::Real, GqlType::Double) => {
Ok(CoercionStrategy::FloatWidening)
}
(GqlType::String { .. }, GqlType::Integer | GqlType::Double | GqlType::Boolean) => {
Ok(CoercionStrategy::StringToOther)
}
(GqlType::Date, GqlType::Timestamp { .. }) => Ok(CoercionStrategy::DateToTimestamp),
(
GqlType::Reference {
target_type: Some(ref_type),
},
target,
) => {
if Self::coerce(ref_type, target).is_ok() {
Ok(CoercionStrategy::ReferenceDeference)
} else {
Err(TypeError::InvalidCast(
format!("{:?}", from),
format!("{:?}", to),
))
}
}
(
source,
GqlType::Reference {
target_type: Some(ref_type),
},
) => {
if Self::coerce(source, ref_type).is_ok() {
Ok(CoercionStrategy::CreateReference)
} else {
Err(TypeError::InvalidCast(
format!("{:?}", from),
format!("{:?}", to),
))
}
}
_ => Err(TypeError::InvalidCast(
format!("{:?}", from),
format!("{:?}", to),
)),
}
}
#[allow(dead_code)] pub fn find_common_type(type1: &GqlType, type2: &GqlType) -> Option<GqlType> {
if type1 == type2 {
return Some(type1.clone());
}
match (type1, type2) {
(GqlType::SmallInt, GqlType::Integer) | (GqlType::Integer, GqlType::SmallInt) => {
Some(GqlType::Integer)
}
(GqlType::SmallInt, GqlType::BigInt) | (GqlType::BigInt, GqlType::SmallInt) => {
Some(GqlType::BigInt)
}
(GqlType::Integer, GqlType::BigInt) | (GqlType::BigInt, GqlType::Integer) => {
Some(GqlType::BigInt)
}
(_, GqlType::Double) | (GqlType::Double, _) => Some(GqlType::Double),
(
GqlType::String {
max_length: Some(l1),
},
GqlType::String {
max_length: Some(l2),
},
) => Some(GqlType::String {
max_length: Some(*l1.max(l2)),
}),
(GqlType::String { .. }, GqlType::String { .. }) => {
Some(GqlType::String { max_length: None })
}
_ => None,
}
}
}
#[derive(Debug, Clone, PartialEq)]
#[allow(dead_code)] pub enum CoercionStrategy {
None,
IntegerWidening,
IntegerToDecimal,
IntegerToFloat,
FloatWidening,
StringToOther,
DateToTimestamp,
ReferenceDeference,
CreateReference,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_numeric_coercion() {
let small = GqlType::SmallInt;
let big = GqlType::BigInt;
let result = TypeCoercion::coerce(&small, &big).unwrap();
assert_eq!(result, CoercionStrategy::IntegerWidening);
assert!(TypeCoercion::coerce(&big, &small).is_err());
}
#[test]
fn test_find_common_type() {
let int = GqlType::Integer;
let bigint = GqlType::BigInt;
let common = TypeCoercion::find_common_type(&int, &bigint).unwrap();
assert_eq!(common, GqlType::BigInt);
}
}