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

在Servant中捕获IO异常:SQLite约束冲突返回409响应

在Servant Handler中捕获SQLError并返回409响应

当然可以在Servant的Handler monad里捕获SQLError异常!这其实是处理用户名唯一约束冲突场景的更优方案——相比让DB.saveUser返回Maybe Int,保留异常能更清晰地传递错误类型信息,也更符合Haskell处理IO错误的惯用方式。

实现步骤

  1. 导入必要模块:需要引入异常捕获、SQLite错误类型以及Servant的错误处理相关模块:
import Control.Exception (catch)
import Database.SQLite.Simple.Error (SQLError(..), ErrorType(ErrorConstraint))
import Servant.Server (Handler, throwError, err409, errBody)
import Data.Text (Text, pack)
  1. 修改createUser函数捕获异常:使用liftIO结合catch拦截SQLError,当判断是约束冲突时抛出409错误,否则继续处理正常流程:
createUser :: UserReq -> Handler (Headers '[Header "Location" Text] NoContent)
createUser ur = do
  userId <- liftIO $ DB.saveUser ur `catch` handleConstraintError
  return . addHeader (pack ("/user/" ++ show userId)) $ NoContent
  where
    handleConstraintError :: SQLError -> IO Int
    handleConstraintError e@SQLError{sqlErrorType = ErrorConstraint} =
      throwIO $ err409 { errBody = pack "Username already exists" }
    handleConstraintError e = throwIO e -- 重新抛出其他类型的SQL错误
  1. 保留原saveUser函数不变:你的saveUser实现已经正确在约束冲突时抛出ErrorConstraint类型的SQLError,不需要修改:
saveUser (UR.UserReq name) = withConnection database $ \conn -> do
  executeNamed conn "INSERT INTO users (name) VALUES (:name)" [":name" := name]
  fromIntegral <$> lastInsertRowId conn

方案优势

  • 精准错误区分:能明确区分是用户名重复的约束错误,还是其他SQL异常(比如连接失败、语法错误等),后续可扩展处理其他错误类型;
  • 语义清晰:异常机制天然对应IO操作中的意外错误场景,比Maybe更能表达“操作失败是因为违反业务规则”的语义;
  • 代码侵入性低:不需要修改数据库层的函数签名,保持DB模块的职责单一(只负责数据持久化,不处理HTTP层面的错误)。

内容的提问来源于stack exchange,提问作者Teresa Siegmantel

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.12 03:50:33