summaryrefslogtreecommitdiff
path: root/src/Language/GraphQL/Execute/Execution.hs
blob: 79646c3881f4a1ef3e8013937e622b93a4fff230 (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
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
{-# LANGUAGE ExplicitForAll #-}
{-# LANGUAGE OverloadedStrings #-}

module Language.GraphQL.Execute.Execution
    ( executeSelectionSet
    ) where

import qualified Data.Aeson as Aeson
import Control.Monad.Trans.Class (lift)
import Control.Monad.Trans.Except (runExceptT)
import Control.Monad.Trans.Reader (runReaderT)
import Control.Monad.Trans.State (gets)
import Data.Map.Strict (Map)
import Data.HashMap.Strict (HashMap)
import qualified Data.HashMap.Strict as HashMap
import qualified Data.Map.Strict as Map
import Data.Maybe (fromMaybe)
import Data.Sequence (Seq(..))
import Data.Text (Text)
import qualified Data.Text as Text
import qualified Data.Sequence as Seq
import Language.GraphQL.AST (Name)
import Language.GraphQL.AST.Core
import Language.GraphQL.Error
import Language.GraphQL.Execute.Coerce
import Language.GraphQL.Execute.Transform
import Language.GraphQL.Trans
import qualified Language.GraphQL.Type.Definition as Definition
import qualified Language.GraphQL.Type.In as In
import qualified Language.GraphQL.Type.Out as Out
import Language.GraphQL.Type.Schema

resolveFieldValue :: Monad m
    => Definition.Value
    -> Definition.Subs
    -> ActionT m a
    -> m (Either Text a)
resolveFieldValue result args =
    flip runReaderT (Context {arguments = Arguments args, values = result})
    . runExceptT
    . runActionT

collectFields :: Monad m
    => Out.ObjectType m
    -> Seq (Selection m)
    -> Map Name (Seq (Field m))
collectFields objectType = foldl forEach Map.empty
  where
    forEach groupedFields (SelectionField field) =
        let responseKey = aliasOrName field
         in Map.insertWith (<>) responseKey (Seq.singleton field) groupedFields
    forEach groupedFields (SelectionFragment selectionFragment)
        | Fragment fragmentType fragmentSelectionSet <- selectionFragment
        , doesFragmentTypeApply fragmentType objectType =
            let fragmentGroupedFieldSet = collectFields objectType fragmentSelectionSet
             in Map.unionWith (<>) groupedFields fragmentGroupedFieldSet
        | otherwise = groupedFields

aliasOrName :: forall m. Field m -> Name
aliasOrName (Field alias name _ _) = fromMaybe name alias

resolveAbstractType :: Monad m
    => AbstractType m
    -> HashMap Name Definition.Value
    -> CollectErrsT m (Maybe (Out.ObjectType m))
resolveAbstractType abstractType values'
    | Just (Definition.String typeName) <- HashMap.lookup "__typename" values' = do
        types' <- gets types
        case HashMap.lookup typeName types' of
            Just (ObjectType objectType) ->
                if instanceOf objectType abstractType
                    then pure $ Just objectType
                    else pure Nothing
            _ -> pure Nothing
    | otherwise = pure Nothing

doesFragmentTypeApply :: forall m
    . CompositeType m
    -> Out.ObjectType m
    -> Bool
doesFragmentTypeApply (CompositeObjectType fragmentType) objectType =
    fragmentType == objectType
doesFragmentTypeApply (CompositeInterfaceType fragmentType) objectType =
    instanceOf objectType $ AbstractInterfaceType fragmentType
doesFragmentTypeApply (CompositeUnionType fragmentType) objectType =
    instanceOf objectType $ AbstractUnionType fragmentType

instanceOf :: forall m. Out.ObjectType m -> AbstractType m -> Bool
instanceOf objectType (AbstractInterfaceType interfaceType) =
    let Out.ObjectType _ _ interfaces _ = objectType
     in foldr go False interfaces
  where
    go objectInterfaceType@(Out.InterfaceType _ _ interfaces _) acc =
        acc || foldr go (interfaceType == objectInterfaceType) interfaces
instanceOf objectType (AbstractUnionType unionType) =
    let Out.UnionType _ _ members = unionType
     in foldr go False members
  where
    go unionMemberType acc = acc || objectType == unionMemberType

executeField :: Monad m
    => Definition.Value
    -> Out.Resolver m
    -> Field m
    -> CollectErrsT m Aeson.Value
executeField prev (Out.Resolver fieldDefinition resolver) field = do
    let Out.Field _ fieldType argumentDefinitions = fieldDefinition
    let Field _ _ arguments' _ = field
    case coerceArgumentValues argumentDefinitions arguments' of
        Nothing -> errmsg "Argument coercing failed."
        Just argumentValues -> do
            answer <- lift $ resolveFieldValue prev argumentValues resolver
            case answer of
                Right result -> completeValue fieldType field result
                Left errorMessage -> errmsg errorMessage

completeValue :: Monad m
    => Out.Type m
    -> Field m
    -> Definition.Value
    -> CollectErrsT m Aeson.Value
completeValue _ _ Definition.Null = pure Aeson.Null
completeValue _ _ (Definition.Int integer) = pure $ Aeson.toJSON integer
completeValue _ _ (Definition.Boolean boolean') = pure $ Aeson.Bool boolean'
completeValue _ _ (Definition.Float float') = pure $ Aeson.toJSON float'
completeValue _ _ (Definition.Enum enum) = pure $ Aeson.String enum
completeValue _ _ (Definition.String string') = pure $ Aeson.String string'
completeValue (Out.ObjectBaseType objectType) (Field _ _ _ seqSelection) result =
    executeSelectionSet result objectType seqSelection
completeValue (Out.ListBaseType listType) selectionField (Definition.List list) =
    Aeson.toJSON <$> traverse (completeValue listType selectionField) list
completeValue (Out.InterfaceBaseType interfaceType) (Field _ _ _ seqSelection) result
    | Definition.Object objectMap <- result = do
        abstractType <- resolveAbstractType (AbstractInterfaceType interfaceType) objectMap
        case abstractType of
            Just objectType -> executeSelectionSet result objectType seqSelection
            Nothing -> errmsg "Value completion failed."
completeValue (Out.UnionBaseType unionType) (Field _ _ _ seqSelection) result
    | Definition.Object objectMap <- result = do
        abstractType <- resolveAbstractType (AbstractUnionType unionType) objectMap
        case abstractType of
            Just objectType -> executeSelectionSet result objectType seqSelection
            Nothing -> errmsg "Value completion failed."
completeValue _ _ _ = errmsg "Value completion failed."

errmsg :: Monad m => Text -> CollectErrsT m Aeson.Value
errmsg errorMessage = addErrMsg errorMessage >> pure Aeson.Null

-- | Takes an 'Out.ObjectType' and a list of 'Selection's and applies each field
-- to each 'Selection'. Resolves into a value containing the resolved
-- 'Selection', or a null value and error information.
executeSelectionSet :: Monad m
    => Definition.Value
    -> Out.ObjectType m
    -> Seq (Selection m)
    -> CollectErrsT m Aeson.Value
executeSelectionSet result objectType@(Out.ObjectType _ _ _ resolvers) selectionSet = do
    resolvedValues <- Map.traverseMaybeWithKey forEach
        $ collectFields objectType selectionSet
    pure $ Aeson.toJSON resolvedValues
  where
    forEach _responseKey (field :<| _) =
        tryResolvers field >>= lift . pure . pure
    forEach _ _ = pure Nothing
    lookupResolver = flip HashMap.lookup resolvers
    tryResolvers fld@(Field _ name _ _)
        | Just typeField <- lookupResolver name =
            executeField result typeField fld
        | otherwise = errmsg $ Text.unwords ["field", name, "not resolved."]

coerceArgumentValues
    :: HashMap Name In.Argument
    -> HashMap Name Input
    -> Maybe Definition.Subs
coerceArgumentValues argumentDefinitions argumentValues =
    HashMap.foldrWithKey forEach (pure mempty) argumentDefinitions
  where
    forEach variableName (In.Argument _ variableType defaultValue) =
        matchFieldValues coerceArgumentValue argumentValues variableName variableType defaultValue
    coerceArgumentValue inputType (Int integer) =
        coerceInputLiteral inputType (Definition.Int integer)
    coerceArgumentValue inputType (Boolean boolean) =
        coerceInputLiteral inputType (Definition.Boolean boolean)
    coerceArgumentValue inputType (String string) =
        coerceInputLiteral inputType (Definition.String string)
    coerceArgumentValue inputType (Float float) =
        coerceInputLiteral inputType (Definition.Float float)
    coerceArgumentValue inputType (Enum enum) =
        coerceInputLiteral inputType (Definition.Enum enum)
    coerceArgumentValue inputType Null
        | In.isNonNullType inputType = Nothing
        | otherwise = coerceInputLiteral inputType Definition.Null
    coerceArgumentValue (In.ListBaseType inputType) (List list) =
        let coerceItem = coerceInputLiteral inputType
         in Definition.List <$> traverse coerceItem list
    coerceArgumentValue (In.InputObjectBaseType inputType) (Object object)
        | In.InputObjectType _ _ inputFields <- inputType = 
            let go = forEachField object
                resultMap = HashMap.foldrWithKey go (pure mempty) inputFields
             in Definition.Object <$> resultMap
    coerceArgumentValue _ (Variable variable) = pure variable
    coerceArgumentValue _ _ = Nothing
    forEachField object variableName (In.InputField _ variableType defaultValue) =
        matchFieldValues coerceArgumentValue object variableName variableType defaultValue