use super::context::{attach_frame_to_parent, Frame};
use crate::document::Item;
use crate::error::{HedlError, HedlResult};
use crate::limits::Limits;
use std::collections::BTreeMap;
pub(super) fn validate_indent_for_child(
stack: &[Frame],
indent: usize,
line_num: usize,
) -> HedlResult<()> {
let expected = match stack.last() {
Some(Frame::Root { .. }) => 0,
Some(Frame::Object {
indent: parent_indent,
..
}) => parent_indent + 1,
Some(Frame::List { row_indent: _, .. }) => {
return Err(HedlError::syntax(
"cannot add key-value inside list context",
line_num,
));
}
None => 0,
};
if indent != expected {
return Err(HedlError::syntax(
format!("expected indent level {}, got {}", expected, indent),
line_num,
));
}
Ok(())
}
pub(super) fn validate_nested_list_indent(
stack: &[Frame],
indent: usize,
line_num: usize,
) -> HedlResult<Option<usize>> {
for (idx, frame) in stack.iter().enumerate().rev() {
match frame {
Frame::List {
row_indent, list, ..
} => {
if indent == *row_indent + 1 {
if list.is_empty() {
return Err(HedlError::orphan_row(
"nested list declaration has no parent row",
line_num,
));
}
return Ok(Some(idx));
}
}
Frame::Root { .. } => {
if indent == 0 {
return Ok(None); }
}
Frame::Object {
indent: obj_indent, ..
} => {
if indent == obj_indent + 1 {
return Ok(None); }
}
}
}
Err(HedlError::syntax(
format!(
"invalid indent level {} for nested list declaration",
indent
),
line_num,
))
}
pub(super) fn check_duplicate_key(
stack: &[Frame],
key: &str,
line_num: usize,
limits: &Limits,
total_keys: &mut usize,
) -> HedlResult<()> {
let object_opt = match stack.last() {
Some(Frame::Root { object }) | Some(Frame::Object { object, .. }) => Some(object),
_ => None,
};
if let Some(object) = object_opt {
if object.contains_key(key) {
return Err(HedlError::semantic(
format!("duplicate key: {}", key),
line_num,
));
}
if object.len() >= limits.max_object_keys {
return Err(HedlError::security(
format!(
"object has too many keys: {} (max: {})",
object.len() + 1,
limits.max_object_keys
),
line_num,
));
}
*total_keys = total_keys
.checked_add(1)
.ok_or_else(|| HedlError::security("total key count overflow", line_num))?;
if *total_keys > limits.max_total_keys {
return Err(HedlError::security(
format!(
"too many total keys: {} exceeds limit {}",
*total_keys, limits.max_total_keys
),
line_num,
));
}
}
Ok(())
}
pub(super) fn insert_into_current(stack: &mut [Frame], key: String, item: Item) {
if let Some(Frame::Root { object } | Frame::Object { object, .. }) = stack.last_mut() {
object.insert(key, item);
}
}
pub(super) fn parse_quoted_string(s: &str, line_num: usize) -> HedlResult<String> {
if !s.starts_with('"') {
return Err(HedlError::syntax("expected quoted string", line_num));
}
let mut result = String::new();
let mut chars = s[1..].chars().peekable();
while let Some(ch) = chars.next() {
if ch == '"' {
if chars.peek() == Some(&'"') {
chars.next();
result.push('"');
} else {
return Ok(result);
}
} else {
result.push(ch);
}
}
Err(HedlError::syntax("unclosed quoted string", line_num))
}
pub(super) fn finalize_stack(mut stack: Vec<Frame>) -> HedlResult<BTreeMap<String, Item>> {
if stack.len() > 1 {
if let Some(Frame::Object { key, object, .. }) = stack.last() {
if object.is_empty() {
return Err(HedlError::syntax(
format!("truncated input: object '{}' has no children", key),
0,
));
}
}
}
while stack.len() > 1 {
let frame = stack.pop().expect("stack has at least 2 elements");
attach_frame_to_parent(&mut stack, frame);
}
match stack.pop() {
Some(Frame::Root { object }) => Ok(object),
_ => Ok(BTreeMap::new()),
}
}