use std::fmt;
pub type Result<T> = std::result::Result<T, PlottingError>;
#[derive(Debug)]
pub enum PlottingError {
DataLengthMismatch {
x_len: usize,
y_len: usize,
series_index: Option<usize>,
},
EmptyDataSet,
NoDataSeries,
InvalidColor(String),
InvalidDimensions { width: u32, height: u32 },
InvalidDPI(u32),
InvalidLineWidth(f32),
InvalidAlpha(f32),
InvalidMargin(f32),
FontError(String),
ThemeError(String),
RenderError(String),
UnsupportedFormat(String),
IoError(std::io::Error),
OutOfMemory,
FeatureNotEnabled { feature: String, operation: String },
SystemError(String),
InvalidInput(String),
InvalidData {
message: String,
position: Option<usize>,
},
DataTypeUnsupported {
source: String,
dtype: String,
expected: String,
},
NullValueNotAllowed {
source: String,
column: Option<String>,
null_count: usize,
},
DataExtractionFailed { source: String, message: String },
LatexError(String),
TypstError(String),
PerformanceLimit {
limit_type: String,
actual: usize,
maximum: usize,
},
DataShaderError {
message: String,
cause: Option<String>,
},
AggregationError {
operation: String,
data_points: usize,
error: String,
},
DataShaderCanvasError {
width: u32,
height: u32,
max_pixels: usize,
},
AtomicOperationError(String),
ParallelRenderError { threads: usize, error: String },
ThreadPoolError(String),
SynchronizationError(String),
WorkStealingError(String),
GpuNotAvailable(String),
GpuInitError { backend: String, error: String },
GpuMemoryError {
requested: usize,
available: Option<usize>,
},
ShaderError { shader_type: String, error: String },
BufferError(String),
CommandError(String),
DeviceLost,
UnsupportedGpuFeature(String),
GpuTimeoutError,
SimdNotAvailable,
SimdAlignmentError { required: usize, actual: usize },
PoolInitError(String),
PoolExhausted { pool_type: String, requested: usize },
PoolCorruption(String),
}
impl fmt::Display for PlottingError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
PlottingError::DataLengthMismatch {
x_len,
y_len,
series_index,
} => {
if let Some(idx) = series_index {
write!(
f,
"Data length mismatch in series {}: x has {} elements, y has {} elements",
idx, x_len, y_len
)
} else {
write!(
f,
"Data length mismatch: x has {} elements, y has {} elements",
x_len, y_len
)
}
}
PlottingError::EmptyDataSet => {
write!(
f,
"Empty data set provided - at least one data point is required"
)
}
PlottingError::NoDataSeries => {
write!(
f,
"No data series added to plot - use line(), scatter(), or bar() to add data"
)
}
PlottingError::InvalidColor(color) => {
write!(f, "Invalid color specification: '{}'", color)
}
PlottingError::InvalidDimensions { width, height } => {
write!(
f,
"Invalid dimensions: {}x{} (minimum 100x100)",
width, height
)
}
PlottingError::InvalidDPI(dpi) => {
write!(f, "Invalid DPI: {} (minimum 72)", dpi)
}
PlottingError::InvalidLineWidth(width) => {
write!(f, "Invalid line width: {} (must be positive)", width)
}
PlottingError::InvalidAlpha(alpha) => {
write!(
f,
"Invalid alpha value: {} (must be between 0.0 and 1.0)",
alpha
)
}
PlottingError::InvalidMargin(margin) => {
write!(
f,
"Invalid margin: {} (must be between 0.0 and 0.5)",
margin
)
}
PlottingError::FontError(msg) => {
write!(f, "Font error: {}", msg)
}
PlottingError::ThemeError(msg) => {
write!(f, "Theme error: {}", msg)
}
PlottingError::RenderError(msg) => {
write!(f, "Rendering error: {}", msg)
}
PlottingError::UnsupportedFormat(format) => {
write!(f, "Unsupported export format: '{}'", format)
}
PlottingError::IoError(err) => {
write!(f, "I/O error: {}", err)
}
PlottingError::OutOfMemory => {
write!(f, "Out of memory during plotting operation")
}
PlottingError::FeatureNotEnabled { feature, operation } => {
write!(
f,
"Feature '{}' not enabled - required for operation: {}",
feature, operation
)
}
PlottingError::SystemError(msg) => {
write!(f, "System error: {}", msg)
}
PlottingError::InvalidInput(msg) => {
write!(f, "Invalid input: {}", msg)
}
PlottingError::InvalidData { message, position } => match position {
Some(pos) => write!(f, "Invalid data at position {}: {}", pos, message),
None => write!(f, "Invalid data: {}", message),
},
PlottingError::DataTypeUnsupported {
source,
dtype,
expected,
} => {
write!(
f,
"Unsupported data type from {}: {} (expected {})",
source, dtype, expected
)
}
PlottingError::NullValueNotAllowed {
source,
column,
null_count,
} => match column {
Some(name) => write!(
f,
"Null values are not allowed for {}.{} ({} null values found)",
source, name, null_count
),
None => write!(
f,
"Null values are not allowed for {} ({} null values found)",
source, null_count
),
},
PlottingError::DataExtractionFailed { source, message } => {
write!(
f,
"Failed to extract numeric data from {}: {}",
source, message
)
}
PlottingError::LatexError(msg) => {
write!(f, "LaTeX rendering error: {}", msg)
}
PlottingError::TypstError(msg) => {
write!(f, "Typst rendering error: {}", msg)
}
PlottingError::PerformanceLimit {
limit_type,
actual,
maximum,
} => {
write!(
f,
"{} limit exceeded: {} (maximum {})",
limit_type, actual, maximum
)
}
PlottingError::DataShaderError { message, cause } => match cause {
Some(c) => write!(f, "DataShader error: {} (cause: {})", message, c),
None => write!(f, "DataShader error: {}", message),
},
PlottingError::AggregationError {
operation,
data_points,
error,
} => {
write!(
f,
"Aggregation '{}' failed on {} points: {}",
operation, data_points, error
)
}
PlottingError::DataShaderCanvasError {
width,
height,
max_pixels,
} => {
write!(
f,
"DataShader canvas {}x{} exceeds maximum {} pixels",
width, height, max_pixels
)
}
PlottingError::AtomicOperationError(msg) => {
write!(f, "Atomic operation error: {}", msg)
}
PlottingError::ParallelRenderError { threads, error } => {
write!(
f,
"Parallel rendering failed with {} threads: {}",
threads, error
)
}
PlottingError::ThreadPoolError(msg) => {
write!(f, "Thread pool error: {}", msg)
}
PlottingError::SynchronizationError(msg) => {
write!(f, "Thread synchronization error: {}", msg)
}
PlottingError::WorkStealingError(msg) => {
write!(f, "Work stealing queue error: {}", msg)
}
PlottingError::GpuNotAvailable(msg) => {
write!(f, "GPU not available: {}", msg)
}
PlottingError::GpuInitError { backend, error } => {
write!(f, "GPU initialization failed for {}: {}", backend, error)
}
PlottingError::GpuMemoryError {
requested,
available,
} => match available {
Some(avail) => write!(
f,
"GPU memory allocation failed: requested {} bytes, only {} available",
requested, avail
),
None => write!(
f,
"GPU memory allocation failed: requested {} bytes",
requested
),
},
PlottingError::ShaderError { shader_type, error } => {
write!(
f,
"Shader compilation failed for {}: {}",
shader_type, error
)
}
PlottingError::BufferError(msg) => {
write!(f, "GPU buffer error: {}", msg)
}
PlottingError::CommandError(msg) => {
write!(f, "GPU command error: {}", msg)
}
PlottingError::DeviceLost => {
write!(f, "GPU device lost - try restarting the application")
}
PlottingError::UnsupportedGpuFeature(feature) => {
write!(f, "GPU feature '{}' not supported on this device", feature)
}
PlottingError::GpuTimeoutError => {
write!(f, "GPU operation timed out")
}
PlottingError::SimdNotAvailable => {
write!(f, "SIMD instructions not available on this CPU")
}
PlottingError::SimdAlignmentError { required, actual } => {
write!(
f,
"SIMD alignment error: required {}-byte alignment, got {}",
required, actual
)
}
PlottingError::PoolInitError(msg) => {
write!(f, "Memory pool initialization failed: {}", msg)
}
PlottingError::PoolExhausted {
pool_type,
requested,
} => {
write!(
f,
"{} pool exhausted: {} bytes requested",
pool_type, requested
)
}
PlottingError::PoolCorruption(msg) => {
write!(f, "Memory pool corruption detected: {}", msg)
}
}
}
}
impl std::error::Error for PlottingError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match self {
PlottingError::IoError(err) => Some(err),
_ => None,
}
}
}
impl From<std::io::Error> for PlottingError {
fn from(err: std::io::Error) -> Self {
PlottingError::IoError(err)
}
}
impl From<crate::render::ColorError> for PlottingError {
fn from(err: crate::render::ColorError) -> Self {
PlottingError::InvalidColor(err.to_string())
}
}
#[cfg(feature = "gpu")]
impl From<crate::render::gpu::GpuError> for PlottingError {
fn from(err: crate::render::gpu::GpuError) -> Self {
use crate::render::gpu::GpuError;
match err {
GpuError::InitializationFailed(msg) => PlottingError::GpuInitError {
backend: "wgpu".to_string(),
error: msg,
},
GpuError::BufferCreationFailed(msg) => PlottingError::BufferError(msg),
GpuError::BufferOperationFailed(msg) => PlottingError::BufferError(msg),
GpuError::OperationFailed(msg) => PlottingError::CommandError(msg),
}
}
}
impl PlottingError {
pub fn validate_data(data: &[f64]) -> Result<()> {
for (i, &value) in data.iter().enumerate() {
if value.is_nan() {
return Err(PlottingError::InvalidData {
message: "NaN value found in data".to_string(),
position: Some(i),
});
}
if value.is_infinite() {
return Err(PlottingError::InvalidData {
message: format!("Infinite value ({}) found in data", value),
position: Some(i),
});
}
}
Ok(())
}
pub fn validate_dimensions(width: u32, height: u32) -> Result<()> {
const MIN_DIMENSION: u32 = 100;
const MAX_DIMENSION: u32 = 16384;
if width < MIN_DIMENSION || height < MIN_DIMENSION {
return Err(PlottingError::InvalidDimensions { width, height });
}
if width > MAX_DIMENSION || height > MAX_DIMENSION {
return Err(PlottingError::PerformanceLimit {
limit_type: "Image dimension".to_string(),
actual: width.max(height) as usize,
maximum: MAX_DIMENSION as usize,
});
}
Ok(())
}
pub fn validate_dpi(dpi: u32) -> Result<()> {
const MIN_DPI: u32 = 72;
const MAX_DPI: u32 = 2400;
if dpi < MIN_DPI {
return Err(PlottingError::InvalidDPI(dpi));
}
if dpi > MAX_DPI {
return Err(PlottingError::PerformanceLimit {
limit_type: "DPI".to_string(),
actual: dpi as usize,
maximum: MAX_DPI as usize,
});
}
Ok(())
}
pub fn check_performance_limits(data_points: usize) -> Result<()> {
const SOFT_LIMIT: usize = 1_000_000; const HARD_LIMIT: usize = 100_000_000;
if data_points > HARD_LIMIT {
return Err(PlottingError::PerformanceLimit {
limit_type: "Data points".to_string(),
actual: data_points,
maximum: HARD_LIMIT,
});
}
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::error::Error;
#[test]
fn test_error_display() {
let err = PlottingError::DataLengthMismatch {
x_len: 5,
y_len: 3,
series_index: None,
};
assert!(err.to_string().contains("mismatch"));
assert!(err.to_string().contains("5"));
assert!(err.to_string().contains("3"));
}
#[test]
fn test_data_validation() {
let valid_data = vec![1.0, 2.0, 3.0, 4.0];
assert!(PlottingError::validate_data(&valid_data).is_ok());
let nan_data = vec![1.0, f64::NAN, 3.0];
assert!(PlottingError::validate_data(&nan_data).is_err());
let inf_data = vec![1.0, f64::INFINITY, 3.0];
assert!(PlottingError::validate_data(&inf_data).is_err());
}
#[test]
fn test_dimension_validation() {
assert!(PlottingError::validate_dimensions(800, 600).is_ok());
assert!(PlottingError::validate_dimensions(50, 50).is_err());
assert!(PlottingError::validate_dimensions(20000, 20000).is_err());
}
#[test]
fn test_dpi_validation() {
assert!(PlottingError::validate_dpi(300).is_ok());
assert!(PlottingError::validate_dpi(50).is_err());
assert!(PlottingError::validate_dpi(5000).is_err());
}
#[test]
fn test_performance_limits() {
assert!(PlottingError::check_performance_limits(10000).is_ok());
assert!(PlottingError::check_performance_limits(1_000_000).is_ok());
assert!(PlottingError::check_performance_limits(200_000_000).is_err());
}
#[test]
fn test_error_source() {
let io_err = std::io::Error::new(std::io::ErrorKind::NotFound, "file not found");
let plot_err = PlottingError::from(io_err);
assert!(plot_err.source().is_some());
}
#[test]
fn test_color_error_conversion() {
let color_err = crate::render::ColorError::InvalidHex;
let plot_err = PlottingError::from(color_err);
match plot_err {
PlottingError::InvalidColor(_) => (),
_ => panic!("Expected InvalidColor"),
}
}
}