use vitaminc_aead::{Encrypt, Int, Logical, Physical};
use vitaminc_normalize::{terms_by, Normalize, NormalizeError, Term, TermsError};
use vitaminc_protected::{Controlled, Protected};
use crate::{fold, OrderPlaintext};
pub mod domain {
pub const INT8: &str = "vitaminc/ore/value/int8/v1";
pub const UINT8: &str = "vitaminc/ore/value/uint8/v1";
pub const INT16: &str = "vitaminc/ore/value/int16/v1";
pub const UINT16: &str = "vitaminc/ore/value/uint16/v1";
pub const INT32: &str = "vitaminc/ore/value/int32/v1";
pub const UINT32: &str = "vitaminc/ore/value/uint32/v1";
pub const INT64: &str = "vitaminc/ore/value/int64/v1";
pub const UINT64: &str = "vitaminc/ore/value/uint64/v1";
pub const INT128: &str = "vitaminc/ore/value/int128/v1";
pub const UINT128: &str = "vitaminc/ore/value/uint128/v1";
pub const FLOAT64: &str = "vitaminc/ore/value/float64/v1";
pub const DATE: &str = "vitaminc/ore/value/date/v1";
pub const TIMESTAMP: &str = "vitaminc/ore/value/timestamp-micros/v1";
pub const DECIMAL: &str = "vitaminc/ore/value/decimal/v1";
pub const TEXT: &str = "vitaminc/ore/value/text-folded/unicode-16/v1";
pub const BYTES: &str = "vitaminc/ore/value/bytes/v1";
pub const UUID: &str = "vitaminc/ore/value/uuid/v1";
pub const ALL: &[&str] = &[
INT8, UINT8, INT16, UINT16, INT32, UINT32, INT64, UINT64, INT128, UINT128, FLOAT64, DATE,
TIMESTAMP, DECIMAL, TEXT, BYTES, UUID,
];
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub enum OrderTarget {
I8,
U8,
I16,
U16,
I32,
U32,
I64,
U64,
I128,
U128,
F64,
Date,
Timestamp,
Decimal,
Text,
Bytes,
Uuid,
}
impl OrderTarget {
pub const ALL: &[OrderTarget] = &[
Self::I8,
Self::U8,
Self::I16,
Self::U16,
Self::I32,
Self::U32,
Self::I64,
Self::U64,
Self::I128,
Self::U128,
Self::F64,
Self::Date,
Self::Timestamp,
Self::Decimal,
Self::Text,
Self::Bytes,
Self::Uuid,
];
pub fn domain(self) -> &'static str {
match self {
Self::I8 => domain::INT8,
Self::U8 => domain::UINT8,
Self::I16 => domain::INT16,
Self::U16 => domain::UINT16,
Self::I32 => domain::INT32,
Self::U32 => domain::UINT32,
Self::I64 => domain::INT64,
Self::U64 => domain::UINT64,
Self::I128 => domain::INT128,
Self::U128 => domain::UINT128,
Self::F64 => domain::FLOAT64,
Self::Date => domain::DATE,
Self::Timestamp => domain::TIMESTAMP,
Self::Decimal => domain::DECIMAL,
Self::Text => domain::TEXT,
Self::Bytes => domain::BYTES,
Self::Uuid => domain::UUID,
}
}
pub fn width(self) -> Option<usize> {
match self {
Self::I8 | Self::U8 => Some(1),
Self::I16 | Self::U16 => Some(2),
Self::I32 | Self::U32 | Self::Date => Some(4),
Self::I64 | Self::U64 | Self::F64 => Some(8),
Self::Timestamp => Some(12),
Self::Decimal => Some(14),
Self::I128 | Self::U128 | Self::Uuid => Some(16),
Self::Text | Self::Bytes => None,
}
}
}
pub fn order(
target: OrderTarget,
ty: Option<Logical>,
v: Physical<'_>,
) -> Result<OrderPlaintext, NormalizeError> {
use OrderTarget as T;
if ty.is_some_and(|ty| !ty.admits(&v)) {
return Err(NormalizeError::Invalid);
}
let bytes = match (target, ty, v) {
(_, None, Physical::Null | Physical::Bool(_)) | (_, Some(Logical::Undefined), _) => {
return Err(NormalizeError::NoDomain)
}
(_, Some(Logical::Unknown(_)), _) => return Err(NormalizeError::NoDomain),
(target, None, Physical::Int(i)) if target.is_int() => {
int(convert(target, i).ok_or(NormalizeError::OutOfRange)?)
}
(T::F64, None, Physical::Float(f)) => vitaminc_normalize::float(f),
(T::Text, None, Physical::Text(s)) => text(s)?,
(T::Bytes, None, Physical::Bytes(b)) => vitaminc_normalize::bytes(b),
(T::Date, Some(Logical::Date), Physical::Int(days)) => {
vitaminc_normalize::date(days).ok_or(NormalizeError::Invalid)?
}
(T::Timestamp, Some(Logical::Timestamp(unit)), Physical::Int(count)) => {
vitaminc_normalize::timestamp(unit, count).ok_or(NormalizeError::Invalid)?
}
(T::Decimal, Some(Logical::Decimal { scale }), Physical::Int(mantissa)) => {
vitaminc_normalize::decimal(scale, mantissa).ok_or(NormalizeError::Invalid)?
}
(T::Uuid, Some(Logical::Uuid), Physical::Bytes(b)) => {
vitaminc_normalize::uuid(b).ok_or(NormalizeError::Invalid)?
}
_ => return Err(NormalizeError::Mismatch),
};
Ok(OrderPlaintext::new(target, bytes))
}
impl OrderTarget {
fn is_int(self) -> bool {
matches!(
self,
Self::I8
| Self::U8
| Self::I16
| Self::U16
| Self::I32
| Self::U32
| Self::I64
| Self::U64
| Self::I128
| Self::U128
)
}
}
fn convert(target: OrderTarget, value: Int) -> Option<Int> {
let Some(v) = value.to_i128() else {
return match (target, value) {
(OrderTarget::U128, Int::U128(v)) => Some(Int::U128(v)),
_ => None,
};
};
match target {
OrderTarget::I8 => i8::try_from(v).ok().map(Int::I8),
OrderTarget::U8 => u8::try_from(v).ok().map(Int::U8),
OrderTarget::I16 => i16::try_from(v).ok().map(Int::I16),
OrderTarget::U16 => u16::try_from(v).ok().map(Int::U16),
OrderTarget::I32 => i32::try_from(v).ok().map(Int::I32),
OrderTarget::U32 => u32::try_from(v).ok().map(Int::U32),
OrderTarget::I64 => i64::try_from(v).ok().map(Int::I64),
OrderTarget::U64 => u64::try_from(v).ok().map(Int::U64),
OrderTarget::I128 => Some(Int::I128(v)),
OrderTarget::U128 => u128::try_from(v).ok().map(Int::U128),
_ => None,
}
}
fn int(value: Int) -> Protected<Vec<u8>> {
match value {
Int::I8(v) => fixed(((v as u8) ^ (1 << 7)).to_be_bytes()),
Int::U8(v) => fixed(v.to_be_bytes()),
Int::I16(v) => fixed(((v as u16) ^ (1 << 15)).to_be_bytes()),
Int::U16(v) => fixed(v.to_be_bytes()),
Int::I32(v) => fixed(((v as u32) ^ (1 << 31)).to_be_bytes()),
Int::U32(v) => fixed(v.to_be_bytes()),
Int::I64(v) => fixed(((v as u64) ^ (1 << 63)).to_be_bytes()),
Int::U64(v) => fixed(v.to_be_bytes()),
Int::I128(v) => fixed(((v as u128) ^ (1 << 127)).to_be_bytes()),
Int::U128(v) => fixed(v.to_be_bytes()),
}
}
pub fn text(text: &str) -> Result<Protected<Vec<u8>>, NormalizeError> {
fold::folded(text)
}
fn fixed<const N: usize>(bytes: [u8; N]) -> Protected<Vec<u8>> {
let bytes = Protected::new(bytes);
Protected::new(bytes.risky_ref().to_vec())
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Order(pub OrderTarget);
impl Normalize for Order {
type Term = OrderPlaintext;
const INDEX: &'static str = "order";
fn normalize(
&self,
ty: Option<Logical>,
value: Physical<'_>,
) -> Result<OrderPlaintext, NormalizeError> {
order(self.0, ty, value)
}
}
pub fn order_terms<P: Encrypt>(
target: OrderTarget,
value: P,
) -> Result<Vec<Term<OrderPlaintext>>, TermsError> {
terms_by(Order(target), value)
}
#[cfg(test)]
#[path = "order_tests.rs"]
mod tests;