How to throw exceptions from Handler - haskell

I'm building a RESTful endpoint that should throw an error if there is invalid data. Here's a simplified (and slightly silly) version:
getEventsR :: Handler Value
getEventsR = do
mpage <- lookupGetParam "page"
let filters = case hasPage mpage of
Right val -> val
Left val -> error $ T.unpack val -- Throw an error
-- The rest...
hasPage :: Maybe Text -> Either Text Text
hasPage =
Left "This should error out"
Is this the correct way - a function should return an Either that will later be expanded to an error?
I probably shouldn't use error, right? What instead?
As a side note, I'd be happy if my thrown error would be sent also in JSON format.
Update:
The reason I've used the term "throw exception", is because my code looks something like this (simplified):
let selectOpt = case addPager [] of
Right val -> val
Left val -> error $ T.unpack val
let filters = case addFilter [] of
Right val -> val
Left val -> error $ T.unpack val
events <- runDB $ selectList filters selectOpt :: Handler [Entity Event]
So if it errors out on selectOpt I'd like it to short-circuit, and not continue with the rest of the handler.

You can use the sendResponseStatus to directly specify the response:
getEventsR = do
mpage <- lookupGetParam "page"
case hasPage mpage of
Left message -> sendResponseStatus status400 message
Right val -> ...continue processing with val...
The second argument to sendResponseStatus can be any type which has a ToTypedContent instance. This includes Text, Value and other common types. See this page for types which have this instance defined by default.
Update
In response to your comments...
get404 is defined as:
get404 key = do
mres <- get key
case mres of
Nothing -> notFound'
Just res -> return res
notFound' :: MonadIO m => m a
notFound' = liftIO $ throwIO $ HCError NotFound
HCError is a constructor of the HandlerContents type:
http://hackage.haskell.org/package/yesod-core-1.4.0/docs/Yesod-Core-Types.html#t:HandlerContents
data HandlerContents:
HCContent Status !TypedContent
HCError ErrorResponse
HCSendFile ContentType FilePath (Maybe FilePart)
HCRedirect Status Text
HCCreated Text
HCWai Response
HCWaiApp Application
An ErrorResponse has the following constructors:
data ErrorResponse:
NotFound
InternalError Text
InvalidArgs [Text]
NotAuthenticated
PermissionDenied Text
BadMethod Method
So you can throwIO a limited number of errors, and the error messages are limited to Text.

Related

Wreq: Stop 404s throwing exceptions

I am trying to test broken links but, when I use Wreq's get method and run into a 404, I get an exception (see bottom) rather than a statusCode to handle. Only 200s seem to be returned.
I tried to follow the error-handling code in the tutorial but I can't find a way to return the same type as get u. Moreover, this seems to be more complexity than I need in this instance.
How can I simply prevent the exception and just return the responseStatus as is
verifySeatme :: Maybe URL -> IO UrlStatus
verifySeatme url = do
case url of
Nothing -> return None
Just "" -> return None
Just u -> do
seatmeResp <- get u --`E.catch` handler
-- r ^? responseBody . key "url"
-- could also check for redirect to errorPage.aspx
if seatmeResp ^. W.responseStatus . statusCode == 200
then return (Working u)
else return Broken
where
handler e#(StatusCodeException s respHeaders _) =
do
return respHeaders
Here is the exception thrown, and you can see it has the stateCode i want
*Main> re <- get "https://www.seatme.nl/restaurant/1371/Londen.htm"
*** Exception: StatusCodeException (Status {statusCode = 404, statusMessage = "Not Found"}) [("Cache-Control","private"),....
Yuras suggested using options, but I have been unable to work from the example using params to one using checkStatus :: Lens' Options (Maybe StatusChecker):
getData :: IO Restos
getData = do
let opts = defaults & customStatusHandler
jdata <- asJSON =<< getWith opts "http://localhost/restos-short.json" :: IO Resp
let
restos = jdata ^. W.responseBody
verified <- mapM processEntry restos
return verified
-- type StatusChecker = Status -> ResponseHeaders -> CookieJar -> Maybe SomeException
customStatusHandler :: W.StatusChecker
customStatusHandler st res _ =
Just res
NOTE: the answer is outdated, see other answers.
I never used Wreq, but it looks like you should use getWith to pass custom options and checkStatus to configure status handling.
An example:
getWith (set checkStatus (Just $ \_ _ _ -> Nothing) defaults)
"http://google.com/hello/world"
The \_ _ _ -> Nothing is a function to check status code, see StatusChecker. It returns nothing indicating that any status code is OK.
To expand on the answer by Evelyn Schneider, I got this to work with
r <- getWith opts url
where
opts = set Network.Wreq.checkResponse (Just $ \_ _ -> return ()) defaults
For posterity: newer versions of wreq (starting with 0.5) have replaced checkStatus with checkResponse, which takes different arguments. An equivalent to Yuras' answer would now be:
getWith opts url
where opts = set checkResponse (\_ _ -> return ()) defaults
Here's the checkStatus function I ended up with after researching a bit. I couldn't figure out how to convert a HttpException to a SomeException, but then I found Control.Monad.Catch.SomeException. This will ignore 404s and re-throw all other exceptions.
import Network.HTTP.Client.Types
import Network.HTTP.Types.Status
import Network.HTTP.Types.Header
import qualified Control.Exception as E
import Control.Monad.Catch (SomeException(..))
notFoundMeansNothing :: Status -> ResponseHeaders -> CookieJar -> Maybe E.SomeException
notFoundMeansNothing s h c
| s == status404 = Nothing
| otherwise =
if statusIsClientError s || statusIsServerError s then
Just . SomeException $ StatusCodeException s h c
else
Nothing

Aeson parsing dynamic objects

I need to parse json API responses which don't have strict structure:
{
response: { /* any object here */ }
}
How is it possible to write parseResponse which will leave ability to parse (or choose a Parser for it) for later use?
My last try is below. I don't like it because it doesn't allow to choose response type as Aeson's decode does.
data APIResponse =
APIResponse { response :: Value } deriving (Show,Generic)
instance FromJSON APIResponse
parseResponse :: BC.ByteString -> Either String Value
parseResponse resp =
case eitherDecode . BLC.fromStrict $ resp of
Right x -> Right $ response x
Left msg -> Left msg
I like to think of Aeson as happening in two distinct steps, the parsing from ByteString -> Value and the mapping from FromJSON a => Value -> a. If you don't immediately know what the correct mapping is you can simply parse to a Value as you've done. Later at runtime when you decide the proper mapping you can do that later.
import qualified Data.HashMap.Strict as Hm
decodeResponse :: ByteString -> Either String Value
decodeResponse bs = do
val <- eitherDecode bs
case val of
Object o -> case Hm.lookup "response" o of
Nothing -> fail "Invalid structure, missing 'response' key"
Just resp -> return resp
_ -> fail "Invalid structure, expected an object"
To perform a mapping without a parse, to convert a Value to a FromJSON a => a, you use the parse/parseEither/parseMaybe family and run the parser generated by parseJSON
mapValue :: FromJSON a => Value -> Either String a
mapValue = parseString parseJSON

simple rss downloader in haskell

Yesterday i tried to write a simple rss downloader in Haskell wtih hte help of the Network.HTTP and Feed libraries. I want to download the link from the rss item and name the downloaded file after the title of the item.
Here is my short code:
import Control.Monad
import Control.Applicative
import Network.HTTP
import Text.Feed.Import
import Text.Feed.Query
import Text.Feed.Types
import Data.Maybe
import qualified Data.ByteString as B
import Network.URI (parseURI, uriToString)
getTitleAndUrl :: Item -> (Maybe String, Maybe String)
getTitleAndUrl item = (getItemTitle item, getItemLink item)
downloadUri :: (String,String) -> IO ()
downloadUri (title,link) = do
file <- get link
B.writeFile title file
where
get url = let uri = case parseURI url of
Nothing -> error $ "invalid uri" ++ url
Just u -> u in
simpleHTTP (defaultGETRequest_ uri) >>= getResponseBody
getTuples :: IO (Maybe [(Maybe String, Maybe String)])
getTuples = fmap (map getTitleAndUrl) <$> fmap (feedItems) <$> parseFeedString <$> (simpleHTTP (getRequest "http://index.hu/24ora/rss/") >>= getResponseBody)
I reached a state where i got a list which contains tuples, which contains name and the corresponding link. And i have a downloadUri function which properly downloads the given link to a file which has the name of the rss item title.
I already tried to modify downloadUri to work on (Maybe String,Maybe String) with fmap- ing on get and writeFile but failed with it horribly.
How can i apply my downloadUri function to the result of the getTuples function. I want to implement the following main function
main :: IO ()
main = some magic incantation donwloadUri more incantation getTuples
The character encoding of the result of getItemTitle broken, it puts code points in the places of the accented characters. The feed is utf8 encoded, and i thought that all haskell string manipulation functions are defaulted to utf8. How can i fix this?
Edit:
Thanks for you help, i implemented successfully my main and helper functions. Here comes the code:
downloadUri :: (Maybe String,Maybe String) -> IO ()
downloadUri (Just title,Just link) = do
item <- get link
B.writeFile title item
where
get url = let uri = case parseURI url of
Nothing -> error $ "invalid uri" ++ url
Just u -> u in
simpleHTTP (defaultGETRequest_ uri) >>= getResponseBody
downloadUri _ = print "Somewhere something went Nothing"
getTuples :: IO (Maybe [(Maybe String, Maybe String)])
getTuples = fmap (map getTitleAndUrl) <$> fmap (feedItems) <$> parseFeedString <$> decodeString <$> (simpleHTTP (getRequest "http://index.hu/24ora/rss/") >>= getResponseBody)
downloadAllItems :: Maybe [(Maybe String, Maybe String)] -> IO ()
downloadAllItems (Just feedlist) = mapM_ downloadUri $ feedlist
downloadAllItems _ = error "feed does not get parsed"
main = getTuples >>= downloadAllItems
The character encoding issue has been partially solved, i put decodeString before the feed parsing, so the files get named properly. But if i want to print it out, the issue still happens. Minimal working example:
main = getTuples
It sounds like it's the Maybes that are giving you trouble. There are many ways to deal with Maybe values, and some useful library functions like fromMaybe and fromJust. However, the simplest way is to do pattern matching on the Maybe value. We can tweak your downloadUri function to work with the Maybe values. Here's an example:
downloadUri :: (Maybe String, Maybe String) -> IO ()
downloadUri (Just title, Just link) = do
file <- get link
B.writeFile title file
where
get url = let uri = case parseURI url of
Nothing -> error $ "invalid uri" ++ url
Just u -> u in
simpleHTTP (defaultGETRequest_ uri) >>= getResponseBody
downloadUri _ = error "One of my parameters was Nothing".
Or maybe you can let the title default to blank, in which case you could insert this just before the last line in the previous example:
downloadUri (Nothing, Just link) = downloadUri (Just "", Just link)
Now the only Maybe you need to work with is the outer one, applied to the array of tuples. Again, we can pattern match. It might be clearest to write a helper function like this:
downloadAllItems (Just ts) = ??? -- hint: try a `mapM`
downloadAllItems Nothing = ??? -- don't do anything, or report an error, or...
As for your encoding issue, my guesses are:
You're reading the information from a file that isn't UTF-8 encoded, or your system doesn't realise that it's UTF-8 encoded.
You are reading the information correctly, but it gets messed up when you output it.
In order to help you with this problem, I need to see a full code example, which shows how you're reading the information and how you output it.
Your main could be something like the shown below. There may be some more concise way to compose these two operations though:
main :: IO ()
main = getTuples >>= process
where
process (Just lst) = foldl (\s v -> do {t <- s; download v}) (return ()) lst
process Nothing = return ()
download (Just t, Just l) = downloadUri (t,l)
download _ = return ()

How to "escape early" in a web monad

Something that happens to me a lot while web programming: I want to run an operation that has a chance of failure. On a failure, I want to send the client a 500. Normally though, I just want to continue executing a series of steps.
doSomeWebStuff :: SomeWebMonad ()
doSomeWebStuff = do
res <- databaseCall
case res of
Left err -> status 500
Right val -> do
res2 <- anotherDatabaseCall (someprop val)
case res2 of
Left err -> status 500
Right val2 -> text $ show val2
since the errs are exceptions, I don't like that I need all that case stuff just to catch them. I want to do the same thing whenever anything is a left. Is there a way to express that on one line with something like guard, but control what it returns on an exit?
In another language I could do this:
function doSomeWebStuff() {
var res = databaseCall()
if (res == Error) return status 500
var res2 = anotherDatabaseCall(res.someprop)
if (res2 == Error) return status 500
return text(res2)
}
So, I'm ok writing some boilerplate, but I don't want the errors to mess with my nesting, when it's far more common to just want to continue forward with the found case.
What's the cleanest way to do this? I know in theory I can use a monad to exit early on a failure, but I've only seen examples with Maybe and it would return Nothing at the end, rather than letting me specify what it returns.
Here's how I would do it with ErrorT. Disclaimer: I have never actually used ErrorT before.
webStuffOr500 :: ErrorT String SomeWebMonad () -> SomeWebMonad ()
webStuffOr500 action = do
res <- runErrorT action
case res of
Left err -> do
logError err -- you probably want to know what went wrong
status 500
Right () -> return ()
doSomeWebStuff :: SomeWebMonad ()
doSomeWebStuff = webStuffOr500 doSomeWebStuff'
doSomeWebStuff' :: ErrorT String SomeWebMonad ()
doSomeWebStuff' = do
val <- ErrorT databaseCall
val2 <- ErrorT $ anotherDatabaseCall (someprop val)
lift $ text $ show val2
Here are the imports and type declarations I used to make sure it all typechecks correctly:
import Control.Monad.Identity
import Control.Monad.Error
import Control.Monad.Trans (lift)
import Control.Monad
type SomeWebMonad = Identity
data Foo = Foo
data Bar = Bar
data Baz = Baz deriving (Show)
someprop :: Foo -> Bar
someprop = undefined
databaseCall :: SomeWebMonad (Either String Foo)
databaseCall = undefined
anotherDatabaseCall :: Bar -> SomeWebMonad (Either String Baz)
anotherDatabaseCall = undefined
logError :: String -> SomeWebMonad ()
logError = undefined
text :: String -> SomeWebMonad ()
text = undefined
status :: Int -> SomeWebMonad ()
status = undefined
If I'm doing this all wrong then please, somebody shout out. It may be wise, if you take this approach, to modify the type signature of databaseCall and anotherDatabaseCall to also use ErrorT, that way a <- ErrorT b can be reduced to a <- b in doSomeWebStuff'.
Since I'm a complete noob at ErrorT, I can't really do any hand-holding besides "here's some code, go have some fun".
Not a direct answer to your question, but have you considered using Snap? In snap, we have short-circuiting behavior built-in with an idiomatic:
getResponse >>= finishWith
where
finishWith :: MonadSnap m => Response -> m a
So given a response object, it will terminate early (and match whatever type comes after that). Haskell laziness will ensure computations within Snap monad after finishWith won't be executed.
I sometimes make a little helper:
finishEarly code str = do
modifyResponse $ setResponseStatus code str
modifyResponse $ addHeader "Content-Type" "text/plain"
writeBS str
getResponse >>= finishWith
which I can then use anywhere in my handlers.
myHandler = do
x <- doSomething
when (x == blah) $ finishEarly 400 "That doesn't work!!"
doOtherStuff

Can't seem to implement Either correctly

Alright so here's my current code:
import System.IO
import System.Environment
import System.Directory
main = do
unfiltered <- getArgs ; home <- getHomeDirectory ; let db = home ++ "/.grindstone"
case unfiltered of
(x:xs) -> return ()
_ -> error "No command given. See --help for more info."
command:args <- getArgs
createDirectoryIfMissing True db
let check = case args of
[] -> error "No arguments given. See --help for more info."
_ -> do let (params#(param:_),rest) = span (\(c:_) -> c=='-') args
if length params > 1 then error ("No arguments given for " ++ param)
else do
let (pArgs,_) = span (\(c:_) -> c/='-') rest
return (param, pArgs) :: Either (IO ()) (String, [String])
let add = print "sup"
let cmds = [("add", add)]
let action = lookup command cmds
case action of
Nothing -> error "Unknown command."
(Just action) -> action
The main problem is with check. I tried implementing the Either type since I want it to either error out, or return something for another function to use, but, it's currently erroring out with:
grindstone.hs:21:23:
No instance for (Monad (Either (IO ())))
arising from a use of `return' at grindstone.hs:21:23-43
Possible fix:
add an instance declaration for (Monad (Either (IO ())))
In the expression:
return (param, pArgs) :: Either (IO ()) (String, [String])
In the expression:
do { let (pArgs, _) = span (\ (c : _) -> ...) rest;
return (param, pArgs) :: Either (IO ()) (String, [String]) }
In the expression:
if length params > 1 then
error ("No arguments given for " ++ param)
else
do { let (pArgs, _) = ...;
return (param, pArgs) :: Either (IO ()) (String, [String]) }
I'm only starting out in haskell and haven't dealt too much with monads yet so just thought I'd ask on here. anyone have any ideas?
The error that is causing your compile problems is that you are directly casting an expression to the type Either (IO ()) (String, [String]) when it is not an Either value. (The compiler is not outputting a very helpful error message.)
To create an Either value [1], we use the data constructors Left and Right. Convention (from the library page) is that errors are a Left value, while correct values are a Right value.
I did a quick rewrite of your arg checking function as
checkArgs :: [String] -> Either String (String, [String])
checkArgs args =
case args of
[] -> Left "No arguments given. See --help for more info."
_ -> let (params#(param:_),rest) = span (\(c:_) -> c=='-') args in
if length params > 1 then
Left ("No arguments given for " ++ param)
else
let (pArgs,_) = span (\(c:_) -> c/='-') rest in
Right (param, pArgs)
Note that the arg checking function does not interact with any external IO () library functions and so has a purely functional type. In general if your code does not have monadic elements (IO ()), it can be clearer to write it in purely functional style. (When starting out in Haskell this is definitely something I would recommend rather than trying to get your head around monads/monad transformers/etc immediately.)
When you are a little more comfortable with monads, you may want to check out Control.Monad.Error [2], which can wraps similar functionality as Either as a monad and would encapsulate some details like Left always being computation errors.
[1] http://www.haskell.org/ghc/docs/6.12.2/html/libraries/base-4.2.0.1/Data-Either.html
[2] http://hackage.haskell.org/packages/archive/mtl/1.1.0.2/doc/html/Control-Monad-Error.html
Either (IO ()) (String, [String]) is a type that contains an IO action or a
(String, [String]), so values of this type could be Left IO () or
Right (String, [String]). Left values usually represents an error
occurrence in Haskell. This error can be represented with any type you want,
for example, an error code (Int) or a String that says what happened.
If you use IO () as the type which represents an error, you won't be able
to extract any information about the error. You just will able to perform an IO action later on.
The type that you are looking for isn't Either (IO ()) (String, [String]),
is Either String (String, [String]). With this type can get information about the
error (String). Now, you dont need any IO action into Either type, so you
can remove all do expressions:
let check = case args of
[] -> Left "No arguments given. See --help for more info."
_ -> let (params#(param:_),rest) = span (\(c:_) -> c=='-') args
in if length params > 1
then Left ("No arguments given for " ++ param)
else let (pArgs,_) = span (\(c:_) -> c/='-') rest
in Right (param, pArgs)

Resources