use serde_json::Value;
use super::ParsingStrategy;
use crate::{
error::Result,
value::{FlexValue, Source},
};
#[derive(Debug, Default)]
pub struct RawPrimitiveStrategy;
impl RawPrimitiveStrategy {
pub fn new() -> Self {
Self
}
fn try_as_bool(&self, input: &str) -> Option<FlexValue> {
let trimmed = input.trim();
match trimmed.to_lowercase().as_str() {
"true" => Some(FlexValue::new(
Value::Bool(true),
Source::Heuristic {
pattern: "raw_bool".to_string(),
},
)),
"false" => Some(FlexValue::new(
Value::Bool(false),
Source::Heuristic {
pattern: "raw_bool".to_string(),
},
)),
_ => {
self.extract_bool_from_text(input)
}
}
}
fn extract_bool_from_text(&self, input: &str) -> Option<FlexValue> {
let lower = input.to_lowercase();
let true_count = lower.matches("true").count();
let false_count = lower.matches("false").count();
if true_count == 1 && false_count == 0 {
Some(FlexValue::new(
Value::Bool(true),
Source::Heuristic {
pattern: "bool_from_text".to_string(),
},
))
} else if false_count == 1 && true_count == 0 {
Some(FlexValue::new(
Value::Bool(false),
Source::Heuristic {
pattern: "bool_from_text".to_string(),
},
))
} else {
None
}
}
fn try_as_number(&self, input: &str) -> Option<FlexValue> {
let trimmed = input.trim().trim_end_matches(',');
if let Ok(n) = trimmed.parse::<i64>() {
if let Some(num) = serde_json::Number::from_f64(n as f64) {
return Some(FlexValue::new(
Value::Number(num),
Source::Heuristic {
pattern: "raw_number".to_string(),
},
));
}
}
if let Ok(f) = trimmed.parse::<f64>() {
if let Some(num) = serde_json::Number::from_f64(f) {
return Some(FlexValue::new(
Value::Number(num),
Source::Heuristic {
pattern: "raw_number".to_string(),
},
));
}
}
let without_commas = trimmed.replace(",", "");
if let Ok(f) = without_commas.parse::<f64>() {
if let Some(num) = serde_json::Number::from_f64(f) {
return Some(FlexValue::new(
Value::Number(num),
Source::Heuristic {
pattern: "raw_number_with_commas".to_string(),
},
));
}
}
None
}
fn as_string(&self, input: &str) -> FlexValue {
FlexValue::new(
Value::String(input.to_string()),
Source::Heuristic {
pattern: "raw_string".to_string(),
},
)
}
}
impl ParsingStrategy for RawPrimitiveStrategy {
fn name(&self) -> &'static str {
"raw_primitive"
}
fn parse(&self, input: &str) -> Result<Vec<FlexValue>> {
let mut candidates = Vec::new();
let trimmed = input.trim();
if trimmed.starts_with('{') || trimmed.starts_with('[') {
return Ok(vec![]);
}
if trimmed.contains('{') || trimmed.contains('[') {
return Ok(vec![]);
}
if trimmed.starts_with('"') {
if serde_json::from_str::<serde_json::Value>(trimmed).is_ok() {
return Ok(vec![]);
}
}
if input.len() > 1000 {
return Ok(vec![]);
}
if let Some(bool_value) = self.try_as_bool(input) {
candidates.push(bool_value);
}
if let Some(number_value) = self.try_as_number(input) {
candidates.push(number_value);
}
candidates.push(self.as_string(input));
Ok(candidates)
}
fn priority(&self) -> u8 {
5
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_parse_raw_bool_true() {
let strategy = RawPrimitiveStrategy::new();
let result = strategy.parse("true").unwrap();
assert!(!result.is_empty());
assert!(result.iter().any(|v| matches!(v.value, Value::Bool(true))));
}
#[test]
fn test_parse_raw_bool_false() {
let strategy = RawPrimitiveStrategy::new();
let result = strategy.parse("False").unwrap();
assert!(!result.is_empty());
assert!(result.iter().any(|v| matches!(v.value, Value::Bool(false))));
}
#[test]
fn test_parse_bool_in_text() {
let strategy = RawPrimitiveStrategy::new();
let result = strategy.parse("The answer is true").unwrap();
assert!(!result.is_empty());
assert!(result.iter().any(|v| matches!(v.value, Value::Bool(true))));
}
#[test]
fn test_parse_number() {
let strategy = RawPrimitiveStrategy::new();
let result = strategy.parse("12111").unwrap();
assert!(!result.is_empty());
assert!(result.iter().any(|v| matches!(v.value, Value::Number(_))));
}
#[test]
fn test_parse_number_with_commas() {
let strategy = RawPrimitiveStrategy::new();
let result = strategy.parse("12,111").unwrap();
assert!(!result.is_empty());
assert!(result.iter().any(|v| matches!(v.value, Value::Number(_))));
}
#[test]
fn test_parse_as_string_fallback() {
let strategy = RawPrimitiveStrategy::new();
let result = strategy.parse("some random text").unwrap();
assert!(!result.is_empty());
assert!(result.iter().any(|v| matches!(v.value, Value::String(_))));
}
#[test]
fn test_long_input_skipped() {
let strategy = RawPrimitiveStrategy::new();
let long_input = "a".repeat(2000);
let result = strategy.parse(&long_input).unwrap();
assert!(result.is_empty());
}
#[test]
fn test_ambiguous_bool_not_extracted() {
let strategy = RawPrimitiveStrategy::new();
let result = strategy.parse("The answer is true or false").unwrap();
assert!(!result.iter().any(|v| matches!(v.value, Value::Bool(_))));
}
}