sqlw 0.1.0

Compile-time SQL query building with schema-safe field references and automatic parameter binding
Documentation
//! Row mapping for query results.
//!
//! Convert database rows to Rust types via [`FromRow`]. The [`FromRow`]
//! derive macro generates implementations for structs.
//!
//! # Example
//!
//! ```ignore
//! use sqlw::FromRow;
//!
//! #[derive(FromRow)]
//! struct User {
//!     id: i64,
//!     name: String,
//!     #[field = "email_address"]
//!     email: String,
//!     #[optional]
//!     bio: Option<String>,
//! }
//! ```
//!
//! ## Field attributes
//!
//! - `#[field = "column_name"]` - Map to a custom column name
//! - `#[field(Table::FIELD)]` - Map to a schema field constant
//! - `#[optional]` - Allow the column to be missing (returns `None`)

use crate::value::{Value, ValueRef};

/// A row cell that may be borrowed from the backend row or owned.
pub enum RowCell<'a> {
    /// A borrowed reference to a value from the database row.
    Borrowed(ValueRef<'a>),
    /// An owned value extracted from the database row.
    Owned(Value),
}

impl<'a> RowCell<'a> {
    /// Returns a borrowed view of this cell value.
    pub fn as_ref(&'a self) -> ValueRef<'a> {
        match self {
            RowCell::Borrowed(value) => *value,
            RowCell::Owned(value) => ValueRef::from(value),
        }
    }

    /// Converts this cell to an owned value.
    pub fn into_owned(self) -> Value {
        match self {
            RowCell::Borrowed(value) => value.to_owned(),
            RowCell::Owned(value) => value,
        }
    }
}

/// A trait for types that can extract typed values from a row.
pub trait RowLike {
    /// Extracts a cell by column name.
    fn cell<'a>(&'a self, name: &str) -> Result<RowCell<'a>, RowError>;

    /// Extracts a typed value by column name.
    fn get_typed<T>(&self, name: &str) -> Result<T, RowError>
    where
        T: for<'a> TryFrom<ValueRef<'a>, Error = RowError>,
    {
        let cell = self.cell(name)?;
        let value_ref = cell.as_ref();
        T::try_from(value_ref)
    }

    /// Tries to extract a typed value by column name.
    ///
    /// Returns `Ok(None)` when the column does not exist in the row.
    fn try_get_typed<T>(&self, name: &str) -> Result<Option<T>, RowError>
    where
        T: for<'a> TryFrom<ValueRef<'a>, Error = RowError>,
    {
        match self.get_typed(name) {
            Ok(value) => Ok(Some(value)),
            Err(err) if matches!(err, RowError::ColumnNotFound { .. }) => Ok(None),
            Err(err) => Err(err),
        }
    }

    /// Extracts a value by column name as an owned `Value`.
    fn get_value(&self, name: &str) -> Result<Value, RowError> {
        Ok(self.cell(name)?.into_owned())
    }

    /// Tries to extract an owned value by column name.
    ///
    /// Returns `Ok(None)` when the column does not exist in the row.
    fn try_get_value(&self, name: &str) -> Result<Option<Value>, RowError> {
        match self.get_value(name) {
            Ok(value) => Ok(Some(value)),
            Err(err) if matches!(err, RowError::ColumnNotFound { .. }) => Ok(None),
            Err(err) => Err(err),
        }
    }
}

/// Types that can be constructed from a database row.
///
/// Implement this trait manually for custom mapping logic,
/// or use the [`FromRow`] derive macro.
pub trait FromRow: Sized {
    /// Constructs Self from a row.
    fn from_row<R: RowLike>(row: &R) -> Result<Self, RowError>;
}

/// Errors that can occur when extracting values from a row.
#[derive(Debug)]
pub enum RowError {
    /// Column not found in row.
    ColumnNotFound {
        /// Missing column name.
        name: String,
    },
    /// Type conversion failed.
    TypeMismatch {
        /// Expected type name.
        expected: &'static str,
        /// Actual value representation.
        found: String,
    },
    /// Other extraction error.
    Any(String),
}

impl std::fmt::Display for RowError {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            RowError::ColumnNotFound { name } => {
                write!(f, "Column '{}' not found", name)
            }
            RowError::TypeMismatch { expected, found } => {
                write!(f, "Type mismatch: expected {}, found {}", expected, found)
            }
            RowError::Any(msg) => write!(f, "Error: {}", msg),
        }
    }
}

impl std::error::Error for RowError {}

impl TryFrom<Value> for String {
    type Error = RowError;

    fn try_from(value: Value) -> Result<Self, Self::Error> {
        match value {
            Value::Text(s) => Ok(s),
            other => Err(RowError::TypeMismatch {
                expected: "Text",
                found: format!("{:?}", other),
            }),
        }
    }
}

impl<'a> TryFrom<ValueRef<'a>> for String {
    type Error = RowError;

    fn try_from(value: ValueRef<'a>) -> Result<Self, Self::Error> {
        match value {
            ValueRef::Text(s) => Ok(s.to_string()),
            other => Err(RowError::TypeMismatch {
                expected: "Text",
                found: format!("{:?}", other),
            }),
        }
    }
}

macro_rules! impl_try_from_int {
    ($($t:ty),*) => {
        $(
            impl TryFrom<Value> for $t {
                type Error = RowError;

                fn try_from(value: Value) -> Result<Self, Self::Error> {
                    match value {
                        Value::Int(i) => i.try_into().map_err(|_| RowError::TypeMismatch {
                            expected: stringify!($t),
                            found: format!("{:?}", i),
                        }),
                        other => Err(RowError::TypeMismatch {
                            expected: stringify!($t),
                            found: format!("{:?}", other),
                        }),
                    }
                }
            }

            impl<'a> TryFrom<ValueRef<'a>> for $t {
                type Error = RowError;

                fn try_from(value: ValueRef<'a>) -> Result<Self, Self::Error> {
                    match value {
                        ValueRef::Int(i) => i.try_into().map_err(|_| RowError::TypeMismatch {
                            expected: stringify!($t),
                            found: format!("{:?}", i),
                        }),
                        other => Err(RowError::TypeMismatch {
                            expected: stringify!($t),
                            found: format!("{:?}", other),
                        }),
                    }
                }
            }
        )*
    };
}

impl_try_from_int!(i8, i16, i32, i64, u8, u16, u32, u64, isize, usize);

macro_rules! impl_try_from_float {
    ($($t:ty),*) => {
        $(
            impl TryFrom<Value> for $t {
                type Error = RowError;

                fn try_from(value: Value) -> Result<Self, Self::Error> {
                    match value {
                        Value::Float(f) => Ok(f as $t),
                        other => Err(RowError::TypeMismatch {
                            expected: stringify!($t),
                            found: format!("{:?}", other),
                        }),
                    }
                }
            }

            impl<'a> TryFrom<ValueRef<'a>> for $t {
                type Error = RowError;

                fn try_from(value: ValueRef<'a>) -> Result<Self, Self::Error> {
                    match value {
                        ValueRef::Float(f) => Ok(f as $t),
                        other => Err(RowError::TypeMismatch {
                            expected: stringify!($t),
                            found: format!("{:?}", other),
                        }),
                    }
                }
            }
        )*
    };
}

impl_try_from_float!(f32, f64);

impl TryFrom<Value> for bool {
    type Error = RowError;

    fn try_from(value: Value) -> Result<Self, Self::Error> {
        match value {
            Value::Bool(b) => Ok(b),
            Value::Int(i) => Ok(i != 0),
            other => Err(RowError::TypeMismatch {
                expected: "Bool",
                found: format!("{:?}", other),
            }),
        }
    }
}

impl<'a> TryFrom<ValueRef<'a>> for bool {
    type Error = RowError;

    fn try_from(value: ValueRef<'a>) -> Result<Self, Self::Error> {
        match value {
            ValueRef::Bool(b) => Ok(b),
            ValueRef::Int(i) => Ok(i != 0),
            other => Err(RowError::TypeMismatch {
                expected: "Bool",
                found: format!("{:?}", other),
            }),
        }
    }
}

impl TryFrom<Value> for Vec<u8> {
    type Error = RowError;

    fn try_from(value: Value) -> Result<Self, Self::Error> {
        match value {
            Value::Blob(b) => Ok(b),
            other => Err(RowError::TypeMismatch {
                expected: "Blob",
                found: format!("{:?}", other),
            }),
        }
    }
}

impl<'a> TryFrom<ValueRef<'a>> for Vec<u8> {
    type Error = RowError;

    fn try_from(value: ValueRef<'a>) -> Result<Self, Self::Error> {
        match value {
            ValueRef::Blob(b) => Ok(b.to_vec()),
            other => Err(RowError::TypeMismatch {
                expected: "Blob",
                found: format!("{:?}", other),
            }),
        }
    }
}

impl<T> TryFrom<Value> for Option<T>
where
    T: TryFrom<Value, Error = RowError>,
{
    type Error = RowError;

    fn try_from(value: Value) -> Result<Self, Self::Error> {
        match value {
            Value::Null => Ok(None),
            other => Ok(Some(T::try_from(other)?)),
        }
    }
}

impl<'a, T> TryFrom<ValueRef<'a>> for Option<T>
where
    T: TryFrom<ValueRef<'a>, Error = RowError>,
{
    type Error = RowError;

    fn try_from(value: ValueRef<'a>) -> Result<Self, Self::Error> {
        match value {
            ValueRef::Null => Ok(None),
            other => Ok(Some(T::try_from(other)?)),
        }
    }
}