use core::fmt;
use core::marker::PhantomData;
use vitaminc_aead::{Bytes as RawBytes, Encrypt};
use crate::{NullOrder, OrderScheme, OrderTarget};
mod private {
pub trait Sealed {}
}
pub trait Target: private::Sealed + Copy + fmt::Debug {
fn runtime(self) -> OrderTarget;
fn null_order(self) -> Option<NullOrder> {
None
}
}
pub trait StaticTarget: Target + Default {
const TARGET: OrderTarget;
}
#[diagnostic::on_unimplemented(
message = "`{Self}` takes no null policy",
label = "not a fixed-width target",
note = "only a fixed-width target places nulls first or last: a text or bytes null already shows by its length, so placing it would hide nothing"
)]
pub trait FixedWidth: StaticTarget {}
pub trait NullPolicy: private::Sealed + Copy + fmt::Debug + Default {
const ORDER: NullOrder;
}
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash)]
pub struct First;
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash)]
pub struct Last;
impl private::Sealed for First {}
impl private::Sealed for Last {}
impl NullPolicy for First {
const ORDER: NullOrder = NullOrder::First;
}
impl NullPolicy for Last {
const ORDER: NullOrder = NullOrder::Last;
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct Nulls<T, P: NullPolicy> {
target: T,
policy: PhantomData<P>,
}
impl<T: FixedWidth, P: NullPolicy> Nulls<T, P> {
pub(crate) fn new(target: T) -> Self {
Self {
target,
policy: PhantomData,
}
}
pub fn target(self) -> T {
self.target
}
pub fn null_order(self) -> NullOrder {
P::ORDER
}
}
impl<T: FixedWidth, P: NullPolicy> private::Sealed for Nulls<T, P> {}
impl<T: FixedWidth, P: NullPolicy> Target for Nulls<T, P> {
fn runtime(self) -> OrderTarget {
self.target.runtime()
}
fn null_order(self) -> Option<NullOrder> {
Some(P::ORDER)
}
}
impl<S: Supports<T> + ?Sized, T: FixedWidth, P: NullPolicy> Supports<Nulls<T, P>> for S {
fn supports(target: Nulls<T, P>) -> bool {
<S as Supports<T>>::supports(target.target)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct RuntimeNulls {
target: OrderTarget,
order: NullOrder,
}
impl RuntimeNulls {
pub(crate) fn new(target: OrderTarget, order: NullOrder) -> Self {
Self { target, order }
}
pub fn target(self) -> OrderTarget {
self.target
}
pub fn null_order(self) -> NullOrder {
self.order
}
}
impl private::Sealed for RuntimeNulls {}
impl Target for RuntimeNulls {
fn runtime(self) -> OrderTarget {
self.target
}
fn null_order(self) -> Option<NullOrder> {
Some(self.order)
}
}
impl<S: Supports<OrderTarget> + ?Sized> Supports<RuntimeNulls> for S {
fn supports(target: RuntimeNulls) -> bool {
<S as Supports<OrderTarget>>::supports(target.target)
}
}
impl private::Sealed for OrderTarget {}
impl Target for OrderTarget {
fn runtime(self) -> OrderTarget {
self
}
}
#[diagnostic::on_unimplemented(
message = "`{Self}` is not a value that always fits `{T}`",
label = "does not always fit `{T}`",
note = "`encrypt` takes a value the encoding never refuses: a narrower integer, an `f32`, bytes, or a typed value such as a `NaiveDate`",
note = "a value the encoding can refuse (an integer that may be out of range, text, a `DateTime<Utc>`, or any other `Encrypt` value) goes through `encrypt_checked`, which checks it while the program runs"
)]
pub trait FitsIn<T: StaticTarget>: Encrypt + private::Sealed {}
#[diagnostic::on_unimplemented(
message = "`{Self}` has no order target of its own",
label = "has no order target of its own",
note = "name the target: `scheme.target(I64).encrypt_checked(value)`",
note = "there is no 8-bit target: an order term over 256 values reveals them, so an `i8` goes into `I16` or wider by name (`scheme.target(I16).encrypt(value)`), or into an equality index"
)]
pub trait OwnTarget: Encrypt + private::Sealed {
type Target: StaticTarget;
}
#[diagnostic::on_unimplemented(
message = "`{Self}` has no order term for `{T}`",
label = "does not implement `Supports<{T}>`",
note = "a scheme lists the targets it takes with `vitaminc_ore::supports_targets!(Scheme; I32, Date)`, which answers for each marker and for `OrderTarget`; pick one it lists, or an `OrderTarget` chosen while the program runs"
)]
pub trait Supports<T: Target>: OrderScheme {
fn supports(target: T) -> bool;
}
#[macro_export]
macro_rules! supports_targets {
($scheme:ty; $($marker:ident),* $(,)?) => {
impl $crate::Supports<$crate::OrderTarget> for $scheme {
fn supports(target: $crate::OrderTarget) -> bool {
#[allow(unreachable_patterns)]
match target {
$($crate::OrderTarget::$marker => true,)*
_ => false,
}
}
}
$(
impl $crate::Supports<$crate::target::$marker> for $scheme {
fn supports(_: $crate::target::$marker) -> bool {
true
}
}
)*
};
}
#[macro_export]
macro_rules! supports_every_target {
($scheme:ty) => {
$crate::supports_targets!($scheme;
I16, U16, I32, U32, I64, U64, I128, U128,
F64, Date, Timestamp, Decimal, Text, Bytes, Uuid);
};
}
macro_rules! markers {
($($(#[$doc:meta])* $marker:ident),* $(,)?) => {
$(
$(#[$doc])*
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash)]
pub struct $marker;
impl private::Sealed for $marker {}
impl Target for $marker {
fn runtime(self) -> OrderTarget {
OrderTarget::$marker
}
}
impl StaticTarget for $marker {
const TARGET: OrderTarget = OrderTarget::$marker;
}
)*
};
}
macro_rules! fixed_width {
($($marker:ident),* $(,)?) => {
$(impl FixedWidth for $marker {})*
};
}
fixed_width!(I16, U16, I32, U32, I64, U64, I128, U128, F64, Date, Timestamp, Decimal, Uuid);
markers! {
I16,
U16,
I32,
U32,
I64,
U64,
I128,
U128,
F64,
Date,
Timestamp,
Decimal,
Text,
Bytes,
Uuid,
}
macro_rules! sealed {
($($ty:ty),* $(,)?) => {
$(
impl private::Sealed for $ty {}
impl private::Sealed for &$ty {}
)*
};
}
macro_rules! fits {
($($target:ident: $($ty:ty),*;)*) => {
$($(
impl FitsIn<$target> for $ty {}
impl FitsIn<$target> for &$ty {}
)*)*
};
}
macro_rules! own {
($($ty:ty => $target:ident),* $(,)?) => {
$(
impl OwnTarget for $ty {
type Target = $target;
}
impl OwnTarget for &$ty {
type Target = $target;
}
)*
};
}
sealed!(
i8,
i16,
u16,
i32,
u32,
i64,
u64,
i128,
u128,
f32,
f64,
str,
String,
[u8],
Vec<u8>,
);
impl private::Sealed for RawBytes<'_> {}
impl private::Sealed for &RawBytes<'_> {}
impl<const N: usize> private::Sealed for [u8; N] {}
impl<const N: usize> private::Sealed for &[u8; N] {}
fits! {
I16: i8, i16;
U16: u16;
I32: i8, i16, i32, u16;
U32: u16, u32;
I64: i8, i16, i32, i64, u16, u32;
U64: u16, u32, u64;
I128: i8, i16, i32, i64, i128, u16, u32, u64;
U128: u16, u32, u64, u128;
F64: f32, f64;
Bytes: Vec<u8>;
}
impl FitsIn<Bytes> for &[u8] {}
impl FitsIn<Bytes> for RawBytes<'_> {}
impl FitsIn<Bytes> for &RawBytes<'_> {}
impl<const N: usize> FitsIn<Bytes> for [u8; N] {}
impl<const N: usize> FitsIn<Bytes> for &[u8; N] {}
own! {
i16 => I16,
u16 => U16,
i32 => I32,
u32 => U32,
i64 => I64,
u64 => U64,
i128 => I128,
u128 => U128,
f32 => F64,
f64 => F64,
String => Text,
Vec<u8> => Bytes,
}
impl OwnTarget for &str {
type Target = Text;
}
impl OwnTarget for &[u8] {
type Target = Bytes;
}
impl OwnTarget for RawBytes<'_> {
type Target = Bytes;
}
impl OwnTarget for &RawBytes<'_> {
type Target = Bytes;
}
impl<const N: usize> OwnTarget for [u8; N] {
type Target = Bytes;
}
impl<const N: usize> OwnTarget for &[u8; N] {
type Target = Bytes;
}
#[cfg(feature = "chrono")]
mod chrono_rows {
use super::*;
use chrono::{DateTime, NaiveDate, Utc};
use vitaminc_aead::Timestamp as Instant;
impl private::Sealed for NaiveDate {}
impl private::Sealed for &NaiveDate {}
impl private::Sealed for DateTime<Utc> {}
impl private::Sealed for &DateTime<Utc> {}
impl private::Sealed for Instant {}
impl private::Sealed for &Instant {}
impl FitsIn<Date> for NaiveDate {}
impl FitsIn<Date> for &NaiveDate {}
impl FitsIn<Timestamp> for Instant {}
impl FitsIn<Timestamp> for &Instant {}
impl OwnTarget for NaiveDate {
type Target = Date;
}
impl OwnTarget for &NaiveDate {
type Target = Date;
}
impl OwnTarget for DateTime<Utc> {
type Target = Timestamp;
}
impl OwnTarget for &DateTime<Utc> {
type Target = Timestamp;
}
impl OwnTarget for Instant {
type Target = Timestamp;
}
impl OwnTarget for &Instant {
type Target = Timestamp;
}
}
#[cfg(feature = "rust_decimal")]
mod decimal_rows {
use super::*;
impl private::Sealed for rust_decimal::Decimal {}
impl private::Sealed for &rust_decimal::Decimal {}
impl FitsIn<Decimal> for rust_decimal::Decimal {}
impl FitsIn<Decimal> for &rust_decimal::Decimal {}
impl OwnTarget for rust_decimal::Decimal {
type Target = Decimal;
}
impl OwnTarget for &rust_decimal::Decimal {
type Target = Decimal;
}
}
#[cfg(feature = "uuid")]
mod uuid_rows {
use super::*;
impl private::Sealed for uuid::Uuid {}
impl private::Sealed for &uuid::Uuid {}
impl FitsIn<Uuid> for uuid::Uuid {}
impl FitsIn<Uuid> for &uuid::Uuid {}
impl OwnTarget for uuid::Uuid {
type Target = Uuid;
}
impl OwnTarget for &uuid::Uuid {
type Target = Uuid;
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn every_marker_stands_for_its_target_and_every_target_is_itself() {
fn check<T: StaticTarget>(expected: OrderTarget) {
assert_eq!(T::TARGET, expected);
assert_eq!(T::default().runtime(), expected);
}
check::<I16>(OrderTarget::I16);
check::<U16>(OrderTarget::U16);
check::<I32>(OrderTarget::I32);
check::<U32>(OrderTarget::U32);
check::<I64>(OrderTarget::I64);
check::<U64>(OrderTarget::U64);
check::<I128>(OrderTarget::I128);
check::<U128>(OrderTarget::U128);
check::<F64>(OrderTarget::F64);
check::<Date>(OrderTarget::Date);
check::<Timestamp>(OrderTarget::Timestamp);
check::<Decimal>(OrderTarget::Decimal);
check::<Text>(OrderTarget::Text);
check::<Bytes>(OrderTarget::Bytes);
check::<Uuid>(OrderTarget::Uuid);
for target in OrderTarget::ALL {
assert_eq!(target.runtime(), *target);
}
}
struct Four;
impl OrderScheme for Four {
const NAME: &'static str = "four";
type Term = Vec<u8>;
type Error = core::convert::Infallible;
type Space = ();
fn open(
&self,
_: OrderTarget,
_: &vitaminc_context::Context<'_>,
) -> Result<(), Self::Error> {
Ok(())
}
fn encrypt_plaintext(
&self,
_: &(),
_: &crate::OrderPlaintext,
) -> Result<Vec<u8>, Self::Error> {
Ok(vec![0])
}
fn compare(a: &Vec<u8>, b: &Vec<u8>) -> Option<core::cmp::Ordering> {
Some(a.cmp(b))
}
}
crate::supports_targets!(Four; I32, U32, Date);
#[test]
fn a_scheme_takes_a_target_with_a_null_policy_exactly_when_it_takes_the_target() {
fn marker<P: NullPolicy>(order: NullOrder) {
assert!(
<Four as Supports<Nulls<I32, P>>>::supports(Nulls::new(I32)),
"{order:?}: a marker the scheme takes"
);
let placed = Nulls::<Date, P>::new(Date);
assert_eq!(placed.target(), Date);
assert_eq!(placed.null_order(), order);
assert_eq!(placed.runtime(), OrderTarget::Date);
assert_eq!(Target::null_order(placed), Some(order));
}
marker::<First>(NullOrder::First);
marker::<Last>(NullOrder::Last);
for order in [NullOrder::First, NullOrder::Last] {
assert!(
<Four as Supports<RuntimeNulls>>::supports(RuntimeNulls::new(
OrderTarget::Date,
order
)),
"{order:?}: a runtime target the scheme takes"
);
assert!(
!<Four as Supports<RuntimeNulls>>::supports(RuntimeNulls::new(
OrderTarget::I64,
order
)),
"{order:?}: a runtime target the scheme does not take"
);
let placed = RuntimeNulls::new(OrderTarget::Date, order);
assert_eq!(placed.target(), OrderTarget::Date);
assert_eq!(placed.null_order(), order);
assert_eq!(placed.runtime(), OrderTarget::Date);
assert_eq!(Target::null_order(placed), Some(order));
}
assert_eq!(I32.null_order(), None, "a marker places no nulls");
assert_eq!(
OrderTarget::I32.null_order(),
None,
"nor does a runtime target"
);
}
struct Refusing;
impl OrderScheme for Refusing {
const NAME: &'static str = "refusing";
type Term = Vec<u8>;
type Error = core::convert::Infallible;
type Space = ();
fn open(
&self,
_: OrderTarget,
_: &vitaminc_context::Context<'_>,
) -> Result<(), Self::Error> {
Ok(())
}
fn encrypt_plaintext(
&self,
_: &(),
_: &crate::OrderPlaintext,
) -> Result<Vec<u8>, Self::Error> {
Ok(vec![0])
}
fn compare(a: &Vec<u8>, b: &Vec<u8>) -> Option<core::cmp::Ordering> {
Some(a.cmp(b))
}
}
impl Supports<OrderTarget> for Refusing {
fn supports(_: OrderTarget) -> bool {
false
}
}
impl Supports<U32> for Refusing {
fn supports(_: U32) -> bool {
false
}
}
#[test]
fn a_policy_target_takes_the_answer_of_the_target_it_is_set_on() {
fn marker<P: NullPolicy>() {
assert!(
!<Refusing as Supports<Nulls<U32, P>>>::supports(Nulls::new(U32)),
"{:?}: a marker the scheme refuses stays refused under a policy",
P::ORDER
);
}
marker::<First>();
marker::<Last>();
}
#[test]
fn each_policy_marker_stands_for_its_null_order() {
assert_eq!(First::ORDER, NullOrder::First);
assert_eq!(Last::ORDER, NullOrder::Last);
}
}