macro_rules! build_query {
($row:expr, $base:expr, $order_by:expr, [$( $field:ident $(=> $map:expr)? ),* $(,)?]) => {{
let mut conditions = Vec::new();
let mut params: Vec<Box<dyn crate::io::database::backend::ToSql>> = Vec::new();
$(
if let Some(value) = build_query!(@value $row.$field.clone() $(, $map)?) {
conditions.push(concat!(stringify!($field), " = ?"));
params.push(Box::new(value));
}
)*
if conditions.is_empty() {
match $order_by {
| Some(order_by) => (format!("{} ORDER BY {}", $base, order_by), vec![]),
| None => ($base.to_string(), vec![]),
}
} else {
let where_clause = conditions.join(" AND ");
match $order_by {
| Some(order_by) => (format!("{} WHERE {} ORDER BY {}", $base, where_clause, order_by), params),
| None => (format!("{} WHERE {}", $base, where_clause), params),
}
}
}};
(@value $value:expr) => {
$value
};
(@value $value:expr, $map:expr) => {
$value.map($map)
};
}
macro_rules! define_required_fn {
(1, $fn_name:ident, $A:ident, $a:ident) => {
fn $fn_name<$A>($a: Option<$A>) -> crate::io::Result<($A,)> {
match $a.ok_or_else(|| color_eyre::eyre::eyre!("Failed to insert: {} is required", stringify!($a))) {
| Ok($a) => Ok(($a,)),
| Err(why) => Err(why),
}
}
};
(3, $fn_name:ident, $A:ident, $a:ident, $B:ident, $b:ident, $C:ident, $c:ident) => {
fn $fn_name<$A, $B, $C>($a: Option<$A>, $b: Option<$B>, $c: Option<$C>) -> crate::io::Result<($A, $B, $C)> {
match (
$a.ok_or_else(|| color_eyre::eyre::eyre!("Failed to insert: {} is required", stringify!($a))),
$b.ok_or_else(|| color_eyre::eyre::eyre!("Failed to insert: {} is required", stringify!($b))),
$c.ok_or_else(|| color_eyre::eyre::eyre!("Failed to insert: {} is required", stringify!($c))),
) {
| (Ok($a), Ok($b), Ok($c)) => Ok(($a, $b, $c)),
| (Err(why), _, _) => Err(why),
| (_, Err(why), _) => Err(why),
| (_, _, Err(why)) => Err(why),
}
}
};
(4, $fn_name:ident, $A:ident, $a:ident, $B:ident, $b:ident, $C:ident, $c:ident, $D:ident, $d:ident) => {
fn $fn_name<$A, $B, $C, $D>($a: Option<$A>, $b: Option<$B>, $c: Option<$C>, $d: Option<$D>) -> crate::io::Result<($A, $B, $C, $D)> {
match (
$a.ok_or_else(|| color_eyre::eyre::eyre!("Failed to insert: {} is required", stringify!($a))),
$b.ok_or_else(|| color_eyre::eyre::eyre!("Failed to insert: {} is required", stringify!($b))),
$c.ok_or_else(|| color_eyre::eyre::eyre!("Failed to insert: {} is required", stringify!($c))),
$d.ok_or_else(|| color_eyre::eyre::eyre!("Failed to insert: {} is required", stringify!($d))),
) {
| (Ok($a), Ok($b), Ok($c), Ok($d)) => Ok(($a, $b, $c, $d)),
| (Err(why), _, _, _) => Err(why),
| (_, Err(why), _, _) => Err(why),
| (_, _, Err(why), _) => Err(why),
| (_, _, _, Err(why)) => Err(why),
}
}
};
(5, $fn_name:ident, $A:ident, $a:ident, $B:ident, $b:ident, $C:ident, $c:ident, $D:ident, $d:ident, $E:ident, $e:ident) => {
fn $fn_name<$A, $B, $C, $D, $E>(
$a: Option<$A>,
$b: Option<$B>,
$c: Option<$C>,
$d: Option<$D>,
$e: Option<$E>,
) -> crate::io::Result<($A, $B, $C, $D, $E)> {
match (
$a.ok_or_else(|| color_eyre::eyre::eyre!("Failed to insert: {} is required", stringify!($a))),
$b.ok_or_else(|| color_eyre::eyre::eyre!("Failed to insert: {} is required", stringify!($b))),
$c.ok_or_else(|| color_eyre::eyre::eyre!("Failed to insert: {} is required", stringify!($c))),
$d.ok_or_else(|| color_eyre::eyre::eyre!("Failed to insert: {} is required", stringify!($d))),
$e.ok_or_else(|| color_eyre::eyre::eyre!("Failed to insert: {} is required", stringify!($e))),
) {
| (Ok($a), Ok($b), Ok($c), Ok($d), Ok($e)) => Ok(($a, $b, $c, $d, $e)),
| (Err(why), _, _, _, _) => Err(why),
| (_, Err(why), _, _, _) => Err(why),
| (_, _, Err(why), _, _) => Err(why),
| (_, _, _, Err(why), _) => Err(why),
| (_, _, _, _, Err(why)) => Err(why),
}
}
};
}
pub(crate) use build_query;
pub(crate) use define_required_fn;
#[cfg(test)]
mod tests {
#![allow(
clippy::unwrap_used,
clippy::expect_used,
clippy::panic,
clippy::indexing_slicing,
clippy::arithmetic_side_effects
)]
define_required_fn!(1, required1_test, A, a);
define_required_fn!(3, required3_test, A, a, B, b, C, c);
define_required_fn!(4, required4_test, A, a, B, b, C, c, D, d);
#[test]
fn required1_returns_value_tuple() {
let result = required1_test::<i32>(Some(7));
assert_eq!(result.ok(), Some((7,)));
}
#[test]
fn required1_returns_error() {
let result = required1_test::<i32>(None);
let message = result.err().map(|why| why.to_string());
assert_eq!(message.as_deref(), Some("Failed to insert: a is required"));
}
#[test]
fn required3_returns_value_tuple() {
let result = required3_test::<i32, i32, i32>(Some(1), Some(2), Some(3));
assert_eq!(result.ok(), Some((1, 2, 3)));
}
#[test]
fn required3_returns_first_error() {
let result = required3_test::<i32, i32, i32>(None, None, None);
let message = result.err().map(|why| why.to_string());
assert_eq!(message.as_deref(), Some("Failed to insert: a is required"));
}
#[test]
fn required4_returns_value_tuple() {
let result = required4_test::<i32, i32, i32, i32>(Some(1), Some(2), Some(3), Some(4));
assert_eq!(result.ok(), Some((1, 2, 3, 4)));
}
#[test]
fn required4_returns_first_error() {
let result = required4_test::<i32, i32, i32, i32>(Some(1), None, None, None);
let message = result.err().map(|why| why.to_string());
assert_eq!(message.as_deref(), Some("Failed to insert: b is required"));
}
}