在Servant中捕获IO异常:SQLite约束冲突返回409响应
在Servant Handler中捕获SQLError并返回409响应
当然可以在Servant的Handler monad里捕获SQLError异常!这其实是处理用户名唯一约束冲突场景的更优方案——相比让DB.saveUser返回Maybe Int,保留异常能更清晰地传递错误类型信息,也更符合Haskell处理IO错误的惯用方式。
实现步骤
- 导入必要模块:需要引入异常捕获、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)
- 修改
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错误
- 保留原
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
相关产品推荐
相关产品推荐

