2020-07-05 14:36:00 +02:00
|
|
|
{-# LANGUAGE DuplicateRecordFields #-}
|
2020-07-17 07:05:03 +02:00
|
|
|
{-# LANGUAGE ExistentialQuantification #-}
|
2019-07-19 06:38:54 +02:00
|
|
|
{-# LANGUAGE RecordWildCards #-}
|
2019-09-20 08:47:14 +02:00
|
|
|
|
|
|
|
-- | Error handling.
|
2019-07-07 06:31:53 +02:00
|
|
|
module Language.GraphQL.Error
|
2020-10-07 05:24:51 +02:00
|
|
|
( CollectErrsT
|
2020-07-05 14:36:00 +02:00
|
|
|
, Error(..)
|
2020-09-16 09:12:49 +02:00
|
|
|
, Path(..)
|
2020-05-27 23:18:35 +02:00
|
|
|
, Resolution(..)
|
2020-07-17 07:05:03 +02:00
|
|
|
, ResolverException(..)
|
2020-07-05 14:36:00 +02:00
|
|
|
, Response(..)
|
2020-07-14 19:37:56 +02:00
|
|
|
, ResponseEventStream
|
2019-07-14 05:58:05 +02:00
|
|
|
, addErr
|
|
|
|
, addErrMsg
|
2020-10-07 05:24:51 +02:00
|
|
|
, parseError
|
2019-07-14 05:58:05 +02:00
|
|
|
, runCollectErrs
|
2019-07-23 06:04:33 +02:00
|
|
|
, singleError
|
2019-07-14 05:58:05 +02:00
|
|
|
) where
|
2016-03-12 00:59:51 +01:00
|
|
|
|
2020-07-14 19:37:56 +02:00
|
|
|
import Conduit
|
2020-07-17 07:05:03 +02:00
|
|
|
import Control.Exception (Exception(..))
|
2020-05-27 23:18:35 +02:00
|
|
|
import Control.Monad.Trans.State (StateT, modify, runStateT)
|
|
|
|
import Data.HashMap.Strict (HashMap)
|
2020-07-05 14:36:00 +02:00
|
|
|
import Data.Sequence (Seq(..), (|>))
|
|
|
|
import qualified Data.Sequence as Seq
|
2019-07-19 06:38:54 +02:00
|
|
|
import Data.Text (Text)
|
2020-07-05 14:36:00 +02:00
|
|
|
import qualified Data.Text as Text
|
2020-07-08 08:16:14 +02:00
|
|
|
import Language.GraphQL.AST (Location(..), Name)
|
2020-07-05 14:36:00 +02:00
|
|
|
import Language.GraphQL.Execute.Coerce
|
2020-10-07 05:24:51 +02:00
|
|
|
import qualified Language.GraphQL.Type.Schema as Schema
|
2020-07-05 14:36:00 +02:00
|
|
|
import Prelude hiding (null)
|
2020-02-20 05:16:14 +01:00
|
|
|
import Text.Megaparsec
|
|
|
|
( ParseErrorBundle(..)
|
|
|
|
, PosState(..)
|
|
|
|
, SourcePos(..)
|
|
|
|
, errorOffset
|
|
|
|
, parseErrorTextPretty
|
|
|
|
, reachOffset
|
|
|
|
, unPos
|
|
|
|
)
|
2016-03-12 00:59:51 +01:00
|
|
|
|
2020-06-03 07:20:38 +02:00
|
|
|
-- | Executor context.
|
2020-05-27 23:18:35 +02:00
|
|
|
data Resolution m = Resolution
|
2020-07-05 14:36:00 +02:00
|
|
|
{ errors :: Seq Error
|
2020-10-07 05:24:51 +02:00
|
|
|
, types :: HashMap Name (Schema.Type m)
|
2020-05-27 23:18:35 +02:00
|
|
|
}
|
|
|
|
|
2016-03-12 00:59:51 +01:00
|
|
|
-- | Wraps a parse error into a list of errors.
|
2020-07-05 14:36:00 +02:00
|
|
|
parseError :: (Applicative f, Serialize a)
|
|
|
|
=> ParseErrorBundle Text Void
|
|
|
|
-> f (Response a)
|
2019-07-19 06:38:54 +02:00
|
|
|
parseError ParseErrorBundle{..} =
|
2020-07-05 14:36:00 +02:00
|
|
|
pure $ Response null $ fst
|
|
|
|
$ foldl go (Seq.empty, bundlePosState) bundleErrors
|
2019-07-19 06:38:54 +02:00
|
|
|
where
|
2020-07-05 14:36:00 +02:00
|
|
|
errorObject s SourcePos{..} = Error
|
2020-07-08 08:16:14 +02:00
|
|
|
{ message = Text.pack $ init $ parseErrorTextPretty s
|
|
|
|
, locations = [Location (unPos' sourceLine) (unPos' sourceColumn)]
|
2020-09-16 09:12:49 +02:00
|
|
|
, path = []
|
2020-07-08 08:16:14 +02:00
|
|
|
}
|
2020-07-05 14:36:00 +02:00
|
|
|
unPos' = fromIntegral . unPos
|
2019-07-19 06:38:54 +02:00
|
|
|
go (result, state) x =
|
2020-02-20 05:16:14 +01:00
|
|
|
let (_, newState) = reachOffset (errorOffset x) state
|
|
|
|
sourcePosition = pstateSourcePos newState
|
2020-07-05 14:36:00 +02:00
|
|
|
in (result |> errorObject x sourcePosition, newState)
|
2016-03-12 00:59:51 +01:00
|
|
|
|
2019-06-28 11:12:28 +02:00
|
|
|
-- | A wrapper to pass error messages around.
|
2020-05-27 23:18:35 +02:00
|
|
|
type CollectErrsT m = StateT (Resolution m) m
|
2016-03-12 00:59:51 +01:00
|
|
|
|
|
|
|
-- | Adds an error to the list of errors.
|
2021-05-10 09:43:39 +02:00
|
|
|
{-# DEPRECATED #-}
|
2020-07-05 14:36:00 +02:00
|
|
|
addErr :: Monad m => Error -> CollectErrsT m ()
|
2020-05-27 23:18:35 +02:00
|
|
|
addErr v = modify appender
|
|
|
|
where
|
2020-07-05 14:36:00 +02:00
|
|
|
appender :: Monad m => Resolution m -> Resolution m
|
|
|
|
appender resolution@Resolution{..} = resolution{ errors = errors |> v }
|
2016-03-12 00:59:51 +01:00
|
|
|
|
2021-05-10 09:43:39 +02:00
|
|
|
{-# DEPRECATED #-}
|
2020-07-05 14:36:00 +02:00
|
|
|
makeErrorMessage :: Text -> Error
|
2020-09-16 09:12:49 +02:00
|
|
|
makeErrorMessage s = Error s [] []
|
2019-07-23 06:04:33 +02:00
|
|
|
|
|
|
|
-- | Constructs a response object containing only the error with the given
|
2020-07-05 14:36:00 +02:00
|
|
|
-- message.
|
2021-05-10 09:43:39 +02:00
|
|
|
{-# DEPRECATED #-}
|
2020-07-05 14:36:00 +02:00
|
|
|
singleError :: Serialize a => Text -> Response a
|
2021-05-10 09:43:39 +02:00
|
|
|
singleError message = Response null $ Seq.singleton $ Error message [] []
|
2016-03-12 00:59:51 +01:00
|
|
|
|
|
|
|
-- | Convenience function for just wrapping an error message.
|
2021-05-10 09:43:39 +02:00
|
|
|
{-# DEPRECATED #-}
|
2020-07-06 19:10:34 +02:00
|
|
|
addErrMsg :: (Monad m, Serialize a) => Text -> CollectErrsT m a
|
|
|
|
addErrMsg errorMessage = (addErr . makeErrorMessage) errorMessage >> pure null
|
2016-03-12 00:59:51 +01:00
|
|
|
|
2020-09-16 09:12:49 +02:00
|
|
|
-- | If an error can be associated to a particular field in the GraphQL result,
|
|
|
|
-- it must contain an entry with the key path that details the path of the
|
|
|
|
-- response field which experienced the error. This allows clients to identify
|
|
|
|
-- whether a null result is intentional or caused by a runtime error.
|
|
|
|
data Path
|
|
|
|
= Segment Text -- ^ Field name.
|
|
|
|
| Index Int -- ^ List index if a field returned a list.
|
|
|
|
deriving (Eq, Show)
|
|
|
|
|
2020-07-05 14:36:00 +02:00
|
|
|
-- | @GraphQL@ error.
|
|
|
|
data Error = Error
|
|
|
|
{ message :: Text
|
2020-07-08 08:16:14 +02:00
|
|
|
, locations :: [Location]
|
2020-09-16 09:12:49 +02:00
|
|
|
, path :: [Path]
|
2020-07-05 14:36:00 +02:00
|
|
|
} deriving (Eq, Show)
|
|
|
|
|
|
|
|
-- | The server\'s response describes the result of executing the requested
|
|
|
|
-- operation if successful, and describes any errors encountered during the
|
|
|
|
-- request.
|
|
|
|
data Response a = Response
|
|
|
|
{ data' :: a
|
|
|
|
, errors :: Seq Error
|
|
|
|
} deriving (Eq, Show)
|
|
|
|
|
2020-07-14 19:37:56 +02:00
|
|
|
-- | Each event in the underlying Source Stream triggers execution of the
|
|
|
|
-- subscription selection set. The results of the execution generate a Response
|
|
|
|
-- Stream.
|
|
|
|
type ResponseEventStream m a = ConduitT () (Response a) m ()
|
|
|
|
|
2020-07-17 07:05:03 +02:00
|
|
|
-- | Only exceptions that inherit from 'ResolverException' a cought by the
|
|
|
|
-- executor.
|
|
|
|
data ResolverException = forall e. Exception e => ResolverException e
|
|
|
|
|
|
|
|
instance Show ResolverException where
|
|
|
|
show (ResolverException e) = show e
|
|
|
|
|
|
|
|
instance Exception ResolverException
|
|
|
|
|
2019-06-28 11:12:28 +02:00
|
|
|
-- | Runs the given query computation, but collects the errors into an error
|
2020-07-05 14:36:00 +02:00
|
|
|
-- list, which is then sent back with the data.
|
|
|
|
runCollectErrs :: (Monad m, Serialize a)
|
2020-10-07 05:24:51 +02:00
|
|
|
=> HashMap Name (Schema.Type m)
|
2020-07-05 14:36:00 +02:00
|
|
|
-> CollectErrsT m a
|
|
|
|
-> m (Response a)
|
2020-05-27 23:18:35 +02:00
|
|
|
runCollectErrs types' res = do
|
2020-07-05 14:36:00 +02:00
|
|
|
(dat, Resolution{..}) <- runStateT res
|
|
|
|
$ Resolution{ errors = Seq.empty, types = types' }
|
|
|
|
pure $ Response dat errors
|