a3s-orm 0.2.1

A type-safe, executor-neutral SQL query builder for Rust
Documentation
use crate::Value;

#[derive(Debug, thiserror::Error, PartialEq, Eq)]
pub enum DecodeError {
    #[error("result row has no column at index {index}")]
    MissingColumn { index: usize },
    #[error("cannot decode {actual} at column {index} as {expected}")]
    TypeMismatch {
        index: usize,
        expected: &'static str,
        actual: &'static str,
    },
    #[error("integer at column {index} is outside the range of {target}")]
    IntegerOverflow { index: usize, target: &'static str },
    #[error("cannot decode array element {element} at column {index}: {source}")]
    ArrayElement {
        index: usize,
        element: usize,
        source: Box<DecodeError>,
    },
}

pub trait Row {
    fn value(&self, index: usize) -> Option<&Value>;
}

pub trait FromValue: Sized {
    const EXPECTED: &'static str;

    fn from_value(value: &Value, index: usize) -> Result<Self, DecodeError>;
}

pub trait FromRow: Sized {
    fn from_row(row: &impl Row) -> Result<Self, DecodeError>;
}

impl<T: FromValue> FromRow for T {
    fn from_row(row: &impl Row) -> Result<Self, DecodeError> {
        decode_at(row, 0)
    }
}

fn decode_at<T: FromValue>(row: &impl Row, index: usize) -> Result<T, DecodeError> {
    let value = row
        .value(index)
        .ok_or(DecodeError::MissingColumn { index })?;
    T::from_value(value, index)
}

fn mismatch<T: FromValue>(value: &Value, index: usize) -> DecodeError {
    DecodeError::TypeMismatch {
        index,
        expected: T::EXPECTED,
        actual: value.kind(),
    }
}

impl FromValue for bool {
    const EXPECTED: &'static str = "boolean";

    fn from_value(value: &Value, index: usize) -> Result<Self, DecodeError> {
        match value {
            Value::Bool(value) => Ok(*value),
            Value::I64(0) => Ok(false),
            Value::I64(1) => Ok(true),
            _ => Err(mismatch::<Self>(value, index)),
        }
    }
}

macro_rules! signed_decoders {
    ($($type:ty),+ $(,)?) => {$(
        impl FromValue for $type {
            const EXPECTED: &'static str = stringify!($type);

            fn from_value(value: &Value, index: usize) -> Result<Self, DecodeError> {
                let value = match value {
                    Value::I64(value) => *value,
                    _ => return Err(mismatch::<Self>(value, index)),
                };
                <$type>::try_from(value).map_err(|_| DecodeError::IntegerOverflow {
                    index,
                    target: stringify!($type),
                })
            }
        }
    )+};
}

macro_rules! unsigned_decoders {
    ($($type:ty),+ $(,)?) => {$(
        impl FromValue for $type {
            const EXPECTED: &'static str = stringify!($type);

            fn from_value(value: &Value, index: usize) -> Result<Self, DecodeError> {
                let value = match value {
                    Value::U64(value) => *value,
                    Value::I64(value) => u64::try_from(*value).map_err(|_| {
                        DecodeError::IntegerOverflow { index, target: stringify!($type) }
                    })?,
                    _ => return Err(mismatch::<Self>(value, index)),
                };
                <$type>::try_from(value).map_err(|_| DecodeError::IntegerOverflow {
                    index,
                    target: stringify!($type),
                })
            }
        }
    )+};
}

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

impl FromValue for f64 {
    const EXPECTED: &'static str = "f64";

    fn from_value(value: &Value, index: usize) -> Result<Self, DecodeError> {
        match value {
            Value::F64(value) => Ok(*value),
            Value::I64(value) => Ok(*value as f64),
            _ => Err(mismatch::<Self>(value, index)),
        }
    }
}

impl FromValue for f32 {
    const EXPECTED: &'static str = "f32";

    fn from_value(value: &Value, index: usize) -> Result<Self, DecodeError> {
        f64::from_value(value, index).map(|value| value as f32)
    }
}

impl FromValue for String {
    const EXPECTED: &'static str = "string";

    fn from_value(value: &Value, index: usize) -> Result<Self, DecodeError> {
        match value {
            Value::String(value) => Ok(value.clone()),
            _ => Err(mismatch::<Self>(value, index)),
        }
    }
}

impl FromValue for Vec<u8> {
    const EXPECTED: &'static str = "bytes";

    fn from_value(value: &Value, index: usize) -> Result<Self, DecodeError> {
        match value {
            Value::Bytes(value) => Ok(value.clone()),
            _ => Err(mismatch::<Self>(value, index)),
        }
    }
}

impl<T: FromValue> FromValue for crate::SqlArray<T> {
    const EXPECTED: &'static str = "array";

    fn from_value(value: &Value, index: usize) -> Result<Self, DecodeError> {
        let Value::Array(values) = value else {
            return Err(mismatch::<Self>(value, index));
        };
        values
            .iter()
            .enumerate()
            .map(|(element, value)| {
                T::from_value(value, index).map_err(|source| DecodeError::ArrayElement {
                    index,
                    element,
                    source: Box::new(source),
                })
            })
            .collect::<Result<Vec<_>, _>>()
            .map(crate::SqlArray)
    }
}

macro_rules! direct_decoder {
    ($feature:literal, $type:ty, $variant:ident, $expected:literal) => {
        #[cfg(feature = $feature)]
        impl FromValue for $type {
            const EXPECTED: &'static str = $expected;

            fn from_value(value: &Value, index: usize) -> Result<Self, DecodeError> {
                match value {
                    Value::$variant(value) => Ok(value.clone()),
                    _ => Err(mismatch::<Self>(value, index)),
                }
            }
        }
    };
}

direct_decoder!("uuid", uuid::Uuid, Uuid, "uuid");
direct_decoder!("json", serde_json::Value, Json, "json");
direct_decoder!("chrono", chrono::NaiveDate, Date, "date");
direct_decoder!("chrono", chrono::NaiveTime, Time, "time");
direct_decoder!("chrono", chrono::NaiveDateTime, DateTime, "timestamp");
direct_decoder!(
    "chrono",
    chrono::DateTime<chrono::Utc>,
    DateTimeUtc,
    "timestamp with time zone"
);
direct_decoder!("decimal", rust_decimal::Decimal, Decimal, "decimal");

impl<T: FromValue> FromValue for Option<T> {
    const EXPECTED: &'static str = T::EXPECTED;

    fn from_value(value: &Value, index: usize) -> Result<Self, DecodeError> {
        match value {
            Value::Null => Ok(None),
            value => T::from_value(value, index).map(Some),
        }
    }
}

macro_rules! tuple_rows {
    ($(($type:ident, $index:tt)),+ $(,)?) => {
        impl<$($type: FromValue),+> FromRow for ($($type,)+) {
            fn from_row(row: &impl Row) -> Result<Self, DecodeError> {
                Ok(($(decode_at::<$type>(row, $index)?,)+))
            }
        }
    };
}

tuple_rows!((A, 0), (B, 1));
tuple_rows!((A, 0), (B, 1), (C, 2));
tuple_rows!((A, 0), (B, 1), (C, 2), (D, 3));
tuple_rows!((A, 0), (B, 1), (C, 2), (D, 3), (E, 4));
tuple_rows!((A, 0), (B, 1), (C, 2), (D, 3), (E, 4), (F, 5));
tuple_rows!((A, 0), (B, 1), (C, 2), (D, 3), (E, 4), (F, 5), (G, 6));
tuple_rows!(
    (A, 0),
    (B, 1),
    (C, 2),
    (D, 3),
    (E, 4),
    (F, 5),
    (G, 6),
    (H, 7)
);