{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE DeriveLift #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE GADTs #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE NamedFieldPuns #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE PolyKinds #-}
{-# LANGUAGE RankNTypes #-}
module Data.Morpheus.Types.Internal.AST.Data
( DataScalar
, DataEnum
, DataObject
, DataArgument
, DataUnion
, DataArguments
, DataField(..)
, DataTypeContent(..)
, DataType(..)
, DataTypeLib(..)
, DataTypeWrapper(..)
, DataValidator(..)
, DataTypeKind(..)
, DataFingerprint(..)
, RawDataType(..)
, ResolverKind(..)
, TypeWrapper(..)
, TypeRef(..)
, ArgsType(..)
, DataEnumValue(..)
, isTypeDefined
, initTypeLib
, defineType
, isFieldNullable
, allDataTypes
, lookupDataType
, kindOf
, toNullableField
, toListField
, isObject
, isInput
, toHSWrappers
, isNullable
, toGQLWrapper
, isWeaker
, isSubscription
, isOutputObject
, sysTypes
, isDefaultTypeName
, isSchemaTypeName
, isPrimitiveTypeName
, OperationType(..)
, QUERY
, MUTATION
, SUBSCRIPTION
, isEntNode
, lookupInputType
, coerceDataObject
, getDataType
, lookupDataObject
, lookupDataUnion
, lookupType
, lookupField
, lookupUnionTypes
, lookupSelectionField
, lookupFieldAsSelectionSet
, createField
, createArgument
, createDataTypeLib
, createEnumType
, createScalarType
, createType
, createUnionType
, createAlias
, createInputUnionFields
, fieldVisibility
, Meta(..)
, Directive(..)
, createEnumValue
, insertType
, TypeUpdater
, lookupDeprecated
, lookupDeprecatedReason
, TypeD(..)
, ConsD(..)
, ClientQuery(..)
, GQLTypeD(..)
, ClientType(..)
, DataInputUnion
, isNullableWrapper
)
where
import Data.HashMap.Lazy ( HashMap
, empty
, fromList
, insert
, toList
, union
)
import qualified Data.HashMap.Lazy as HM
( lookup )
import Data.Semigroup ( (<>) )
import Language.Haskell.TH.Syntax ( Lift )
import Instances.TH.Lift ( )
import Data.List ( find )
import Data.Morpheus.Error.Internal ( internalError )
import Data.Morpheus.Error.Selection ( cannotQueryField
, hasNoSubfields
)
import Data.Morpheus.Types.Internal.AST.Base
( Key
, Position
, Name
, Description
, TypeWrapper(..)
, TypeRef(..)
)
import Data.Morpheus.Types.Internal.Resolving.Core
( Validation
, Failure(..)
, GQLErrors
, LibUpdater
, resolveUpdates
)
import Data.Morpheus.Types.Internal.AST.Value
( Value(..)
, ValidValue
, ScalarValue(..)
)
import Data.Morpheus.Error.Schema ( nameCollisionError )
type QUERY = 'Query
type MUTATION = 'Mutation
type SUBSCRIPTION = 'Subscription
isDefaultTypeName :: Key -> Bool
isDefaultTypeName x = isSchemaTypeName x || isPrimitiveTypeName x
isSchemaTypeName :: Key -> Bool
isSchemaTypeName = (`elem` sysTypes)
isPrimitiveTypeName :: Key -> Bool
isPrimitiveTypeName = (`elem` ["String", "Float", "Int", "Boolean", "ID"])
sysTypes :: [Key]
sysTypes =
[ "__Schema"
, "__Type"
, "__Directive"
, "__TypeKind"
, "__Field"
, "__DirectiveLocation"
, "__InputValue"
, "__EnumValue"
]
data OperationType
= Query
| Subscription
| Mutation
deriving (Show, Eq, Lift)
isSubscription :: DataTypeKind -> Bool
isSubscription (KindObject (Just Subscription)) = True
isSubscription _ = False
isOutputObject :: DataTypeKind -> Bool
isOutputObject (KindObject _) = True
isOutputObject _ = False
isObject :: DataTypeKind -> Bool
isObject (KindObject _) = True
isObject KindInputObject = True
isObject _ = False
isInput :: DataTypeKind -> Bool
isInput KindInputObject = True
isInput _ = False
data DataTypeKind
= KindScalar
| KindObject (Maybe OperationType)
| KindUnion
| KindEnum
| KindInputObject
| KindList
| KindNonNull
| KindInputUnion
deriving (Eq, Show, Lift)
data ResolverKind
= PlainResolver
| TypeVarResolver
| ExternalResolver
deriving (Show, Eq, Lift)
isFieldNullable :: DataField -> Bool
isFieldNullable = isNullable . fieldType
isNullable :: TypeRef -> Bool
isNullable TypeRef { typeWrappers = typeWrappers } = isNullableWrapper typeWrappers
isNullableWrapper :: [TypeWrapper] -> Bool
isNullableWrapper (TypeMaybe : _ ) = True
isNullableWrapper _ = False
isWeaker :: [TypeWrapper] -> [TypeWrapper] -> Bool
isWeaker (TypeMaybe : xs1) (TypeMaybe : xs2) = isWeaker xs1 xs2
isWeaker (TypeMaybe : _ ) _ = True
isWeaker (_ : xs1) (_ : xs2) = isWeaker xs1 xs2
isWeaker _ _ = False
toGQLWrapper :: [TypeWrapper] -> [DataTypeWrapper]
toGQLWrapper (TypeMaybe : (TypeMaybe : tw)) = toGQLWrapper (TypeMaybe : tw)
toGQLWrapper (TypeMaybe : (TypeList : tw)) = ListType : toGQLWrapper tw
toGQLWrapper (TypeList : tw) = [NonNullType, ListType] <> toGQLWrapper tw
toGQLWrapper [TypeMaybe ] = []
toGQLWrapper [] = [NonNullType]
toHSWrappers :: [DataTypeWrapper] -> [TypeWrapper]
toHSWrappers (NonNullType : (NonNullType : xs)) =
toHSWrappers (NonNullType : xs)
toHSWrappers (NonNullType : (ListType : xs)) = TypeList : toHSWrappers xs
toHSWrappers (ListType : xs) = [TypeMaybe, TypeList] <> toHSWrappers xs
toHSWrappers [] = [TypeMaybe]
toHSWrappers [NonNullType] = []
data DataFingerprint = DataFingerprint Name [String] deriving (Show, Eq, Ord, Lift)
newtype DataValidator = DataValidator
{ validateValue :: ValidValue -> Either Key ValidValue
}
instance Show DataValidator where
show _ = "DataValidator"
type DataScalar = DataValidator
type DataEnum = [DataEnumValue]
type DataObject = [(Key, DataField)]
type DataArgument = DataField
type DataUnion = [Key]
type DataInputUnion = [(Key, Bool)]
type DataArguments = [(Key, DataArgument)]
data DataTypeWrapper
= ListType
| NonNullType
deriving (Show, Lift)
data ArgsType = ArgsType
{ argsTypeName :: Key
, resKind :: ResolverKind
} deriving (Show,Lift)
data Directive = Directive {
directiveName :: Name,
directiveArgs :: [(Name, ValidValue)]
} deriving (Show,Lift)
data Meta = Meta {
metaDescription:: Maybe Description,
metaDirectives :: [Directive]
} deriving (Show,Lift)
lookupDeprecated :: Meta -> Maybe Directive
lookupDeprecated Meta { metaDirectives } = find isDeprecation metaDirectives
where
isDeprecation Directive { directiveName = "deprecated" } = True
isDeprecation _ = False
lookupDeprecatedReason :: Directive -> Maybe Key
lookupDeprecatedReason Directive { directiveArgs } =
maybeString . snd <$> find isReason directiveArgs
where
maybeString :: ValidValue -> Name
maybeString (Scalar (String x)) = x
maybeString _ = "can't read deprecated Reason Value"
isReason ("reason", _) = True
isReason _ = False
data DataEnumValue = DataEnumValue{
enumName :: Name,
enumMeta :: Maybe Meta
} deriving (Show, Lift)
data DataField = DataField
{ fieldName :: Key
, fieldArgs :: [(Key, DataArgument)]
, fieldArgsType :: Maybe ArgsType
, fieldType :: TypeRef
, fieldMeta :: Maybe Meta
} deriving (Show,Lift)
fieldVisibility :: (Key, DataField) -> Bool
fieldVisibility ("__typename", _) = False
fieldVisibility ("__schema" , _) = False
fieldVisibility ("__type" , _) = False
fieldVisibility _ = True
createField :: DataArguments -> Key -> ([TypeWrapper], Key) -> DataField
createField fieldArgs fieldName (typeWrappers, typeConName) = DataField
{ fieldArgs
, fieldArgsType = Nothing
, fieldName
, fieldType = TypeRef { typeConName, typeWrappers, typeArgs = Nothing }
, fieldMeta = Nothing
}
createArgument :: Key -> ([TypeWrapper], Key) -> (Key, DataField)
createArgument fieldName x = (fieldName, createField [] fieldName x)
toNullableField :: DataField -> DataField
toNullableField dataField
| isNullable (fieldType dataField) = dataField
| otherwise = dataField { fieldType = nullable (fieldType dataField) }
where
nullable alias@TypeRef { typeWrappers } =
alias { typeWrappers = TypeMaybe : typeWrappers }
toListField :: DataField -> DataField
toListField dataField = dataField { fieldType = listW (fieldType dataField) }
where
listW alias@TypeRef { typeWrappers } =
alias { typeWrappers = TypeList : typeWrappers }
lookupField :: Failure error m => Key -> [(Key, field)] -> error -> m field
lookupField key fields gqlError = case lookup key fields of
Nothing -> failure gqlError
Just field -> pure field
lookupSelectionField
:: Failure GQLErrors Validation
=> Position
-> Name
-> Name
-> DataObject
-> Validation DataField
lookupSelectionField position fieldName typeName fields = lookupField
fieldName
fields
gqlError
where gqlError = cannotQueryField fieldName typeName position
data RawDataType
= FinalDataType DataType
| Interface {
interfaceName :: Key
, interfaceMeta :: Maybe Meta
, interfaceContent :: DataObject
}
| Implements {
implementsName :: Key
, implementsInterfaces :: [Key]
, implementsMeta :: Maybe Meta
, implementsContent :: DataObject
}
deriving (Show)
data DataTypeContent
= DataScalar DataScalar
| DataEnum DataEnum
| DataInputObject DataObject
| DataObject DataObject
| DataUnion DataUnion
| DataInputUnion [(Key,Bool)]
deriving (Show)
data DataType = DataType
{ typeName :: Key
, typeFingerprint :: DataFingerprint
, typeMeta :: Maybe Meta
, typeContent :: DataTypeContent
} deriving (Show)
createType :: Key -> DataTypeContent -> DataType
createType typeName typeContent = DataType
{ typeName
, typeMeta = Nothing
, typeFingerprint = DataFingerprint typeName []
, typeContent
}
createScalarType :: Key -> (Key, DataType)
createScalarType typeName =
(typeName, createType typeName $ DataScalar (DataValidator pure))
createEnumType :: Key -> [Key] -> (Key, DataType)
createEnumType typeName typeData =
(typeName, createType typeName $ DataEnum enumValues)
where enumValues = map createEnumValue typeData
createEnumValue :: Key -> DataEnumValue
createEnumValue enumName = DataEnumValue { enumName, enumMeta = Nothing }
createUnionType :: Key -> [Key] -> (Key, DataType)
createUnionType typeName typeData =
(typeName, createType typeName $ DataUnion typeData)
isEntNode :: DataTypeContent -> Bool
isEntNode DataScalar{} = True
isEntNode DataEnum{} = True
isEntNode _ = False
isInputDataType :: DataType -> Bool
isInputDataType DataType { typeContent } = __isInput typeContent
where
__isInput DataScalar{} = True
__isInput DataEnum{} = True
__isInput DataInputObject{} = True
__isInput DataInputUnion{} = True
__isInput _ = False
coerceDataObject :: Failure error m => error -> DataType -> m (Name,DataObject)
coerceDataObject _ DataType { typeContent = DataObject object , typeName } = pure (typeName,object)
coerceDataObject gqlError _ = failure gqlError
coerceDataUnion :: Failure error m => error -> DataType -> m DataUnion
coerceDataUnion _ DataType { typeContent = DataUnion members } = pure members
coerceDataUnion gqlError _ = failure gqlError
kindOf :: DataType -> DataTypeKind
kindOf DataType { typeContent } = __kind typeContent
where
__kind (DataScalar _) = KindScalar
__kind (DataEnum _) = KindEnum
__kind (DataInputObject _) = KindInputObject
__kind (DataObject _) = KindObject Nothing
__kind (DataUnion _) = KindUnion
__kind (DataInputUnion _) = KindInputUnion
data DataTypeLib = DataTypeLib
{ types :: HashMap Key DataType
, query :: (Name,DataType)
, mutation :: Maybe (Name, DataType)
, subscription :: Maybe (Name, DataType)
} deriving (Show)
type TypeRegister = HashMap Key DataType
initTypeLib :: (Key, DataType) -> DataTypeLib
initTypeLib query = DataTypeLib { types = empty
, query = query
, mutation = Nothing
, subscription = Nothing
}
allDataTypes :: DataTypeLib -> [(Key, DataType)]
allDataTypes DataTypeLib { types, query, mutation, subscription } =
concatMap fromOperation [Just query, mutation, subscription] <> toList types
typeRegister :: DataTypeLib -> TypeRegister
typeRegister DataTypeLib { types, query, mutation, subscription } =
types `union` fromList
(concatMap fromOperation [Just query, mutation, subscription])
fromOperation :: Maybe (Key, DataType) -> [(Key, DataType)]
fromOperation (Just (key, datatype)) = [(key, datatype)]
fromOperation Nothing = []
lookupDataType :: Key -> DataTypeLib -> Maybe DataType
lookupDataType name lib = name `HM.lookup` typeRegister lib
getDataType :: Failure error m => Key -> DataTypeLib -> error -> m DataType
getDataType name lib gqlError = case lookupDataType name lib of
Just x -> pure x
_ -> failure gqlError
lookupDataObject
:: (Monad m, Failure e m) => e -> Key -> DataTypeLib -> m (Name,DataObject)
lookupDataObject validationError name lib =
getDataType name lib validationError >>= coerceDataObject validationError
lookupDataUnion
:: (Monad m, Failure e m) => e -> Key -> DataTypeLib -> m DataUnion
lookupDataUnion validationError name lib =
getDataType name lib validationError >>= coerceDataUnion validationError
lookupUnionTypes
:: (Monad m, Failure GQLErrors m)
=> Position
-> Key
-> DataTypeLib
-> DataField
-> m [(Name,DataObject)]
lookupUnionTypes position key lib DataField { fieldType = TypeRef { typeConName = typeName } }
= lookupDataUnion gqlError typeName lib
>>= mapM (flip (lookupDataObject gqlError) lib)
where gqlError = hasNoSubfields key typeName position
lookupFieldAsSelectionSet
:: (Monad m, Failure GQLErrors m)
=> Position
-> Key
-> DataTypeLib
-> DataField
-> m (Name,DataObject)
lookupFieldAsSelectionSet position key lib DataField { fieldType = TypeRef { typeConName } }
= lookupDataObject gqlError typeConName lib
where gqlError = hasNoSubfields key typeConName position
lookupInputType :: Failure e m => Key -> DataTypeLib -> e -> m DataType
lookupInputType name lib errors = case lookupDataType name lib of
Just x | isInputDataType x -> pure x
_ -> failure errors
isTypeDefined :: Key -> DataTypeLib -> Maybe DataFingerprint
isTypeDefined name lib = typeFingerprint <$> lookupDataType name lib
defineType :: (Key, DataType) -> DataTypeLib -> DataTypeLib
defineType (key, datatype@DataType { typeName, typeContent = DataInputUnion enumKeys, typeFingerprint }) lib
= lib { types = insert name unionTags (insert key datatype (types lib)) }
where
name = typeName <> "Tags"
unionTags = DataType
{ typeName = name
, typeFingerprint
, typeMeta = Nothing
, typeContent = DataEnum $ map (createEnumValue . fst) enumKeys
}
defineType (key, datatype) lib =
lib { types = insert key datatype (types lib) }
lookupType :: Failure e m => e -> [(Key, a)] -> Key -> m a
lookupType err lib typeName = case lookup typeName lib of
Nothing -> failure err
Just x -> pure x
createDataTypeLib :: [(Key, DataType)] -> Validation DataTypeLib
createDataTypeLib types = case takeByKey "Query" types of
(Just query, lib1) -> case takeByKey "Mutation" lib1 of
(mutation, lib2) -> case takeByKey "Subscription" lib2 of
(subscription, lib3) ->
pure
((foldr defineType (initTypeLib query) lib3) { mutation
, subscription
}
)
_ -> internalError "Query Not Defined"
where
takeByKey key lib = case lookup key lib of
Just dt@DataType { typeContent = DataObject {} } ->
(Just (key, dt), filter ((/= key) . fst) lib)
_ -> (Nothing, lib)
createInputUnionFields :: Key -> [Key] -> [(Key, DataField)]
createInputUnionFields name members = fieldTag : map unionField members
where
fieldTag =
( "__typename"
, DataField { fieldName = "__typename"
, fieldArgs = []
, fieldArgsType = Nothing
, fieldType = createAlias (name <> "Tags")
, fieldMeta = Nothing
}
)
unionField memberName =
( memberName
, DataField
{ fieldArgs = []
, fieldArgsType = Nothing
, fieldName = memberName
, fieldType = TypeRef { typeConName = memberName
, typeWrappers = [TypeMaybe]
, typeArgs = Nothing
}
, fieldMeta = Nothing
}
)
createAlias :: Key -> TypeRef
createAlias typeConName =
TypeRef { typeConName, typeWrappers = [], typeArgs = Nothing }
type TypeUpdater = LibUpdater DataTypeLib
insertType :: (Key, DataType) -> TypeUpdater
insertType nextType@(name, datatype) lib = case isTypeDefined name lib of
Nothing -> resolveUpdates (defineType nextType lib) []
Just fingerprint | fingerprint == typeFingerprint datatype -> return lib
|
otherwise -> failure $ nameCollisionError name
data ClientQuery = ClientQuery
{ queryText :: String
, queryTypes :: [ClientType]
, queryArgsType :: Maybe TypeD
} deriving (Show)
data ClientType = ClientType {
clientType :: TypeD,
clientKind :: DataTypeKind
} deriving (Show)
data GQLTypeD = GQLTypeD
{ typeD :: TypeD
, typeKindD :: DataTypeKind
, typeArgD :: [TypeD]
, typeOriginal:: (Name,DataType)
} deriving (Show)
data TypeD = TypeD
{ tName :: String
, tNamespace :: [String]
, tCons :: [ConsD]
, tMeta :: Maybe Meta
} deriving (Show)
data ConsD = ConsD
{ cName :: String
, cFields :: [DataField]
} deriving (Show)