use proc_macro2::Ident;
use quote::quote;
use crate::config::ConstraintDef;
pub fn build_validation_expr(
constraint_def: &ConstraintDef,
float_type: &Ident,
) -> proc_macro2::TokenStream {
let mut checks = Vec::new();
checks.push(quote! { value.is_finite() });
if let Some(lower) = constraint_def.bounds.lower {
let lower_check = build_bound_check(lower, true, constraint_def.excludes_zero, float_type);
checks.push(lower_check);
}
if let Some(upper) = constraint_def.bounds.upper {
let upper_check = build_bound_check(upper, false, constraint_def.excludes_zero, float_type);
checks.push(upper_check);
}
if constraint_def.excludes_zero && needs_explicit_zero_check(constraint_def) {
checks.push(quote! { value != 0.0 });
}
quote! {
#(#checks)&&*
}
}
fn build_bound_check(
bound: f64,
is_lower: bool,
excludes_zero: bool,
float_type: &Ident,
) -> proc_macro2::TokenStream {
let is_f32 = *float_type == "f32";
let use_strict = excludes_zero && bound == 0.0;
let bound_value = if is_f32 {
quote! { (#bound as f64) }
} else {
quote! { #bound }
};
let value_expr = if is_f32 {
quote! { (value as f64) }
} else {
quote! { value }
};
if is_lower {
if use_strict {
quote! { #value_expr > #bound_value }
} else {
quote! { #value_expr >= #bound_value }
}
} else if use_strict {
quote! { #value_expr < #bound_value }
} else {
quote! { #value_expr <= #bound_value }
}
}
fn needs_explicit_zero_check(constraint_def: &ConstraintDef) -> bool {
let lower_excludes_zero =
constraint_def.bounds.lower == Some(0.0) && constraint_def.excludes_zero;
let upper_excludes_zero =
constraint_def.bounds.upper == Some(0.0) && constraint_def.excludes_zero;
!lower_excludes_zero && !upper_excludes_zero
}