use keelson_core::expr::Expr;
use keelson_core::{ToValue, Value};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum Set<T> {
#[default]
Unset,
Null,
Value(T),
}
pub fn set<T>(value: impl Into<T>) -> Set<T> {
Set::Value(value.into())
}
pub fn null<T>() -> Set<T> {
Set::Null
}
impl<T> Set<T> {
pub fn is_unset(&self) -> bool {
matches!(self, Set::Unset)
}
}
impl<T: ToValue> Set<T> {
pub fn into_expr(self) -> Option<Expr> {
match self {
Set::Unset => None,
Set::Null => Some(Expr::Arg(Value::Null)),
Set::Value(v) => Some(Expr::Arg(v.to_value())),
}
}
pub fn push_into(
self,
column: &'static str,
columns: &mut Vec<&'static str>,
values: &mut Vec<Expr>,
) {
if let Some(e) = self.into_expr() {
columns.push(column);
values.push(e);
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn default_is_unset_so_struct_update_syntax_leaves_fields_out() {
#[derive(Default)]
struct Setter {
name: Set<String>,
email: Set<String>,
age: Set<i32>,
}
let s = Setter {
name: set("Stephen"),
email: null(),
..Default::default()
};
assert_eq!(s.name, Set::Value("Stephen".to_owned()));
assert_eq!(s.email, Set::Null);
assert_eq!(s.age, Set::Unset);
assert!(s.age.is_unset());
}
#[test]
fn the_three_states_produce_nothing_null_and_a_bound_value() {
assert!(Set::<i32>::Unset.into_expr().is_none());
assert!(matches!(
null::<i32>().into_expr(),
Some(Expr::Arg(Value::Null))
));
assert!(matches!(
set::<i32>(7).into_expr(),
Some(Expr::Arg(Value::I32(7)))
));
}
#[test]
fn push_into_skips_unset_fields_entirely() {
let mut cols = Vec::new();
let mut vals = Vec::new();
set::<String>("ada").push_into("name", &mut cols, &mut vals);
Set::<String>::Unset.push_into("email", &mut cols, &mut vals);
null::<i32>().push_into("age", &mut cols, &mut vals);
assert_eq!(cols, vec!["name", "age"]);
assert_eq!(vals.len(), 2, "the unset column binds nothing at all");
}
}