use super::types::{OCRError, OpaqueError, ProcessingStage};
#[derive(Clone, Debug)]
pub struct ModelInferenceErrorBuilder {
model_name: String,
operation: String,
batch_index: usize,
input_shape: Vec<usize>,
context: String,
}
impl ModelInferenceErrorBuilder {
pub fn new(model_name: impl Into<String>, operation: impl Into<String>) -> Self {
Self {
model_name: model_name.into(),
operation: operation.into(),
batch_index: 0,
input_shape: Vec::new(),
context: String::new(),
}
}
pub fn batch_index(mut self, batch_index: usize) -> Self {
self.batch_index = batch_index;
self
}
pub fn input_shape(mut self, shape: &[usize]) -> Self {
self.input_shape = shape.to_vec();
self
}
pub fn context(mut self, context: impl Into<String>) -> Self {
self.context = context.into();
self
}
pub fn build(self, error: impl std::error::Error + Send + Sync + 'static) -> OCRError {
OCRError::ModelInference {
model_name: self.model_name,
operation: self.operation,
batch_index: self.batch_index,
input_shape: self.input_shape,
context: self.context,
source: Box::new(error),
}
}
}
impl OCRError {
pub fn tensor_operation(
context: &str,
error: impl std::error::Error + Send + Sync + 'static,
) -> Self {
Self::tensor_operation_error("unknown", &[], &[], context, error)
}
#[inline]
fn processing_with_context(
kind: ProcessingStage,
context: impl Into<String>,
error: impl std::error::Error + Send + Sync + 'static,
) -> Self {
Self::Processing {
kind,
context: context.into(),
source: Box::new(error),
}
}
#[inline]
fn inference_with_context(
model_name: impl Into<String>,
context: impl Into<String>,
error: impl std::error::Error + Send + Sync + 'static,
) -> Self {
Self::Inference {
model_name: model_name.into(),
context: context.into(),
source: Box::new(error),
}
}
pub fn post_processing(
context: &str,
error: impl std::error::Error + Send + Sync + 'static,
) -> Self {
Self::processing_with_context(ProcessingStage::PostProcessing, context, error)
}
pub fn normalization(
context: &str,
error: impl std::error::Error + Send + Sync + 'static,
) -> Self {
Self::processing_with_context(ProcessingStage::Normalization, context, error)
}
pub fn resize_error(
context: &str,
error: impl std::error::Error + Send + Sync + 'static,
) -> Self {
Self::processing_with_context(ProcessingStage::Resize, context, error)
}
pub fn image_processing(
context: &str,
error: impl std::error::Error + Send + Sync + 'static,
) -> Self {
Self::processing_with_context(ProcessingStage::ImageProcessing, context, error)
}
pub fn image_processing_error(message: impl Into<String>) -> Self {
Self::image_processing(
&message.into(),
OpaqueError("Image processing failed".to_string()),
)
}
pub fn batch_processing(
context: &str,
error: impl std::error::Error + Send + Sync + 'static,
) -> Self {
Self::batch_processing_error(ProcessingStage::BatchProcessing, 0, 1, context, error)
}
pub fn processing_error(
kind: ProcessingStage,
context: &str,
error: impl std::error::Error + Send + Sync + 'static,
) -> Self {
Self::processing_error_with_details(kind, "unknown", context, error)
}
pub fn basic_inference_error(error: impl std::error::Error + Send + Sync + 'static) -> Self {
Self::inference_with_context("Unknown", "Basic inference error", error)
}
pub fn invalid_input(message: impl Into<String>) -> Self {
Self::InvalidInput {
message: message.into(),
}
}
pub fn config_error(message: impl Into<String>) -> Self {
Self::ConfigError {
message: message.into(),
}
}
pub fn config_error_with_context(field: &str, value: &str, reason: &str) -> Self {
Self::ConfigError {
message: format!(
"Configuration error in field '{field}' with value '{value}': {reason}"
),
}
}
pub fn validation_error(component: &str, field: &str, expected: &str, actual: &str) -> Self {
Self::InvalidInput {
message: format!(
"Validation failed in {component}: field '{field}' expected {expected}, but got '{actual}'"
),
}
}
pub fn resource_limit_error(resource: &str, limit: usize, requested: usize) -> Self {
Self::InvalidInput {
message: format!(
"Resource limit exceeded for {resource}: requested {requested} but limit is {limit}"
),
}
}
pub fn processing_error_with_details(
stage: ProcessingStage,
operation: &str,
input_info: &str,
error: impl std::error::Error + Send + Sync + 'static,
) -> Self {
let ctx = format!("Operation '{operation}' failed on input '{input_info}': {error}");
Self::processing_with_context(stage, ctx, error)
}
pub fn model_inference_error(
model_name: &str,
operation: &str,
batch_index: usize,
input_shape: &[usize],
context: &str,
error: impl std::error::Error + Send + Sync + 'static,
) -> Self {
Self::model_inference_error_builder(model_name, operation)
.batch_index(batch_index)
.input_shape(input_shape)
.context(context)
.build(error)
}
pub fn model_inference_error_builder(
model_name: impl Into<String>,
operation: impl Into<String>,
) -> ModelInferenceErrorBuilder {
ModelInferenceErrorBuilder::new(model_name, operation)
}
pub fn inference_error(
model_name: &str,
context: &str,
error: impl std::error::Error + Send + Sync + 'static,
) -> Self {
Self::inference_with_context(model_name, context, error)
}
pub fn model_load_error(
model_path: impl AsRef<std::path::Path>,
reason: impl Into<String>,
suggestion: Option<&str>,
source: Option<impl std::error::Error + Send + Sync + 'static>,
) -> Self {
let suggestion = suggestion
.map(|s| format!("; suggested fix: {}", s))
.unwrap_or_default();
Self::ModelLoad {
model_path: model_path.as_ref().display().to_string(),
reason: reason.into(),
suggestion,
source: source.map(|e| Box::new(e) as _),
}
}
pub fn tensor_operation_error(
operation: &str,
expected_shape: &[usize],
actual_shape: &[usize],
context: &str,
error: impl std::error::Error + Send + Sync + 'static,
) -> Self {
Self::TensorOperation {
operation: operation.to_string(),
expected_shape: expected_shape.to_vec(),
actual_shape: actual_shape.to_vec(),
context: context.to_string(),
source: Box::new(error),
}
}
pub fn batch_processing_error(
stage: ProcessingStage,
batch_index: usize,
batch_size: usize,
operation: &str,
error: impl std::error::Error + Send + Sync + 'static,
) -> Self {
let ctx = format!(
"Batch processing failed: operation '{operation}' failed on item {batch_index}/{batch_size}"
);
Self::processing_with_context(stage, ctx, error)
}
pub fn pipeline_stage_error(
stage_name: &str,
stage_id: &str,
input_count: usize,
operation: &str,
error: impl std::error::Error + Send + Sync + 'static,
) -> Self {
let ctx = format!(
"Pipeline stage '{stage_name}' (id: {stage_id}) failed: operation '{operation}' on {input_count} items"
);
Self::processing_with_context(ProcessingStage::PipelineExecution, ctx, error)
}
}
impl OCRError {
pub fn batch_item_error(
stage_name: &str,
batch_context: Option<&str>,
item_index: usize,
total_items: Option<usize>,
operation: &str,
error: impl std::error::Error + Send + Sync + 'static,
) -> Self {
let batch_info = match (batch_context, total_items) {
(Some(context), Some(total)) => {
format!(" in batch '{context}' (item {}/{total})", item_index + 1)
}
(Some(context), None) => format!(" in batch '{context}' (item {})", item_index + 1),
(None, Some(total)) => format!(" (item {}/{total})", item_index + 1),
(None, None) => format!(" (item {})", item_index + 1),
};
Self::Processing {
kind: ProcessingStage::BatchProcessing,
context: format!("{stage_name} processing failed{batch_info}: operation '{operation}'"),
source: Box::new(error),
}
}
pub fn format_batch_error_message(
stage_name: &str,
batch_context: &str,
affected_indices: &[usize],
error: &dyn std::error::Error,
) -> String {
format!(
"{stage_name} batch '{batch_context}' failed: {error} (affected indices: {affected_indices:?})"
)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_batch_item_error_with_full_context() {
let underlying_error = std::io::Error::other("test error");
let error = OCRError::batch_item_error(
"recognition",
Some("batch_123"),
2,
Some(5),
"predict",
underlying_error,
);
match error {
OCRError::Processing { context, .. } => {
assert_eq!(
context,
"recognition processing failed in batch 'batch_123' (item 3/5): operation 'predict'"
);
}
_ => panic!("Expected Processing error"),
}
}
#[test]
fn test_batch_item_error_minimal_context() {
let underlying_error = std::io::Error::other("test error");
let error =
OCRError::batch_item_error("orientation", None, 0, None, "process", underlying_error);
match error {
OCRError::Processing { context, .. } => {
assert_eq!(
context,
"orientation processing failed (item 1): operation 'process'"
);
}
_ => panic!("Expected Processing error"),
}
}
#[test]
fn test_format_batch_error_message() {
let underlying_error = std::io::Error::other("network timeout");
let affected_indices = vec![1, 3, 5];
let message = OCRError::format_batch_error_message(
"text recognition",
"group_aspect_1.2",
&affected_indices,
&underlying_error,
);
assert_eq!(
message,
"text recognition batch 'group_aspect_1.2' failed: network timeout (affected indices: [1, 3, 5])"
);
}
}