{-# LANGUAGE GADTs #-}
{-# LANGUAGE NamedFieldPuns #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE QuasiQuotes #-}
{-# LANGUAGE RecordWildCards #-}
{-# LANGUAGE TemplateHaskell #-}
module Data.Morpheus.Server.Document.Introspect
( deriveObjectRep,
instanceIntrospect,
)
where
import Data.Maybe (maybeToList)
import Data.Morpheus.Internal.TH
( instanceFunD,
instanceHeadMultiT,
instanceHeadT,
instanceProxyFunD,
mkTypeName,
nameConT,
nameVarT,
tyConArgs,
typeT,
)
import Data.Morpheus.Server.Deriving.Introspect
( DeriveTypeContent (..),
Introspect (..),
deriveCustomInputObjectType,
)
import Data.Morpheus.Server.Types.GQLType
( GQLType (__typeName, implements),
TRUE,
)
import Data.Morpheus.Types.Internal.AST
( ANY,
ArgumentsDefinition (..),
ConsD (..),
DataTypeKind (..),
FieldDefinition (..),
TypeContent (..),
TypeD (..),
TypeDefinition (..),
TypeName,
TypeRef (..),
TypeUpdater,
insertType,
unsafeFromFields,
)
import Data.Morpheus.Types.Internal.Resolving
( resolveUpdates,
)
import Data.Proxy (Proxy (..))
import Data.Typeable (Typeable)
import Language.Haskell.TH
instanceIntrospect :: TypeDefinition cat -> Q [Dec]
instanceIntrospect TypeDefinition {typeName, typeContent = DataEnum enumType, ..}
| typeName `elem` ["__DirectiveLocation", "__TypeKind"] = pure []
| otherwise = pure <$> instanceD (cxt []) iHead [defineIntrospect]
where
iHead = instanceHeadT ''Introspect typeName []
defineIntrospect = instanceProxyFunD ('introspect, body)
where
body = [|insertType TypeDefinition {typeContent = DataEnum enumType, ..}|]
instanceIntrospect _ = pure []
deriveObjectRep :: (TypeD, Maybe (TypeDefinition ANY), Maybe DataTypeKind) -> Q [Dec]
deriveObjectRep (TypeD {tName, tCons = [ConsD {cFields}]}, _, tKind) =
pure <$> instanceD (cxt constrains) iHead methods
where
mainTypeName = typeT (mkTypeName tName) typeArgs
typeArgs = concatMap tyConArgs (maybeToList tKind)
constrains = map conTypeable typeArgs
where
conTypeable name = typeT ''Typeable [name]
iHead = instanceHeadMultiT ''DeriveTypeContent (conT ''TRUE) [mainTypeName]
methods = [instanceFunD 'deriveTypeContent ["_proxy1", "_proxy2"] body]
where
body
| tKind == Just KindInputObject || null tKind =
[|
( DataInputObject
(unsafeFromFields $(buildFields cFields)),
$(typeUpdates)
)
|]
| otherwise =
[|
( DataObject
(interfaceNames $(proxy))
(unsafeFromFields $(buildFields cFields)),
interfaceTypes $(proxy)
: $(typeUpdates)
)
|]
typeUpdates = buildTypes cFields
proxy = [|(Proxy :: Proxy $(mainTypeName))|]
deriveObjectRep _ = pure []
interfaceNames :: GQLType a => Proxy a -> [TypeName]
interfaceNames = map fst . implements
interfaceTypes :: GQLType a => Proxy a -> TypeUpdater
interfaceTypes = flip resolveUpdates . map snd . implements
buildTypes :: [FieldDefinition cat] -> ExpQ
buildTypes = listE . concatMap introspectField
introspectField :: FieldDefinition cat -> [ExpQ]
introspectField FieldDefinition {fieldType, fieldArgs} =
[|introspect $(proxyT fieldType)|] : inputTypes fieldArgs
where
inputTypes :: ArgumentsDefinition -> [ExpQ]
inputTypes ArgumentsDefinition {argumentsTypename = Just argsTypeName}
| argsTypeName /= "()" = [[|deriveCustomInputObjectType (argsTypeName, $(proxyT tAlias))|]]
where
tAlias = TypeRef {typeConName = argsTypeName, typeWrappers = [], typeArgs = Nothing}
inputTypes _ = []
proxyT :: TypeRef -> Q Exp
proxyT TypeRef {typeConName, typeArgs} = [|(Proxy :: Proxy $(genSig typeArgs))|]
where
genSig (Just m) = appT (nameConT typeConName) (nameVarT m)
genSig _ = nameConT typeConName
buildFields :: [FieldDefinition cat] -> ExpQ
buildFields = listE . map buildField
where
buildField f@FieldDefinition {fieldType} = [|f {fieldType = fieldType {typeConName = __typeName $(proxyT fieldType)}}|]