cratestack-sql 0.3.1

Rust-native schema-first framework for typed HTTP APIs, generated clients, and backend services.
Documentation
use cratestack_core::Value;

#[derive(Debug, Clone, PartialEq)]
pub enum SqlValue {
    Bool(bool),
    Int(i64),
    Float(f64),
    String(String),
    Bytes(Vec<u8>),
    Uuid(uuid::Uuid),
    DateTime(chrono::DateTime<chrono::Utc>),
    Json(Value),
    Decimal(cratestack_core::Decimal),
    NullBool,
    NullInt,
    NullFloat,
    NullString,
    NullBytes,
    NullUuid,
    NullDateTime,
    NullJson,
    NullDecimal,
}

#[derive(Debug, Clone, PartialEq)]
pub enum FilterValue {
    None,
    Single(SqlValue),
    Many(Vec<SqlValue>),
}

#[derive(Debug, Clone, PartialEq)]
pub struct SqlColumnValue {
    pub column: &'static str,
    pub value: SqlValue,
}

pub trait CreateModelInput<M> {
    fn sql_values(&self) -> Vec<SqlColumnValue>;
    /// Run schema-derived validators (`@length`, `@email`, `@regex`, ...) on
    /// the input. Default impl is a no-op for inputs without validators.
    fn validate(&self) -> Result<(), cratestack_core::CoolError> {
        Ok(())
    }
}

pub trait UpdateModelInput<M> {
    fn sql_values(&self) -> Vec<SqlColumnValue>;
    fn validate(&self) -> Result<(), cratestack_core::CoolError> {
        Ok(())
    }
}

/// Input shape for the upsert primitive — `INSERT … ON CONFLICT (<pk>) DO
/// UPDATE …`. `sql_values()` must include the primary-key column (so the
/// backend can target the conflict), and `primary_key_value()` exposes the
/// PK separately so the runtime can issue a `SELECT … FOR UPDATE` before
/// the upsert to drive `Created` vs. `Updated` event / audit semantics.
///
/// Only models with a client-supplied primary key (i.e. `@id` *without*
/// `@default(...)`) emit this trait impl; models with server-generated PKs
/// don't get an `.upsert()` builder at all. That's intentional — at v1 the
/// upsert primitive is PK-conflict only, and a server-generated PK can't be
/// upserted without the caller supplying one anyway.
pub trait UpsertModelInput<M>: Send {
    /// Full set of column→value bindings, *including* the primary key.
    fn sql_values(&self) -> Vec<SqlColumnValue>;

    /// The primary-key value, used to issue the `SELECT … FOR UPDATE` probe
    /// inside the upsert transaction. Must match the PK column carried in
    /// `sql_values()`.
    fn primary_key_value(&self) -> SqlValue;

    fn validate(&self) -> Result<(), cratestack_core::CoolError> {
        Ok(())
    }
}

pub trait IntoSqlValue {
    fn into_sql_value(self) -> SqlValue;
}

impl IntoSqlValue for bool {
    fn into_sql_value(self) -> SqlValue {
        SqlValue::Bool(self)
    }
}

impl IntoSqlValue for i64 {
    fn into_sql_value(self) -> SqlValue {
        SqlValue::Int(self)
    }
}

impl IntoSqlValue for f64 {
    fn into_sql_value(self) -> SqlValue {
        SqlValue::Float(self)
    }
}

impl IntoSqlValue for String {
    fn into_sql_value(self) -> SqlValue {
        SqlValue::String(self)
    }
}

impl IntoSqlValue for &str {
    fn into_sql_value(self) -> SqlValue {
        SqlValue::String(self.to_owned())
    }
}

impl IntoSqlValue for uuid::Uuid {
    fn into_sql_value(self) -> SqlValue {
        SqlValue::Uuid(self)
    }
}

impl IntoSqlValue for chrono::DateTime<chrono::Utc> {
    fn into_sql_value(self) -> SqlValue {
        SqlValue::DateTime(self)
    }
}

impl IntoSqlValue for Value {
    fn into_sql_value(self) -> SqlValue {
        SqlValue::Json(self)
    }
}

impl IntoSqlValue for cratestack_core::Decimal {
    fn into_sql_value(self) -> SqlValue {
        SqlValue::Decimal(self)
    }
}