{-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE RecordWildCards #-} module Language.GraphQL.Error ( parseError , CollectErrsT , addErr , addErrMsg , runCollectErrs , runAppendErrs , singleError ) where import qualified Data.Aeson as Aeson import Data.Text (Text) import Data.Void (Void) import Control.Monad.Trans.Class (lift) import Control.Monad.Trans.State ( StateT , modify , runStateT ) import Text.Megaparsec ( ParseErrorBundle(..) , SourcePos(..) , errorOffset , parseErrorTextPretty , reachOffset , unPos ) -- | Wraps a parse error into a list of errors. parseError :: Applicative f => ParseErrorBundle Text Void -> f Aeson.Value parseError ParseErrorBundle{..} = pure $ Aeson.object [("errors", Aeson.toJSON $ fst $ foldl go ([], bundlePosState) bundleErrors)] where errorObject s SourcePos{..} = Aeson.object [ ("message", Aeson.toJSON $ init $ parseErrorTextPretty s) , ("line", Aeson.toJSON $ unPos sourceLine) , ("column", Aeson.toJSON $ unPos sourceColumn) ] go (result, state) x = let (sourcePosition, _, newState) = reachOffset (errorOffset x) state in (errorObject x sourcePosition : result, newState) -- | A wrapper to pass error messages around. type CollectErrsT m = StateT [Aeson.Value] m -- | Adds an error to the list of errors. addErr :: Monad m => Aeson.Value -> CollectErrsT m () addErr v = modify (v :) makeErrorMessage :: Text -> Aeson.Value makeErrorMessage s = Aeson.object [("message", Aeson.toJSON s)] -- | Constructs a response object containing only the error with the given -- message. singleError :: Text -> Aeson.Value singleError message = Aeson.object [ ("errors", Aeson.toJSON [makeErrorMessage message]) ] -- | Convenience function for just wrapping an error message. addErrMsg :: Monad m => Text -> CollectErrsT m () addErrMsg = addErr . makeErrorMessage -- | Appends the given list of errors to the current list of errors. appendErrs :: Monad m => [Aeson.Value] -> CollectErrsT m () appendErrs errs = modify (errs ++) -- | Runs the given query computation, but collects the errors into an error -- list, which is then sent back with the data. runCollectErrs :: Monad m => CollectErrsT m Aeson.Value -> m Aeson.Value runCollectErrs res = do (dat, errs) <- runStateT res [] if null errs then return $ Aeson.object [("data", dat)] else return $ Aeson.object [("data", dat), ("errors", Aeson.toJSON $ reverse errs)] -- | Runs the given computation, collecting the errors and appending them -- to the previous list of errors. runAppendErrs :: Monad m => CollectErrsT m a -> CollectErrsT m a runAppendErrs f = do (v, errs) <- lift $ runStateT f [] appendErrs errs return v