robotech 1.5.0

Backend service implementation for the RoboTech platform, providing RESTful APIs and business logic for web applications.
use crate::dao::eo::{ForeignKey, UniqueKey};
use crate::dao::{calc_key_of_foreign_key, get_from_foreign_keys, get_from_unique_keys};
use anyhow::anyhow;
use idworker::IdWorkerError;
use regex::{Captures, Regex};
use robotech_macros::log_call;
use sea_orm::DbErr;
use std::sync::LazyLock;
use std::time::SystemTimeError;

/// # 正则匹配重复键错误-Postgres
/// 格式: duplicate key value violates unique constraint "...", detail: Some("Key (<字段名>)=(<字段值>) already exists."), ...
static REGEX_DUPLICATE_KEY_POSTGRES: LazyLock<Regex> = LazyLock::new(|| {
    Regex::new(r#"duplicate key value violates unique constraint \\"(?P<ak_name>[^']*)\\"", detail: Some\("Key \(([^)]+)\)=\((?P<value>[^)]*)\) already exists\."#)
        .expect("正则表达式错误")
});

/// # 正则匹配重复键错误-MySQL
/// 格式: Duplicate entry '<字段值>' for key '<字段名>'
static REGEX_DUPLICATE_KEY_MYSQL: LazyLock<Regex> = LazyLock::new(|| {
    Regex::new(r#"Duplicate entry '(?P<value>[^']+)' for key '(?P<ak_name>[^']*)'"#)
        .expect("正则表达式错误")
});

/// # 正则匹配插入(或更新)操作违反了约束条件错误-Postgres
/// 格式: insert or update on table "..." violates foreign key constraint "..."
static REGEX_INSERT_VIOLATE_FK_POSTGRES: LazyLock<Regex> = LazyLock::new(|| {
    Regex::new(r#"insert or update on table \\"(?P<fk_table>[A-Za-z_0-9]+)\\" violates foreign key constraint \\"fk_(?P<fk_column>[A-Za-z_0-9]+)__from__(?P<pk_table>[A-Za-z_0-9]+)"#).expect("正则表达式错误")
});

/// # 正则匹配插入(或更新)操作违反了约束条件错误-MySQL
/// 格式: Cannot add or update a child row: a foreign key constraint fails (`db_name`.`table_name`, CONSTRAINT `fk_column_name` FOREIGN KEY (`column_name`) REFERENCES `ref_table_name`)
static REGEX_INSERT_VIOLATE_FK_MYSQL: LazyLock<Regex> = LazyLock::new(|| {
    Regex::new(r#"Cannot add or update a child row: a foreign key constraint fails \(`[A-Za-z_0-9]+`\.`(?P<fk_table>[A-Za-z_0-9]+)`, CONSTRAINT `[A-Za-z_0-9]+` FOREIGN KEY \(`(?P<fk_column>[A-Za-z_0-9]+)`\) REFERENCES `(?P<pk_table>[A-Za-z_0-9]+)`"#).expect("正则表达式错误")
});

/// # 正则匹配删除(或更新)操作违反了约束条件错误-Postgres
/// 格式: update or delete on table "..." violates foreign key constraint "..." on table "..."
static REGEX_DELETE_VIOLATE_FK_POSTGRES: LazyLock<Regex> = LazyLock::new(|| {
    Regex::new(r#"update or delete on table \\"(?P<pk_table>[A-Za-z_0-9]+)\\" violates foreign key constraint \\"fk_(?P<fk_column>[A-Za-z_0-9]+)__from__[A-Za-z_0-9]+\\" on table \\"(?P<fk_table>[A-Za-z_0-9]+)\\""#).expect("正则表达式错误")
});

/// # 正则匹配删除(或更新)操作违反了约束条件错误-MySQL
/// 格式:
static REGEX_DELETE_VIOLATE_FK_MYSQL: LazyLock<Regex> = LazyLock::new(|| {
    Regex::new(r#"Cannot delete or update a parent row: a foreign key constraint fails \(`[A-Za-z_0-9]+`\.`(?P<fk_table>[A-Za-z_0-9]+)`, CONSTRAINT `[A-Za-z_0-9]+` FOREIGN KEY \(`(?P<fk_column>[A-Za-z_0-9]+)`\) REFERENCES `(?P<pk_table>[A-Za-z_0-9]+)`"#).expect("正则表达式错误")
});

/// # 自定义服务层的错误枚举
///
/// 该枚举定义了服务层可能遇到的各种错误类型,包括数据未找到、重复键约束违反、
/// IO错误和数据库错误。这些错误类型用于在服务层统一处理各种异常情况,
/// 并提供清晰的错误信息反馈给调用方。
///
/// ## 错误类型说明
/// - `NotFound`: 表示请求的数据未找到,通常用于查询操作
/// - `DuplicateKey`: 表示违反了唯一性约束,如重复的用户名或邮箱
/// - `IoError`: 表示输入输出相关的错误,如文件读写失败
/// - `DatabaseError`: 表示底层数据库操作发生的错误
#[derive(Debug, thiserror::Error)]
pub enum DaoError {
    #[error("运行时错误: {0}")]
    Runtime(#[from] anyhow::Error),
    #[error("获取DB_CONN错误")]
    GetDbConn(),
    #[error("系统时钟错误: {0}")]
    SystemTime(#[from] SystemTimeError),
    #[error("ID工作者错误: {0}")]
    IdWorker(#[from] IdWorkerError),
    #[error("重复键错误: {0} -> {1}")]
    DuplicateKey(UniqueKey, String),
    #[error("插入(或更新)操作违反了数据库外键约束条件: {0}")]
    InsertViolateFk(ForeignKey),
    #[error("删除(或更新)操作违反了数据库外键约束条件: {0}")]
    DeleteViolateFk(ForeignKey),
    #[error("数据库错误: {0}")]
    Db(#[from] DbErr),
    #[error("未初始化错误: {0}")]
    NotInitialized(String),
    #[error("已经初始化错误: {0}")]
    AlreadyInitialized(String),
}

impl DaoError {
    /// # 处理数据库错误,并转换为服务层错误
    ///
    /// 该函数用于将数据库层的错误(DbErr)转换为服务层错误(SvcError),
    /// 特别处理了重复键错误,能够识别Postgres和MySQL的重复键错误格式,
    /// 并将其转换为带有字段名称和值的DuplicateKey错误。
    ///
    /// ## 参数
    /// * `db_err` - 数据库错误对象
    /// * `unique_key_hashmap` - 用于映射数据库列名到业务字段名的哈希表
    ///
    /// ## 返回值
    /// 返回对应的SvcError服务层错误对象
    #[log_call(level = warn, mode = enter)]
    pub fn parse_db_err(db_err: DbErr) -> DaoError {
        let db_err_string = format!("{:?}", db_err);
        if let Some(caps) = REGEX_DUPLICATE_KEY_POSTGRES.captures(&db_err_string) {
            // 正则匹配重复键错误-Postgres
            return Self::parse_duplicate_key(caps);
        } else if let Some(caps) = REGEX_DUPLICATE_KEY_MYSQL.captures(&db_err_string) {
            // 正则匹配重复键错误-MySQL
            return Self::parse_duplicate_key(caps);
        } else if let Some(caps) = REGEX_INSERT_VIOLATE_FK_POSTGRES.captures(&db_err_string) {
            // 正则匹配插入操作违反了约束条件错误-Postgres
            return Self::parse_insert_violate_fk(caps);
        } else if let Some(caps) = REGEX_INSERT_VIOLATE_FK_MYSQL.captures(&db_err_string) {
            // 正则匹配插入操作违反了约束条件错误-MySQL
            return Self::parse_insert_violate_fk(caps);
        } else if let Some(caps) = REGEX_DELETE_VIOLATE_FK_POSTGRES.captures(&db_err_string) {
            // 正则匹配删除操作违反了约束条件错误-Postgres
            return Self::parse_delete_violate_fk(caps);
        } else if let Some(caps) = REGEX_DELETE_VIOLATE_FK_MYSQL.captures(&db_err_string) {
            // 正则匹配删除操作违反了约束条件错误-MySQL
            return Self::parse_delete_violate_fk(caps);
        }

        DaoError::from(db_err)
    }

    /// # 从正则匹配中抓取有用信息转换成重复键错误
    ///
    /// 该函数用于从正则表达式匹配结果中提取重复键错误的相关信息,
    /// 包括冲突的列名和值,并通过映射表转换为业务层的字段名,
    /// 最终构造出一个包含字段名和冲突值的DuplicateKey服务错误。
    ///
    /// ## 参数
    /// * `caps` - 正则表达式匹配结果,包含column和value两个命名捕获组
    /// * `unique_key_hashmap` - 数据库列名到业务字段名的映射表
    ///
    /// ## 返回值
    /// 返回一个包含字段名和冲突值的SvcError::DuplicateKey错误
    fn parse_duplicate_key(caps: Captures) -> DaoError {
        let ak_name = caps["ak_name"].to_lowercase().to_string();
        let value = caps["value"].to_string();
        let unique_filed = match get_from_unique_keys(&ak_name) {
            Ok(Some(unique_filed)) => unique_filed,
            Ok(None) => {
                return DaoError::from(anyhow!(format!("获取unique字段列表错误: {ak_name}不存在")));
            }
            Err(e) => {
                return DaoError::from(anyhow!(format!("获取unique字段列表错误: {e}")));
            }
        };

        DaoError::DuplicateKey(unique_filed.clone(), value)
    }

    fn parse_violate_fk(caps: Captures) -> Result<ForeignKey, DaoError> {
        let fk_table = caps["fk_table"].to_string();
        let fk_column = caps["fk_column"].to_string();
        let pk_table = caps["pk_table"].to_string();

        let key = calc_key_of_foreign_key(&fk_table, &fk_column, &pk_table);

        let foreign_key = match get_from_foreign_keys(&key) {
            Ok(Some(foreign_key)) => foreign_key,
            Ok(None) => {
                return Err(DaoError::from(anyhow!(format!(
                    "获取foreign key列表错误: {key}不存在"
                ))))?;
            }
            Err(e) => {
                return Err(DaoError::from(anyhow!(format!(
                    "获取foreign key列表错误: {e}"
                ))))?;
            }
        };

        Ok(foreign_key.clone())
    }

    fn parse_insert_violate_fk(caps: Captures) -> DaoError {
        match Self::parse_violate_fk(caps) {
            Ok(foreign_key) => DaoError::InsertViolateFk(foreign_key),
            Err(e) => e,
        }
    }

    fn parse_delete_violate_fk(caps: Captures) -> DaoError {
        match Self::parse_violate_fk(caps) {
            Ok(foreign_key) => DaoError::DeleteViolateFk(foreign_key),
            Err(e) => e,
        }
    }
}