4
votes

Continuing my exploration of conduit and aeson, how would I go about using my own data type in stead of Value in this (slightly modified) code snippet from the Yesod book.

{-# LANGUAGE OverloadedStrings, TemplateHaskell #-}
import Network.Wai (Response, responseLBS, Application, requestBody)
import Network.HTTP.Types (status200, status400)
import Network.Wai.Handler.Warp (run)
import Data.Aeson.Parser (json)
import Data.Conduit.Attoparsec (sinkParser)
import Control.Monad.IO.Class (liftIO)
import Data.Aeson (Value(..), encode, object, (.=))
import Control.Exception (SomeException)
import Data.ByteString (ByteString)
import Data.Conduit (ResourceT, ($$))
import Control.Exception.Lifted (handle)

import qualified Data.HashMap.Strict as M
import Data.Aeson.TH (deriveJSON)

-- I ADDED THIS

data JSONRequest = JSONRequest {
    command :: ByteString,
    params :: M.HashMap ByteString ByteString
}

deriveJSON id ''JSONRequest

-- END OF WHAT I ADDED

main :: IO ()
main = run 3000 app

app :: Application
app req = handle invalidJson $ do
    value <- requestBody req $$ sinkParser json
    newValue <- liftIO $ dispatch value
    return $ responseLBS
        status200
        [("Content-Type", "application/json")]
        $ encode newValue

invalidJson :: SomeException -> ResourceT IO Response
invalidJson ex = return $ responseLBS
    status400
    [("Content-Type", "application/json")]
    $ encode $ object
        [ ("message" .= show ex)
        ]

-- Application-specific logic would go here.
dispatch :: Value -> IO Value
dispatch = return

Basically, I want to change the type of dispatch to JSONRequest -> IO JSONRequest. How do I tell the parser to use my own derived instance of fromJSON?

I tried just adding a type declaration, praying for polymorphic return type on json, but I realised it is strictly for Value.

1

1 Answers

3
votes

Just looking at the types, don't you just need to fmap your fromJSON over the result coming from json? With a suitable signature for dispatch we just need:

-- import Data.Aeson
app :: Application
app req = handle invalidJson $ do
      result <- requestBody req $$ sinkParser (fmap fromJSON json)
      next_result <- liftIO $ dispatch result
      return $ responseLBS status200 [("Content-Type", "application/json")] 
             $ encode next_result   

dispatch :: Result JSONRequest -> IO JSONRequest
dispatch (Error str) = undefined
dispatch (Success jsonreq) = return jsonreq

But maybe it's a little clearer written thus:

-- import Data.Aeson
-- import qualified Data.Attoparsec as Atto
toRequest ::    Value -> Result JSONRequest
toRequest = fromJSON   -- specialized now to your fromJSON

jsonRequestParser :: Atto.Parser (Result JSONRequest)
jsonRequestParser = fmap toRequest json 

app :: Application
app req = handle invalidJson $ do
      result <- requestBody req $$ sinkParser jsonRequestParser
      next_result <- liftIO $ dispatch result
      return $ responseLBS status200 [("Content-Type", "application/json")]
             $ encode next_result   

dispatch :: Result JSONRequest -> IO JSONRequest
dispatch (Error str) = undefined
dispatch (Success jsonreq) = return jsonreq

I left the parser returning a Result JSONRequest so dispatch is handling Error cases too, which might mean you need your exception handling somehow?