fuzzy-from-json-value 0.1.0

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

/// A fuzzy deserializer for `HashMap<u8, V>`.
///
/// - The JSON must be an object `{ "someKey": value, ... }`.
/// - Each key is a string that we'll parse as a fuzzy float/integer in `[0..255]`.
///   We clamp/truncate out-of-range or fractional keys and log an `error!`.
/// - If two keys collide after truncation/clamp, the later key overwrites the earlier entry.
/// - Values are deserialized as normal for type `V`.
pub fn fuzzy_map_u8<'de, D, V>(deserializer: D) -> Result<HashMap<u8, V>, D::Error>
where
    D: Deserializer<'de>,
    V: serde::de::DeserializeOwned,
{
    // We'll parse the entire object as a `HashMap<String, JsonValue>` first,
    // then convert each key fuzzily to u8, and parse each value as `V`.
    let raw_map = HashMap::<String, JsonValue>::deserialize(deserializer)?;
    let mut out = HashMap::with_capacity(raw_map.len());

    for (k_str, val) in raw_map {
        let k_u8 = fuzzy_key_to_u8(&k_str).map_err(de::Error::custom)?;
        // Now parse `val` as normal for type `V`:
        let parsed_v = match serde_json::from_value::<V>(val) {
            Ok(v) => v,
            Err(e) => {
                let msg = format!("Failed to parse value for key='{}' => {}", k_str, e);
                return Err(de::Error::custom(msg));
            }
        };
        // Insert, overwriting if a collision occurs:
        out.insert(k_u8, parsed_v);
    }

    Ok(out)
}

/// Convert a string key into a `u8` by fuzzily parsing it as a float or int,
/// then clamping to 0..=255, logging if we do so.
fn fuzzy_key_to_u8(key: &str) -> Result<u8, String> {
    // Try to parse it as a float. We'll do the same logic as our other fuzzy functions.
    let raw_f = match key.parse::<f64>() {
        Ok(f) => f,
        Err(e) => {
            let msg = format!("Cannot parse map key '{}' as float => {}", key, e);
            return Err(msg);
        }
    };

    if raw_f < 0.0 {
        warn!("Key '{}' => negative => clamping to 0", key);
        Ok(0)
    } else if raw_f > 255.0 {
        warn!("Key '{}' => >255 => clamping to 255", key);
        Ok(255)
    } else {
        let truncated = raw_f.trunc();
        if (raw_f - truncated).abs() > f64::EPSILON {
            warn!("Key '{}' => float => truncating {} to {}", key, raw_f, truncated);
        }
        Ok(truncated as u8)
    }
}