2019-11-11 15:46:52 +01:00
|
|
|
{-# LANGUAGE ExplicitForAll #-}
|
2020-05-21 10:20:59 +02:00
|
|
|
{-# LANGUAGE LambdaCase #-}
|
2020-05-22 10:11:48 +02:00
|
|
|
{-# LANGUAGE OverloadedStrings #-}
|
2020-05-26 11:13:55 +02:00
|
|
|
{-# LANGUAGE RecordWildCards #-}
|
2019-12-06 22:52:24 +01:00
|
|
|
{-# LANGUAGE TupleSections #-}
|
2019-11-03 10:42:10 +01:00
|
|
|
|
2020-05-26 11:13:55 +02:00
|
|
|
-- | After the document is parsed, before getting executed, the AST is
|
|
|
|
-- transformed into a similar, simpler AST. Performed transformations include:
|
|
|
|
--
|
|
|
|
-- * Replacing variables with their values.
|
|
|
|
-- * Inlining fragments. Some fragments can be completely eliminated and
|
|
|
|
-- replaced by the selection set they represent. Invalid (recursive and
|
|
|
|
-- non-existing) fragments are skipped. The most fragments are inlined, so the
|
|
|
|
-- executor doesn't have to perform additional lookups later.
|
2020-05-27 23:18:35 +02:00
|
|
|
-- * Evaluating directives (@\@include@ and @\@skip@).
|
2020-05-26 11:13:55 +02:00
|
|
|
--
|
|
|
|
-- This module is also responsible for smaller rewrites that touch only parts of
|
|
|
|
-- the original AST.
|
2019-12-07 09:46:00 +01:00
|
|
|
module Language.GraphQL.Execute.Transform
|
2020-05-21 10:20:59 +02:00
|
|
|
( Document(..)
|
2020-06-06 21:22:11 +02:00
|
|
|
, Field(..)
|
2020-05-27 23:18:35 +02:00
|
|
|
, Fragment(..)
|
2020-06-06 21:22:11 +02:00
|
|
|
, Input(..)
|
2020-05-27 23:18:35 +02:00
|
|
|
, Operation(..)
|
2020-06-06 21:22:11 +02:00
|
|
|
, QueryError(..)
|
2020-05-27 23:18:35 +02:00
|
|
|
, Selection(..)
|
2020-05-21 10:20:59 +02:00
|
|
|
, document
|
2020-05-22 10:11:48 +02:00
|
|
|
, queryError
|
2019-07-14 05:58:05 +02:00
|
|
|
) where
|
2017-01-29 22:44:03 +01:00
|
|
|
|
2019-11-11 15:46:52 +01:00
|
|
|
import Control.Monad (foldM, unless)
|
2019-11-06 06:34:36 +01:00
|
|
|
import Control.Monad.Trans.Class (lift)
|
2020-05-23 06:46:21 +02:00
|
|
|
import Control.Monad.Trans.State (State, evalStateT, gets, modify)
|
2020-05-22 10:11:48 +02:00
|
|
|
import Data.Foldable (find)
|
2020-05-23 06:46:21 +02:00
|
|
|
import Data.Functor.Identity (Identity(..))
|
2019-10-31 07:32:51 +01:00
|
|
|
import Data.HashMap.Strict (HashMap)
|
|
|
|
import qualified Data.HashMap.Strict as HashMap
|
2020-06-06 21:22:11 +02:00
|
|
|
import Data.Int (Int32)
|
2020-05-23 06:46:21 +02:00
|
|
|
import Data.Maybe (fromMaybe)
|
2020-05-22 10:11:48 +02:00
|
|
|
import Data.List.NonEmpty (NonEmpty(..))
|
2017-01-29 22:44:03 +01:00
|
|
|
import qualified Data.List.NonEmpty as NonEmpty
|
2019-11-16 11:41:40 +01:00
|
|
|
import Data.Sequence (Seq, (<|), (><))
|
2020-05-22 10:11:48 +02:00
|
|
|
import Data.Text (Text)
|
|
|
|
import qualified Data.Text as Text
|
2019-07-07 06:31:53 +02:00
|
|
|
import qualified Language.GraphQL.AST as Full
|
2020-06-07 06:16:45 +02:00
|
|
|
import Language.GraphQL.AST (Name)
|
2020-05-27 23:18:35 +02:00
|
|
|
import Language.GraphQL.AST.Core
|
2020-05-22 10:11:48 +02:00
|
|
|
import Language.GraphQL.Execute.Coerce
|
2020-06-06 21:22:11 +02:00
|
|
|
import Language.GraphQL.Type.Directive (Directive(..))
|
2019-12-17 09:03:18 +01:00
|
|
|
import qualified Language.GraphQL.Type.Directive as Directive
|
2020-06-06 21:22:11 +02:00
|
|
|
import qualified Language.GraphQL.Type.Definition as Definition
|
2020-05-24 13:51:00 +02:00
|
|
|
import qualified Language.GraphQL.Type.In as In
|
2020-05-26 11:13:55 +02:00
|
|
|
import qualified Language.GraphQL.Type.Out as Out
|
2020-05-22 10:11:48 +02:00
|
|
|
import Language.GraphQL.Type.Schema
|
2017-01-29 22:44:03 +01:00
|
|
|
|
2020-06-06 21:22:11 +02:00
|
|
|
-- | Associates a fragment name with a list of 'Field's.
|
2020-05-26 11:13:55 +02:00
|
|
|
data Replacement m = Replacement
|
2020-05-27 23:18:35 +02:00
|
|
|
{ fragments :: HashMap Full.Name (Fragment m)
|
2020-05-26 11:13:55 +02:00
|
|
|
, fragmentDefinitions :: FragmentDefinitions
|
2020-06-06 21:22:11 +02:00
|
|
|
, variableValues :: Definition.Subs
|
2020-05-26 11:13:55 +02:00
|
|
|
, types :: HashMap Full.Name (Type m)
|
2019-11-06 06:34:36 +01:00
|
|
|
}
|
|
|
|
|
2020-05-26 11:13:55 +02:00
|
|
|
type FragmentDefinitions = HashMap Full.Name Full.FragmentDefinition
|
2019-12-07 09:46:00 +01:00
|
|
|
|
2020-05-27 23:18:35 +02:00
|
|
|
-- | Represents fragments and inline fragments.
|
|
|
|
data Fragment m
|
|
|
|
= Fragment (CompositeType m) (Seq (Selection m))
|
|
|
|
|
|
|
|
-- | Single selection element.
|
|
|
|
data Selection m
|
|
|
|
= SelectionFragment (Fragment m)
|
|
|
|
| SelectionField (Field m)
|
|
|
|
|
|
|
|
-- | GraphQL has 3 operation types: queries, mutations and subscribtions.
|
|
|
|
--
|
|
|
|
-- Currently only queries and mutations are supported.
|
|
|
|
data Operation m
|
|
|
|
= Query (Maybe Text) (Seq (Selection m))
|
|
|
|
| Mutation (Maybe Text) (Seq (Selection m))
|
|
|
|
|
|
|
|
-- | Single GraphQL field.
|
2020-06-06 21:22:11 +02:00
|
|
|
data Field m = Field
|
|
|
|
(Maybe Full.Name) Full.Name (HashMap Full.Name Input) (Seq (Selection m))
|
2020-05-27 23:18:35 +02:00
|
|
|
|
2020-05-26 11:13:55 +02:00
|
|
|
-- | Contains the operation to be executed along with its root type.
|
2020-05-27 23:18:35 +02:00
|
|
|
data Document m = Document
|
|
|
|
(HashMap Full.Name (Type m)) (Out.ObjectType m) (Operation m)
|
2020-05-21 10:20:59 +02:00
|
|
|
|
|
|
|
data OperationDefinition = OperationDefinition
|
|
|
|
Full.OperationType
|
|
|
|
(Maybe Full.Name)
|
|
|
|
[Full.VariableDefinition]
|
|
|
|
[Full.Directive]
|
|
|
|
Full.SelectionSet
|
|
|
|
|
2020-05-22 10:11:48 +02:00
|
|
|
-- | Query error types.
|
|
|
|
data QueryError
|
|
|
|
= OperationNotFound Text
|
|
|
|
| OperationNameRequired
|
|
|
|
| CoercionError
|
|
|
|
| TransformationError
|
|
|
|
| EmptyDocument
|
2020-05-26 11:13:55 +02:00
|
|
|
| UnsupportedRootOperation
|
2020-05-22 10:11:48 +02:00
|
|
|
|
2020-06-06 21:22:11 +02:00
|
|
|
data Input
|
|
|
|
= Int Int32
|
|
|
|
| Float Double
|
|
|
|
| String Text
|
|
|
|
| Boolean Bool
|
|
|
|
| Null
|
|
|
|
| Enum Name
|
|
|
|
| List [Definition.Value]
|
|
|
|
| Object (HashMap Name Input)
|
|
|
|
| Variable Definition.Value
|
|
|
|
deriving (Eq, Show)
|
|
|
|
|
2020-05-22 10:11:48 +02:00
|
|
|
queryError :: QueryError -> Text
|
|
|
|
queryError (OperationNotFound operationName) = Text.unwords
|
|
|
|
["Operation", operationName, "couldn't be found in the document."]
|
|
|
|
queryError OperationNameRequired = "Missing operation name."
|
|
|
|
queryError CoercionError = "Coercion error."
|
|
|
|
queryError TransformationError = "Schema transformation error."
|
|
|
|
queryError EmptyDocument =
|
|
|
|
"The document doesn't contain any executable operations."
|
2020-05-26 11:13:55 +02:00
|
|
|
queryError UnsupportedRootOperation =
|
|
|
|
"Root operation type couldn't be found in the schema."
|
2020-05-22 10:11:48 +02:00
|
|
|
|
|
|
|
getOperation
|
|
|
|
:: Maybe Full.Name
|
|
|
|
-> NonEmpty OperationDefinition
|
|
|
|
-> Either QueryError OperationDefinition
|
|
|
|
getOperation Nothing (operation' :| []) = pure operation'
|
|
|
|
getOperation Nothing _ = Left OperationNameRequired
|
|
|
|
getOperation (Just operationName) operations
|
|
|
|
| Just operation' <- find matchingName operations = pure operation'
|
|
|
|
| otherwise = Left $ OperationNotFound operationName
|
|
|
|
where
|
|
|
|
matchingName (OperationDefinition _ name _ _ _) =
|
|
|
|
name == Just operationName
|
|
|
|
|
|
|
|
lookupInputType
|
|
|
|
:: Full.Type
|
2020-05-25 07:41:21 +02:00
|
|
|
-> HashMap.HashMap Full.Name (Type m)
|
|
|
|
-> Maybe In.Type
|
2020-05-22 10:11:48 +02:00
|
|
|
lookupInputType (Full.TypeNamed name) types =
|
|
|
|
case HashMap.lookup name types of
|
2020-05-25 07:41:21 +02:00
|
|
|
Just (ScalarType scalarType) ->
|
|
|
|
Just $ In.NamedScalarType scalarType
|
|
|
|
Just (EnumType enumType) ->
|
|
|
|
Just $ In.NamedEnumType enumType
|
|
|
|
Just (InputObjectType objectType) ->
|
|
|
|
Just $ In.NamedInputObjectType objectType
|
2020-05-22 10:11:48 +02:00
|
|
|
_ -> Nothing
|
|
|
|
lookupInputType (Full.TypeList list) types
|
2020-05-25 07:41:21 +02:00
|
|
|
= In.ListType
|
2020-05-22 10:11:48 +02:00
|
|
|
<$> lookupInputType list types
|
|
|
|
lookupInputType (Full.TypeNonNull (Full.NonNullTypeNamed nonNull)) types =
|
|
|
|
case HashMap.lookup nonNull types of
|
2020-05-25 07:41:21 +02:00
|
|
|
Just (ScalarType scalarType) ->
|
|
|
|
Just $ In.NonNullScalarType scalarType
|
|
|
|
Just (EnumType enumType) ->
|
|
|
|
Just $ In.NonNullEnumType enumType
|
|
|
|
Just (InputObjectType objectType) ->
|
|
|
|
Just $ In.NonNullInputObjectType objectType
|
2020-05-22 10:11:48 +02:00
|
|
|
_ -> Nothing
|
|
|
|
lookupInputType (Full.TypeNonNull (Full.NonNullTypeList nonNull)) types
|
2020-05-25 07:41:21 +02:00
|
|
|
= In.NonNullListType
|
2020-05-22 10:11:48 +02:00
|
|
|
<$> lookupInputType nonNull types
|
|
|
|
|
2020-05-26 11:13:55 +02:00
|
|
|
coerceVariableValues :: VariableValue a
|
|
|
|
=> forall m
|
|
|
|
. HashMap Full.Name (Type m)
|
2020-05-22 10:11:48 +02:00
|
|
|
-> OperationDefinition
|
|
|
|
-> HashMap.HashMap Full.Name a
|
2020-06-06 21:22:11 +02:00
|
|
|
-> Either QueryError Definition.Subs
|
|
|
|
coerceVariableValues types operationDefinition variableValues =
|
2020-05-26 11:13:55 +02:00
|
|
|
let OperationDefinition _ _ variableDefinitions _ _ = operationDefinition
|
2020-05-22 10:11:48 +02:00
|
|
|
in maybe (Left CoercionError) Right
|
2020-06-06 21:22:11 +02:00
|
|
|
$ foldr forEach (Just HashMap.empty) variableDefinitions
|
2020-05-22 10:11:48 +02:00
|
|
|
where
|
2020-06-06 21:22:11 +02:00
|
|
|
forEach variableDefinition coercedValues = do
|
2020-05-23 06:46:21 +02:00
|
|
|
let Full.VariableDefinition variableName variableTypeName defaultValue =
|
2020-05-22 10:11:48 +02:00
|
|
|
variableDefinition
|
2020-05-23 06:46:21 +02:00
|
|
|
let defaultValue' = constValue <$> defaultValue
|
2020-05-26 11:13:55 +02:00
|
|
|
variableType <- lookupInputType variableTypeName types
|
2020-06-06 21:22:11 +02:00
|
|
|
|
|
|
|
matchFieldValues coerceVariableValue' variableValues variableName variableType defaultValue' coercedValues
|
|
|
|
coerceVariableValue' variableType value'
|
|
|
|
= coerceVariableValue variableType value'
|
|
|
|
>>= coerceInputLiteral variableType
|
|
|
|
|
|
|
|
constValue :: Full.ConstValue -> Definition.Value
|
|
|
|
constValue (Full.ConstInt i) = Definition.Int i
|
|
|
|
constValue (Full.ConstFloat f) = Definition.Float f
|
|
|
|
constValue (Full.ConstString x) = Definition.String x
|
|
|
|
constValue (Full.ConstBoolean b) = Definition.Boolean b
|
|
|
|
constValue Full.ConstNull = Definition.Null
|
|
|
|
constValue (Full.ConstEnum e) = Definition.Enum e
|
|
|
|
constValue (Full.ConstList l) = Definition.List $ constValue <$> l
|
2020-05-23 06:46:21 +02:00
|
|
|
constValue (Full.ConstObject o) =
|
2020-06-06 21:22:11 +02:00
|
|
|
Definition.Object $ HashMap.fromList $ constObjectField <$> o
|
2020-05-23 06:46:21 +02:00
|
|
|
where
|
|
|
|
constObjectField (Full.ObjectField key value') = (key, constValue value')
|
2020-05-22 10:11:48 +02:00
|
|
|
|
2019-08-29 07:40:50 +02:00
|
|
|
-- | Rewrites the original syntax tree into an intermediate representation used
|
|
|
|
-- for query execution.
|
2020-05-26 11:13:55 +02:00
|
|
|
document :: VariableValue a
|
|
|
|
=> forall m
|
|
|
|
. Schema m
|
2020-05-22 10:11:48 +02:00
|
|
|
-> Maybe Full.Name
|
|
|
|
-> HashMap Full.Name a
|
|
|
|
-> Full.Document
|
2020-05-26 11:13:55 +02:00
|
|
|
-> Either QueryError (Document m)
|
2020-05-22 10:11:48 +02:00
|
|
|
document schema operationName subs ast = do
|
2020-05-26 11:13:55 +02:00
|
|
|
let referencedTypes = collectReferencedTypes schema
|
|
|
|
|
|
|
|
(operations, fragmentTable) <- defragment ast
|
|
|
|
chosenOperation <- getOperation operationName operations
|
|
|
|
coercedValues <- coerceVariableValues referencedTypes chosenOperation subs
|
|
|
|
|
|
|
|
let replacement = Replacement
|
|
|
|
{ fragments = HashMap.empty
|
|
|
|
, fragmentDefinitions = fragmentTable
|
|
|
|
, variableValues = coercedValues
|
|
|
|
, types = referencedTypes
|
|
|
|
}
|
|
|
|
case chosenOperation of
|
|
|
|
OperationDefinition Full.Query _ _ _ _ ->
|
2020-05-27 23:18:35 +02:00
|
|
|
pure $ Document referencedTypes (query schema)
|
|
|
|
$ operation chosenOperation replacement
|
2020-05-26 11:13:55 +02:00
|
|
|
OperationDefinition Full.Mutation _ _ _ _
|
|
|
|
| Just mutationType <- mutation schema ->
|
2020-05-27 23:18:35 +02:00
|
|
|
pure $ Document referencedTypes mutationType
|
|
|
|
$ operation chosenOperation replacement
|
2020-05-26 11:13:55 +02:00
|
|
|
_ -> Left UnsupportedRootOperation
|
2020-05-22 10:11:48 +02:00
|
|
|
|
2020-05-26 11:13:55 +02:00
|
|
|
defragment
|
|
|
|
:: Full.Document
|
|
|
|
-> Either QueryError (NonEmpty OperationDefinition, FragmentDefinitions)
|
|
|
|
defragment ast =
|
|
|
|
let (operations, fragmentTable) = foldr defragment' ([], HashMap.empty) ast
|
|
|
|
nonEmptyOperations = NonEmpty.nonEmpty operations
|
|
|
|
emptyDocument = Left EmptyDocument
|
|
|
|
in (, fragmentTable) <$> maybe emptyDocument Right nonEmptyOperations
|
2017-01-29 22:44:03 +01:00
|
|
|
where
|
2020-05-26 11:13:55 +02:00
|
|
|
defragment' definition (operations, fragments')
|
2020-05-21 10:20:59 +02:00
|
|
|
| (Full.ExecutableDefinition executable) <- definition
|
|
|
|
, (Full.DefinitionOperation operation') <- executable =
|
|
|
|
(transform operation' : operations, fragments')
|
|
|
|
| (Full.ExecutableDefinition executable) <- definition
|
|
|
|
, (Full.DefinitionFragment fragment) <- executable
|
|
|
|
, (Full.FragmentDefinition name _ _ _) <- fragment =
|
|
|
|
(operations, HashMap.insert name fragment fragments')
|
2020-05-26 11:13:55 +02:00
|
|
|
defragment' _ acc = acc
|
2020-05-21 10:20:59 +02:00
|
|
|
transform = \case
|
|
|
|
Full.OperationDefinition type' name variables directives' selections ->
|
|
|
|
OperationDefinition type' name variables directives' selections
|
|
|
|
Full.SelectionSet selectionSet ->
|
|
|
|
OperationDefinition Full.Query Nothing mempty mempty selectionSet
|
2017-01-29 22:44:03 +01:00
|
|
|
|
2017-02-04 01:48:26 +01:00
|
|
|
-- * Operation
|
|
|
|
|
2020-05-27 23:18:35 +02:00
|
|
|
operation :: OperationDefinition -> Replacement m -> Operation m
|
|
|
|
operation operationDefinition replacement
|
2020-05-23 06:46:21 +02:00
|
|
|
= runIdentity
|
2020-05-27 23:18:35 +02:00
|
|
|
$ evalStateT (collectFragments >> transform operationDefinition) replacement
|
2020-05-21 10:20:59 +02:00
|
|
|
where
|
|
|
|
transform (OperationDefinition Full.Query name _ _ sels) =
|
2020-05-27 23:18:35 +02:00
|
|
|
Query name <$> appendSelection sels
|
2020-05-21 10:20:59 +02:00
|
|
|
transform (OperationDefinition Full.Mutation name _ _ sels) =
|
2020-05-27 23:18:35 +02:00
|
|
|
Mutation name <$> appendSelection sels
|
2019-10-31 07:32:51 +01:00
|
|
|
|
2019-12-06 22:52:24 +01:00
|
|
|
-- * Selection
|
|
|
|
|
2020-05-27 23:18:35 +02:00
|
|
|
selection
|
|
|
|
:: Full.Selection
|
|
|
|
-> State (Replacement m) (Either (Seq (Selection m)) (Selection m))
|
|
|
|
selection (Full.Field alias name arguments' directives' selections) =
|
|
|
|
maybe (Left mempty) (Right . SelectionField) <$> do
|
2020-06-06 21:22:11 +02:00
|
|
|
fieldArguments <- foldM go HashMap.empty arguments'
|
2020-05-27 23:18:35 +02:00
|
|
|
fieldSelections <- appendSelection selections
|
2019-12-25 06:45:29 +01:00
|
|
|
fieldDirectives <- Directive.selection <$> directives directives'
|
2020-05-27 23:18:35 +02:00
|
|
|
let field' = Field alias name fieldArguments fieldSelections
|
2019-12-25 06:45:29 +01:00
|
|
|
pure $ field' <$ fieldDirectives
|
2020-06-06 21:22:11 +02:00
|
|
|
where
|
|
|
|
go arguments (Full.Argument name' value') =
|
|
|
|
inputField arguments name' value'
|
|
|
|
|
2020-05-27 23:18:35 +02:00
|
|
|
selection (Full.FragmentSpread name directives') =
|
|
|
|
maybe (Left mempty) (Right . SelectionFragment) <$> do
|
2019-12-25 06:45:29 +01:00
|
|
|
spreadDirectives <- Directive.selection <$> directives directives'
|
|
|
|
fragments' <- gets fragments
|
2020-05-23 06:46:21 +02:00
|
|
|
|
2019-12-25 06:45:29 +01:00
|
|
|
fragmentDefinitions' <- gets fragmentDefinitions
|
2020-05-23 06:46:21 +02:00
|
|
|
case HashMap.lookup name fragments' of
|
|
|
|
Just definition -> lift $ pure $ definition <$ spreadDirectives
|
2020-05-27 23:18:35 +02:00
|
|
|
Nothing
|
|
|
|
| Just definition <- HashMap.lookup name fragmentDefinitions' -> do
|
|
|
|
fragDef <- fragmentDefinition definition
|
|
|
|
case fragDef of
|
|
|
|
Just fragment -> lift $ pure $ fragment <$ spreadDirectives
|
|
|
|
_ -> lift $ pure Nothing
|
|
|
|
| otherwise -> lift $ pure Nothing
|
|
|
|
selection (Full.InlineFragment type' directives' selections) = do
|
2019-12-25 06:45:29 +01:00
|
|
|
fragmentDirectives <- Directive.selection <$> directives directives'
|
|
|
|
case fragmentDirectives of
|
|
|
|
Nothing -> pure $ Left mempty
|
|
|
|
_ -> do
|
2020-05-27 23:18:35 +02:00
|
|
|
fragmentSelectionSet <- appendSelection selections
|
|
|
|
|
|
|
|
case type' of
|
|
|
|
Nothing -> pure $ Left fragmentSelectionSet
|
|
|
|
Just typeName -> do
|
|
|
|
typeCondition' <- lookupTypeCondition typeName
|
|
|
|
case typeCondition' of
|
|
|
|
Just typeCondition -> pure $
|
|
|
|
selectionFragment typeCondition fragmentSelectionSet
|
|
|
|
Nothing -> pure $ Left mempty
|
2019-12-25 06:45:29 +01:00
|
|
|
where
|
|
|
|
selectionFragment typeName = Right
|
2020-05-27 23:18:35 +02:00
|
|
|
. SelectionFragment
|
|
|
|
. Fragment typeName
|
2019-12-06 22:52:24 +01:00
|
|
|
|
2020-05-26 11:13:55 +02:00
|
|
|
appendSelection :: Traversable t
|
2020-05-27 23:18:35 +02:00
|
|
|
=> t Full.Selection
|
|
|
|
-> State (Replacement m) (Seq (Selection m))
|
|
|
|
appendSelection = foldM go mempty
|
2019-12-06 22:52:24 +01:00
|
|
|
where
|
2020-05-27 23:18:35 +02:00
|
|
|
go acc sel = append acc <$> selection sel
|
2019-12-06 22:52:24 +01:00
|
|
|
append acc (Left list) = list >< acc
|
|
|
|
append acc (Right one) = one <| acc
|
|
|
|
|
2020-06-06 21:22:11 +02:00
|
|
|
directives :: [Full.Directive] -> State (Replacement m) [Directive]
|
2019-12-06 22:52:24 +01:00
|
|
|
directives = traverse directive
|
|
|
|
where
|
2020-06-06 21:22:11 +02:00
|
|
|
directive (Full.Directive directiveName directiveArguments)
|
|
|
|
= Directive directiveName . Arguments
|
|
|
|
<$> foldM go HashMap.empty directiveArguments
|
|
|
|
go arguments (Full.Argument name value') = do
|
|
|
|
substitutedValue <- value value'
|
|
|
|
return $ HashMap.insert name substitutedValue arguments
|
2017-02-04 01:48:26 +01:00
|
|
|
|
2017-01-29 22:44:03 +01:00
|
|
|
-- * Fragment replacement
|
|
|
|
|
2019-11-11 15:46:52 +01:00
|
|
|
-- | Extract fragment definitions into a single 'HashMap'.
|
2020-05-27 23:18:35 +02:00
|
|
|
collectFragments :: State (Replacement m) ()
|
|
|
|
collectFragments = do
|
2019-11-11 15:46:52 +01:00
|
|
|
fragDefs <- gets fragmentDefinitions
|
|
|
|
let nextValue = head $ HashMap.elems fragDefs
|
|
|
|
unless (HashMap.null fragDefs) $ do
|
2020-05-27 23:18:35 +02:00
|
|
|
_ <- fragmentDefinition nextValue
|
|
|
|
collectFragments
|
|
|
|
|
|
|
|
lookupTypeCondition :: Full.Name -> State (Replacement m) (Maybe (CompositeType m))
|
|
|
|
lookupTypeCondition type' = do
|
|
|
|
types' <- gets types
|
|
|
|
case HashMap.lookup type' types' of
|
|
|
|
Just (ObjectType objectType) ->
|
|
|
|
lift $ pure $ Just $ CompositeObjectType objectType
|
|
|
|
Just (UnionType unionType) ->
|
|
|
|
lift $ pure $ Just $ CompositeUnionType unionType
|
|
|
|
Just (InterfaceType interfaceType) ->
|
|
|
|
lift $ pure $ Just $ CompositeInterfaceType interfaceType
|
|
|
|
_ -> lift $ pure Nothing
|
|
|
|
|
|
|
|
fragmentDefinition
|
|
|
|
:: Full.FragmentDefinition
|
|
|
|
-> State (Replacement m) (Maybe (Fragment m))
|
|
|
|
fragmentDefinition (Full.FragmentDefinition name type' _ selections) = do
|
2019-11-13 20:40:09 +01:00
|
|
|
modify deleteFragmentDefinition
|
2020-05-27 23:18:35 +02:00
|
|
|
fragmentSelection <- appendSelection selections
|
|
|
|
compositeType <- lookupTypeCondition type'
|
|
|
|
|
|
|
|
case compositeType of
|
|
|
|
Just compositeType' -> do
|
|
|
|
let newValue = Fragment compositeType' fragmentSelection
|
|
|
|
modify $ insertFragment newValue
|
|
|
|
lift $ pure $ Just newValue
|
|
|
|
_ -> lift $ pure Nothing
|
2019-10-31 07:32:51 +01:00
|
|
|
where
|
2020-05-26 11:13:55 +02:00
|
|
|
deleteFragmentDefinition replacement@Replacement{..} =
|
|
|
|
let newDefinitions = HashMap.delete name fragmentDefinitions
|
|
|
|
in replacement{ fragmentDefinitions = newDefinitions }
|
|
|
|
insertFragment newValue replacement@Replacement{..} =
|
|
|
|
let newFragments = HashMap.insert name newValue fragments
|
|
|
|
in replacement{ fragments = newFragments }
|
2017-01-29 22:44:03 +01:00
|
|
|
|
2020-06-06 21:22:11 +02:00
|
|
|
value :: forall m. Full.Value -> State (Replacement m) Definition.Value
|
2020-05-23 06:46:21 +02:00
|
|
|
value (Full.Variable name) =
|
2020-06-06 21:22:11 +02:00
|
|
|
gets (fromMaybe Definition.Null . HashMap.lookup name . variableValues)
|
|
|
|
value (Full.Int i) = pure $ Definition.Int i
|
|
|
|
value (Full.Float f) = pure $ Definition.Float f
|
|
|
|
value (Full.String x) = pure $ Definition.String x
|
|
|
|
value (Full.Boolean b) = pure $ Definition.Boolean b
|
|
|
|
value Full.Null = pure Definition.Null
|
|
|
|
value (Full.Enum e) = pure $ Definition.Enum e
|
|
|
|
value (Full.List l) = Definition.List <$> traverse value l
|
2019-11-06 06:34:36 +01:00
|
|
|
value (Full.Object o) =
|
2020-06-06 21:22:11 +02:00
|
|
|
Definition.Object . HashMap.fromList <$> traverse objectField o
|
|
|
|
where
|
|
|
|
objectField (Full.ObjectField name value') = (name,) <$> value value'
|
|
|
|
|
|
|
|
input :: forall m. Full.Value -> State (Replacement m) (Maybe Input)
|
|
|
|
input (Full.Variable name) =
|
|
|
|
gets (fmap Variable . HashMap.lookup name . variableValues)
|
|
|
|
input (Full.Int i) = pure $ pure $ Int i
|
|
|
|
input (Full.Float f) = pure $ pure $ Float f
|
|
|
|
input (Full.String x) = pure $ pure $ String x
|
|
|
|
input (Full.Boolean b) = pure $ pure $ Boolean b
|
|
|
|
input Full.Null = pure $ pure Null
|
|
|
|
input (Full.Enum e) = pure $ pure $ Enum e
|
|
|
|
input (Full.List list) = pure . List <$> traverse value list
|
|
|
|
input (Full.Object object) = do
|
|
|
|
objectFields <- foldM objectField HashMap.empty object
|
|
|
|
pure $ pure $ Object objectFields
|
|
|
|
where
|
|
|
|
objectField resultMap (Full.ObjectField name value') =
|
|
|
|
inputField resultMap name value'
|
|
|
|
|
|
|
|
inputField :: forall m
|
|
|
|
. HashMap Full.Name Input
|
|
|
|
-> Full.Name
|
|
|
|
-> Full.Value
|
|
|
|
-> State (Replacement m) (HashMap Full.Name Input)
|
|
|
|
inputField resultMap name value' = do
|
|
|
|
objectFieldValue <- input value'
|
|
|
|
case objectFieldValue of
|
|
|
|
Just fieldValue -> pure $ HashMap.insert name fieldValue resultMap
|
|
|
|
Nothing -> pure resultMap
|