module GraphQL.Resolver
( ResolverError(..)
, HasResolver(..)
, (:<>)(..)
, Defaultable(..)
, Result(..)
, unionValue
) where
import Protolude hiding (Enum, TypeError)
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.API
( (:>)
, HasAnnotatedType(..)
, HasAnnotatedInputType(..)
)
import qualified GraphQL.API as API
import qualified GraphQL.Value as GValue
import GraphQL.Value
( Value
, pattern ValueEnum
)
import GraphQL.Value.FromValue (FromValue(..))
import GraphQL.Value.ToValue (ToValue(..))
import GraphQL.Internal.Name (Name, NameError(..), HasName(..), nameFromSymbol)
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 NameError
| FieldNotFoundError Name
| ValueMissing Name
| InvalidValue Name Text
| ValidationError ValidationErrors
| SubSelectionOnLeaf (SelectionSetByType Value)
| MissingSelectionSet
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."
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
class HasResolver m a where
type Handler m a
resolve :: Handler m a -> Maybe (SelectionSetByType Value) -> m (Result Value)
class Defaultable a where
defaultFor :: Name -> Maybe a
defaultFor _ = empty
valueMissing :: Defaultable a => Name -> Either ResolverError a
valueMissing name = maybe (Left (ValueMissing name)) Right (defaultFor name)
instance Defaultable Int32
instance Defaultable Double
instance Defaultable Bool
instance Defaultable Text
instance Defaultable (Maybe a) where
defaultFor _ = pure Nothing
instance forall m. (Applicative m) => HasResolver m Int32 where
type Handler m Int32 = m Int32
resolve handler Nothing = map (ok . toValue) handler
resolve _ (Just ss) = throwE (SubSelectionOnLeaf ss)
instance forall m. (Applicative m) => HasResolver m Double where
type Handler m Double = m Double
resolve handler Nothing = map (ok . toValue) handler
resolve _ (Just ss) = throwE (SubSelectionOnLeaf ss)
instance forall m. (Applicative m) => HasResolver m Text where
type Handler m Text = m Text
resolve handler Nothing = map (ok . toValue) handler
resolve _ (Just ss) = throwE (SubSelectionOnLeaf ss)
instance forall m. (Applicative m) => HasResolver m Bool where
type Handler m Bool = m Bool
resolve handler Nothing = map (ok . toValue) 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 [Handler m hg]
resolve handler selectionSet = do
h <- handler
let a = traverse (flip (resolve @m @hg) selectionSet) h
map aggregateResults a
instance forall m ksN enum. (Applicative m, API.GraphQLEnum enum) => HasResolver m (API.Enum ksN enum) where
type Handler m (API.Enum ksN enum) = enum
resolve handler Nothing = (pure . 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 (Maybe (Handler m hg))
resolve handler selectionSet = do
result <- handler
case result of
Just x -> resolve @m @hg (x :: Handler m hg) selectionSet
Nothing -> (pure . ok) GValue.ValueNull
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 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
, 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
, 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 (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)
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 mHandler (Just selectionSet) =
case getSelectionSet of
Left err -> throwE err
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 (GValue.ValueObject obj))
where
getSelectionSet = do
defn <- first SchemaError $ API.getDefinition @(API.Object typeName interfaces fields)
(SelectionSet ss') <- first ValidationError $ getSelectionSetForType defn selectionSet
pure ss'
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