{-# LANGUAGE ConstraintKinds #-}
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE DefaultSignatures #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE NamedFieldPuns #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE PolyKinds #-}
{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TypeApplications #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE TypeOperators #-}
{-# LANGUAGE UndecidableInstances #-}
module Data.Morpheus.Execution.Server.Introspect
( TypeUpdater
, Introspect(..)
, ObjectFields(..)
, IntroCon
, resolveTypes
, updateLib
, buildType
) where
import Control.Monad (foldM)
import Data.Function ((&))
import Data.Map (Map)
import Data.Proxy (Proxy (..))
import Data.Semigroup ((<>))
import Data.Set (Set)
import Data.Text (Text, pack)
import GHC.Generics
import Data.Morpheus.Error.Schema (nameCollisionError)
import Data.Morpheus.Execution.Server.Generics.EnumRep (EnumRep (..))
import Data.Morpheus.Kind (Context (..), ENUM, GQL_KIND, INPUT_OBJECT,
INPUT_UNION, OBJECT, SCALAR, UNION)
import Data.Morpheus.Types.Custom (MapKind, Pair)
import Data.Morpheus.Types.GQLScalar (GQLScalar (..))
import Data.Morpheus.Types.GQLType (GQLType (..))
import Data.Morpheus.Types.Internal.Data (DataArguments, DataField (..), DataFullType (..),
DataLeaf (..), DataTyCon (..), DataTypeLib,
TypeAlias (..), defineType, isTypeDefined,
toListField, toNullableField)
import Data.Morpheus.Types.Internal.Validation (SchemaValidation)
type IntroCon a = (GQLType a, ObjectFields (CUSTOM a) a)
class Introspect a where
field :: proxy a -> Text -> DataField
introspect :: proxy a -> TypeUpdater
default field :: GQLType a =>
proxy a -> Text -> DataField
field _ = buildField (Proxy @a) []
instance {-# OVERLAPPABLE #-} (GQLType a, IntrospectKind (KIND a) a) => Introspect a where
introspect _ = introspectKind (Context :: Context (KIND a) a)
instance Introspect a => Introspect (Maybe a) where
field _ = toNullableField . field (Proxy @a)
introspect _ = introspect (Proxy @a)
instance Introspect a => Introspect [a] where
field _ = toListField . field (Proxy @a)
introspect _ = introspect (Proxy @a)
instance Introspect (Pair k v) => Introspect (k, v) where
field _ = field (Proxy @(Pair k v))
introspect _ = introspect (Proxy @(Pair k v))
instance Introspect [a] => Introspect (Set a) where
field _ = field (Proxy @[a])
introspect _ = introspect (Proxy @[a])
instance Introspect (MapKind k v Maybe) => Introspect (Map k v) where
field _ = field (Proxy @(MapKind k v Maybe))
introspect _ = introspect (Proxy @(MapKind k v Maybe))
instance (ObjectFields 'False a, Introspect b) => Introspect (a -> m b) where
field _ name = (field (Proxy @b) name) {fieldArgs = fst $ objectFields (Proxy :: Proxy 'False) (Proxy @a)}
introspect _ typeLib = resolveTypes typeLib (introspect (Proxy @b) : argTypes)
where
argTypes :: [TypeUpdater]
argTypes = snd $ objectFields (Proxy :: Proxy 'False) (Proxy @a)
class IntrospectKind (kind :: GQL_KIND) a where
introspectKind :: Context kind a -> TypeUpdater
instance (GQLType a, GQLScalar a) => IntrospectKind SCALAR a where
introspectKind _ = updateLib scalarType [] (Proxy @a)
where
scalarType = Leaf . CustomScalar . buildType (scalarValidator (Proxy @a))
instance (GQL_TYPE a, EnumRep (Rep a)) => IntrospectKind ENUM a where
introspectKind _ = updateLib enumType [] (Proxy @a)
where
enumType = Leaf . LeafEnum . buildType (enumTags (Proxy @(Rep a)))
instance (GQL_TYPE a, ObjectFields (CUSTOM a) a) => IntrospectKind INPUT_OBJECT a where
introspectKind _ = updateLib (InputObject . buildType fields) types (Proxy @a)
where
(fields, types) = objectFields (Proxy @(CUSTOM a)) (Proxy @a)
instance (GQL_TYPE a, ObjectFields (CUSTOM a) a) => IntrospectKind OBJECT a where
introspectKind _ = updateLib (OutputObject . buildType (__typename : fields)) types (Proxy @a)
where
__typename =
( "__typename"
, DataField
{ fieldName = "__typename"
, fieldArgs = []
, fieldArgsType = Nothing
, fieldType = buildAlias "String"
, fieldHidden = True
})
(fields, types) = objectFields (Proxy @(CUSTOM a)) (Proxy @a)
instance (GQL_TYPE a, GQLRep UNION (Rep a)) => IntrospectKind UNION a where
introspectKind _ = updateLib (Union . buildType fields) stack (Proxy @a)
where
(fields, stack) = unzip $ gqlRep (Context :: Context UNION (Rep a))
instance (GQL_TYPE a, GQLRep UNION (Rep a)) => IntrospectKind INPUT_UNION a where
introspectKind _ = updateLib (InputUnion . buildType (fieldTag : fields)) (tagsEnumType : stack) (Proxy @a)
where
(inputUnions, stack) = unzip $ gqlRep (Context :: Context UNION (Rep a))
fields = map toNullableField inputUnions
tagsEnumType :: TypeUpdater
tagsEnumType x = pure $ defineType (typeName, Leaf $ LeafEnum tagsEnum) x
where
tagsEnum =
DataTyCon
{ typeName
, typeFingerprint = __typeFingerprint (Proxy @a)
, typeVisibility = __typeVisibility (Proxy @a)
, typeDescription = Nothing
, typeData = map fieldName inputUnions
}
typeName = __typeName (Proxy @a) <> "Tags"
fieldTag =
DataField
{ fieldName = "tag"
, fieldArgs = []
, fieldArgsType = Nothing
, fieldType = buildAlias typeName
, fieldHidden = False
}
type TypeUpdater = DataTypeLib -> SchemaValidation DataTypeLib
type GQL_TYPE a = (Generic a, GQLType a)
class ObjectFields (custom :: Bool) a where
objectFields :: proxy1 custom -> proxy2 a -> ([(Text, DataField)], [TypeUpdater])
instance GQLRep OBJECT (Rep a) => ObjectFields 'False a where
objectFields _ _ = unzip $ gqlRep (Context :: Context OBJECT (Rep a))
type family GQLRepResult (a :: GQL_KIND) :: *
type instance GQLRepResult OBJECT = (Text, DataField)
type instance GQLRepResult UNION = DataField
class GQLRep (kind :: GQL_KIND) f where
gqlRep :: Context kind f -> [(GQLRepResult kind, TypeUpdater)]
instance GQLRep kind f => GQLRep kind (M1 D d f) where
gqlRep _ = gqlRep (Context :: Context kind f)
instance GQLRep kind f => GQLRep kind (M1 C c f) where
gqlRep _ = gqlRep (Context :: Context kind f)
instance (GQLRep UNION a, GQLRep UNION b) => GQLRep UNION (a :+: b) where
gqlRep _ = gqlRep (Context :: Context UNION a) ++ gqlRep (Context :: Context UNION b)
instance (GQL_TYPE a, Introspect a) => GQLRep UNION (M1 S s (Rec0 a)) where
gqlRep _ = [(buildField (Proxy @a) [] (__typeName (Proxy @a)), introspect (Proxy @a))]
instance (GQLRep OBJECT a, GQLRep OBJECT b) => GQLRep OBJECT (a :*: b) where
gqlRep _ = gqlRep (Context :: Context OBJECT a) ++ gqlRep (Context :: Context OBJECT b)
instance (Selector s, Introspect a) => GQLRep OBJECT (M1 S s (Rec0 a)) where
gqlRep _ = [((name, field (Proxy @a) name), introspect (Proxy @a))]
where
name = pack $ selName (undefined :: M1 S s (Rec0 ()) ())
instance GQLRep OBJECT U1 where
gqlRep _ = []
buildAlias :: Text -> TypeAlias
buildAlias aliasTyCon = TypeAlias {aliasTyCon, aliasWrappers = [], aliasArgs = Nothing}
resolveTypes :: DataTypeLib -> [TypeUpdater] -> SchemaValidation DataTypeLib
resolveTypes = foldM (&)
buildField :: GQLType a => Proxy a -> DataArguments -> Text -> DataField
buildField proxy fieldArgs fieldName =
DataField
{fieldName, fieldArgs, fieldArgsType = Nothing, fieldType = buildAlias $ __typeName proxy, fieldHidden = False}
buildType :: GQLType a => t -> Proxy a -> DataTyCon t
buildType typeData proxy =
DataTyCon
{ typeName = __typeName proxy
, typeFingerprint = __typeFingerprint proxy
, typeDescription = description proxy
, typeVisibility = __typeVisibility proxy
, typeData
}
updateLib :: GQLType a => (Proxy a -> DataFullType) -> [TypeUpdater] -> Proxy a -> TypeUpdater
updateLib typeBuilder stack proxy lib' =
case isTypeDefined (__typeName proxy) lib' of
Nothing -> resolveTypes (defineType (__typeName proxy, typeBuilder proxy) lib') stack
Just fingerprint'
| fingerprint' == __typeFingerprint proxy -> return lib'
Just _ -> Left $ nameCollisionError (__typeName proxy)