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?
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:
Maybe a
, if not you directly get an a
but it errors out when no value is given. This is Required
vs Optional
.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?