Skip to content

Commit 621ff4f

Browse files
Add comprehensive error reporting function
Implements create_comprehensive_error() which creates rich error objects with both parse and validation errors attached. - Uses ParserError for parse errors, ValueError for validation-only errors - Attaches structured error details as attributes for programmatic access - Uses miette for pretty-printed error display with source code context - Combines multiple error types into a single, informative exception The function will be used by Bundle::new to report all errors at once.
1 parent abd7fd7 commit 621ff4f

File tree

1 file changed

+85
-0
lines changed

1 file changed

+85
-0
lines changed

src/lib.rs

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -604,6 +604,91 @@ fn has_cycle(
604604
false
605605
}
606606

607+
/// Helper function to create a comprehensive error with all parse and validation errors
608+
fn create_comprehensive_error(
609+
parse_errors: &[ParseErrorDetail],
610+
validation_errors: &[ValidationError],
611+
) -> PyErr {
612+
Python::with_gil(|py| {
613+
if !parse_errors.is_empty() {
614+
// If there are parse errors, use ParserError as primary
615+
// But attach validation errors too!
616+
617+
let first_file = parse_errors[0]
618+
.filename
619+
.as_ref()
620+
.map(|s| s.as_str())
621+
.unwrap_or("<string>");
622+
623+
// Try to read source for miette display
624+
let source = std::fs::read_to_string(first_file).ok();
625+
626+
// Create miette error with labels
627+
let mut labels = Vec::with_capacity(parse_errors.len());
628+
for error in parse_errors {
629+
labels.push(LabeledSpan::at(
630+
error.byte_start..error.byte_end,
631+
error.message.clone(),
632+
));
633+
}
634+
635+
let miette_error = if let Some(source) = source {
636+
miette!(
637+
labels = labels,
638+
"Found {} parse error(s) and {} validation error(s)",
639+
parse_errors.len(),
640+
validation_errors.len(),
641+
)
642+
.with_source_code(source)
643+
} else {
644+
miette!(
645+
"Found {} parse error(s) and {} validation error(s) in {}",
646+
parse_errors.len(),
647+
validation_errors.len(),
648+
first_file,
649+
)
650+
};
651+
652+
let err = ParserError::new_err(format!("{miette_error:?}"));
653+
654+
// Attach structured errors for programmatic access
655+
if let Ok(exc) = err
656+
.value(py)
657+
.downcast::<pyo3::exceptions::PyBaseException>()
658+
{
659+
let _ = exc.setattr("parse_errors", parse_errors.to_vec());
660+
let _ = exc.setattr("validation_errors", validation_errors.to_vec());
661+
let _ = exc.setattr("error_count", parse_errors.len() + validation_errors.len());
662+
}
663+
664+
err
665+
} else {
666+
// Only validation errors
667+
let message = format!(
668+
"Found {} validation error(s):\n{}",
669+
validation_errors.len(),
670+
validation_errors
671+
.iter()
672+
.map(|e| format!(" - {}", e.message))
673+
.collect::<Vec<_>>()
674+
.join("\n")
675+
);
676+
677+
let err = PyValueError::new_err(message);
678+
679+
if let Ok(exc) = err
680+
.value(py)
681+
.downcast::<pyo3::exceptions::PyBaseException>()
682+
{
683+
let _ = exc.setattr("validation_errors", validation_errors.to_vec());
684+
let _ = exc.setattr("error_count", validation_errors.len());
685+
}
686+
687+
err
688+
}
689+
})
690+
}
691+
607692
#[pymodule]
608693
mod rustfluent {
609694
use super::*;

0 commit comments

Comments
 (0)