use chrono::{DateTime, NaiveDate, Utc};
use rust_decimal::Decimal;
use super::{Plaintext, PlaintextNullVariant};
use crate::encryption::errors::TypeParseError;
pub trait TryFromPlaintext: Sized {
fn try_from_plaintext(value: Plaintext) -> Result<Self, TypeParseError>;
fn try_from_optional_plaintext(value: Option<Plaintext>) -> Result<Self, TypeParseError>;
}
macro_rules! impl_try_from_plaintext {
($ty:ty, $variant:ident) => {
impl TryFromPlaintext for $ty {
fn try_from_plaintext(value: Plaintext) -> Result<Self, TypeParseError> {
match value {
Plaintext::$variant(Some(ref v)) => Ok(v.to_owned()),
_ => Err(TypeParseError(format!(
"Cannot convert type to {}",
std::any::type_name::<Self>()
))),
}
}
fn try_from_optional_plaintext(
value: Option<Plaintext>,
) -> Result<Self, TypeParseError> {
match value {
Some(plaintext) => Self::try_from_plaintext(plaintext),
_ => Err(TypeParseError(format!(
"Cannot convert type to {}",
std::any::type_name::<Self>()
))),
}
}
}
impl TryFrom<Plaintext> for $ty {
type Error = TypeParseError;
fn try_from(value: Plaintext) -> Result<Self, Self::Error> {
Self::try_from_plaintext(value)
}
}
impl TryFrom<Plaintext> for Option<$ty> {
type Error = TypeParseError;
fn try_from(value: Plaintext) -> Result<Self, Self::Error> {
Self::try_from_plaintext(value)
}
}
};
}
impl<T> TryFromPlaintext for Option<T>
where
T: TryFromPlaintext + PlaintextNullVariant,
{
fn try_from_optional_plaintext(value: Option<Plaintext>) -> Result<Self, TypeParseError> {
match value {
Some(value) => Self::try_from_plaintext(value),
None => Ok(None),
}
}
fn try_from_plaintext(value: Plaintext) -> Result<Self, TypeParseError> {
match (&value, T::null()) {
(Plaintext::BigInt(None), Plaintext::BigInt(_))
| (Plaintext::BigUInt(None), Plaintext::BigUInt(_))
| (Plaintext::Boolean(None), Plaintext::Boolean(_))
| (Plaintext::Decimal(None), Plaintext::Decimal(_))
| (Plaintext::Float(None), Plaintext::Float(_))
| (Plaintext::Int(None), Plaintext::Int(_))
| (Plaintext::NaiveDate(None), Plaintext::NaiveDate(_))
| (Plaintext::SmallInt(None), Plaintext::SmallInt(_))
| (Plaintext::Timestamp(None), Plaintext::Timestamp(_))
| (Plaintext::Utf8Str(None), Plaintext::Utf8Str(_)) => Ok(None),
(Plaintext::JsonB(None), Plaintext::JsonB(_)) => Ok(None),
(Plaintext::BigInt(Some(_)), Plaintext::BigInt(_))
| (Plaintext::BigUInt(Some(_)), Plaintext::BigUInt(_))
| (Plaintext::Boolean(Some(_)), Plaintext::Boolean(_))
| (Plaintext::Decimal(Some(_)), Plaintext::Decimal(_))
| (Plaintext::Float(Some(_)), Plaintext::Float(_))
| (Plaintext::Int(Some(_)), Plaintext::Int(_))
| (Plaintext::NaiveDate(Some(_)), Plaintext::NaiveDate(_))
| (Plaintext::SmallInt(Some(_)), Plaintext::SmallInt(_))
| (Plaintext::Timestamp(Some(_)), Plaintext::Timestamp(_))
| (Plaintext::Utf8Str(Some(_)), Plaintext::Utf8Str(_))
| (Plaintext::JsonB(Some(_)), Plaintext::JsonB(_)) => {
T::try_from_plaintext(value).map(Some)
}
_ => Err(TypeParseError(format!(
"Cannot convert type to {}",
std::any::type_name::<Self>()
))),
}
}
}
macro_rules! from_conversions_and_tests {
($(($($value:expr),*) => $ty:ty => $variant:ident),*) => {
$(
impl_try_from_plaintext!($ty, $variant);
)*
#[allow(dead_code)]
fn exhaustive_check(value: Plaintext) {
match value {
$(
Plaintext::$variant(_) => {},
)*
}
}
#[cfg(test)]
mod tests {
use super::*;
$(
paste::paste! {
// The following tests are for the old TryFrom impls
#[test]
fn [<test_try_from_ $variant:snake>]() {
$(
assert_eq!(
$ty::try_from(Plaintext::$variant(Some($ty::from($value)))).unwrap(),
$ty::from($value)
);
)*
}
#[test]
fn [<test_try_from_ $variant:snake _option>]() {
$(
assert_eq!(
Option::<$ty>::try_from(Plaintext::$variant(Some($ty::from($value)))).unwrap(),
Option::<$ty>::Some($value.into())
);
)*
}
#[test]
fn [<test_try_from_ $variant:snake _option_none>]() {
assert_eq!(
Option::<$ty>::try_from(Plaintext::$variant(None)).unwrap(),
Option::<$ty>::None
);
}
#[test]
fn [<test_try_from_plaintext_ $variant:snake>]() {
$(
assert_eq!(
$ty::try_from_plaintext(Plaintext::$variant(Some($ty::from($value)))).unwrap(),
$ty::from($value)
);
)*
}
#[test]
fn [<test_try_from_optional_plaintext_ $variant:snake _some>]() {
$(
assert_eq!(
$ty::try_from_optional_plaintext(Some(Plaintext::$variant(Some($ty::from($value))))).unwrap(),
$ty::from($value)
);
)*
}
#[test]
fn [<test_try_from_plaintext_ $variant:snake _option>]() {
$(
assert_eq!(
Option::<$ty>::try_from_plaintext(Plaintext::$variant(Some($ty::from($value)))).unwrap(),
Option::<$ty>::Some($value.into())
);
)*
}
#[test]
fn [<test_try_from_optional_plaintext_ $variant:snake _option_some>]() {
$(
assert_eq!(
Option::<$ty>::try_from_optional_plaintext(Some(Plaintext::$variant(Some($ty::from($value))))).unwrap(),
Option::<$ty>::Some($value.into())
);
)*
}
#[test]
fn [<test_try_from_plaintext_ $variant:snake _option_none>]() {
assert_eq!(
Option::<$ty>::try_from_plaintext(Plaintext::$variant(None)).unwrap(),
Option::<$ty>::None
);
}
#[test]
fn [<test_try_from_optional_plaintext_ $variant:snake _option_none>]() {
assert_eq!(
Option::<$ty>::try_from_optional_plaintext(None).unwrap(),
Option::<$ty>::None
);
}
}
)*
}
};
}
from_conversions_and_tests! {
(10_i64, -10_i64) => i64 => BigInt,
(10_u64, 100_u64) => u64 => BigUInt,
(10_i32, -10_i32) => i32 => Int,
(10_i16, -10_i16) => i16 => SmallInt,
(0., -1., 100.) => f64 => Float,
(Decimal::new(10, 0), Decimal::new(-10, 0)) => Decimal => Decimal,
(NaiveDate::from_ymd_opt(2020, 1, 1).expect("Expected date to create")) => NaiveDate => NaiveDate,
(DateTime::<Utc>::from_timestamp(1000, 0).expect("Expected timestamp to create")) => DateTime::<Utc> => Timestamp,
(true, false) => bool => Boolean,
("hello", "") => String => Utf8Str,
(serde_json::json!({"hello": "world"}), serde_json::json!({})) => serde_json::Value => JsonB
}