use alloc::vec::Vec;
use crate::error::enhanced::EnhancedError;
use crate::error::{ErrorKind, YamlError};
pub use crate::error::enhanced::RecoveryStrategy;
#[derive(Debug)]
pub struct ErrorCollection {
errors: Vec<EnhancedError>,
continue_on_error: bool,
max_errors: usize,
}
impl ErrorCollection {
pub fn new() -> Self {
Self {
errors: Vec::new(),
continue_on_error: true,
max_errors: 100,
}
}
pub fn with_config(continue_on_error: bool, max_errors: usize) -> Self {
Self {
errors: Vec::new(),
continue_on_error,
max_errors,
}
}
pub fn add(&mut self, error: EnhancedError) {
self.errors.push(error);
}
pub fn should_continue(&self) -> bool {
self.continue_on_error && self.errors.len() < self.max_errors
}
pub fn errors(&self) -> &[EnhancedError] {
&self.errors
}
pub fn len(&self) -> usize {
self.errors.len()
}
pub fn is_empty(&self) -> bool {
self.errors.is_empty()
}
pub fn clear(&mut self) {
self.errors.clear();
}
pub fn into_result<T>(self, value: T) -> Result<T, EnhancedError> {
if self.errors.is_empty() {
Ok(value)
} else {
Err(self.errors.into_iter().next().unwrap())
}
}
}
impl Default for ErrorCollection {
fn default() -> Self {
Self::new()
}
}
pub struct RecoveryHandler {
strategies: [RecoveryStrategy; 13],
}
impl RecoveryHandler {
pub fn new() -> Self {
Self {
strategies: [
RecoveryStrategy::SkipLine, RecoveryStrategy::SkipLine, RecoveryStrategy::Abort, RecoveryStrategy::SkipLine, RecoveryStrategy::InsertDefault, RecoveryStrategy::SkipLine, RecoveryStrategy::SkipLine, RecoveryStrategy::Abort, RecoveryStrategy::SkipLine, RecoveryStrategy::Abort, RecoveryStrategy::SkipLine, RecoveryStrategy::SkipLine, RecoveryStrategy::Abort, ],
}
}
pub fn strict() -> Self {
Self {
strategies: [RecoveryStrategy::Abort; 13],
}
}
pub fn lenient() -> Self {
Self {
strategies: [
RecoveryStrategy::SkipLine,
RecoveryStrategy::SkipLine,
RecoveryStrategy::SkipLine,
RecoveryStrategy::SkipLine,
RecoveryStrategy::InsertDefault,
RecoveryStrategy::SkipLine,
RecoveryStrategy::SkipLine,
RecoveryStrategy::SkipLine,
RecoveryStrategy::SkipLine,
RecoveryStrategy::SkipLine,
RecoveryStrategy::SkipLine,
RecoveryStrategy::SkipLine,
RecoveryStrategy::SkipLine,
],
}
}
pub fn set_strategy(&mut self, kind: &ErrorKind, strategy: RecoveryStrategy) {
let index = error_kind_to_index(kind);
self.strategies[index] = strategy;
}
pub fn get_strategy(&self, kind: &ErrorKind) -> RecoveryStrategy {
let index = error_kind_to_index(kind);
self.strategies[index]
}
pub fn recover(&self, error: &YamlError) -> RecoveryStrategy {
self.get_strategy(error.kind())
}
}
impl Default for RecoveryHandler {
fn default() -> Self {
Self::new()
}
}
fn error_kind_to_index(kind: &ErrorKind) -> usize {
match kind {
ErrorKind::SyntaxError => 0,
ErrorKind::ParseError => 1,
ErrorKind::UnterminatedString => 2,
ErrorKind::InvalidTag => 3,
ErrorKind::UndefinedAlias => 4,
ErrorKind::InvalidAnchor => 5,
ErrorKind::DuplicateAnchor => 6,
ErrorKind::IoError => 7,
ErrorKind::ValidationError => 8,
ErrorKind::UnexpectedEof => 9,
ErrorKind::UnexpectedCharacter => 10,
ErrorKind::InvalidEscape => 11,
ErrorKind::Unsupported => 12,
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ParserState {
DocumentStart,
BlockMapping,
BlockSequence,
FlowMapping,
FlowSequence,
Scalar,
DocumentEnd,
}
#[derive(Debug)]
pub struct RecoveryContext {
pub state: ParserState,
pub indent_level: usize,
pub in_flow: bool,
pub bracket_depth: usize,
}
impl RecoveryContext {
pub fn new() -> Self {
Self {
state: ParserState::DocumentStart,
indent_level: 0,
in_flow: false,
bracket_depth: 0,
}
}
pub fn enter_block_mapping(&mut self) {
self.state = ParserState::BlockMapping;
}
pub fn enter_flow_mapping(&mut self) {
self.state = ParserState::FlowMapping;
self.in_flow = true;
self.bracket_depth += 1;
}
pub fn exit_flow(&mut self) {
if self.bracket_depth > 0 {
self.bracket_depth -= 1;
}
if self.bracket_depth == 0 {
self.in_flow = false;
}
}
pub fn can_recover(&self, strategy: RecoveryStrategy) -> bool {
match strategy {
RecoveryStrategy::Abort => false,
RecoveryStrategy::SkipLine => true,
RecoveryStrategy::SkipToNextMapping => {
matches!(
self.state,
ParserState::BlockMapping | ParserState::FlowMapping
)
}
RecoveryStrategy::SkipCollection => self.bracket_depth > 0 || self.indent_level > 0,
RecoveryStrategy::InsertDefault => true,
}
}
}
impl Default for RecoveryContext {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_error_collection() {
let mut collection = ErrorCollection::new();
assert!(collection.is_empty());
assert!(collection.should_continue());
let error = EnhancedError::new(YamlError::new(ErrorKind::SyntaxError, "test"));
collection.add(error);
assert_eq!(collection.len(), 1);
assert!(!collection.is_empty());
}
#[test]
fn test_error_collection_max() {
let mut collection = ErrorCollection::with_config(true, 2);
collection.add(EnhancedError::new(YamlError::new(
ErrorKind::SyntaxError,
"error1",
)));
assert!(collection.should_continue());
collection.add(EnhancedError::new(YamlError::new(
ErrorKind::SyntaxError,
"error2",
)));
assert!(!collection.should_continue());
}
#[test]
fn test_recovery_handler_default() {
let handler = RecoveryHandler::new();
let error = YamlError::new(ErrorKind::SyntaxError, "test");
assert_eq!(handler.recover(&error), RecoveryStrategy::SkipLine);
let error = YamlError::new(ErrorKind::UnexpectedEof, "test");
assert_eq!(handler.recover(&error), RecoveryStrategy::Abort);
}
#[test]
fn test_recovery_handler_strict() {
let handler = RecoveryHandler::strict();
let error = YamlError::new(ErrorKind::SyntaxError, "test");
assert_eq!(handler.recover(&error), RecoveryStrategy::Abort);
}
#[test]
fn test_recovery_handler_lenient() {
let handler = RecoveryHandler::lenient();
let error = YamlError::new(ErrorKind::UnexpectedEof, "test");
assert_eq!(handler.recover(&error), RecoveryStrategy::SkipLine);
}
#[test]
fn test_recovery_handler_custom() {
let mut handler = RecoveryHandler::new();
handler.set_strategy(&ErrorKind::SyntaxError, RecoveryStrategy::Abort);
let error = YamlError::new(ErrorKind::SyntaxError, "test");
assert_eq!(handler.recover(&error), RecoveryStrategy::Abort);
}
#[test]
fn test_recovery_context() {
let mut ctx = RecoveryContext::new();
assert_eq!(ctx.state, ParserState::DocumentStart);
assert!(!ctx.in_flow);
ctx.enter_flow_mapping();
assert_eq!(ctx.state, ParserState::FlowMapping);
assert!(ctx.in_flow);
assert_eq!(ctx.bracket_depth, 1);
ctx.exit_flow();
assert!(!ctx.in_flow);
assert_eq!(ctx.bracket_depth, 0);
}
#[test]
fn test_recovery_context_can_recover() {
let mut ctx = RecoveryContext::new();
assert!(ctx.can_recover(RecoveryStrategy::SkipLine));
assert!(!ctx.can_recover(RecoveryStrategy::Abort));
ctx.enter_block_mapping();
assert!(ctx.can_recover(RecoveryStrategy::SkipToNextMapping));
}
#[test]
fn test_error_collection_clear() {
let mut collection = ErrorCollection::new();
collection.add(EnhancedError::new(YamlError::new(ErrorKind::SyntaxError, "test")));
assert!(!collection.is_empty());
collection.clear();
assert!(collection.is_empty());
}
#[test]
fn test_error_collection_into_result() {
let collection = ErrorCollection::new();
let result: Result<i32, EnhancedError> = collection.into_result(42);
assert_eq!(result.unwrap(), 42);
let mut collection = ErrorCollection::new();
collection.add(EnhancedError::new(YamlError::new(ErrorKind::SyntaxError, "fail")));
let result: Result<i32, EnhancedError> = collection.into_result(42);
assert!(result.is_err());
}
#[test]
fn test_recovery_handler_strict_abort() {
let handler = RecoveryHandler::strict();
let error = YamlError::new(ErrorKind::ParseError, "test");
assert_eq!(handler.recover(&error), RecoveryStrategy::Abort);
}
#[test]
fn test_recovery_context_deep_bracket() {
let mut ctx = RecoveryContext::new();
for _ in 0..5 {
ctx.enter_flow_mapping();
}
assert_eq!(ctx.bracket_depth, 5);
assert!(ctx.in_flow);
ctx.exit_flow();
assert_eq!(ctx.bracket_depth, 4);
ctx.exit_flow();
ctx.exit_flow();
ctx.exit_flow();
ctx.exit_flow();
assert_eq!(ctx.bracket_depth, 0);
assert!(!ctx.in_flow);
}
}