{-# LANGUAGE ConstraintKinds #-} {-# LANGUAGE DataKinds #-} {-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE GADTs #-} {-# LANGUAGE MultiParamTypeClasses #-} {-# LANGUAGE NamedFieldPuns #-} {-# LANGUAGE PolyKinds #-} {-# LANGUAGE RankNTypes #-} {-# LANGUAGE RecordWildCards #-} {-# LANGUAGE ScopedTypeVariables #-} {-# LANGUAGE TypeApplications #-} {-# LANGUAGE UndecidableInstances #-} {-# LANGUAGE NoImplicitPrelude #-} module Data.Morpheus.Server.Deriving.Encode ( deriveModel, EncodeConstraints, ContextValue (..), ) where import Control.Monad.Except (MonadError) import qualified Data.Map as M import Data.Morpheus.App.Internal.Resolving ( LiftOperation, ObjectTypeResolver, Resolver, ResolverState, ResolverValue, ResolverValue (..), RootResolverValue (..), getArguments, liftResolverState, mkEnum, mkObject, mkUnion, requireObject, ) import Data.Morpheus.Internal.Ext (GQLResult) import Data.Morpheus.Kind ( CUSTOM, DerivingKind, SCALAR, TYPE, WRAPPER, ) import Data.Morpheus.Server.Deriving.Channels ( ChannelsConstraint, channelResolver, ) import Data.Morpheus.Server.Deriving.Decode ( DecodeConstraint, decodeArguments, ) import Data.Morpheus.Server.Deriving.Utils ( ConsRep (..), DataType (..), FieldRep (..), TypeConstraint (..), TypeRep (..), isUnionRef, toFieldRes, toValue, ) import Data.Morpheus.Server.Types.GQLType ( GQLType, KIND, __isEmptyType, ) import Data.Morpheus.Server.Types.Types ( Pair (..), TypeGuard (..), ) import Data.Morpheus.Types ( RootResolver (..), ) import Data.Morpheus.Types.GQLScalar ( EncodeScalar (..), ) import Data.Morpheus.Types.GQLWrapper (EncodeWrapper (..)) import Data.Morpheus.Types.Internal.AST ( GQLError, IN, MUTATION, OperationType, QUERY, SUBSCRIPTION, TypeRef (..), ) import GHC.Generics ( Generic (..), ) import Relude newtype ContextValue (kind :: DerivingKind) a = ContextValue { unContextValue :: a } class Encode (m :: * -> *) resolver where encode :: resolver -> m (ResolverValue m) instance (EncodeKind (KIND a) m a) => Encode m a where encode resolver = encodeKind (ContextValue resolver :: ContextValue (KIND a) a) -- ENCODE GQL KIND class EncodeKind (kind :: DerivingKind) (m :: * -> *) (a :: *) where encodeKind :: ContextValue kind a -> m (ResolverValue m) instance ( EncodeWrapper f, Encode m a, Monad m ) => EncodeKind WRAPPER m (f a) where encodeKind = encodeWrapper encode . unContextValue instance ( EncodeScalar a, Monad m ) => EncodeKind SCALAR m a where encodeKind = pure . ResScalar . encodeScalar . unContextValue instance ( EncodeConstraint m a, MonadError GQLError m ) => EncodeKind TYPE m a where encodeKind = pure . exploreResolvers . unContextValue -- Tuple (a,b) instance Encode m (Pair k v) => EncodeKind CUSTOM m (k, v) where encodeKind = encode . uncurry Pair . unContextValue -- Map instance (Monad m, Encode m [Pair k v]) => EncodeKind CUSTOM m (Map k v) where encodeKind = encode . fmap (uncurry Pair) . M.toList . unContextValue -- INTERFACE Types instance (MonadError GQLError m, EncodeConstraint m guard, EncodeConstraint m union) => EncodeKind CUSTOM m (TypeGuard guard union) where encodeKind (ContextValue (ResolveType value)) = pure (exploreResolvers value) encodeKind (ContextValue (ResolveInterface value)) = pure (exploreResolvers value) -- GQL a -> Resolver b, MUTATION, SUBSCRIPTION, QUERY instance ( DecodeConstraint a, Generic a, Monad m, Encode (Resolver o e m) b, LiftOperation o ) => EncodeKind CUSTOM (Resolver o e m) (a -> b) where encodeKind (ContextValue f) = getArguments >>= liftResolverState . decodeArguments >>= encode . f -- GQL a -> Resolver b, MUTATION, SUBSCRIPTION, QUERY instance (Monad m, Encode (Resolver o e m) b, LiftOperation o) => EncodeKind CUSTOM (Resolver o e m) (Resolver o e m b) where encodeKind (ContextValue value) = value >>= encode convertNode :: forall m. MonadError GQLError m => DataType (m (ResolverValue m)) -> ResolverValue m convertNode DataType { tyName, tyIsUnion, tyCons = cons@ConsRep {consFields, consName} } | tyIsUnion = encodeUnion consFields | otherwise = mkObject tyName (toFieldRes <$> consFields) where -- ENUM encodeUnion :: [FieldRep (m (ResolverValue m))] -> ResolverValue m encodeUnion [] = mkEnum consName -- Type References -------------------------------------------------------------- encodeUnion [FieldRep {fieldTypeRef = TypeRef {typeConName}, fieldValue}] | isUnionRef tyName cons = ResLazy (ResObject (Just typeConName) <$> (fieldValue >>= requireObject)) -- Inline Union Types ---------------------------------------------------------------------------- encodeUnion fields = mkUnion consName (toFieldRes <$> fields) -- Types & Constrains ------------------------------------------------------- exploreResolvers :: forall m a. ( EncodeConstraint m a, MonadError GQLError m ) => a -> ResolverValue m exploreResolvers = convertNode . toValue ( TypeConstraint (encode . runIdentity) :: TypeConstraint (Encode m) (m (ResolverValue m)) Identity ) (Proxy @IN) ----- HELPERS ---------------------------- objectResolvers :: ( EncodeConstraint m a, MonadError GQLError m ) => a -> ResolverState (ObjectTypeResolver m) objectResolvers value = requireObject (exploreResolvers value) type EncodeConstraint (m :: * -> *) a = ( GQLType a, Generic a, TypeRep (Encode m) (m (ResolverValue m)) (Rep a) ) type EncodeObjectConstraint (o :: OperationType) e (m :: * -> *) a = EncodeConstraint (Resolver o e m) (a (Resolver o e m)) type EncodeConstraints e m query mut sub = ( ChannelsConstraint e m sub, EncodeObjectConstraint QUERY e m query, EncodeObjectConstraint MUTATION e m mut, EncodeObjectConstraint SUBSCRIPTION e m sub ) deriveModel :: forall e m query mut sub. (Monad m, EncodeConstraints e m query mut sub) => RootResolver m e query mut sub -> GQLResult (RootResolverValue e m) deriveModel RootResolver {..} = pure RootResolverValue { queryResolver = objectResolvers queryResolver, mutationResolver = objectResolvers mutationResolver, subscriptionResolver = objectResolvers subscriptionResolver, channelMap } where channelMap | __isEmptyType (Proxy :: Proxy (sub (Resolver SUBSCRIPTION e m))) = Nothing | otherwise = Just (channelResolver subscriptionResolver)