use serde_json::Value;
use crate::{Diagnostics, Record, SkipReason, SkipReport};
pub(super) fn json_kind(value: &Value) -> &'static str {
match value {
Value::Null => "null",
Value::Bool(_) => "boolean",
Value::Number(_) => "number",
Value::String(_) => "string",
Value::Array(_) => "array",
Value::Object(_) => "object",
}
}
pub(super) fn exact_key(value: &Value) -> Option<String> {
match value {
Value::String(s) => Some(s.clone()),
Value::Bool(b) => Some(if *b { "true".into() } else { "false".into() }),
Value::Null => Some("null".into()),
Value::Number(n) => {
if let Some(i) = n.as_i64() {
Some(i.to_string())
} else {
n.as_u64().map(|u| u.to_string())
}
}
Value::Array(_) | Value::Object(_) => None,
}
}
pub(super) fn extract_numeric(value: &Value) -> Option<f64> {
match value {
Value::Bool(_) => None,
Value::Number(n) => n.as_f64(),
_ => None,
}
}
pub(super) fn resolve_numeric<D: Diagnostics + ?Sized>(
items: Vec<Record>,
field: &str,
diag: &mut D,
) -> Vec<(f64, Record)> {
let mut out: Vec<(f64, Record)> = Vec::with_capacity(items.len());
for (index, record) in items.into_iter().enumerate() {
let Ok(resolved) = crate::path::resolve(&record.raw, field) else {
continue;
};
match extract_numeric(resolved) {
Some(v) if v.is_finite() => out.push((v, record)),
_ => diag.record_skip(SkipReport {
record_index: index,
reason: SkipReason::WrongType {
field: field.to_string(),
kind: json_kind(resolved).to_string(),
},
}),
}
}
out
}
pub(super) fn score_range(group: &[Record]) -> (Option<f64>, Option<f64>) {
let mut min: Option<f64> = None;
let mut max: Option<f64> = None;
for r in group {
if let Some(s) = r.score {
min = Some(match min {
Some(m) if m < s => m,
_ => s,
});
max = Some(match max {
Some(m) if m > s => m,
_ => s,
});
}
}
(min, max)
}