use fraiseql_error::FraiseQLError;
const MAX_LOGICAL_DEPTH: usize = 64;
pub fn parse_logical_group(
input: &str,
dsl_key: &str,
depth: usize,
) -> Result<serde_json::Value, FraiseQLError> {
use crate::routes::rest::params::{bracket::parse_bracket_key, helpers::validation_error};
if depth > MAX_LOGICAL_DEPTH {
return Err(validation_error(format!(
"Logical operator nesting depth exceeds maximum ({MAX_LOGICAL_DEPTH})."
)));
}
let trimmed = input.trim();
if !trimmed.starts_with('(') || !trimmed.ends_with(')') {
return Err(validation_error(format!(
"Logical operator value must be enclosed in parentheses: `{dsl_key}=(...)`. \
Got: `{trimmed}`"
)));
}
let inner = &trimmed[1..trimmed.len() - 1];
let parts = split_logical_parts(inner);
let mut conditions = Vec::with_capacity(parts.len());
for part in &parts {
let part = part.trim();
if part.is_empty() {
continue;
}
if let Some((nested_op, nested_val)) = parse_nested_logical(part) {
let nested_key = format!("_{nested_op}");
let nested = parse_logical_group(nested_val, &nested_key, depth + 1)?;
conditions.push(nested);
} else if let Some((field_op, value)) = part.split_once('=') {
let json_val = parse_logical_value(value);
if let Some((field, op)) = parse_bracket_key(field_op) {
conditions.push(serde_json::json!({ field: { op: json_val } }));
} else {
conditions.push(serde_json::json!({ field_op: { "eq": json_val } }));
}
}
}
Ok(serde_json::json!({ dsl_key: conditions }))
}
#[must_use]
pub fn split_logical_parts(input: &str) -> Vec<String> {
let mut parts = Vec::new();
let mut current = String::new();
let mut depth = 0;
for ch in input.chars() {
match ch {
'(' => {
depth += 1;
current.push(ch);
},
')' => {
depth -= 1;
current.push(ch);
},
',' if depth == 0 => {
parts.push(current.clone());
current.clear();
},
_ => current.push(ch),
}
}
if !current.is_empty() {
parts.push(current);
}
parts
}
#[must_use]
pub fn parse_nested_logical(part: &str) -> Option<(&str, &str)> {
for op in &["and", "or", "not"] {
let prefix = format!("{op}=");
if let Some(rest) = part.strip_prefix(&prefix) {
if rest.starts_with('(') && rest.ends_with(')') {
return Some((op, rest));
}
}
}
None
}
#[must_use]
pub fn parse_logical_value(raw: &str) -> serde_json::Value {
if let Ok(v) = raw.parse::<i64>() {
return serde_json::Value::Number(v.into());
}
if let Ok(v) = raw.parse::<f64>() {
if let Some(n) = serde_json::Number::from_f64(v) {
return serde_json::Value::Number(n);
}
}
match raw {
"true" => return serde_json::Value::Bool(true),
"false" => return serde_json::Value::Bool(false),
_ => {},
}
serde_json::Value::String(raw.to_string())
}