use super::{PolyglotError, TursoRow, TursoValue};
pub trait TursoCodec: Sized {
fn encode_turso(&self) -> Result<TursoValue, PolyglotError>;
fn decode_turso(value: &TursoValue) -> Result<Self, PolyglotError>;
}
pub trait TursoPrimaryKey: TursoCodec {
fn is_unset(&self) -> bool;
fn assign_turso(&mut self, value: &TursoValue) -> Result<(), PolyglotError>;
}
pub trait TursoModel: Sized + Send + Sync + 'static {
fn table_name() -> &'static str;
fn columns() -> &'static [&'static str];
fn primary_key_column() -> &'static str;
fn encode_turso(&self) -> Result<Vec<TursoValue>, PolyglotError>;
fn decode_turso(row: &TursoRow) -> Result<Self, PolyglotError>;
fn primary_key_value(&self) -> Result<TursoValue, PolyglotError>;
fn primary_key_is_unset(&self) -> bool;
fn assign_primary_key(&mut self, value: &TursoValue) -> Result<(), PolyglotError>;
}
fn type_error(expected: &'static str, value: &TursoValue) -> PolyglotError {
PolyglotError::Serialization(format!(
"expected {expected}, received {}",
value.kind_name()
))
}
impl TursoValue {
pub(super) const fn kind_name(&self) -> &'static str {
match self {
Self::Null => "NULL",
Self::Integer(_) => "INTEGER",
Self::Real(_) => "REAL",
Self::Text(_) => "TEXT",
Self::Blob(_) => "BLOB",
}
}
}
impl TursoCodec for TursoValue {
fn encode_turso(&self) -> Result<TursoValue, PolyglotError> {
Ok(self.clone())
}
fn decode_turso(value: &TursoValue) -> Result<Self, PolyglotError> {
Ok(value.clone())
}
}
impl TursoCodec for String {
fn encode_turso(&self) -> Result<TursoValue, PolyglotError> {
Ok(TursoValue::Text(self.clone()))
}
fn decode_turso(value: &TursoValue) -> Result<Self, PolyglotError> {
match value {
TursoValue::Text(value) => Ok(value.clone()),
other => Err(type_error("TEXT", other)),
}
}
}
impl TursoPrimaryKey for String {
fn is_unset(&self) -> bool {
self.is_empty()
}
fn assign_turso(&mut self, value: &TursoValue) -> Result<(), PolyglotError> {
*self = Self::decode_turso(value)?;
Ok(())
}
}
impl TursoCodec for Vec<u8> {
fn encode_turso(&self) -> Result<TursoValue, PolyglotError> {
Ok(TursoValue::Blob(self.clone()))
}
fn decode_turso(value: &TursoValue) -> Result<Self, PolyglotError> {
match value {
TursoValue::Blob(value) => Ok(value.clone()),
other => Err(type_error("BLOB", other)),
}
}
}
impl TursoCodec for bool {
fn encode_turso(&self) -> Result<TursoValue, PolyglotError> {
Ok(TursoValue::Integer(i64::from(*self)))
}
fn decode_turso(value: &TursoValue) -> Result<Self, PolyglotError> {
match value {
TursoValue::Integer(0) => Ok(false),
TursoValue::Integer(1) => Ok(true),
other => Err(type_error("boolean INTEGER 0 or 1", other)),
}
}
}
macro_rules! signed_integer_codec {
($($kind:ty),+ $(,)?) => {
$(
impl TursoCodec for $kind {
fn encode_turso(&self) -> Result<TursoValue, PolyglotError> {
let value = i64::try_from(*self).map_err(PolyglotError::serialization)?;
Ok(TursoValue::Integer(value))
}
fn decode_turso(value: &TursoValue) -> Result<Self, PolyglotError> {
match value {
TursoValue::Integer(value) => {
<$kind>::try_from(*value).map_err(PolyglotError::serialization)
}
other => Err(type_error("INTEGER", other)),
}
}
}
)+
};
}
signed_integer_codec!(i8, i16, i32, i64, isize, u8, u16, u32, u64, usize);
macro_rules! primary_integer {
($($kind:ty),+ $(,)?) => {
$(
impl TursoPrimaryKey for $kind {
fn is_unset(&self) -> bool {
*self == 0
}
fn assign_turso(&mut self, value: &TursoValue) -> Result<(), PolyglotError> {
*self = <$kind as TursoCodec>::decode_turso(value)?;
Ok(())
}
}
)+
};
}
primary_integer!(i8, i16, i32, i64, isize, u8, u16, u32, u64, usize);
impl TursoCodec for f32 {
fn encode_turso(&self) -> Result<TursoValue, PolyglotError> {
if !self.is_finite() {
return Err(PolyglotError::Serialization(
"non-finite floating-point values are not supported".to_owned(),
));
}
Ok(TursoValue::Real(f64::from(*self)))
}
fn decode_turso(value: &TursoValue) -> Result<Self, PolyglotError> {
match value {
TursoValue::Real(value)
if value.is_finite() && *value >= f32::MIN as f64 && *value <= f32::MAX as f64 =>
{
Ok(*value as f32)
}
other => Err(type_error("finite REAL", other)),
}
}
}
impl TursoCodec for f64 {
fn encode_turso(&self) -> Result<TursoValue, PolyglotError> {
if !self.is_finite() {
return Err(PolyglotError::Serialization(
"non-finite floating-point values are not supported".to_owned(),
));
}
Ok(TursoValue::Real(*self))
}
fn decode_turso(value: &TursoValue) -> Result<Self, PolyglotError> {
match value {
TursoValue::Real(value) if value.is_finite() => Ok(*value),
other => Err(type_error("finite REAL", other)),
}
}
}
impl<T> TursoCodec for Option<T>
where
T: TursoCodec,
{
fn encode_turso(&self) -> Result<TursoValue, PolyglotError> {
match self {
Some(value) => value.encode_turso(),
None => Ok(TursoValue::Null),
}
}
fn decode_turso(value: &TursoValue) -> Result<Self, PolyglotError> {
match value {
TursoValue::Null => Ok(None),
value => T::decode_turso(value).map(Some),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn scalar_codecs_are_strict_and_checked() {
assert!(bool::decode_turso(&TursoValue::Integer(1)).unwrap());
assert!(bool::decode_turso(&TursoValue::Integer(2)).is_err());
assert_eq!(i32::decode_turso(&TursoValue::Integer(42)).unwrap(), 42);
assert!(u8::decode_turso(&TursoValue::Integer(300)).is_err());
assert!(f32::INFINITY.encode_turso().is_err());
assert!(f64::NAN.encode_turso().is_err());
assert_eq!(
Option::<String>::decode_turso(&TursoValue::Null).unwrap(),
None
);
}
}