use crate::error::repair::{RepairAction, RepairType};
use std::collections::VecDeque;
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct RepairConfidence(f32);
impl RepairConfidence {
pub fn new(value: f32) -> Self {
RepairConfidence(value.clamp(0.0, 1.0))
}
#[inline(always)]
pub fn value(&self) -> f32 {
self.0
}
#[inline(always)]
pub fn is_above(&self, threshold: f32) -> bool {
self.0 >= threshold
}
pub fn level(&self) -> &'static str {
match self.0 {
x if x >= 0.9 => "high",
x if x >= 0.7 => "medium",
x if x >= 0.5 => "low",
_ => "very low",
}
}
}
#[derive(Debug, Clone)]
pub struct RepairStrategy {
pub action: RepairAction,
pub confidence: RepairConfidence,
pub alternatives: Vec<RepairStrategy>,
}
#[derive(Debug, Clone)]
pub struct RepairPreview {
pub original: String,
pub repaired: String,
pub repairs: Vec<RepairStrategy>,
pub overall_confidence: RepairConfidence,
}
#[derive(Debug, Clone)]
pub struct RepairHistory {
max_entries: usize,
entries: VecDeque<RepairHistoryEntry>,
}
#[derive(Debug, Clone)]
pub struct RepairHistoryEntry {
pub timestamp: std::time::SystemTime,
pub original: String,
pub repaired: String,
pub repairs: Vec<RepairAction>,
pub success: bool,
}
pub struct AdvancedJsonRepairer {
#[allow(dead_code)]
max_repairs: usize,
confidence_threshold: f32,
preview_mode: bool,
history: RepairHistory,
type_coercion_rules: TypeCoercionRules,
}
#[derive(Debug, Clone)]
pub struct TypeCoercionRules {
pub unquote_numbers: bool,
pub fix_literals: bool,
pub fix_quotes: bool,
pub quote_keys: bool,
}
impl Default for TypeCoercionRules {
fn default() -> Self {
Self {
unquote_numbers: true,
fix_literals: true,
fix_quotes: true,
quote_keys: true,
}
}
}
impl AdvancedJsonRepairer {
pub fn new() -> Self {
Self {
max_repairs: 50,
confidence_threshold: 0.7,
preview_mode: false,
history: RepairHistory::new(100),
type_coercion_rules: TypeCoercionRules::default(),
}
}
pub fn with_confidence_threshold(mut self, threshold: f32) -> Self {
self.confidence_threshold = threshold;
self
}
pub fn with_preview_mode(mut self, enabled: bool) -> Self {
self.preview_mode = enabled;
self
}
pub fn with_type_coercion_rules(mut self, rules: TypeCoercionRules) -> Self {
self.type_coercion_rules = rules;
self
}
pub fn repair(&mut self, input: &str) -> Result<(String, Vec<RepairStrategy>), String> {
let strategies = self.analyze_and_plan_repairs(input)?;
if self.preview_mode {
return Ok((input.to_string(), strategies));
}
let mut repaired = input.to_string();
let mut applied_strategies = Vec::new();
let mut applicable_strategies: Vec<RepairStrategy> = strategies
.into_iter()
.filter(|s| s.confidence.is_above(self.confidence_threshold))
.collect();
applicable_strategies.sort_by(|a, b| b.action.position.cmp(&a.action.position));
for strategy in applicable_strategies {
repaired = self.apply_repair(&repaired, &strategy.action)?;
applied_strategies.push(strategy);
}
self.history.add_entry(RepairHistoryEntry {
timestamp: std::time::SystemTime::now(),
original: input.to_string(),
repaired: repaired.clone(),
repairs: applied_strategies
.iter()
.map(|s| s.action.clone())
.collect(),
success: true,
});
Ok((repaired, applied_strategies))
}
pub fn preview_repairs(&self, input: &str) -> Result<RepairPreview, String> {
let strategies = self.analyze_and_plan_repairs(input)?;
let mut preview_result = input.to_string();
for strategy in &strategies {
if strategy.confidence.is_above(self.confidence_threshold) {
preview_result = self.apply_repair(&preview_result, &strategy.action)?;
}
}
let overall_confidence = self.calculate_overall_confidence(&strategies);
Ok(RepairPreview {
original: input.to_string(),
repaired: preview_result,
repairs: strategies,
overall_confidence,
})
}
fn analyze_and_plan_repairs(&self, input: &str) -> Result<Vec<RepairStrategy>, String> {
let mut strategies = Vec::new();
if let Some(quote_strategies) = self.analyze_quote_issues(input) {
strategies.extend(quote_strategies);
}
if let Some(type_strategies) = self.analyze_type_coercion(input) {
strategies.extend(type_strategies);
}
if let Some(comma_strategies) = self.analyze_missing_commas(input) {
strategies.extend(comma_strategies);
}
if let Some(key_strategies) = self.analyze_unquoted_keys(input) {
strategies.extend(key_strategies);
}
strategies.sort_by(|a, b| {
b.confidence
.value()
.partial_cmp(&a.confidence.value())
.unwrap()
});
Ok(strategies)
}
fn analyze_quote_issues(&self, input: &str) -> Option<Vec<RepairStrategy>> {
let mut strategies = Vec::new();
let chars: Vec<char> = input.chars().collect();
let mut i = 0;
while i < chars.len() {
if chars[i] == '\'' && self.type_coercion_rules.fix_quotes {
let start = i;
i += 1;
while i < chars.len() && chars[i] != '\'' {
if chars[i] == '\\' {
i += 2; } else {
i += 1;
}
}
if i < chars.len() {
strategies.push(RepairStrategy {
action: RepairAction {
action_type: RepairType::ReplaceQuotes,
position: start,
original: "'".to_string(),
replacement: "\"".to_string(),
description: "Replace single quotes with double quotes".to_string(),
},
confidence: RepairConfidence::new(0.9),
alternatives: vec![],
});
strategies.push(RepairStrategy {
action: RepairAction {
action_type: RepairType::ReplaceQuotes,
position: i,
original: "'".to_string(),
replacement: "\"".to_string(),
description: "Replace closing single quote with double quote"
.to_string(),
},
confidence: RepairConfidence::new(0.9),
alternatives: vec![],
});
}
}
i += 1;
}
if strategies.is_empty() {
None
} else {
Some(strategies)
}
}
fn analyze_type_coercion(&self, input: &str) -> Option<Vec<RepairStrategy>> {
let mut strategies = Vec::new();
if self.type_coercion_rules.unquote_numbers {
let quoted_number_pattern =
regex::Regex::new(r#""(-?\d+\.?\d*(?:[eE][+-]?\d+)?)""#).unwrap();
for capture in quoted_number_pattern.captures_iter(input) {
if let Some(number_match) = capture.get(1) {
let number_str = number_match.as_str();
if number_str.parse::<f64>().is_ok() {
strategies.push(RepairStrategy {
action: RepairAction {
action_type: RepairType::TypeCoercion,
position: capture.get(0).unwrap().start(),
original: capture.get(0).unwrap().as_str().to_string(),
replacement: number_str.to_string(),
description: format!(
"Convert quoted number \"{number_str}\" to unquoted"
),
},
confidence: RepairConfidence::new(0.8),
alternatives: vec![],
});
}
}
}
}
if strategies.is_empty() {
None
} else {
Some(strategies)
}
}
fn analyze_missing_commas(&self, input: &str) -> Option<Vec<RepairStrategy>> {
let mut strategies = Vec::new();
let pattern = regex::Regex::new(r#"([}\]])(\s*)"#).unwrap();
for capture in pattern.captures_iter(input) {
if let Some(match_) = capture.get(0) {
let next_char_idx = match_.end();
if next_char_idx < input.len() {
let remaining = &input[next_char_idx..];
if remaining.trim_start().starts_with('"')
|| remaining.trim_start().starts_with('{')
|| remaining.trim_start().starts_with('[')
{
strategies.push(RepairStrategy {
action: RepairAction {
action_type: RepairType::InsertComma,
position: match_.end(),
original: String::new(),
replacement: ",".to_string(),
description: "Insert missing comma between elements".to_string(),
},
confidence: RepairConfidence::new(0.85),
alternatives: vec![],
});
}
}
}
}
if strategies.is_empty() {
None
} else {
Some(strategies)
}
}
fn analyze_unquoted_keys(&self, input: &str) -> Option<Vec<RepairStrategy>> {
if !self.type_coercion_rules.quote_keys {
return None;
}
let mut strategies = Vec::new();
let pattern = regex::Regex::new(r#"(\w+)\s*:"#).unwrap();
for capture in pattern.captures_iter(input) {
if let Some(key_match) = capture.get(1) {
let start = key_match.start();
if start > 0 {
let prev_char = input.chars().nth(start - 1);
if prev_char == Some('"') || prev_char == Some('\'') {
continue; }
}
strategies.push(RepairStrategy {
action: RepairAction {
action_type: RepairType::QuoteKey,
position: key_match.start(),
original: key_match.as_str().to_string(),
replacement: format!("\"{}\"", key_match.as_str()),
description: format!("Add quotes to key '{}'", key_match.as_str()),
},
confidence: RepairConfidence::new(0.75),
alternatives: vec![],
});
}
}
if strategies.is_empty() {
None
} else {
Some(strategies)
}
}
fn apply_repair(&self, input: &str, action: &RepairAction) -> Result<String, String> {
match action.action_type {
RepairType::InsertText | RepairType::InsertComma => {
if action.position > input.len() {
return Err(format!(
"Insert position {} is out of bounds for input of length {}",
action.position,
input.len()
));
}
let mut result = String::new();
result.push_str(&input[..action.position]);
result.push_str(&action.replacement);
result.push_str(&input[action.position..]);
Ok(result)
}
RepairType::ReplaceText
| RepairType::ReplaceQuotes
| RepairType::TypeCoercion
| RepairType::QuoteKey => {
let original_len = action.original.len();
if action.position > input.len() {
return Err(format!(
"Replace position {} is out of bounds for input of length {}",
action.position,
input.len()
));
}
if action.position + original_len > input.len() {
return Err(format!(
"Replace end position {} is out of bounds for input of length {}",
action.position + original_len,
input.len()
));
}
let mut result = String::new();
result.push_str(&input[..action.position]);
result.push_str(&action.replacement);
result.push_str(&input[action.position + original_len..]);
Ok(result)
}
_ => Ok(input.to_string()), }
}
fn calculate_overall_confidence(&self, strategies: &[RepairStrategy]) -> RepairConfidence {
if strategies.is_empty() {
return RepairConfidence::new(1.0);
}
let sum: f32 = strategies.iter().map(|s| s.confidence.value()).sum();
let avg = sum / strategies.len() as f32;
RepairConfidence::new(avg)
}
pub fn history(&self) -> &RepairHistory {
&self.history
}
pub fn clear_history(&mut self) {
self.history.clear();
}
}
impl RepairHistory {
pub fn new(max_entries: usize) -> Self {
Self {
max_entries,
entries: VecDeque::new(),
}
}
pub fn add_entry(&mut self, entry: RepairHistoryEntry) {
self.entries.push_front(entry);
while self.entries.len() > self.max_entries {
self.entries.pop_back();
}
}
pub fn entries(&self) -> &VecDeque<RepairHistoryEntry> {
&self.entries
}
pub fn clear(&mut self) {
self.entries.clear();
}
#[inline(always)]
pub fn len(&self) -> usize {
self.entries.len()
}
#[inline(always)]
pub fn is_empty(&self) -> bool {
self.entries.is_empty()
}
}
impl Default for AdvancedJsonRepairer {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_repair_confidence() {
let confidence = RepairConfidence::new(0.85);
assert_eq!(confidence.value(), 0.85);
assert!(confidence.is_above(0.8));
assert!(!confidence.is_above(0.9));
assert_eq!(confidence.level(), "medium");
}
#[test]
fn test_quote_repair() {
let mut repairer = AdvancedJsonRepairer::new();
let input = "{'name': 'John', 'age': 30}";
let (repaired, strategies) = repairer.repair(input).unwrap();
assert!(repaired.contains("\"name\""));
assert!(repaired.contains("\"John\""));
assert!(!repaired.contains("'"));
assert!(!strategies.is_empty());
}
#[test]
fn test_type_coercion() {
let mut repairer = AdvancedJsonRepairer::new();
let input = r#"{"count": "42", "price": "19.99"}"#;
let (repaired, strategies) = repairer.repair(input).unwrap();
assert!(repaired.contains(": 42"));
assert!(repaired.contains(": 19.99"));
assert!(!strategies.is_empty());
}
#[test]
fn test_preview_mode() {
let repairer = AdvancedJsonRepairer::new().with_preview_mode(true);
let input = "{'test': true}";
let preview = repairer.preview_repairs(input).unwrap();
assert_eq!(preview.original, input);
assert!(preview.repaired.contains("\"test\""));
assert!(!preview.repairs.is_empty());
}
#[test]
fn test_repair_history() {
let mut repairer = AdvancedJsonRepairer::new();
let input = "{'test': 123}";
repairer.repair(input).unwrap();
assert_eq!(repairer.history().len(), 1);
let entry = &repairer.history().entries()[0];
assert_eq!(entry.original, input);
assert!(entry.success);
}
}