在scotty进行错误处理

January 18, 2014

在最后两个星期才开始写这学期的项目设计,AngularJS也是现学现卖,虽然时间有点赶,我自己还是比较满意的。因为是个挺RESTful的东西,服务器端是用scotty写的,最初有很多嵌套的if…then…else处理错误:

handleSend :: AppState -> ActionM ()
handleSend AppState{..} = do
    logined <- isLogined tokenStore
    when logined $ do
        msend <- jsonDataMaybe
        resp <- case msend of
            Nothing  -> return $ Fail 0 "请输入完整信息"
            Just req@SendMsg{..} -> do
                isfriend <- isFriend database from to
                if (not isfriend)
                    then return $ Fail 1 "发送失败"
                    else do
                        tmsg <- liftIO $ tagTime req
                        liftIO $ void $ forkIO $ do
                            sendMessage messageQueue (TM tmsg)
                            insertMessage database (TM tmsg)
                        return $ Success tmsg
        jsonResp resp

简直无法直视啊。其实我需要的只是early return。想过用monad transformer,但是只能实现类似ActionT a (ErrorT b IO)的monad,ErrorTActionT之下,在ErrorT里的短路是不会影响到ActionT的。后来我才发现ActionT其实就是有ErrorT的,可以直接用。

handleSend :: AppState -> ActionT ErrorResult IO ()
handleSend AppState{..} = handleError $ do
    isLogined tokenStore `expectTrue` Unauth

    req@SendMsg{..} <- jsonData <?> Fail 0 "请输入完整信息"

    isFriend database from to `expectTrue` Fail 1 "发送失败"

    tmsg <- liftIO $ tagTime req
    liftIO $ void $ forkIO $ do
        sendMessage messageQueue (TM tmsg)
        insertMessage database (TM tmsg)
    json $ Success tmsg

其中用到一些自己定义的函数,用来检查结果和加上自定义的错误类型(ErrorResult ):

act <?> err = act `rescue` (const $ raise err)

expectTrue act err = do
    true <- act
    when (not true) (raise err)

expectRight act err = act >>= either (const $ raise err) return

expectJust act err = act >>= maybe (raise err) return

handleError act = act `rescue` json

还可以defaultHandler替换全局的异常处理函数。另一个问题是raiserescue只能处理ErrorT里的错误,不能处理异常(Control.Exception),那些异常还是要自己处理。


正文:

好像是两天前,带着报告和笔记本准备去答辩,徘徊了半个钟才找到老师。

老师: 答辩还是交报告?

我:交报告

啊,终于不用被答辩啦,虽然感觉不太对劲。