summaryrefslogtreecommitdiff
path: root/src/Language/GraphQL/Resolver.hs
blob: dbf9a6a020b96ab175158082651f8beed7e7fb34 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
{- This Source Code Form is subject to the terms of the Mozilla Public License,
   v. 2.0. If a copy of the MPL was not distributed with this file, You can
   obtain one at https://mozilla.org/MPL/2.0/. -}

{-# LANGUAGE OverloadedStrings #-}

-- | Helper functions and exceptions to write resolvers.
module Language.GraphQL.Resolver
    ( argument
    , defaultResolver
    ) where

import Control.Monad.Catch (Exception(..), MonadCatch(..), MonadThrow(..))
import Control.Monad.Trans.Reader (ReaderT, asks)
import Data.HashMap.Strict ((!))
import Data.Text (Text)
import qualified Data.Text as Text
import Data.Typeable (cast)
import Language.GraphQL.AST.Document (Name)
import Language.GraphQL.Error
import qualified Language.GraphQL.Type as Type
import Language.GraphQL.Class (FromGraphQL(..))

-- | Exceptions thrown by the functions in this module.
data ServerException
    = FieldNotResolvedException !Text
    | ErroneousArgumentTypeException !Text

instance Show ServerException where
    show (FieldNotResolvedException fieldName) =
        Text.unpack $ Text.unwords ["Field", fieldName, "not resolved."]
    show (ErroneousArgumentTypeException argumentName) =
        Text.unpack $ Text.unwords
            [ "Unable to convert the argument"
            , argumentName
            , "to a user-defined type."
            ]

instance Exception ServerException where
    toException = toException . ResolverException
    fromException x = do
        ResolverException a <- fromException x
        cast a

-- | Default resolver expects that the field value is returned by the parent
-- object. If the parent is not an object or it doesn't contain the requested
-- field name, an error is thrown.
defaultResolver :: MonadCatch m => Name -> Type.Resolve m
defaultResolver fieldName = do
    values' <- asks Type.values
    case values' of
        Type.Object objectValue -> pure $ objectValue ! fieldName
        _nonObject -> throwM $ FieldNotResolvedException fieldName

-- | Takes an argument name, validates that the argument exists, and optionally
-- converts it to a user-defined type.
argument :: (MonadCatch m, FromGraphQL a) => Name -> ReaderT Type.Context m a
argument argumentName =
    Type.argument argumentName >>= maybe throwError pure . fromGraphQL
  where
    throwError = throwM $ ErroneousArgumentTypeException argumentName