tier 0.1.17

Rust configuration library for layered TOML, env, and CLI settings
Documentation
use std::str::FromStr;

use serde::de::{Visitor, value::Error as ValueDeError};
use serde_json::Value;

use super::model::CoercingDeserializer;

pub(super) trait FiniteFloat: FromStr + Copy {
    fn is_finite(self) -> bool;
}

impl FiniteFloat for f32 {
    fn is_finite(self) -> bool {
        f32::is_finite(self)
    }
}

impl FiniteFloat for f64 {
    fn is_finite(self) -> bool {
        f64::is_finite(self)
    }
}

pub(super) fn parse_bool(raw: &str) -> Option<bool> {
    let raw = raw.trim();
    if raw.eq_ignore_ascii_case("true")
        || raw.eq_ignore_ascii_case("yes")
        || raw.eq_ignore_ascii_case("y")
        || raw.eq_ignore_ascii_case("on")
        || raw == "1"
    {
        return Some(true);
    }

    if raw.eq_ignore_ascii_case("false")
        || raw.eq_ignore_ascii_case("no")
        || raw.eq_ignore_ascii_case("n")
        || raw.eq_ignore_ascii_case("off")
        || raw == "0"
    {
        return Some(false);
    }

    None
}

pub(super) fn deserialize_integer<'de, 'a, V, T, F>(
    de: &CoercingDeserializer<'a>,
    visitor: V,
    visit: F,
) -> Result<V::Value, ValueDeError>
where
    V: Visitor<'de>,
    T: FromStr,
    F: FnOnce(V, T) -> Result<V::Value, ValueDeError>,
{
    deserialize_number(de, visitor, visit)
}

pub(super) fn deserialize_float<'de, 'a, V, T, F>(
    de: &CoercingDeserializer<'a>,
    visitor: V,
    visit: F,
) -> Result<V::Value, ValueDeError>
where
    V: Visitor<'de>,
    T: FiniteFloat,
    F: FnOnce(V, T) -> Result<V::Value, ValueDeError>,
{
    if let Some(raw) = de.coercible_string() {
        let value = parse_finite_float(raw.trim().parse::<T>(), || {
            de.invalid_string_type(raw, &visitor)
        })?;
        return visit(visitor, value);
    }

    match de.value {
        Value::Number(number) => {
            let value = parse_finite_float(number.to_string().parse::<T>(), || {
                de.invalid_type(&visitor)
            })?;
            visit(visitor, value)
        }
        _ => Err(de.invalid_type(&visitor)),
    }
}

fn parse_finite_float<T, E>(
    parsed: Result<T, E>,
    error: impl Fn() -> ValueDeError,
) -> Result<T, ValueDeError>
where
    T: FiniteFloat,
{
    let value = parsed.map_err(|_| error())?;
    if value.is_finite() {
        Ok(value)
    } else {
        Err(error())
    }
}

fn deserialize_number<'de, 'a, V, T, F>(
    de: &CoercingDeserializer<'a>,
    visitor: V,
    visit: F,
) -> Result<V::Value, ValueDeError>
where
    V: Visitor<'de>,
    T: FromStr,
    F: FnOnce(V, T) -> Result<V::Value, ValueDeError>,
{
    if let Some(raw) = de.coercible_string() {
        return raw
            .trim()
            .parse::<T>()
            .map_err(|_| de.invalid_string_type(raw, &visitor))
            .and_then(|value| visit(visitor, value));
    }

    match de.value {
        Value::Number(number) => number
            .to_string()
            .parse::<T>()
            .map_err(|_| de.invalid_type(&visitor))
            .and_then(|value| visit(visitor, value)),
        _ => Err(de.invalid_type(&visitor)),
    }
}