use arrow::datatypes::ArrowPrimitiveType;
use std::ops::Bound;
#[derive(Debug, Clone, PartialEq)]
pub enum Literal {
Integer(i128),
Float(f64),
String(String),
}
macro_rules! impl_from_for_literal {
($variant:ident, $($t:ty),*) => {
$(
impl From<$t> for Literal {
fn from(v: $t) -> Self {
Literal::$variant(v.into())
}
}
)*
};
}
impl_from_for_literal!(Integer, i8, i16, i32, i64, i128, u8, u16, u32, u64);
impl_from_for_literal!(Float, f32, f64);
impl From<&str> for Literal {
fn from(v: &str) -> Self {
Literal::String(v.to_string())
}
}
#[derive(Debug, Clone, PartialEq)]
pub enum LiteralCastError {
TypeMismatch {
expected: &'static str,
got: &'static str,
},
OutOfRange { target: &'static str, value: i128 },
FloatOutOfRange { target: &'static str, value: f64 },
}
impl std::fmt::Display for LiteralCastError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
LiteralCastError::TypeMismatch { expected, got } => {
write!(f, "expected {}, got {}", expected, got)
}
LiteralCastError::OutOfRange { target, value } => {
write!(f, "value {} out of range for {}", value, target)
}
LiteralCastError::FloatOutOfRange { target, value } => {
write!(f, "value {} out of range for {}", value, target)
}
}
}
}
impl std::error::Error for LiteralCastError {}
pub trait FromLiteral: Sized {
fn from_literal(lit: &Literal) -> Result<Self, LiteralCastError>;
}
macro_rules! impl_from_literal_int {
($($ty:ty),* $(,)?) => {
$(
impl FromLiteral for $ty {
fn from_literal(lit: &Literal) -> Result<Self, LiteralCastError> {
match lit {
Literal::Integer(i) => <$ty>::try_from(*i).map_err(|_| {
LiteralCastError::OutOfRange {
target: std::any::type_name::<$ty>(),
value: *i,
}
}),
Literal::Float(_) => Err(LiteralCastError::TypeMismatch {
expected: "integer",
got: "float",
}),
Literal::String(_) => Err(LiteralCastError::TypeMismatch {
expected: "integer",
got: "string",
}),
}
}
}
)*
};
}
impl_from_literal_int!(i8, i16, i32, i64, i128, isize, u8, u16, u32, u64, usize);
impl FromLiteral for f32 {
fn from_literal(lit: &Literal) -> Result<Self, LiteralCastError> {
let value = match lit {
Literal::Float(f) => *f,
Literal::Integer(i) => *i as f64,
Literal::String(_) => {
return Err(LiteralCastError::TypeMismatch {
expected: "float",
got: "string",
});
}
};
let cast = value as f32;
if value.is_finite() && !cast.is_finite() {
return Err(LiteralCastError::FloatOutOfRange {
target: "f32",
value,
});
}
Ok(cast)
}
}
impl FromLiteral for bool {
fn from_literal(lit: &Literal) -> Result<Self, LiteralCastError> {
match lit {
Literal::Integer(i) => match *i {
0 => Ok(false),
1 => Ok(true),
value => Err(LiteralCastError::OutOfRange {
target: "bool",
value,
}),
},
Literal::Float(_) => Err(LiteralCastError::TypeMismatch {
expected: "bool",
got: "float",
}),
Literal::String(s) => {
let normalized = s.trim().to_ascii_lowercase();
match normalized.as_str() {
"true" | "t" | "1" => Ok(true),
"false" | "f" | "0" => Ok(false),
_ => Err(LiteralCastError::TypeMismatch {
expected: "bool",
got: "string",
}),
}
}
}
}
}
impl FromLiteral for f64 {
fn from_literal(lit: &Literal) -> Result<Self, LiteralCastError> {
match lit {
Literal::Float(f) => Ok(*f),
Literal::Integer(i) => Ok(*i as f64),
Literal::String(_) => Err(LiteralCastError::TypeMismatch {
expected: "float",
got: "string",
}),
}
}
}
fn literal_type_name(lit: &Literal) -> &'static str {
match lit {
Literal::Integer(_) => "integer",
Literal::Float(_) => "float",
Literal::String(_) => "string",
}
}
pub fn literal_to_string(lit: &Literal) -> Result<String, LiteralCastError> {
match lit {
Literal::String(s) => Ok(s.clone()),
_ => Err(LiteralCastError::TypeMismatch {
expected: "string",
got: literal_type_name(lit),
}),
}
}
pub fn literal_to_native<T>(lit: &Literal) -> Result<T, LiteralCastError>
where
T: FromLiteral + Copy + 'static,
{
T::from_literal(lit)
}
pub fn bound_to_native<T>(bound: &Bound<Literal>) -> Result<Bound<T::Native>, LiteralCastError>
where
T: ArrowPrimitiveType,
T::Native: FromLiteral + Copy,
{
Ok(match bound {
Bound::Unbounded => Bound::Unbounded,
Bound::Included(l) => Bound::Included(literal_to_native::<T::Native>(l)?),
Bound::Excluded(l) => Bound::Excluded(literal_to_native::<T::Native>(l)?),
})
}