{-# LANGUAGE TypeSynonymInstances, OverloadedStrings #-}
{-# LANGUAGE CPP #-}
{-# OPTIONS_GHC -fno-warn-orphans #-}
module Yesod.Core.Json
(
defaultLayoutJson
, jsonToRepJson
, returnJson
, returnJsonEncoding
, provideJson
, parseCheckJsonBody
, parseInsecureJsonBody
, requireCheckJsonBody
, requireInsecureJsonBody
, parseJsonBody
, parseJsonBody_
, requireJsonBody
, J.Value (..)
, J.ToJSON (..)
, J.FromJSON (..)
, array
, object
, (.=)
, (J..:)
, jsonOrRedirect
, jsonEncodingOrRedirect
, acceptsJson
, contentTypeHeaderIsJson
) where
import Yesod.Core.Handler (HandlerFor, getRequest, invalidArgs, redirect, selectRep, provideRep, rawRequestBody, ProvidedRep, lookupHeader)
import Control.Monad.Trans.Writer (Writer)
import Data.Monoid (Endo)
import Yesod.Core.Content (TypedContent)
import Yesod.Core.Types (reqAccept)
import Yesod.Core.Class.Yesod (defaultLayout, Yesod)
import Yesod.Core.Class.Handler
import Yesod.Core.Widget (WidgetFor)
import Yesod.Routes.Class
import qualified Data.Aeson as J
import qualified Data.Aeson.Parser as JP
import Data.Aeson ((.=), object)
import Data.Conduit.Attoparsec (sinkParser)
import Data.Text (pack)
import qualified Data.Vector as V
import Data.Conduit
import Data.Conduit.Lift
import qualified Data.ByteString.Char8 as B8
import Data.Maybe (listToMaybe)
import Control.Monad (liftM)
defaultLayoutJson :: (Yesod site, J.ToJSON a)
=> WidgetFor site ()
-> HandlerFor site a
-> HandlerFor site TypedContent
defaultLayoutJson w json = selectRep $ do
provideRep $ defaultLayout w
provideRep $ fmap J.toEncoding json
jsonToRepJson :: (Monad m, J.ToJSON a) => a -> m J.Value
jsonToRepJson = return . J.toJSON
{-# DEPRECATED jsonToRepJson "Use returnJson instead" #-}
returnJson :: (Monad m, J.ToJSON a) => a -> m J.Value
returnJson = return . J.toJSON
returnJsonEncoding :: (Monad m, J.ToJSON a) => a -> m J.Encoding
returnJsonEncoding = return . J.toEncoding
provideJson :: (Monad m, J.ToJSON a) => a -> Writer (Endo [ProvidedRep m]) ()
provideJson = provideRep . return . J.toEncoding
parseJsonBody :: (MonadHandler m, J.FromJSON a) => m (J.Result a)
parseJsonBody = parseInsecureJsonBody
{-# DEPRECATED parseJsonBody "Use parseCheckJsonBody or parseInsecureJsonBody instead" #-}
parseInsecureJsonBody :: (MonadHandler m, J.FromJSON a) => m (J.Result a)
parseInsecureJsonBody = do
eValue <- runConduit $ rawRequestBody .| runCatchC (sinkParser JP.value')
return $ case eValue of
Left e -> J.Error $ show e
Right value -> J.fromJSON value
parseCheckJsonBody :: (MonadHandler m, J.FromJSON a) => m (J.Result a)
parseCheckJsonBody = do
mct <- lookupHeader "content-type"
case fmap contentTypeHeaderIsJson mct of
Just True -> parseInsecureJsonBody
_ -> return $ J.Error $ "Non-JSON content type: " ++ show mct
parseJsonBody_ :: (MonadHandler m, J.FromJSON a) => m a
parseJsonBody_ = requireInsecureJsonBody
{-# DEPRECATED parseJsonBody_ "Use requireCheckJsonBody or requireInsecureJsonBody instead" #-}
requireJsonBody :: (MonadHandler m, J.FromJSON a) => m a
requireJsonBody = requireInsecureJsonBody
{-# DEPRECATED requireJsonBody "Use requireCheckJsonBody or requireInsecureJsonBody instead" #-}
requireInsecureJsonBody :: (MonadHandler m, J.FromJSON a) => m a
requireInsecureJsonBody = do
ra <- parseInsecureJsonBody
case ra of
J.Error s -> invalidArgs [pack s]
J.Success a -> return a
requireCheckJsonBody :: (MonadHandler m, J.FromJSON a) => m a
requireCheckJsonBody = do
ra <- parseCheckJsonBody
case ra of
J.Error s -> invalidArgs [pack s]
J.Success a -> return a
array :: J.ToJSON a => [a] -> J.Value
array = J.Array . V.fromList . map J.toJSON
jsonOrRedirect :: (MonadHandler m, J.ToJSON a)
=> Route (HandlerSite m)
-> a
-> m J.Value
jsonOrRedirect = jsonOrRedirect' J.toJSON
jsonEncodingOrRedirect :: (MonadHandler m, J.ToJSON a)
=> Route (HandlerSite m)
-> a
-> m J.Encoding
jsonEncodingOrRedirect = jsonOrRedirect' J.toEncoding
jsonOrRedirect' :: MonadHandler m
=> (a -> b)
-> Route (HandlerSite m)
-> a
-> m b
jsonOrRedirect' f r j = do
q <- acceptsJson
if q then return (f j)
else redirect r
acceptsJson :: MonadHandler m => m Bool
acceptsJson = (maybe False ((== "application/json") . B8.takeWhile (/= ';'))
. listToMaybe
. reqAccept)
`liftM` getRequest
contentTypeHeaderIsJson :: B8.ByteString -> Bool
contentTypeHeaderIsJson bs = B8.takeWhile (/= ';') bs == "application/json"