use crate::value::{FlexValue, Source};
pub const DEPTH_SCORE_MULTIPLIER: u32 = 10;
#[inline]
pub fn score_candidate(candidate: &FlexValue) -> u32 {
score_candidate_recursive(candidate, false)
}
pub fn score_candidate_recursive(candidate: &FlexValue, use_recursive: bool) -> u32 {
let mut score = source_base_score(&candidate.source);
let transformation_score: u32 = candidate
.transformations()
.iter()
.map(|t| t.penalty())
.sum();
if use_recursive && candidate.max_transformation_depth() > 0 {
let multiplier = DEPTH_SCORE_MULTIPLIER.pow(candidate.max_transformation_depth() as u32);
score += transformation_score * multiplier;
} else {
score += transformation_score;
}
let confidence_penalty = ((1.0 - candidate.confidence()) * 100.0) as u32;
score += confidence_penalty;
score
}
#[inline(always)]
fn source_base_score(source: &Source) -> u32 {
match source {
Source::Direct => 0,
Source::Markdown { .. } => 10,
Source::Yaml => 15,
Source::Fixed { fixes } => {
20 + fixes.iter().map(|f| f.penalty()).sum::<u32>()
}
Source::MultiJsonArray => 25, Source::MultiJson { .. } => 30,
Source::Heuristic { .. } => 50,
}
}
pub fn rank_candidates(mut candidates: Vec<FlexValue>) -> Vec<FlexValue> {
candidates.sort_by_cached_key(score_candidate);
candidates
}
pub fn best_candidate(candidates: Vec<FlexValue>) -> Option<FlexValue> {
if candidates.is_empty() {
return None;
}
let ranked = rank_candidates(candidates);
ranked.into_iter().next()
}
#[cfg(test)]
mod tests {
use serde_json::json;
use super::*;
use crate::value::{JsonFix, Transformation};
#[test]
fn test_score_direct_source() {
let value = FlexValue::new(json!(1), Source::Direct);
assert_eq!(score_candidate(&value), 0);
}
#[test]
fn test_score_markdown_source() {
let value = FlexValue::new(json!(1), Source::Markdown { lang: None });
assert_eq!(score_candidate(&value), 10);
}
#[test]
fn test_score_fixed_source() {
let value = FlexValue::from_fixed_json(
json!(1),
vec![JsonFix::TrailingCommas, JsonFix::SingleQuotes],
);
assert_eq!(score_candidate(&value), 33);
}
#[test]
fn test_score_with_transformations() {
let mut value = FlexValue::new(json!(42), Source::Direct);
value.add_transformation(Transformation::StringToNumber {
original: "42".to_string(),
});
assert_eq!(score_candidate(&value), 7);
}
#[test]
fn test_rank_candidates() {
let candidates = vec![
FlexValue::from_fixed_json(json!(1), vec![JsonFix::TrailingCommas]),
FlexValue::new(json!(2), Source::Direct),
FlexValue::new(json!(3), Source::Markdown { lang: None }),
];
let ranked = rank_candidates(candidates);
assert_eq!(ranked[0].value, json!(2));
assert_eq!(ranked[1].value, json!(3));
assert_eq!(ranked[2].value, json!(1));
}
#[test]
fn test_best_candidate() {
let candidates = vec![
FlexValue::from_fixed_json(json!(1), vec![]),
FlexValue::new(json!(2), Source::Direct),
FlexValue::new(json!(3), Source::Markdown { lang: None }),
];
let best = best_candidate(candidates).unwrap();
assert_eq!(best.value, json!(2));
}
#[test]
fn test_best_candidate_empty() {
let candidates = vec![];
let best = best_candidate(candidates);
assert!(best.is_none());
}
#[test]
fn test_source_base_score() {
assert_eq!(source_base_score(&Source::Direct), 0);
assert_eq!(source_base_score(&Source::Markdown { lang: None }), 10);
assert_eq!(
source_base_score(&Source::Fixed {
fixes: vec![JsonFix::TrailingCommas] }),
21 );
assert_eq!(source_base_score(&Source::MultiJsonArray), 25);
assert_eq!(source_base_score(&Source::MultiJson { index: 0 }), 30);
assert_eq!(
source_base_score(&Source::Heuristic {
pattern: "test".to_string()
}),
50
);
}
#[test]
fn test_multiple_transformations() {
let mut value = FlexValue::new(json!(42), Source::Direct);
value.add_transformation(Transformation::StringToNumber {
original: "42".to_string(),
});
value.add_transformation(Transformation::SingleToArray);
let score = score_candidate(&value);
assert!((7..=20).contains(&score)); }
#[test]
fn test_rank_preserves_all_candidates() {
let candidates = vec![
FlexValue::new(json!(1), Source::Direct),
FlexValue::new(json!(2), Source::Direct),
FlexValue::new(json!(3), Source::Direct),
];
let count = candidates.len();
let ranked = rank_candidates(candidates);
assert_eq!(ranked.len(), count);
}
}