use std::num::{NonZero, TryFromIntError}; pub enum Value<'a> { Int(i64), Float(f64), Text(&'a str), Blob(&'a [u8]), Null, } // this is an owned trait to get around some problems with blanket implementations pub trait FromSql: Sized { type Error: std::error::Error; fn try_from_sql(value: Value<'_>) -> Result; } #[derive(Debug, thiserror::Error)] #[error("invalid type")] pub struct InvalidTypeError; #[derive(Debug, thiserror::Error)] pub enum FromSqlIntError { #[error(transparent)] InvalidType(#[from] InvalidTypeError), #[error(transparent)] TryFromInt(#[from] TryFromIntError), } impl FromSql for i64 { type Error = FromSqlIntError; fn try_from_sql(value: Value<'_>) -> Result { if let Value::Int(i) = value { Ok(i) } else { Err(InvalidTypeError.into()) } } } impl FromSql for NonZero { type Error = FromSqlIntError; fn try_from_sql(value: Value) -> Result { let i: i64 = FromSql::try_from_sql(value)?; Ok(i.try_into()?) } } macro_rules! int_impl { ($($source:ty),+) => {$( impl FromSql for $source { type Error = FromSqlIntError; fn try_from_sql(value: Value<'_>) -> Result { let i: i64 = FromSql::try_from_sql(value)?; Ok(i.try_into()?) } } impl FromSql for NonZero<$source> { type Error = FromSqlIntError; fn try_from_sql(value: Value<'_>) -> Result { let i: NonZero = FromSql::try_from_sql(value)?; Ok(i.try_into()?) } } )*} } int_impl!(i8, i16, i32, u8, u16, u32, u64); impl<'a, T: FromSql> FromSql for Option { type Error = T::Error; fn try_from_sql(value: Value<'_>) -> Result { if let Value::Null = value { Ok(None) } else { FromSql::try_from_sql(value).map(Some) } } }