183 lines
6.8 KiB
Haskell
183 lines
6.8 KiB
Haskell
{- 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 ExplicitForAll #-}
|
|
{-# LANGUAGE LambdaCase #-}
|
|
{-# LANGUAGE RecordWildCards #-}
|
|
|
|
module Language.GraphQL.Executor
|
|
( Error(..)
|
|
, Operation(..)
|
|
, QueryError(..)
|
|
, Response(..)
|
|
, Segment(..)
|
|
, coerceVariableValues
|
|
, executeRequest
|
|
) where
|
|
|
|
import qualified Language.GraphQL.AST.Document as Full
|
|
import qualified Data.Aeson as Aeson
|
|
import Data.Foldable (find)
|
|
import Data.HashMap.Strict (HashMap)
|
|
import qualified Data.HashMap.Strict as HashMap
|
|
import qualified Data.Text as Text
|
|
import qualified Language.GraphQL.Execute.Coerce as Coerce
|
|
import qualified Language.GraphQL.Type as Type
|
|
import qualified Language.GraphQL.Type.Internal as Type.Internal
|
|
import qualified Language.GraphQL.Type.Schema as Schema
|
|
|
|
data Segment = Segment String | Index Int
|
|
|
|
data Error = Error
|
|
{ message :: String
|
|
, locations :: [Full.Location]
|
|
, path :: [Segment]
|
|
}
|
|
|
|
data Response = Response
|
|
{ data' :: Aeson.Object
|
|
, errors :: [Error]
|
|
}
|
|
|
|
data QueryError
|
|
= OperationNameRequired
|
|
| OperationNotFound String
|
|
| CoercionError Full.VariableDefinition
|
|
| UnknownInputType Full.VariableDefinition
|
|
|
|
queryError :: QueryError -> Error
|
|
queryError OperationNameRequired =
|
|
Error{ message = "Operation name is required.", locations = [], path = [] }
|
|
queryError (OperationNotFound operationName) =
|
|
let queryErrorMessage = concat
|
|
[ "Operation \""
|
|
, operationName
|
|
, "\" not found."
|
|
]
|
|
in Error{ message = queryErrorMessage, locations = [], path = [] }
|
|
queryError (CoercionError variableDefinition) =
|
|
let Full.VariableDefinition variableName _ _ location = variableDefinition
|
|
queryErrorMessage = concat
|
|
[ "Failed to coerce the variable \""
|
|
, Text.unpack variableName
|
|
, "\"."
|
|
]
|
|
in Error{ message = queryErrorMessage, locations = [location], path = [] }
|
|
queryError (UnknownInputType variableDefinition) =
|
|
let Full.VariableDefinition variableName variableTypeName _ location = variableDefinition
|
|
queryErrorMessage = concat
|
|
[ "Variable \""
|
|
, Text.unpack variableName
|
|
, "\" has unknown type \""
|
|
, show variableTypeName
|
|
, "\"."
|
|
]
|
|
in Error{ message = queryErrorMessage, locations = [location], path = [] }
|
|
|
|
respondWithQueryError :: QueryError -> Response
|
|
respondWithQueryError = Response mempty . pure . queryError
|
|
|
|
-- operationName selectionSet location
|
|
data Operation = Operation
|
|
Full.OperationType
|
|
(Maybe String)
|
|
[Full.VariableDefinition]
|
|
Full.SelectionSet
|
|
Full.Location
|
|
|
|
document :: Full.Document -> [Operation]
|
|
document = foldr filterOperation []
|
|
where
|
|
filterOperation (Full.ExecutableDefinition executableDefinition) accumulator
|
|
| Full.DefinitionOperation operationDefinition' <- executableDefinition =
|
|
operationDefinition operationDefinition' : accumulator
|
|
filterOperation _ accumulator = accumulator -- Fragment.
|
|
|
|
operationDefinition :: Full.OperationDefinition -> Operation
|
|
operationDefinition = \case
|
|
Full.OperationDefinition operationType operationName variables _ selectionSet operationLocation ->
|
|
let maybeOperationName = Text.unpack <$> operationName
|
|
in Operation operationType maybeOperationName variables selectionSet operationLocation
|
|
Full.SelectionSet selectionSet operationLocation ->
|
|
Operation Full.Query Nothing [] selectionSet operationLocation
|
|
|
|
executeRequest :: Type.Internal.Schema IO
|
|
-> Full.Document
|
|
-> Maybe String
|
|
-> Aeson.Object
|
|
-> Aeson.Object
|
|
-> IO Response
|
|
executeRequest _schema sourceDocument operationName _variableValues _initialValue =
|
|
let transformedDocument = document sourceDocument
|
|
operation = getOperation transformedDocument operationName
|
|
in case operation of
|
|
Left queryError' -> pure $ respondWithQueryError queryError'
|
|
Right (Operation Full.Query _ _ _ _) -> executeQuery
|
|
Right (Operation Full.Mutation _ _ _ _) -> executeMutation
|
|
Right (Operation Full.Subscription _ _ _ _) -> subscribe
|
|
|
|
getOperation :: [Operation] -> Maybe String -> Either QueryError Operation
|
|
getOperation [operation] Nothing = Right operation
|
|
getOperation operations (Just givenOperationName)
|
|
= maybe (Left $ OperationNotFound givenOperationName) Right
|
|
$ find findOperationByName operations
|
|
where
|
|
findOperationByName (Operation _ (Just operationName) _ _ _) =
|
|
givenOperationName == operationName
|
|
findOperationByName _ = False
|
|
getOperation _ _ = Left OperationNameRequired
|
|
|
|
executeQuery :: IO Response
|
|
executeQuery = pure $ Response mempty mempty
|
|
|
|
executeMutation :: IO Response
|
|
executeMutation = pure $ Response mempty mempty
|
|
|
|
subscribe :: IO Response
|
|
subscribe = pure $ Response mempty mempty
|
|
|
|
coerceVariableValues :: Coerce.VariableValue a
|
|
=> forall m
|
|
. HashMap Full.Name (Schema.Type m)
|
|
-> Operation
|
|
-> HashMap Full.Name a
|
|
-> Either QueryError Type.Subs
|
|
coerceVariableValues types operationDefinition' variableValues =
|
|
let Operation _ _ variableDefinitions _ _ = operationDefinition'
|
|
in foldr forEach (Right HashMap.empty) variableDefinitions
|
|
where
|
|
forEach variableDefinition (Right coercedValues) =
|
|
let Full.VariableDefinition variableName variableTypeName defaultValue _ =
|
|
variableDefinition
|
|
defaultValue' = constValue . Full.node <$> defaultValue
|
|
in case Type.Internal.lookupInputType variableTypeName types of
|
|
Just variableType ->
|
|
maybe (Left $ CoercionError variableDefinition) Right
|
|
$ Coerce.matchFieldValues
|
|
coerceVariableValue'
|
|
variableValues
|
|
variableName
|
|
variableType
|
|
defaultValue'
|
|
$ Just coercedValues
|
|
Nothing -> Left $ UnknownInputType variableDefinition
|
|
forEach _ coercedValuesOrError = coercedValuesOrError
|
|
coerceVariableValue' variableType value'
|
|
= Coerce.coerceVariableValue variableType value'
|
|
>>= Coerce.coerceInputLiteral variableType
|
|
|
|
constValue :: Full.ConstValue -> Type.Value
|
|
constValue (Full.ConstInt i) = Type.Int i
|
|
constValue (Full.ConstFloat f) = Type.Float f
|
|
constValue (Full.ConstString x) = Type.String x
|
|
constValue (Full.ConstBoolean b) = Type.Boolean b
|
|
constValue Full.ConstNull = Type.Null
|
|
constValue (Full.ConstEnum e) = Type.Enum e
|
|
constValue (Full.ConstList list) = Type.List $ constValue . Full.node <$> list
|
|
constValue (Full.ConstObject o) =
|
|
Type.Object $ HashMap.fromList $ constObjectField <$> o
|
|
where
|
|
constObjectField Full.ObjectField{value = value', ..} =
|
|
(name, constValue $ Full.node value')
|