use std::collections::HashMap;
use nodedb_types::Value;
use crate::error::SqlError;
use super::scan::{parse_object, skip_ws};
pub fn parse_object_literal(s: &str) -> Option<Result<HashMap<String, Value>, SqlError>> {
let trimmed = s.trim();
if !trimmed.starts_with('{') {
return None;
}
let chars: Vec<char> = trimmed.chars().collect();
let mut pos = 0;
Some(parse_object(&chars, &mut pos))
}
pub fn parse_object_literal_complete(s: &str) -> Option<Result<HashMap<String, Value>, SqlError>> {
let trimmed = s.trim();
if !trimmed.starts_with('{') {
return None;
}
let chars: Vec<char> = trimmed.chars().collect();
let mut pos = 0;
Some(
parse_object(&chars, &mut pos).and_then(|fields| match leftover(&chars, pos) {
Some(rest) => Err(trailing_input_error("object literal", &rest)),
None => Ok(fields),
}),
)
}
pub fn parse_object_literal_array_complete(
s: &str,
) -> Option<Result<Vec<HashMap<String, Value>>, SqlError>> {
let trimmed = s.trim();
if !trimmed.starts_with('[') {
return None;
}
let objects = match parse_object_literal_array(trimmed)? {
Ok(objects) => objects,
Err(error) => return Some(Err(error)),
};
let chars: Vec<char> = trimmed.chars().collect();
let Some(end) = matching_bracket(&chars) else {
return Some(Err(SqlError::Parse {
detail: "unterminated array of objects".to_string(),
}));
};
Some(match leftover(&chars, end + 1) {
Some(rest) => Err(trailing_input_error("array of object literals", &rest)),
None => Ok(objects),
})
}
fn leftover(chars: &[char], pos: usize) -> Option<String> {
let rest: String = chars.get(pos..).unwrap_or_default().iter().collect();
let rest = rest.trim().trim_end_matches(';').trim();
if rest.is_empty() {
None
} else {
Some(rest.to_string())
}
}
fn trailing_input_error(what: &str, rest: &str) -> SqlError {
SqlError::Parse {
detail: format!(
"unexpected input after {what}: `{rest}`. The brace form takes no trailing clause — \
write `INSERT INTO <collection> (cols) VALUES (...) {rest}` to use it"
),
}
}
fn matching_bracket(chars: &[char]) -> Option<usize> {
let mut depth = 0usize;
let mut in_string = false;
let mut i = 0;
while i < chars.len() {
match chars[i] {
'\'' if in_string && chars.get(i + 1) == Some(&'\'') => i += 1,
'\'' => in_string = !in_string,
'[' if !in_string => depth += 1,
']' if !in_string => {
depth = depth.saturating_sub(1);
if depth == 0 {
return Some(i);
}
}
_ => {}
}
i += 1;
}
None
}
pub fn parse_object_literal_array(
s: &str,
) -> Option<Result<Vec<HashMap<String, Value>>, SqlError>> {
let trimmed = s.trim();
if !trimmed.starts_with('[') {
return None;
}
let chars: Vec<char> = trimmed.chars().collect();
let mut pos = 0;
pos += 1;
let mut objects = Vec::new();
loop {
skip_ws(&chars, &mut pos);
if pos >= chars.len() {
return Some(Err(SqlError::Parse {
detail: "unterminated array of objects".to_string(),
}));
}
if chars[pos] == ']' {
break;
}
if chars[pos] == ',' {
pos += 1;
continue;
}
if chars[pos] != '{' {
return Some(Err(SqlError::Parse {
detail: format!("expected '{{' at position {pos}, found '{}'", chars[pos]),
}));
}
match parse_object(&chars, &mut pos) {
Ok(obj) => objects.push(obj),
Err(e) => return Some(Err(e)),
}
skip_ws(&chars, &mut pos);
if pos < chars.len() && chars[pos] == ',' {
pos += 1;
}
}
Some(Ok(objects))
}