use serde_json::Value;
use crate::types::Suggestion;
fn number_to_json_value(n: f64) -> Value {
if n.fract() == 0.0 && n.abs() < (i64::MAX as f64) {
serde_json::json!(n as i64)
} else {
serde_json::json!(n)
}
}
pub fn suggest_type_fix(data: &Value, expected_type: &str) -> Option<Suggestion> {
match expected_type {
"number" | "integer" => {
if let Some(s) = data.as_str() {
let trimmed = s.trim();
if !trimmed.is_empty() {
if let Ok(num) = trimmed.parse::<f64>() {
if expected_type == "integer" {
if num.fract() == 0.0 {
return Some(Suggestion {
action: "convert".to_string(),
description: format!("Convert \"{}\" to integer", s),
suggested_value: Some(number_to_json_value(num)),
});
}
return None;
}
return Some(Suggestion {
action: "convert".to_string(),
description: format!("Convert \"{}\" to number", s),
suggested_value: Some(number_to_json_value(num)),
});
}
}
}
if let Some(b) = data.as_bool() {
let num = if b { 1 } else { 0 };
return Some(Suggestion {
action: "convert".to_string(),
description: format!("Convert {} to number", b),
suggested_value: Some(serde_json::json!(num)),
});
}
None
}
"string" => {
if let Some(n) = data.as_f64() {
let s = format_number_as_js(n);
return Some(Suggestion {
action: "convert".to_string(),
description: format!("Convert {} to string", format_json_value(data)),
suggested_value: Some(Value::String(s)),
});
}
if let Some(b) = data.as_bool() {
return Some(Suggestion {
action: "convert".to_string(),
description: format!("Convert {} to string", b),
suggested_value: Some(Value::String(b.to_string())),
});
}
None
}
"boolean" => {
if let Some(s) = data.as_str() {
if s == "true" || s == "false" {
let bval = s == "true";
return Some(Suggestion {
action: "convert".to_string(),
description: format!("Convert \"{}\" to boolean", s),
suggested_value: Some(serde_json::json!(bval)),
});
}
}
None
}
_ => None,
}
}
pub fn suggest_number_fix(data: f64, schema: &Value) -> Option<Suggestion> {
if let Some(min) = schema.get("minimum").and_then(|v| v.as_f64()) {
if data < min {
return Some(Suggestion {
action: "clamp".to_string(),
description: format!("Clamp value to minimum {}", format_number(min)),
suggested_value: Some(number_to_json_value(min)),
});
}
}
if let Some(max) = schema.get("maximum").and_then(|v| v.as_f64()) {
if data > max {
return Some(Suggestion {
action: "clamp".to_string(),
description: format!("Clamp value to maximum {}", format_number(max)),
suggested_value: Some(number_to_json_value(max)),
});
}
}
if let Some(exc_min) = schema.get("exclusiveMinimum").and_then(|v| v.as_f64()) {
if data <= exc_min {
return Some(Suggestion {
action: "clamp".to_string(),
description: format!("Value must be greater than {}", format_number(exc_min)),
suggested_value: Some(number_to_json_value(exc_min)),
});
}
}
if let Some(exc_max) = schema.get("exclusiveMaximum").and_then(|v| v.as_f64()) {
if data >= exc_max {
return Some(Suggestion {
action: "clamp".to_string(),
description: format!("Value must be less than {}", format_number(exc_max)),
suggested_value: Some(number_to_json_value(exc_max)),
});
}
}
None
}
pub fn suggest_string_fix(data: &str, schema: &Value) -> Option<Suggestion> {
if let Some(max_len) = schema.get("maxLength").and_then(|v| v.as_u64()) {
let max_len = max_len as usize;
if data.len() > max_len {
let truncated: String = data.chars().take(max_len).collect();
return Some(Suggestion {
action: "truncate".to_string(),
description: format!("Truncate string to {} characters", max_len),
suggested_value: Some(Value::String(truncated)),
});
}
}
None
}
pub fn suggest_missing_required(property: &str) -> Suggestion {
Suggestion {
action: "add".to_string(),
description: format!("Add required property \"{}\"", property),
suggested_value: None,
}
}
pub fn suggest_remove_additional(property: &str) -> Suggestion {
Suggestion {
action: "remove".to_string(),
description: format!("Remove additional property \"{}\"", property),
suggested_value: None,
}
}
pub fn suggest_array_fix(schema: &Value) -> Option<Suggestion> {
if let Some(max_items) = schema.get("maxItems").and_then(|v| v.as_u64()) {
return Some(Suggestion {
action: "truncate".to_string(),
description: format!("Truncate array to {} items", max_items),
suggested_value: None,
});
}
None
}
fn format_number_as_js(n: f64) -> String {
if n.fract() == 0.0 && n.abs() < 1e15 {
format!("{}", n as i64)
} else {
format!("{}", n)
}
}
fn format_number(n: f64) -> String {
if n.fract() == 0.0 && n.abs() < 1e15 {
format!("{}", n as i64)
} else {
format!("{}", n)
}
}
fn format_json_value(v: &Value) -> String {
match v {
Value::Number(n) => {
if let Some(f) = n.as_f64() {
format_number(f)
} else {
n.to_string()
}
}
_ => v.to_string(),
}
}