{-# LANGUAGE AllowAmbiguousTypes #-}
{-# LANGUAGE ConstraintKinds #-}
{-# LANGUAGE DeriveFunctor #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE PatternSynonyms #-}
{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE RoleAnnotations #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE TypeFamilyDependencies #-}
{-# LANGUAGE TypeInType #-}
{-# LANGUAGE TypeOperators #-}
{-# LANGUAGE UndecidableInstances #-}
{-# OPTIONS_HADDOCK not-home #-}
module GraphQL.Internal.Resolver
( ResolverError(..)
, HasResolver(..)
, OperationResolverConstraint
, (:<>)(..)
, Result(..)
, unionValue
, resolveOperation
, returns
, handlerError
) where
import Protolude hiding (Enum, TypeError, throwE)
import qualified Data.Text as Text
import qualified Data.List.NonEmpty as NonEmpty
import GHC.TypeLits (KnownSymbol, TypeError, ErrorMessage(..), Symbol, symbolVal)
import GHC.Types (Type)
import qualified GHC.Exts (Any)
import Unsafe.Coerce (unsafeCoerce)
import GraphQL.Internal.API
( HasAnnotatedType(..)
, HasAnnotatedInputType(..)
, (:>)
)
import qualified GraphQL.Internal.API as API
import qualified GraphQL.Value as GValue
import GraphQL.Value
( Value
, pattern ValueEnum
, FromValue(..)
, ToValue(..)
)
import GraphQL.Internal.Name (Name, HasName(..))
import qualified GraphQL.Internal.OrderedMap as OrderedMap
import GraphQL.Internal.Output (GraphQLError(..))
import GraphQL.Internal.Validation
( SelectionSetByType
, SelectionSet(..)
, Field
, ValidationErrors
, getSubSelectionSet
, getSelectionSetForType
, lookupArgument
)
data ResolverError
= SchemaError API.SchemaError
| FieldNotFoundError Name
| ValueMissing Name
| InvalidValue Name Text
| ValidationError ValidationErrors
| SubSelectionOnLeaf (SelectionSetByType Value)
| MissingSelectionSet
| HandlerError Text
deriving (Show, Eq)
instance GraphQLError ResolverError where
formatError (SchemaError e) =
"Schema error: " <> formatError e
formatError (FieldNotFoundError field) =
"Field not supported by the API: " <> show field
formatError (ValueMissing name) =
"No value provided for " <> show name <> ", and no default specified."
formatError (InvalidValue name text) =
"Could not coerce " <> show name <> " to valid value: " <> text
formatError (ValidationError errs) =
"Validation errors: " <> Text.intercalate ", " (map formatError (NonEmpty.toList errs))
formatError (SubSelectionOnLeaf ss) =
"Tried to get values within leaf field: " <> show ss
formatError MissingSelectionSet =
"Tried to treat object as if it were leaf field."
formatError (HandlerError err) =
"Handler error: " <> err
data a :<> b = a :<> b
infixr 8 :<>
data Result a = Result [ResolverError] a deriving (Show, Functor, Eq)
aggregateResults :: [Result Value] -> Result Value
aggregateResults r = toValue <$> sequenceA r
throwE :: Applicative f => ResolverError -> f (Result Value)
throwE err = pure (Result [err] GValue.ValueNull)
instance Applicative Result where
pure v = Result [] v
(Result e1 f) <*> (Result e2 x) = Result (e1 <> e2) (f x)
ok :: Value -> Result Value
ok = pure
type HandlerResult a = Either Text a
returns :: Applicative f => a -> f (HandlerResult a)
returns = pure . Right
handlerError :: Applicative f => Text -> f (HandlerResult a)
handlerError = pure . Left
class HasResolver m a where
type Handler m a
resolve :: Handler m a -> Maybe (SelectionSetByType Value) -> m (Result Value)
type OperationResolverConstraint m fields typeName interfaces =
( RunFields m (RunFieldsType m fields)
, API.HasObjectDefinition (API.Object typeName interfaces fields)
, Monad m
)
resolveOperation
:: forall m fields typeName interfaces.
( OperationResolverConstraint m fields typeName interfaces )
=> Handler m (API.Object typeName interfaces fields)
-> SelectionSetByType Value
-> m (Result GValue.Object)
resolveOperation handler ss =
resolveObject @m @fields @typeName @interfaces handler ss
valueMissing :: API.Defaultable a => Name -> Either ResolverError a
valueMissing name = maybe (Left (ValueMissing name)) Right (API.defaultFor name)
gotHandlerErr :: Text -> Result Value
gotHandlerErr err = Result [HandlerError err] GValue.ValueNull
handlerResult :: (Applicative f, ToValue a) => f (HandlerResult a) -> f (Result Value)
handlerResult = fmap (either gotHandlerErr (ok . toValue))
instance forall m. (Applicative m) => HasResolver m Int32 where
type Handler m Int32 = m (HandlerResult Int32)
resolve handler Nothing = handlerResult @m handler
resolve _ (Just ss) = throwE (SubSelectionOnLeaf ss)
instance forall m. (Applicative m) => HasResolver m Double where
type Handler m Double = m (HandlerResult Double)
resolve handler Nothing = handlerResult handler
resolve _ (Just ss) = throwE (SubSelectionOnLeaf ss)
instance forall m. (Applicative m) => HasResolver m Text where
type Handler m Text = m (HandlerResult Text)
resolve handler Nothing = handlerResult handler
resolve _ (Just ss) = throwE (SubSelectionOnLeaf ss)
instance forall m. (Applicative m) => HasResolver m Bool where
type Handler m Bool = m (HandlerResult Bool)
resolve handler Nothing = handlerResult handler
resolve _ (Just ss) = throwE (SubSelectionOnLeaf ss)
instance forall m hg. (Monad m, Applicative m, HasResolver m hg) => HasResolver m (API.List hg) where
type Handler m (API.List hg) = m (HandlerResult [Handler m hg])
resolve handler selectionSet = do
handler >>= \case
Right h ->
let a = traverse (flip (resolve @m @hg) selectionSet) h
in map aggregateResults a
Left err -> pure $ gotHandlerErr err
instance forall m ksN enum. (Applicative m, API.GraphQLEnum enum) => HasResolver m (API.Enum ksN enum) where
type Handler m (API.Enum ksN enum) = m (HandlerResult enum)
resolve handler Nothing = either gotHandlerErr (ok . GValue.ValueEnum . API.enumToValue) <$> handler
resolve _ (Just ss) = throwE (SubSelectionOnLeaf ss)
instance forall m hg. (HasResolver m hg, Monad m) => HasResolver m (Maybe hg) where
type Handler m (Maybe hg) = m (HandlerResult (Maybe (Handler m hg)))
resolve handler selectionSet = do
result <- handler
case result of
Right res ->
case res of
Just x -> resolve @m @hg (x :: Handler m hg) selectionSet
Nothing -> (pure . ok) GValue.ValueNull
Left err -> pure $ gotHandlerErr err
type ResolveFieldResult = Result (Maybe GValue.Value)
type family FieldName (a :: Type) = (r :: Symbol) where
FieldName (JustHandler (API.Field name t)) = name
FieldName (PlainArgument a f) = FieldName f
FieldName (EnumArgument a f) = FieldName f
FieldName x = TypeError ('Text "Unexpected branch in FieldName type family. Please file a bug!" ':<>: 'ShowType x)
resolveField :: forall dispatchType (m :: Type -> Type).
(BuildFieldResolver m dispatchType, Monad m, KnownSymbol (FieldName dispatchType))
=> FieldHandler m dispatchType -> m ResolveFieldResult -> Field Value -> m ResolveFieldResult
resolveField handler nextHandler field =
case API.nameFromSymbol @(FieldName dispatchType) of
Left err -> pure (Result [SchemaError err] (Just GValue.ValueNull))
Right name'
| getName field == name' ->
case buildFieldResolver @m @dispatchType handler field of
Left err -> pure (Result [err] (Just GValue.ValueNull))
Right resolver -> do
Result errs value <- resolver
pure (Result errs (Just value))
| otherwise -> nextHandler
data JustHandler a
data EnumArgument a b
data PlainArgument a b
type family FieldResolverDispatchType (a :: Type) = (r :: Type) | r -> a where
FieldResolverDispatchType (API.Field ksA t) = JustHandler (API.Field ksA t)
FieldResolverDispatchType (API.Argument ksB (API.Enum name t) :> f) = EnumArgument (API.Argument ksB (API.Enum name t)) (FieldResolverDispatchType f)
FieldResolverDispatchType (API.Argument ksC t :> f) = PlainArgument (API.Argument ksC t) (FieldResolverDispatchType f)
type family FieldHandler (m :: Type -> Type) (a :: Type) = (r :: Type) where
FieldHandler m (JustHandler (API.Field ksD t)) = Handler m t
FieldHandler m (PlainArgument (API.Argument ksE t) f) = t -> FieldHandler m f
FieldHandler m (EnumArgument (API.Argument ksF (API.Enum name t)) f) = t -> FieldHandler m f
class BuildFieldResolver m fieldResolverType where
buildFieldResolver :: FieldHandler m fieldResolverType -> Field Value -> Either ResolverError (m (Result Value))
instance forall ksG t m.
( KnownSymbol ksG, HasResolver m t, HasAnnotatedType t, Monad m
) => BuildFieldResolver m (JustHandler (API.Field ksG t)) where
buildFieldResolver handler field = do
pure (resolve @m @t handler (getSubSelectionSet field))
instance forall ksH t f m.
( KnownSymbol ksH
, BuildFieldResolver m f
, FromValue t
, API.Defaultable t
, HasAnnotatedInputType t
, Monad m
) => BuildFieldResolver m (PlainArgument (API.Argument ksH t) f) where
buildFieldResolver handler field = do
argument <- first SchemaError (API.getArgumentDefinition @(API.Argument ksH t))
let argName = getName argument
value <- case lookupArgument field argName of
Nothing -> valueMissing @t argName
Just v -> first (InvalidValue argName) (fromValue @t v)
buildFieldResolver @m @f (handler value) field
instance forall ksK t f m name.
( KnownSymbol ksK
, BuildFieldResolver m f
, KnownSymbol name
, API.Defaultable t
, API.GraphQLEnum t
, Monad m
) => BuildFieldResolver m (EnumArgument (API.Argument ksK (API.Enum name t)) f) where
buildFieldResolver handler field = do
argName <- first SchemaError (API.nameFromSymbol @ksK)
value <- case lookupArgument field argName of
Nothing -> valueMissing @t argName
Just (ValueEnum enum) -> first (InvalidValue argName) (API.enumFromValue @t enum)
Just value -> Left (InvalidValue argName (show value <> " not an enum: " <> show (API.enumValues @t)))
buildFieldResolver @m @f (handler value) field
type family RunFieldsType (m :: Type -> Type) (a :: [Type]) = (r :: Type) where
RunFieldsType m '[API.Field ksI t] = API.Field ksI t
RunFieldsType m '[a :> b] = a :> b
RunFieldsType m ((API.Field ksJ t) ': rest) = API.Field ksJ t :<> RunFieldsType m rest
RunFieldsType m ((a :> b) ': rest) = (a :> b) :<> RunFieldsType m rest
RunFieldsType m a = TypeError (
'Text "All field entries in an Object must be Field or Argument :> Field. Got: " ':<>: 'ShowType a)
type family RunFieldsHandler (m :: Type -> Type) (a :: Type) = (r :: Type) where
RunFieldsHandler m (f :<> fs) = FieldHandler m (FieldResolverDispatchType f) :<> RunFieldsHandler m fs
RunFieldsHandler m (API.Field ksL t) = FieldHandler m (FieldResolverDispatchType (API.Field ksL t))
RunFieldsHandler m (a :> b) = FieldHandler m (FieldResolverDispatchType (a :> b))
RunFieldsHandler m a = TypeError (
'Text "Unexpected RunFieldsHandler types: " ':<>: 'ShowType a)
class RunFields m a where
runFields :: RunFieldsHandler m a -> Field Value -> m ResolveFieldResult
instance forall f fs m dispatchType.
( BuildFieldResolver m dispatchType
, dispatchType ~ FieldResolverDispatchType f
, RunFields m fs
, KnownSymbol (FieldName dispatchType)
, Monad m
) => RunFields m (f :<> fs) where
runFields (handler :<> nextHandlers) field =
resolveField @dispatchType @m handler nextHandler field
where
nextHandler = runFields @m @fs nextHandlers field
instance forall ksM t m dispatchType.
( BuildFieldResolver m dispatchType
, KnownSymbol ksM
, dispatchType ~ FieldResolverDispatchType (API.Field ksM t)
, Monad m
) => RunFields m (API.Field ksM t) where
runFields handler field =
resolveField @dispatchType @m handler nextHandler field
where
nextHandler = pure (Result [FieldNotFoundError (getName field)] Nothing)
instance forall m a b dispatchType.
( BuildFieldResolver m dispatchType
, dispatchType ~ FieldResolverDispatchType (a :> b)
, KnownSymbol (FieldName dispatchType)
, Monad m
) => RunFields m (a :> b) where
runFields handler field =
resolveField @dispatchType @m handler nextHandler field
where
nextHandler = pure (Result [FieldNotFoundError (getName field)] Nothing)
resolveObject
:: forall m fields typeName interfaces.
( OperationResolverConstraint m fields typeName interfaces )
=> Handler m (API.Object typeName interfaces fields)
-> SelectionSetByType Value
-> m (Result GValue.Object)
resolveObject mHandler selectionSet =
case getSelectionSet of
Left err -> return (Result [err] (GValue.Object' OrderedMap.empty))
Right ss -> do
handler <- mHandler
r <- traverse (runFields @m @(RunFieldsType m fields) handler) ss
let (Result errs obj) = GValue.objectFromOrderedMap . OrderedMap.catMaybes <$> sequenceA r
pure (Result errs obj)
where
getSelectionSet = do
defn <- first SchemaError $ API.getDefinition @(API.Object typeName interfaces fields)
(SelectionSet ss') <- first ValidationError $ getSelectionSetForType defn selectionSet
pure ss'
instance forall typeName interfaces fields m.
( RunFields m (RunFieldsType m fields)
, API.HasObjectDefinition (API.Object typeName interfaces fields)
, Monad m
) => HasResolver m (API.Object typeName interfaces fields) where
type Handler m (API.Object typeName interfaces fields) = m (RunFieldsHandler m (RunFieldsType m fields))
resolve _ Nothing = throwE MissingSelectionSet
resolve handler (Just ss) = do
result <- resolveObject @m @fields @typeName @interfaces handler ss
return $ GValue.ValueObject <$> result
type family TypeIndex (m :: Type -> Type) (object :: Type) (union :: Type) = (result :: Type) where
TypeIndex m (API.Object name interfaces fields) (API.Union uName (API.Object name interfaces fields:_)) =
Handler m (API.Object name interfaces fields)
TypeIndex m (API.Object name interfaces fields) (API.Union uName (API.Object name' i' f':objects)) =
TypeIndex m (API.Object name interfaces fields) (API.Union uName objects)
TypeIndex _ (API.Object name interfaces fields) (API.Union uName '[]) =
TypeError ('Text "Type not found in union definition: " ':<>: 'ShowType (API.Object name interfaces fields))
TypeIndex _ (API.Object name interfaces fields) x =
TypeError ('Text "3rd type must be a union but it is: " ':<>: 'ShowType x)
TypeIndex _ o _ =
TypeError ('Text "Invalid TypeIndex. Must be Object but got: " ':<>: 'ShowType o)
type role DynamicUnionValue representational representational
data DynamicUnionValue (union :: Type) (m :: Type -> Type) = DynamicUnionValue { _label :: Text, _value :: GHC.Exts.Any }
class RunUnion m union objects where
runUnion :: DynamicUnionValue union m -> SelectionSetByType Value -> m (Result Value)
instance forall m union objects name interfaces fields.
( Monad m
, KnownSymbol name
, TypeIndex m (API.Object name interfaces fields) union ~ Handler m (API.Object name interfaces fields)
, RunFields m (RunFieldsType m fields)
, API.HasObjectDefinition (API.Object name interfaces fields)
, RunUnion m union objects
) => RunUnion m union (API.Object name interfaces fields:objects) where
runUnion duv selectionSet =
case extractUnionValue @(API.Object name interfaces fields) @union @m duv of
Just handler -> resolve @m @(API.Object name interfaces fields) handler (Just selectionSet)
Nothing -> runUnion @m @union @objects duv selectionSet
instance forall m union. RunUnion m union '[] where
runUnion (DynamicUnionValue label _) selection =
panic ("Unexpected branch in runUnion, got " <> show selection <> " for label " <> label <> ". Please file a bug.")
instance forall m unionName objects.
( Monad m
, KnownSymbol unionName
, RunUnion m (API.Union unionName objects) objects
) => HasResolver m (API.Union unionName objects) where
type Handler m (API.Union unionName objects) = m (DynamicUnionValue (API.Union unionName objects) m)
resolve _ Nothing = throwE MissingSelectionSet
resolve mHandler (Just selectionSet) = do
duv <- mHandler
runUnion @m @(API.Union unionName objects) @objects duv selectionSet
symbolText :: forall ks. KnownSymbol ks => Text
symbolText = toS (symbolVal @ks Proxy)
unionValue ::
forall (object :: Type) (union :: Type) m (name :: Symbol) interfaces fields.
(Monad m, API.Object name interfaces fields ~ object, KnownSymbol name)
=> TypeIndex m object union -> m (DynamicUnionValue union m)
unionValue x =
pure (DynamicUnionValue (symbolText @name) (unsafeCoerce x))
extractUnionValue ::
forall (object :: Type) (union :: Type) m (name :: Symbol) interfaces fields.
(API.Object name interfaces fields ~ object, KnownSymbol name)
=> DynamicUnionValue union m -> Maybe (TypeIndex m object union)
extractUnionValue (DynamicUnionValue uName uValue) =
if uName == symbolText @name
then Just (unsafeCoerce uValue)
else Nothing