#[derive(Debug, Clone)]
pub struct NumericPolicy {
pub(crate) bool_as: Option<fn(bool) -> f64>,
pub(crate) text_parse: bool,
pub(crate) none_value: Option<f64>,
}
impl Default for NumericPolicy {
fn default() -> Self {
Self {
bool_as: None,
text_parse: false,
none_value: None,
}
}
}
impl NumericPolicy {
pub fn strict() -> Self {
Self::default()
}
pub fn permissive() -> Self {
Self {
bool_as: Some(|b| if b { 1.0 } else { 0.0 }),
text_parse: true,
none_value: Some(0.0),
}
}
pub fn allow_bool(mut self) -> Self {
self.bool_as = Some(|b| if b { 1.0 } else { 0.0 });
self
}
pub fn allow_text_parse(mut self) -> Self {
self.text_parse = true;
self
}
pub fn none_as(mut self, value: f64) -> Self {
self.none_value = Some(value);
self
}
pub fn none_as_nan(self) -> Self {
self.none_as(f64::NAN)
}
pub(crate) fn coerce(&self, elem: &super::Element, position: usize) -> Result<f64, String> {
use super::Element;
match elem {
Element::Float(v) => Ok(*v),
Element::Int(i) => Ok(*i as f64),
Element::Bool(b) => match self.bool_as {
Some(f) => Ok(f(*b)),
None => Err(format!(
"element at position {position} is Bool({b}) and cannot be coerced \
to f64 under the current NumericPolicy; use allow_bool() or \
fill_none / explicit conversion first"
)),
},
Element::Text(t) => {
if self.text_parse {
t.parse::<f64>().map_err(|_| {
format!(
"element at position {position} is Text({t:?}) and could \
not be parsed as f64 under allow_text_parse()"
)
})
} else {
Err(format!(
"element at position {position} is Text({t:?}) and cannot be \
coerced to f64 under the current NumericPolicy; use \
allow_text_parse() or fill_none first"
))
}
}
Element::None => match self.none_value {
Some(v) => Ok(v),
None => Err(format!(
"element at position {position} is None and cannot be coerced to \
f64 under the current NumericPolicy; use none_as(value) or \
fill_none first"
)),
},
}
}
}