use core::marker::PhantomData;
use crate::{IndexId, PropertyKeyId, PropertyType, PropertyValue, error::DbError};
mod sealed {
pub trait Sealed {}
}
pub trait ValueType: sealed::Sealed + Copy {
const TYPE: PropertyType;
}
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
pub struct Text;
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
pub struct Int;
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
pub struct Bool;
impl sealed::Sealed for Text {}
impl sealed::Sealed for Int {}
impl sealed::Sealed for Bool {}
impl ValueType for Text {
const TYPE: PropertyType = PropertyType::Text;
}
impl ValueType for Int {
const TYPE: PropertyType = PropertyType::Integer;
}
impl ValueType for Bool {
const TYPE: PropertyType = PropertyType::Boolean;
}
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
pub struct Key<T: ValueType> {
id: PropertyKeyId,
_ty: PhantomData<T>,
}
impl<T: ValueType> Key<T> {
#[must_use]
pub const fn from_id(id: PropertyKeyId) -> Self {
Self {
id,
_ty: PhantomData,
}
}
#[must_use]
pub const fn id(self) -> PropertyKeyId {
self.id
}
}
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
pub struct EqualityIndex<T: ValueType> {
id: IndexId,
_ty: PhantomData<T>,
}
impl<T: ValueType> EqualityIndex<T> {
#[must_use]
pub const fn from_id(id: IndexId) -> Self {
Self {
id,
_ty: PhantomData,
}
}
#[must_use]
pub const fn id(self) -> IndexId {
self.id
}
}
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
pub struct RangeIndex<T: ValueType> {
id: IndexId,
_ty: PhantomData<T>,
}
impl<T: ValueType> RangeIndex<T> {
#[must_use]
pub const fn from_id(id: IndexId) -> Self {
Self {
id,
_ty: PhantomData,
}
}
#[must_use]
pub const fn id(self) -> IndexId {
self.id
}
}
pub trait Assignable<T: ValueType> {
fn into_value(self) -> Result<PropertyValue, DbError>;
}
impl Assignable<Text> for &str {
fn into_value(self) -> Result<PropertyValue, DbError> {
Ok(PropertyValue::Text(self.to_owned()))
}
}
impl Assignable<Text> for String {
fn into_value(self) -> Result<PropertyValue, DbError> {
Ok(PropertyValue::Text(self))
}
}
impl Assignable<Int> for i64 {
fn into_value(self) -> Result<PropertyValue, DbError> {
Ok(PropertyValue::Integer(self))
}
}
impl Assignable<Int> for u64 {
fn into_value(self) -> Result<PropertyValue, DbError> {
PropertyValue::try_from(self)
}
}
impl Assignable<Int> for usize {
fn into_value(self) -> Result<PropertyValue, DbError> {
PropertyValue::try_from(self)
}
}
impl Assignable<Bool> for bool {
fn into_value(self) -> Result<PropertyValue, DbError> {
Ok(PropertyValue::Boolean(self))
}
}
pub trait Readable<T: ValueType>: Sized {
fn read(value: &PropertyValue) -> Option<Self>;
}
impl Readable<Text> for String {
fn read(value: &PropertyValue) -> Option<Self> {
value.as_text().map(str::to_owned)
}
}
impl Readable<Int> for i64 {
fn read(value: &PropertyValue) -> Option<Self> {
value.as_int()
}
}
impl Readable<Int> for usize {
fn read(value: &PropertyValue) -> Option<Self> {
value.as_count()
}
}
impl Readable<Bool> for bool {
fn read(value: &PropertyValue) -> Option<Self> {
value.as_bool()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn markers_map_to_property_types() {
assert_eq!(<Text as ValueType>::TYPE, PropertyType::Text);
assert_eq!(<Int as ValueType>::TYPE, PropertyType::Integer);
assert_eq!(<Bool as ValueType>::TYPE, PropertyType::Boolean);
}
#[test]
fn typed_keys_roundtrip_their_plain_ids() {
let raw = PropertyKeyId::new(7);
let key = Key::<Text>::from_id(raw);
assert_eq!(key.id(), raw);
}
#[test]
fn assignable_checks_range_for_unsigned() {
assert_eq!(
Assignable::<Int>::into_value(5_u64).ok(),
Some(PropertyValue::Integer(5))
);
assert!(Assignable::<Int>::into_value(u64::MAX).is_err());
assert_eq!(
Assignable::<Text>::into_value("hi").ok(),
Some(PropertyValue::Text("hi".to_owned()))
);
}
#[test]
fn readable_projects_matching_values() {
assert_eq!(
<i64 as Readable<Int>>::read(&PropertyValue::Integer(3)),
Some(3)
);
assert_eq!(
<String as Readable<Text>>::read(&PropertyValue::Integer(3)),
None
);
}
}