You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何抛出及自定义tokio_postgres::Error?用户创建场景下邮箱重复验证的错误处理问题

解决tokio_postgres::Error无法直接实例化的问题

你遇到的错误核心原因是:tokio_postgres::Error的结构体是私有的,它的内部构造逻辑对外不可见,所以不能直接用tokio_postgres::Error("User already exists")这种方式手动创建实例。结合你的业务场景(邮箱重复验证),更合理的做法是自定义业务错误类型,把数据库错误和业务逻辑错误统一管理,下面是具体的修改方案:

1. 定义自定义错误枚举

先创建一个包含业务错误和数据库错误的枚举类型,同时实现From<tokio_postgres::Error> trait,这样就能自动把数据库错误转换成自定义错误,简化后续的错误处理:

use tokio_postgres::{Error as PostgresError, error::SqlState};

#[derive(Debug)]
pub enum AppError {
    // 封装原生的数据库错误
    Postgres(PostgresError),
    // 业务错误:用户已存在,携带重复的邮箱地址
    UserAlreadyExists(String),
}

// 实现From trait,让PostgresError可以自动转为AppError
impl From<PostgresError> for AppError {
    fn from(err: PostgresError) -> Self {
        AppError::Postgres(err)
    }
}

// 可选:实现Display trait,让错误信息更友好
impl std::fmt::Display for AppError {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            AppError::Postgres(e) => write!(f, "数据库错误: {}", e),
            AppError::UserAlreadyExists(email) => write!(f, "邮箱 {} 已存在", email),
        }
    }
}

// 可选:实现Error trait,让自定义错误符合Rust标准错误类型规范
impl std::error::Error for AppError {}

2. 优化插入用户的函数逻辑

调整insert_usuario的返回类型为Result<&Usuario, AppError>,同时简化邮箱存在性检查的逻辑(去掉多余的变量,正确区分"用户不存在"和"数据库异常"两种情况):

pub async fn insert_usuario(usuario: &Usuario) -> Result<&Usuario, AppError> { 
    match get_usuario_by_email(&usuario.email).await { 
        Ok(_) => {
            // 查询到用户,说明邮箱已存在,直接返回业务错误
            return Err(AppError::UserAlreadyExists(usuario.email.clone()));
        }
        Err(e) => {
            // 检查错误类型:如果是"无数据返回"(即邮箱不存在),则继续执行插入
            if let Some(db_err) = e.as_db_error() {
                if db_err.code() == &SqlState::NO_DATA {
                    // 用户不存在,继续下一步操作
                } else {
                    // 其他数据库错误,返回封装后的错误
                    return Err(e.into());
                }
            } else {
                // 非数据库类错误,返回封装后的错误
                return Err(e.into());
            }
        }
    } 

    // 执行数据库插入操作
    let client = get_client().await?; 
    client.execute(
        "insert into usuario (nome, email, senha) values ($1, $2, $3);", 
        &[&usuario.nome, &usuario.email, &usuario.senha]
    ).await?; 

    Ok(usuario) 
}

关键细节说明

  • 为什么不用tokio_postgres::Error承载业务错误?因为该类型是专门为数据库相关异常设计的,"用户已存在"属于业务逻辑错误,用自定义错误更清晰,也符合单一职责原则。
  • as_db_error()方法可以把PostgresError转换成DbError,方便我们通过SQL状态码判断错误类型(比如NO_DATA表示查询不到数据,对应邮箱不存在的正常情况)。
  • 通过From trait的实现,我们可以在代码中直接用?操作符把PostgresError自动转为AppError,不用手动处理转换逻辑,让代码更简洁。

内容的提问来源于stack exchange,提问作者Marcio Sansone

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.28 17:52:43