3

Given the following Servant server:

{-# LANGUAGE DataKinds #-}
{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE TypeOperators #-}

module ServantSample (main) where

import Data.Aeson
import Data.Aeson.TH
import Network.Wai
import Network.Wai.Handler.Warp
import Servant

data Spec = Spec
  { schema :: Object
  } deriving (Eq, Show)
$(deriveJSON defaultOptions ''Spec)

type Api = ReqBody '[JSON] Spec :> Post '[JSON] NoContent

server :: Server Api
server = postDoc

postDoc :: Spec -> Handler NoContent
postDoc _ = return NoContent

api :: Proxy Api
api = Proxy

app :: Application
app = serve api server

main :: IO ()
main = run 8080 app

...and the following curl to a running instance of the above server:

curl localhost:8080 -H 'Content-Type: application/json' --data '{"schema": "I am not an object but I should be!"}'

I get back:

Error in $.schema: expected HashMap ~Text v, encountered String

Is there a way to intercept the Aeson error and replace it with something that doesn't leak implementation details to the client? As far as I can tell, this all happens behind the scenes in Servant's machinery, and I can't find any documentation about how to hook into it.

For instance, I'd love to return something like:

Expected a JSON Object under the key "schema", but got the String "I am not an object but I should be!"

Thanks!

adlaika
  • 107
  • 1
  • 8
  • I believe [this](https://stackoverflow.com/questions/41753516/custom-json-errors-for-servant-server) might be related. – Mor A. May 31 '18 at 08:24

1 Answers1

2

Writing the FromJSON instance by hand solves at least half of your problem.

instance FromJSON Spec where
  parseJSON (Object o) = do
    schema <- o .: "schema"
    case schema of
      (Object s) -> pure $ Spec s
      (String s) -> fail $ "Expected a JSON Object under the key \"schema\", but got the String \"" ++ unpack s ++ "\"\n"
      _          -> fail $ "Expected a JSON Object under the key \"schema\", but got the other type"
  parseJSON wat = typeMismatch "Spec" wat

Your curl command then returns:

Error in $: Expected a JSON Object under the key "schema", but got the String "I am not an object but I should be!"

You can obviously check for the different Value type constructors from Aeson and factor it into a separate function.

Got the code from looking at the implementation of Data.Aeson.Types.typeMismatch

Mikkel
  • 762
  • 5
  • 17
  • The "Error in $" part of the error message comes from within the Parser in Aeson. No idea of how difficult it would be to replace. – Mikkel May 31 '18 at 14:00
  • Oh duh--in retrospect, obvious. I ended up using your solution in concert with the middleware solution from [here](https://stackoverflow.com/questions/41753516/custom-json-errors-for-servant-server) to remove the "Error in $:" and also embed the error in a JSON object. Thanks for the help! – adlaika May 31 '18 at 18:16