{-# LANGUAGE ConstraintKinds #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE NamedFieldPuns #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TypeApplications #-}
module Data.Morpheus.Execution.Server.Resolve
( statelessResolver
, byteStringIO
, streamResolver
, statefulResolver
, RootResCon
, fullSchema
) where
import Control.Monad.Except (liftEither)
import Control.Monad.Trans.Except (ExceptT (..), runExceptT)
import Data.Aeson (encode)
import Data.Aeson.Internal (formatError, ifromJSON)
import Data.Aeson.Parser (eitherDecodeWith, jsonNoDup)
import qualified Data.ByteString.Lazy.Char8 as L
import Data.Functor.Identity (Identity (..))
import Data.Proxy (Proxy (..))
import Data.Morpheus.Error.Utils (badRequestError, renderErrors)
import Data.Morpheus.Execution.Internal.GraphScanner (resolveUpdates)
import Data.Morpheus.Execution.Server.Encode (EncodeCon, EncodeMutCon, EncodeSubCon, OBJ_RES,
encodeOperation, encodeQuery)
import Data.Morpheus.Execution.Server.Introspect (IntroCon, ObjectFields (..))
import Data.Morpheus.Execution.Subscription.ClientRegister (GQLState, publishUpdates)
import Data.Morpheus.Parsing.Request.Parser (parseGQL)
import Data.Morpheus.Schema.Schema (Root)
import Data.Morpheus.Schema.SchemaAPI (defaultTypes, hiddenRootFields, schemaAPI)
import Data.Morpheus.Types.GQLType (GQLType (CUSTOM))
import Data.Morpheus.Types.Internal.AST.Operation (Operation (..), ValidOperation)
import Data.Morpheus.Types.Internal.Data (DataFingerprint (..), DataTyCon (..),
DataTypeLib (..), OperationKind (..), initTypeLib)
import Data.Morpheus.Types.Internal.Stream (Event (..), ResponseEvent (..), ResponseStream,
StreamState (..), StreamT (..), closeStream, mapS)
import Data.Morpheus.Types.Internal.Validation (Validation)
import Data.Morpheus.Types.Internal.Value (Value (..))
import Data.Morpheus.Types.IO (GQLRequest (..), GQLResponse (..))
import Data.Morpheus.Types.Resolver (GQLRootResolver (..), Resolver, ResponseT)
import Data.Morpheus.Validation.Internal.Utils (VALIDATION_MODE (..))
import Data.Morpheus.Validation.Query.Validation (validateRequest)
import Data.Typeable (Typeable)
type EventCon event = Eq event
type RootResCon m event cont query mutation subscription
= ( EventCon event
, Typeable m
, IntroCon query
, IntroCon mutation
, IntroCon subscription
, OBJ_RES m (Root (Resolver m)) Value
, EncodeCon m query Value
, EncodeMutCon m event cont mutation
, EncodeSubCon m event cont subscription)
decodeNoDup :: L.ByteString -> Either String GQLRequest
decodeNoDup str =
case eitherDecodeWith jsonNoDup ifromJSON str of
Left (path, x) -> Left $ formatError path x
Right value -> Right value
byteStringIO :: Monad m => (GQLRequest -> m GQLResponse) -> L.ByteString -> m L.ByteString
byteStringIO resolver request =
case decodeNoDup request of
Left aesonError' -> return $ badRequestError aesonError'
Right req -> encode <$> resolver req
statelessResolver ::
(Monad m, RootResCon m s cont query mut sub)
=> GQLRootResolver m s cont query mut sub
-> GQLRequest
-> m GQLResponse
statelessResolver root = fmap snd . closeStream . streamResolver root
streamResolver ::
(Monad m, RootResCon m event cont query mut sub)
=> GQLRootResolver m event cont query mut sub
-> GQLRequest
-> ResponseStream m event cont GQLResponse
streamResolver root@GQLRootResolver {queryResolver, mutationResolver, subscriptionResolver} request =
renderResponse <$> runExceptT (validRequest >>= execOperator)
where
renderResponse (Left errors) = Errors $ renderErrors errors
renderResponse (Right value) = Data value
validRequest :: Monad m => ResponseT m event cont (DataTypeLib, ValidOperation)
validRequest =
liftEither $ do
schema <- fullSchema $ Identity root
query <- parseGQL request >>= validateRequest schema FULL_VALIDATION
Right (schema, query)
execOperator (schema, operation@Operation {operationKind = Query}) =
ExceptT $
StreamT
(StreamState [] <$>
runExceptT
(do schemaRes <- schemaAPI schema
ExceptT (encodeQuery schemaRes queryResolver operation)))
execOperator (_, operation@Operation {operationKind = Mutation}) =
ExceptT $ mapS Publish (encodeOperation mutationResolver operation)
execOperator (_, operation@Operation {operationKind = Subscription}) =
ExceptT $ StreamT $ handleActions <$> closeStream (encodeOperation subscriptionResolver operation)
where
handleActions (_, Left gqlError) = StreamState [] (Left gqlError)
handleActions (channels, Right subResolver) =
StreamState [Subscribe $ Event (concat channels) handleRes] (Right Null)
where
handleRes event = renderResponse <$> runExceptT (subResolver event)
statefulResolver ::
EventCon s
=> GQLState IO s cont
-> (L.ByteString -> ResponseStream IO s cont L.ByteString)
-> L.ByteString
-> IO L.ByteString
statefulResolver state streamApi request = do
(actions, value) <- closeStream (streamApi request)
mapM_ execute actions
pure value
where
execute (Publish updates) = publishUpdates state updates
execute Subscribe {} = pure ()
fullSchema ::
forall proxy m s cont query mutation subscription. (IntroCon query, IntroCon mutation, IntroCon subscription)
=> proxy (GQLRootResolver m s cont query mutation subscription)
-> Validation DataTypeLib
fullSchema _ = querySchema >>= mutationSchema >>= subscriptionSchema
where
querySchema =
resolveUpdates (initTypeLib (operatorType (hiddenRootFields ++ fields) "Query")) (defaultTypes : types)
where
(fields, types) = objectFields (Proxy @(CUSTOM query)) (Proxy @query)
mutationSchema lib = resolveUpdates (lib {mutation = maybeOperator fields "Mutation"}) types
where
(fields, types) = objectFields (Proxy @(CUSTOM mutation)) (Proxy @mutation)
subscriptionSchema lib = resolveUpdates (lib {subscription = maybeOperator fields "Subscription"}) types
where
(fields, types) = objectFields (Proxy @(CUSTOM subscription)) (Proxy @subscription)
maybeOperator [] = const Nothing
maybeOperator fields = Just . operatorType fields
operatorType typeData typeName =
( typeName
, DataTyCon {typeData, typeName, typeFingerprint = SystemFingerprint typeName, typeDescription = Nothing})