rullst-orm 12.3.0

Fast, un-opinionated, database-agnostic ORM and query builder for Rust
Documentation
use super::{PolyglotError, TursoRow, TursoValue};

/// Converts one Rust field to and from Turso/libSQL scalar storage.
///
/// Implement this trait for application-specific scalar newtypes. Structured
/// domain values should normally be encoded explicitly instead of relying on
/// an implicit database-wide JSON convention.
pub trait TursoCodec: Sized {
    /// Encodes one model field as a bound SQL value.
    fn encode_turso(&self) -> Result<TursoValue, PolyglotError>;

    /// Decodes one result cell into a model field.
    fn decode_turso(value: &TursoValue) -> Result<Self, PolyglotError>;
}

/// Primary-key behavior required by the Turso Active Record facade.
pub trait TursoPrimaryKey: TursoCodec {
    /// Returns whether a model has not been inserted yet.
    fn is_unset(&self) -> bool;

    /// Replaces the key with the value returned by `INSERT ... RETURNING`.
    fn assign_turso(&mut self, value: &TursoValue) -> Result<(), PolyglotError>;
}

/// Typed model contract generated by `#[derive(Orm)]` with
/// `#[orm(backend = "turso")]` or by the dedicated `#[derive(TursoModel)]`.
pub trait TursoModel: Sized + Send + Sync + 'static {
    /// Validated SQL table identifier.
    fn table_name() -> &'static str;

    /// Model columns in deterministic declaration order.
    fn columns() -> &'static [&'static str];

    /// Primary-key column. The v12 derive currently requires an `id` field.
    fn primary_key_column() -> &'static str;

    /// Encodes every declared persistent field in [`Self::columns`] order.
    fn encode_turso(&self) -> Result<Vec<TursoValue>, PolyglotError>;

    /// Decodes one complete row returned by the adapter.
    fn decode_turso(row: &TursoRow) -> Result<Self, PolyglotError>;

    /// Returns the current primary-key value.
    fn primary_key_value(&self) -> Result<TursoValue, PolyglotError>;

    /// Returns whether the key represents a not-yet-inserted model.
    fn primary_key_is_unset(&self) -> bool;

    /// Assigns an insert-returned primary key.
    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
        );
    }
}