use thiserror::Error;
use std::num::ParseFloatError;
use std::num::ParseIntError;
#[derive(Error, Debug)]
pub enum CadError {
#[error("几何错误: {message}")]
Geometry {
message: String,
#[source]
source: Option<Box<dyn std::error::Error + Send + Sync>>,
},
#[error("渲染错误: {message}")]
Render {
message: String,
#[source]
source: Option<Box<dyn std::error::Error + Send + Sync>>,
},
#[error("IO 错误: {source}")]
IO {
#[from]
source: std::io::Error,
},
#[error("解析错误: {message}")]
Parse {
message: String,
#[source]
source: Option<Box<dyn std::error::Error + Send + Sync>>,
},
#[error("验证错误: {message}")]
Validation {
message: String,
field: Option<String>,
},
#[error("变换错误: {message}")]
Transform {
message: String,
#[source]
source: Option<Box<dyn std::error::Error + Send + Sync>>,
},
#[error("索引错误: {message}")]
Index {
message: String,
index: Option<usize>,
max_index: Option<usize>,
},
#[error("文档错误: {message}")]
Document {
message: String,
#[source]
source: Option<Box<dyn std::error::Error + Send + Sync>>,
},
#[error("层错误: {message}")]
Layer {
message: String,
layer_name: Option<String>,
},
#[error("块错误: {message}")]
Block {
message: String,
block_name: Option<String>,
},
#[error("实体错误: {entity_type} - {message}")]
Entity {
entity_type: String,
entity_id: Option<String>,
message: String,
},
#[error("捕捉错误: {message}")]
Snap {
message: String,
snap_type: Option<String>,
},
#[error("命令错误: {command} - {message}")]
Command {
command: String,
message: String,
},
#[error("文件格式错误: {format} - {message}")]
Format {
format: String,
message: String,
#[source]
source: Option<Box<dyn std::error::Error + Send + Sync>>,
},
#[error("序列化错误: {message}")]
Serialization {
message: String,
#[source]
source: Option<Box<dyn std::error::Error + Send + Sync>>,
},
#[error("未知错误: {message}")]
Unknown {
message: String,
#[source]
source: Option<Box<dyn std::error::Error + Send + Sync>>,
},
}
impl CadError {
pub fn geometry(message: impl Into<String>) -> Self {
CadError::Geometry {
message: message.into(),
source: None,
}
}
pub fn geometry_with_source(
message: impl Into<String>,
source: impl Into<Box<dyn std::error::Error + Send + Sync>>,
) -> Self {
CadError::Geometry {
message: message.into(),
source: Some(source.into()),
}
}
pub fn render(message: impl Into<String>) -> Self {
CadError::Render {
message: message.into(),
source: None,
}
}
pub fn validation(message: impl Into<String>) -> Self {
CadError::Validation {
message: message.into(),
field: None,
}
}
pub fn validation_with_field(message: impl Into<String>, field: impl Into<String>) -> Self {
CadError::Validation {
message: message.into(),
field: Some(field.into()),
}
}
pub fn transform(message: impl Into<String>) -> Self {
CadError::Transform {
message: message.into(),
source: None,
}
}
pub fn index(message: impl Into<String>, index: usize, max_index: usize) -> Self {
CadError::Index {
message: message.into(),
index: Some(index),
max_index: Some(max_index),
}
}
pub fn command(command: impl Into<String>, message: impl Into<String>) -> Self {
CadError::Command {
command: command.into(),
message: message.into(),
}
}
pub fn document(message: impl Into<String>) -> Self {
CadError::Document {
message: message.into(),
source: None,
}
}
pub fn layer(message: impl Into<String>, layer_name: impl Into<String>) -> Self {
CadError::Layer {
message: message.into(),
layer_name: Some(layer_name.into()),
}
}
pub fn block(message: impl Into<String>, block_name: impl Into<String>) -> Self {
CadError::Block {
message: message.into(),
block_name: Some(block_name.into()),
}
}
pub fn entity(
entity_type: impl Into<String>,
message: impl Into<String>,
) -> Self {
CadError::Entity {
entity_type: entity_type.into(),
entity_id: None,
message: message.into(),
}
}
pub fn snap(message: impl Into<String>, snap_type: impl Into<String>) -> Self {
CadError::Snap {
message: message.into(),
snap_type: Some(snap_type.into()),
}
}
pub fn format(format: impl Into<String>, message: impl Into<String>) -> Self {
CadError::Format {
format: format.into(),
message: message.into(),
source: None,
}
}
pub fn serialization(message: impl Into<String>) -> Self {
CadError::Serialization {
message: message.into(),
source: None,
}
}
pub fn is_transient(&self) -> bool {
matches!(self, CadError::IO { .. } | CadError::Parse { .. })
}
pub fn is_fatal(&self) -> bool {
matches!(
self,
CadError::Validation { .. } | CadError::Format { .. }
)
}
pub fn user_message(&self) -> String {
match self {
CadError::Geometry { message, .. } => format!("几何计算问题: {}", message),
CadError::Render { message, .. } => format!("渲染问题: {}", message),
CadError::IO { source, .. } => format!("文件操作问题: {}", source),
CadError::Parse { message, .. } => format!("数据解析问题: {}", message),
CadError::Validation { message, field, .. } => {
if let Some(field) = field {
format!("输入验证问题 ({}): {}", field, message)
} else {
format!("输入验证问题: {}", message)
}
}
CadError::Transform { message, .. } => format!("坐标变换问题: {}", message),
CadError::Index { message, index, max_index, .. } => {
if let (Some(idx), Some(max)) = (index, max_index) {
format!("{} (索引 {} 超出范围 0-{})", message, idx, max)
} else {
format!("索引问题: {}", message)
}
}
CadError::Document { message, .. } => format!("文档操作问题: {}", message),
CadError::Layer { message, layer_name, .. } => {
if let Some(name) = layer_name {
format!("图层 '{}' 问题: {}", name, message)
} else {
format!("图层问题: {}", message)
}
}
CadError::Block { message, block_name, .. } => {
if let Some(name) = block_name {
format!("图块 '{}' 问题: {}", name, message)
} else {
format!("图块问题: {}", message)
}
}
CadError::Entity {
entity_type, message, ..
} => format!("{} 对象问题: {}", entity_type, message),
CadError::Snap { message, .. } => format!("捕捉点问题: {}", message),
CadError::Command { command, message, .. } => {
format!("命令 '{}' 执行问题: {}", command, message)
}
CadError::Format { format, message, .. } => {
format!("{} 文件格式问题: {}", format, message)
}
CadError::Serialization { message, .. } => format!("数据保存问题: {}", message),
CadError::Unknown { message, .. } => format!("意外问题: {}", message),
}
}
}
impl From<ParseFloatError> for CadError {
fn from(error: ParseFloatError) -> Self {
CadError::Parse {
message: error.to_string(),
source: Some(Box::new(error)),
}
}
}
impl From<ParseIntError> for CadError {
fn from(error: ParseIntError) -> Self {
CadError::Parse {
message: error.to_string(),
source: Some(Box::new(error)),
}
}
}
impl From<serde_json::Error> for CadError {
fn from(error: serde_json::Error) -> Self {
CadError::Serialization {
message: error.to_string(),
source: Some(Box::new(error)),
}
}
}
#[cfg(feature = "io")]
impl From<quick_xml::Error> for CadError {
fn from(error: quick_xml::Error) -> Self {
CadError::Parse {
message: error.to_string(),
source: Some(Box::new(error)),
}
}
}
pub type CadResult<T> = Result<T, CadError>;
pub trait Validate {
fn validate(&self) -> CadResult<()>;
}
pub mod validation {
use super::*;
#[inline]
pub fn positive(value: f64, name: &str) -> CadResult<f64> {
if value > 0.0 {
Ok(value)
} else {
Err(CadError::validation_with_field(
format!("{} 必须大于 0,实际值: {}", name, value),
name,
))
}
}
#[inline]
pub fn non_negative(value: f64, name: &str) -> CadResult<f64> {
if value >= 0.0 {
Ok(value)
} else {
Err(CadError::validation_with_field(
format!("{} 不能为负数,实际值: {}", name, value),
name,
))
}
}
#[inline]
pub fn in_range(value: f64, min: f64, max: f64, name: &str) -> CadResult<f64> {
if value >= min && value <= max {
Ok(value)
} else {
Err(CadError::validation_with_field(
format!("{} 必须在 [{}, {}] 范围内,实际值: {}", name, min, max, value),
name,
))
}
}
#[inline]
pub fn index<T>(index: usize, max: usize, name: &str) -> CadResult<usize> {
if index < max {
Ok(index)
} else {
Err(CadError::index(
format!("{} 索引超出范围", name),
index,
max.saturating_sub(1),
))
}
}
#[inline]
pub fn non_empty<'a>(value: &'a str, name: &str) -> CadResult<&'a str> {
if !value.is_empty() {
Ok(value)
} else {
Err(CadError::validation_with_field(
format!("{} 不能为空字符串", name),
name,
))
}
}
#[inline]
pub fn scale_factor(value: f64, name: &str) -> CadResult<f64> {
if value.is_finite() && value > 0.0 {
Ok(value)
} else {
Err(CadError::validation_with_field(
format!("{} 必须是有效正数,实际值: {}", name, value),
name,
))
}
}
#[inline]
pub fn angle_radians(value: f64, name: &str) -> CadResult<f64> {
if value.is_finite() {
Ok(value)
} else {
Err(CadError::validation_with_field(
format!("{} 必须是有效角度值,实际值: {}", name, value),
name,
))
}
}
#[inline]
pub fn angle_degrees(value: f64, name: &str) -> CadResult<f64> {
if value.is_finite() {
Ok(value)
} else {
Err(CadError::validation_with_field(
format!("{} 必须是有效角度值,实际值: {}", name, value),
name,
))
}
}
#[inline]
pub fn coordinate(value: f64, name: &str) -> CadResult<f64> {
if value.is_finite() {
Ok(value)
} else {
Err(CadError::validation_with_field(
format!("{} 必须是有效坐标值,实际值: {}", name, value),
name,
))
}
}
}
pub mod numeric {
use super::*;
#[inline]
pub fn safe_mul(a: f64, b: f64) -> CadResult<f64> {
let result = a * b;
if result.is_finite() {
Ok(result)
} else {
Err(CadError::geometry("乘法运算导致数值溢出"))
}
}
#[inline]
pub fn safe_add(a: f64, b: f64) -> CadResult<f64> {
let result = a + b;
if result.is_finite() {
Ok(result)
} else {
Err(CadError::geometry("加法运算导致数值溢出"))
}
}
#[inline]
pub fn safe_div(a: f64, b: f64, context: &str) -> CadResult<f64> {
if b.abs() > 1e-15 {
let result = a / b;
if result.is_finite() {
Ok(result)
} else {
Err(CadError::geometry(format!("除法运算导致数值溢出: {}", context)))
}
} else {
Err(CadError::geometry(format!("除以零: {}", context)))
}
}
#[inline]
pub fn safe_sqrt(value: f64, context: &str) -> CadResult<f64> {
if value >= 0.0 {
let result = value.sqrt();
if result.is_finite() {
Ok(result)
} else {
Err(CadError::geometry(format!("平方根运算导致数值溢出: {}", context)))
}
} else {
Err(CadError::geometry(format!("负数平方根: {}", context)))
}
}
#[inline]
pub fn degrees_to_radians(degrees: f64) -> CadResult<f64> {
if degrees.is_finite() {
Ok(degrees * std::f64::consts::PI / 180.0)
} else {
Err(CadError::geometry("角度值无效"))
}
}
#[inline]
pub fn radians_to_degrees(radians: f64) -> CadResult<f64> {
if radians.is_finite() {
Ok(radians * 180.0 / std::f64::consts::PI)
} else {
Err(CadError::geometry("弧度值无效"))
}
}
#[inline]
pub fn approx_eq(a: f64, b: f64, epsilon: f64) -> bool {
(a - b).abs() <= epsilon
}
#[inline]
pub fn approx_zero(value: f64, epsilon: f64) -> bool {
value.abs() <= epsilon
}
#[inline]
pub fn lerp(a: f64, b: f64, t: f64) -> CadResult<f64> {
if t.is_finite() && t >= 0.0 && t <= 1.0 {
Ok(a + (b - a) * t)
} else {
Err(CadError::geometry("插值参数 t 必须在 [0, 1] 范围内"))
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_validation_positive() {
assert!(validation::positive(1.0, "test").is_ok());
assert!(validation::positive(0.0, "test").is_err());
assert!(validation::positive(-1.0, "test").is_err());
}
#[test]
fn test_validation_in_range() {
assert!(validation::in_range(5.0, 0.0, 10.0, "test").is_ok());
assert!(validation::in_range(0.0, 0.0, 10.0, "test").is_ok());
assert!(validation::in_range(10.0, 0.0, 10.0, "test").is_ok());
assert!(validation::in_range(-1.0, 0.0, 10.0, "test").is_err());
assert!(validation::in_range(11.0, 0.0, 10.0, "test").is_err());
}
#[test]
fn test_numeric_safe_div() {
assert!(numeric::safe_div(10.0, 2.0, "test").is_ok());
assert!(numeric::safe_div(10.0, 0.0, "test").is_err());
}
#[test]
fn test_numeric_safe_sqrt() {
assert!(numeric::safe_sqrt(16.0, "test").is_ok());
assert!(numeric::safe_sqrt(-1.0, "test").is_err());
}
#[test]
fn test_approx_eq() {
assert!(numeric::approx_eq(1.0, 1.000001, 0.0001));
assert!(!numeric::approx_eq(1.0, 1.001, 0.0001));
}
#[test]
fn test_user_message() {
let error = CadError::validation_with_field("test message", "test_field");
assert!(error.user_message().contains("test_field"));
}
#[test]
fn test_error_from_io() {
let io_error = std::io::Error::new(std::io::ErrorKind::NotFound, "file not found");
let cad_error: CadError = io_error.into();
assert!(matches!(cad_error, CadError::IO { .. }));
}
}