use super::{PathFieldSet, TypeUnion, Value};
use crate::{
Result,
schema::app::{FieldId, ModelId},
stmt,
};
#[derive(Debug, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum Type {
Bool,
String,
I8,
I16,
I32,
I64,
U8,
U16,
U32,
U64,
Uuid,
Key(ModelId),
Model(ModelId),
ForeignKey(FieldId),
List(Box<Type>),
Record(Vec<Type>),
Bytes,
#[cfg(feature = "rust_decimal")]
Decimal,
#[cfg(feature = "bigdecimal")]
BigDecimal,
#[cfg(feature = "jiff")]
Timestamp,
#[cfg(feature = "jiff")]
Zoned,
#[cfg(feature = "jiff")]
Date,
#[cfg(feature = "jiff")]
Time,
#[cfg(feature = "jiff")]
DateTime,
Null,
SparseRecord(PathFieldSet),
Unit,
Unknown,
Union(TypeUnion),
}
impl Type {
pub fn list(ty: impl Into<Self>) -> Self {
Self::List(Box::new(ty.into()))
}
#[track_caller]
pub fn as_list_unwrap(&self) -> &Type {
match self {
stmt::Type::List(items) => items,
_ => panic!("expected stmt::Type::List; actual={self:#?}"),
}
}
pub fn is_bool(&self) -> bool {
matches!(self, Self::Bool)
}
pub fn is_model(&self) -> bool {
matches!(self, Self::Model(_))
}
pub fn is_list(&self) -> bool {
matches!(self, Self::List(_))
}
pub fn is_string(&self) -> bool {
matches!(self, Self::String)
}
pub fn is_unit(&self) -> bool {
matches!(self, Self::Unit)
}
pub fn is_record(&self) -> bool {
matches!(self, Self::Record(..))
}
pub fn is_bytes(&self) -> bool {
matches!(self, Self::Bytes)
}
pub fn is_decimal(&self) -> bool {
#[cfg(feature = "rust_decimal")]
{
matches!(self, Self::Decimal)
}
#[cfg(not(feature = "rust_decimal"))]
{
false
}
}
pub fn is_big_decimal(&self) -> bool {
#[cfg(feature = "bigdecimal")]
{
matches!(self, Self::BigDecimal)
}
#[cfg(not(feature = "bigdecimal"))]
{
false
}
}
pub fn is_uuid(&self) -> bool {
matches!(self, Self::Uuid)
}
pub fn is_sparse_record(&self) -> bool {
matches!(self, Self::SparseRecord(..))
}
pub fn is_numeric(&self) -> bool {
matches!(
self,
Self::I8
| Self::I16
| Self::I32
| Self::I64
| Self::U8
| Self::U16
| Self::U32
| Self::U64
)
}
pub fn cast(&self, value: Value) -> Result<Value> {
use stmt::Value;
if value.is_null() {
return Ok(value);
}
#[cfg(feature = "jiff")]
if let Some(value) = self.cast_jiff(&value)? {
return Ok(value);
}
Ok(match (value, self) {
(value @ Value::String(_), Self::String) => value,
(Value::Uuid(value), Self::String) => Value::String(value.to_string()),
(Value::String(value), Self::Uuid) => {
Value::Uuid(value.parse().expect("could not parse uuid"))
}
(Value::Uuid(value), Self::Bytes) => Value::Bytes(value.as_bytes().to_vec()),
(Value::Bytes(value), Self::Uuid) => {
let bytes = value.clone();
Value::Uuid(
value
.try_into()
.map_err(|_| crate::Error::type_conversion(Value::Bytes(bytes), "Uuid"))?,
)
}
#[cfg(feature = "rust_decimal")]
(Value::Decimal(value), Self::String) => Value::String(value.to_string()),
#[cfg(feature = "rust_decimal")]
(Value::String(value), Self::Decimal) => {
Value::Decimal(value.parse().expect("could not parse Decimal"))
}
#[cfg(feature = "bigdecimal")]
(Value::BigDecimal(value), Self::String) => Value::String(value.to_string()),
#[cfg(feature = "bigdecimal")]
(Value::String(value), Self::BigDecimal) => {
Value::BigDecimal(value.parse().expect("could not parse BigDecimal"))
}
(Value::Record(record), Self::SparseRecord(fields)) => {
Value::sparse_record(fields.clone(), record)
}
(value, Self::I8) => Value::I8(i8::try_from(value)?),
(value, Self::I16) => Value::I16(i16::try_from(value)?),
(value, Self::I32) => Value::I32(i32::try_from(value)?),
(value, Self::I64) => Value::I64(i64::try_from(value)?),
(value, Self::U8) => Value::U8(u8::try_from(value)?),
(value, Self::U16) => Value::U16(u16::try_from(value)?),
(value, Self::U32) => Value::U32(u32::try_from(value)?),
(value, Self::U64) => Value::U64(u64::try_from(value)?),
(value, _) => todo!("value={value:#?}; ty={self:#?}"),
})
}
pub fn is_subtype_of(&self, other: &Type) -> bool {
if matches!(self, Type::Null) || matches!(other, Type::Null) {
return true;
}
match (self, other) {
(Type::Bool, Type::Bool) => true,
(Type::String, Type::String) => true,
(Type::I8, Type::I8) => true,
(Type::I16, Type::I16) => true,
(Type::I32, Type::I32) => true,
(Type::I64, Type::I64) => true,
(Type::U8, Type::U8) => true,
(Type::U16, Type::U16) => true,
(Type::U32, Type::U32) => true,
(Type::U64, Type::U64) => true,
(Type::Uuid, Type::Uuid) => true,
(Type::Bytes, Type::Bytes) => true,
(Type::Unit, Type::Unit) => true,
(Type::Unknown, Type::Unknown) => true,
#[cfg(feature = "rust_decimal")]
(Type::Decimal, Type::Decimal) => true,
#[cfg(feature = "bigdecimal")]
(Type::BigDecimal, Type::BigDecimal) => true,
#[cfg(feature = "jiff")]
(Type::Timestamp, Type::Timestamp) => true,
#[cfg(feature = "jiff")]
(Type::Zoned, Type::Zoned) => true,
#[cfg(feature = "jiff")]
(Type::Date, Type::Date) => true,
#[cfg(feature = "jiff")]
(Type::Time, Type::Time) => true,
#[cfg(feature = "jiff")]
(Type::DateTime, Type::DateTime) => true,
(Type::Key(a), Type::Key(b)) => a == b,
(Type::Model(a), Type::Model(b)) => a == b,
(Type::ForeignKey(a), Type::ForeignKey(b)) => a == b,
(Type::List(a), Type::List(b)) => a.is_subtype_of(b),
(Type::Record(a), Type::Record(b)) => {
a.len() == b.len() && a.iter().zip(b.iter()).all(|(a, b)| a.is_subtype_of(b))
}
(Type::SparseRecord(a), Type::SparseRecord(b)) => a == b,
(Type::Union(a), Type::Union(b)) => a
.iter()
.all(|a_ty| b.iter().any(|b_ty| a_ty.is_subtype_of(b_ty))),
(ty, Type::Union(union)) => union.iter().any(|member| ty.is_subtype_of(member)),
(Type::Union(union), other) => union.iter().all(|member| member.is_subtype_of(other)),
_ => false,
}
}
}
impl From<&Self> for Type {
fn from(value: &Self) -> Self {
value.clone()
}
}
impl From<ModelId> for Type {
fn from(value: ModelId) -> Self {
Self::Model(value)
}
}