Search code examples
haskellservant

Servant QueryParams parse error


Given the following code:

newtype HelloMessage = HelloMessage { msg :: String }
  deriving (Generic)

instance ToJSON HelloMessage

type API2 = "hello"
            :> QueryParam "age" Int
            :> Get '[JSON] HelloMessage

appAPI2 :: Proxy API2
appAPI2 = Proxy

myHandler :: Server API2
myHandler = helloHandler
  where
    helloHandler :: Maybe Int -> Handler HelloMessage
    helloHandler mAge =
      let
        sAge = case mAge of
                 Nothing -> "0"
                 Just ag -> show ag
      in
        return . HelloMessage $ sAge

app2 :: Application
app2 = serve appAPI2 myHandler

main :: IO ()
main = run 8080 app2

I can access /hello which returns { "msg" : 0} or /hello?age=20 which returns { "msg" : 20}. But if I set the age to be a non-integer parseable e.g. "foobar" which makes the url to be /hello?age=foobar and access it, it returns an error message about parsing error on "foobar".

This behave differently with Capture where if I give the same treatment it'll just return a http 400.

What is wrong my code?

Edit: After further exploration, it does return http 400 on parse error. Now I'm changing the question. How to return custom error message if that happens?


Solution

  • The default behaviour of QueryParam is to behave like this: error out if you can't decode, but return Nothing when not specified.

    Since servant 0.13, you can however override this.

    If you look at the definition of QueryParam, you can see that it is in fact just a special case of a more general QueryParam' type:

    type QueryParam = QueryParam' '[Optional, Strict]
    

    QueryParam' takes what we call "modifiers", which affect two things:

    • whether you can survive without a value for this parameter: if you can, your handler gets a Maybe a, if not you directly get an a but it errors out when no value is given. This is Required vs Optional.
    • whether you want decoding failures to be fatal or not: if you do, then when decoding fails servant errors out for you otherwise your handler gets an a. If you don't, then your handler gets an Either Text a and you're then free to do whatever you want in case of a decoding error (it's the Left case of the Either, with a textual error message). This is Strict vs Lenient.

    So you probably want to define something like type MyQueryParam name a = QueryParam' '[Optional, Lenient] and use this whenever appropriate.

    Does that solve your problem?