fuzzy-from-json-value 0.1.0

todo: write a description here
Documentation
// ---------------- [ File: fuzzy-from-json-value/src/fuzzy_option_u8.rs ]
crate::ix!();

/// A fuzzy deserialization helper for `Option<u8>`.
/// 
/// - If the JSON is `null` or missing -> `None`.
/// - If the JSON is an integer or float -> clamp to [0..255], logging any
///   truncation/clamping in the process, then return `Some(u8)`.
/// - If the JSON is something else (string, bool, object, array) -> error out.
pub fn fuzzy_option_u8<'de, D>(deserializer: D) -> Result<Option<u8>, D::Error>
where
    D: Deserializer<'de>,
{
    let val: Option<JsonValue> = Option::deserialize(deserializer)?;
    match val {
        None => {
            // If JSON is null, or the field is missing (due to `Option<_>`),
            // then we yield None
            Ok(None)
        }
        Some(json_val) => {
            // If the JSON is present and not null => parse it fuzzily
            match json_val {
                JsonValue::Number(num) => {
                    // 1) as_u64 => if it fits in 64 bits
                    if let Some(u) = num.as_u64() {
                        if u <= 255 {
                            Ok(Some(u as u8))
                        } else {
                            warn!("JsonValue {} out of range for u8 => clamping to 255", u);
                            Ok(Some(255))
                        }
                    }
                    // 2) as_f64 => handle float
                    else if let Some(f) = num.as_f64() {
                        if f < 0.0 {
                            warn!("Float {} < 0 => clamping to 0", f);
                            Ok(Some(0))
                        } else if f > 255.0 {
                            warn!("Float {} > 255 => clamping to 255", f);
                            Ok(Some(255))
                        } else {
                            // Truncate fractional
                            let truncated = f.trunc();
                            if (f - truncated).abs() > f64::EPSILON {
                                warn!("Truncating float {} => {}", f, truncated);
                            }
                            Ok(Some(truncated as u8))
                        }
                    } else {
                        // The JSON number was not convertible to f64 or u64
                        let msg = format!("Number not convertible to u64/f64 => {:?}", num);
                        Err(de::Error::custom(msg))
                    }
                }
                JsonValue::Null => {
                    // If we somehow got `JsonValue::Null` inside the Some(...) => that's also None
                    Ok(None)
                }
                other => {
                    // e.g. string, bool, object => error
                    let msg = format!("Expected numeric or null, got {:?}", other);
                    Err(de::Error::custom(msg))
                }
            }
        }
    }
}

/// Same idea, but for an Option<u8> with your existing fuzzy logic:
pub fn fuzzy_option_u8_from_value(val: &JsonValue) -> Result<Option<u8>, String> {
    match val {
        // If null => None
        JsonValue::Null => Ok(None),

        // If a numeric => clamp/truncate => Some(...)
        JsonValue::Number(num) => {
            if let Some(u) = num.as_u64() {
                if u > 255 {
                    warn!("JsonValue {} out of range => clamp to 255", u);
                    Ok(Some(255))
                } else {
                    Ok(Some(u as u8))
                }
            } else if let Some(f) = num.as_f64() {
                if f < 0.0 {
                    warn!("Float {} < 0 => clamp to 0", f);
                    Ok(Some(0))
                } else if f > 255.0 {
                    warn!("Float {} > 255 => clamp to 255", f);
                    Ok(Some(255))
                } else {
                    let truncated = f.trunc();
                    if (f - truncated).abs() > f64::EPSILON {
                        warn!("Truncating float {} => {}", f, truncated);
                    }
                    Ok(Some(truncated as u8))
                }
            } else {
                Err(format!("Number not convertible to f64 or u64 => {:?}", num))
            }
        }

        // If e.g. string/bool/object => error or interpret differently, your choice
        other => Err(format!("Expected numeric or null for fuzzy_option_u8, got {:?}", other)),
    }
}