I use servant
for a simple JSON api, that allows you to create users, whose names must be unique. This is enforced by a unique constraint in SQLite. I have a function DB.saveUser :: UserReq -> IO Int
that (unsurprisingly) saves users to SQLite and returns the generated id. It will throw a SQLError ErrorConstraint _ _
if the name has already been taken. I want to return a HTTP response code 409 if that occurs. So my question would be, is there some way to catch the SQLError
in the Handler Monad? If not, what would be the cleanest way to achieve what I'm looking for? I thought about making DB.saveUser
return a Maybe Int
but somehow I think there must be a better solution.
createUser :: UserReq -> Handler (Headers '[Header "Location" Text] NoContent)
createUser ur = do id <- liftIO (DB.saveUser ur)
return . addHeader (T.pack ("/user/" ++ show id)) $ NoContent
saveUser (UR.UserReq name) = withConnection database $ \conn ->
do executeNamed conn "INSERT INTO users (name) VALUES (:name)" [":name" := name]
fromIntegral <$> lastInsertRowId conn
Servant's Handler
is a newtype wrapper:
newtype Handler a = Handler { runHandler' :: ExceptT ServantErr IO a }
inside, there is a ExceptT ServantErr
, where ServantErr
is:
data ServantErr = ServantErr { errHTTPCode :: Int
, errReasonPhrase :: String
, errBody :: LBS.ByteString
, errHeaders :: [HTTP.Header]
} deriving (Show, Eq, Read, Typeable)
So, you can run your DB operations in eg try
or, to handle releasing resources: bracket
, and map your DB exceptions to ServantErr
to get the HTTP response code that you want.
How to throw ServantErr
after you catch DB exception: https://haskell-servant.readthedocs.io/en/stable/tutorial/Server.html#failing-through-servanterr