aboutsummaryrefslogtreecommitdiff
path: root/src/Language/GraphQL/Execute/Execution.hs
diff options
context:
space:
mode:
Diffstat (limited to 'src/Language/GraphQL/Execute/Execution.hs')
-rw-r--r--src/Language/GraphQL/Execute/Execution.hs229
1 files changed, 229 insertions, 0 deletions
diff --git a/src/Language/GraphQL/Execute/Execution.hs b/src/Language/GraphQL/Execute/Execution.hs
new file mode 100644
index 0000000..0c10419
--- /dev/null
+++ b/src/Language/GraphQL/Execute/Execution.hs
@@ -0,0 +1,229 @@
+{-# LANGUAGE ExplicitForAll #-}
+{-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE ViewPatterns #-}
+
+module Language.GraphQL.Execute.Execution
+ ( executeSelectionSet
+ ) where
+
+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.List.NonEmpty (NonEmpty(..))
+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 Language.GraphQL.AST (Name)
+import Language.GraphQL.AST.Core
+import Language.GraphQL.Error
+import Language.GraphQL.Execute.Coerce
+import qualified Language.GraphQL.Execute.Transform as Transform
+import Language.GraphQL.Trans
+import qualified Language.GraphQL.Type as Type
+import qualified Language.GraphQL.Type.In as In
+import qualified Language.GraphQL.Type.Out as Out
+import Language.GraphQL.Type.Schema
+import Prelude hiding (null)
+
+resolveFieldValue :: Monad m
+ => Type.Value
+ -> Type.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 (Transform.Selection m)
+ -> Map Name (NonEmpty (Transform.Field m))
+collectFields objectType = foldl forEach Map.empty
+ where
+ forEach groupedFields (Transform.SelectionField field) =
+ let responseKey = aliasOrName field
+ in Map.insertWith (<>) responseKey (field :| []) groupedFields
+ forEach groupedFields (Transform.SelectionFragment selectionFragment)
+ | Transform.Fragment fragmentType fragmentSelectionSet <- selectionFragment
+ , doesFragmentTypeApply fragmentType objectType =
+ let fragmentGroupedFieldSet = collectFields objectType fragmentSelectionSet
+ in Map.unionWith (<>) groupedFields fragmentGroupedFieldSet
+ | otherwise = groupedFields
+
+aliasOrName :: forall m. Transform.Field m -> Name
+aliasOrName (Transform.Field alias name _ _) = fromMaybe name alias
+
+resolveAbstractType :: Monad m
+ => AbstractType m
+ -> Type.Subs
+ -> CollectErrsT m (Maybe (Out.ObjectType m))
+resolveAbstractType abstractType values'
+ | Just (Type.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, Serialize a)
+ => Out.Resolver m
+ -> Type.Value
+ -> NonEmpty (Transform.Field m)
+ -> CollectErrsT m a
+executeField (Out.Resolver fieldDefinition resolver) prev fields = do
+ let Out.Field _ fieldType argumentDefinitions = fieldDefinition
+ let (Transform.Field _ _ arguments' _ :| []) = fields
+ 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 fields result
+ Left errorMessage -> errmsg errorMessage
+
+completeValue :: (Monad m, Serialize a)
+ => Out.Type m
+ -> NonEmpty (Transform.Field m)
+ -> Type.Value
+ -> CollectErrsT m a
+completeValue (Out.isNonNullType -> False) _ Type.Null = pure null
+completeValue outputType@(Out.ListBaseType listType) fields (Type.List list)
+ = traverse (completeValue listType fields) list
+ >>= coerceResult outputType . List
+completeValue outputType@(Out.ScalarBaseType _) _ (Type.Int int) =
+ coerceResult outputType $ Int int
+completeValue outputType@(Out.ScalarBaseType _) _ (Type.Boolean boolean) =
+ coerceResult outputType $ Boolean boolean
+completeValue outputType@(Out.ScalarBaseType _) _ (Type.Float float) =
+ coerceResult outputType $ Float float
+completeValue outputType@(Out.ScalarBaseType _) _ (Type.String string) =
+ coerceResult outputType $ String string
+completeValue outputType@(Out.EnumBaseType enumType) _ (Type.Enum enum) =
+ let Type.EnumType _ _ enumMembers = enumType
+ in if HashMap.member enum enumMembers
+ then coerceResult outputType $ Enum enum
+ else errmsg "Value completion failed."
+completeValue (Out.ObjectBaseType objectType) fields result =
+ executeSelectionSet result objectType $ mergeSelectionSets fields
+completeValue (Out.InterfaceBaseType interfaceType) fields result
+ | Type.Object objectMap <- result = do
+ let abstractType = AbstractInterfaceType interfaceType
+ concreteType <- resolveAbstractType abstractType objectMap
+ case concreteType of
+ Just objectType -> executeSelectionSet result objectType
+ $ mergeSelectionSets fields
+ Nothing -> errmsg "Value completion failed."
+completeValue (Out.UnionBaseType unionType) fields result
+ | Type.Object objectMap <- result = do
+ let abstractType = AbstractUnionType unionType
+ concreteType <- resolveAbstractType abstractType objectMap
+ case concreteType of
+ Just objectType -> executeSelectionSet result objectType
+ $ mergeSelectionSets fields
+ Nothing -> errmsg "Value completion failed."
+completeValue _ _ _ = errmsg "Value completion failed."
+
+mergeSelectionSets :: Monad m => NonEmpty (Transform.Field m) -> Seq (Transform.Selection m)
+mergeSelectionSets = foldr forEach mempty
+ where
+ forEach (Transform.Field _ _ _ fieldSelectionSet) selectionSet =
+ selectionSet <> fieldSelectionSet
+
+errmsg :: (Monad m, Serialize a) => Text -> CollectErrsT m a
+errmsg errorMessage = addErrMsg errorMessage >> pure null
+
+coerceResult :: (Monad m, Serialize a)
+ => Out.Type m
+ -> Output a
+ -> CollectErrsT m a
+coerceResult outputType result
+ | Just serialized <- serialize outputType result = pure serialized
+ | otherwise = errmsg "Result coercion failed."
+
+-- | Takes an 'Out.ObjectType' and a list of 'Transform.Selection's and applies
+-- each field to each 'Transform.Selection'. Resolves into a value containing
+-- the resolved 'Transform.Selection', or a null value and error information.
+executeSelectionSet :: (Monad m, Serialize a)
+ => Type.Value
+ -> Out.ObjectType m
+ -> Seq (Transform.Selection m)
+ -> CollectErrsT m a
+executeSelectionSet result objectType@(Out.ObjectType _ _ _ resolvers) selectionSet = do
+ let fields = collectFields objectType selectionSet
+ resolvedValues <- Map.traverseMaybeWithKey forEach fields
+ coerceResult (Out.NonNullObjectType objectType) $ Object resolvedValues
+ where
+ forEach _ fields@(field :| _) =
+ let Transform.Field _ name _ _ = field
+ in traverse (tryResolver fields) $ lookupResolver name
+ lookupResolver = flip HashMap.lookup resolvers
+ tryResolver fields resolver =
+ executeField resolver result fields >>= lift . pure
+
+coerceArgumentValues
+ :: HashMap Name In.Argument
+ -> HashMap Name Transform.Input
+ -> Maybe Type.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 (Transform.Int integer) =
+ coerceInputLiteral inputType (Type.Int integer)
+ coerceArgumentValue inputType (Transform.Boolean boolean) =
+ coerceInputLiteral inputType (Type.Boolean boolean)
+ coerceArgumentValue inputType (Transform.String string) =
+ coerceInputLiteral inputType (Type.String string)
+ coerceArgumentValue inputType (Transform.Float float) =
+ coerceInputLiteral inputType (Type.Float float)
+ coerceArgumentValue inputType (Transform.Enum enum) =
+ coerceInputLiteral inputType (Type.Enum enum)
+ coerceArgumentValue inputType Transform.Null
+ | In.isNonNullType inputType = Nothing
+ | otherwise = coerceInputLiteral inputType Type.Null
+ coerceArgumentValue (In.ListBaseType inputType) (Transform.List list) =
+ let coerceItem = coerceInputLiteral inputType
+ in Type.List <$> traverse coerceItem list
+ coerceArgumentValue (In.InputObjectBaseType inputType) (Transform.Object object)
+ | In.InputObjectType _ _ inputFields <- inputType =
+ let go = forEachField object
+ resultMap = HashMap.foldrWithKey go (pure mempty) inputFields
+ in Type.Object <$> resultMap
+ coerceArgumentValue _ (Transform.Variable variable) = pure variable
+ coerceArgumentValue _ _ = Nothing
+ forEachField object variableName (In.InputField _ variableType defaultValue) =
+ matchFieldValues coerceArgumentValue object variableName variableType defaultValue