use std::fmt::{Display, Write};
#[macro_export]
#[cfg(feature = "macros")]
macro_rules! qb_where {
($item:ty, $first:ident$(.$nested:ident)*, $op:expr) => {
{
const _: () = {
fn _type_check(v: $item) {
let _ = v.$first$(.unwrap().$nested)*;
}
};
unsafe {
$crate::paste! {
TypedWhereClause::<$item>::new(
stringify!([<$first:camel>]$(.[<$nested:camel>])*),
$op,
)
}
}
}
};
}
#[derive(Debug, PartialEq, Clone)]
pub struct TypedWhereClause<QB> {
pub field: &'static str,
pub operator: Operator,
pub values: Vec<String>,
_phantom: std::marker::PhantomData<QB>,
}
impl<QB> TypedWhereClause<QB> {
#[must_use]
pub unsafe fn new(field: &'static str, operator: Operator) -> Self {
Self {
field,
operator,
values: Vec::new(),
_phantom: std::marker::PhantomData,
}
}
#[must_use]
pub fn add_value<T: Display>(mut self, value: T) -> Self {
self.values.push(value.to_string());
self
}
#[must_use]
pub fn add_values<I, T>(mut self, values: I) -> Self
where
I: Iterator<Item = T>,
T: Display,
{
self.values.extend(values.map(|v| v.to_string()));
self
}
}
impl<T> From<TypedWhereClause<T>> for WhereClause {
fn from(val: TypedWhereClause<T>) -> Self {
WhereClause {
field: val.field,
operator: val.operator,
values: val.values,
}
}
}
#[cfg(test)]
mod typed_where_tests {
use super::*;
use quickbooks_types::Customer;
#[test]
fn test_typed_where_clause_creation() {
#[cfg(feature = "macros")]
let clause = qb_where!(Customer, display_name, Operator::Like);
#[cfg(not(feature = "macros"))]
let clause: TypedWhereClause<Customer> =
unsafe { TypedWhereClause::new("DisplayName", Operator::Like) };
assert_eq!(clause.field, "DisplayName");
assert_eq!(clause.operator, Operator::Like);
}
#[test]
fn test_nested_typed_where_clause_creation() {
#[cfg(feature = "macros")]
let clause = qb_where!(Customer, primary_email_addr.address, Operator::Equal);
#[cfg(not(feature = "macros"))]
let clause: TypedWhereClause<Customer> =
unsafe { TypedWhereClause::new("PrimaryEmailAddr.Address", Operator::Equal) };
assert_eq!(clause.field, "PrimaryEmailAddr.Address");
assert_eq!(clause.operator, Operator::Equal);
}
}
#[derive(Debug, PartialEq, Clone)]
pub struct WhereClause {
pub field: &'static str,
pub operator: Operator,
pub values: Vec<String>,
}
impl WhereClause {
#[must_use]
pub fn new(field: &'static str, operator: Operator) -> Self {
Self {
field,
operator,
values: Vec::new(),
}
}
#[must_use]
pub fn add_value<T: Display>(mut self, value: T) -> Self {
self.values.push(value.to_string());
self
}
#[must_use]
pub fn add_values<I, T>(mut self, values: I) -> Self
where
I: Iterator<Item = T>,
T: Display,
{
self.values.extend(values.map(|v| v.to_string()));
self
}
}
impl WhereClause {
pub fn extend_query(&self, query: &mut String) {
let op_str = match self.operator {
Operator::In => "IN",
Operator::Like => "LIKE",
Operator::Equal => "=",
Operator::Less => "<",
Operator::Greater => ">",
Operator::LessEqual => "<=",
Operator::GreaterEqual => ">=",
};
if self.operator == Operator::In {
write!(query, " {} IN (", self.field).unwrap();
for (i, value) in self.values.iter().enumerate() {
if i > 0 {
query.push_str(", ");
}
write!(query, "'{value}'").unwrap();
}
query.push(')');
} else {
write!(query, " {} {} '{}'", self.field, op_str, self.values[0]).unwrap();
}
}
}
#[derive(Debug, PartialEq, Clone)]
pub enum Operator {
In,
Like,
Equal,
Less,
Greater,
LessEqual,
GreaterEqual,
}