{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE NamedFieldPuns #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TypeOperators #-}
module Data.Morpheus.Execution.Client.Selection
( operationTypes
) where
import Data.Semigroup ((<>))
import Data.Text (Text, unpack)
import Data.Morpheus.Error.Utils (globalErrorMessage)
import Data.Morpheus.Types.Internal.AST.Operation (Operation (..), ValidOperation, Variable (..),
VariableDefinitions)
import Data.Morpheus.Types.Internal.AST.Selection (Selection (..), SelectionRec (..))
import Data.Morpheus.Types.Internal.Data (DataField (..), DataFullType (..), DataLeaf (..),
DataTyCon (..), DataTypeLib (..), TypeAlias (..),
allDataTypes)
import Data.Morpheus.Types.Internal.DataD (ConsD (..), TypeD (..))
import Data.Morpheus.Types.Internal.Validation (GQLErrors, Validation)
import Data.Morpheus.Validation.Internal.Utils (lookupType)
compileError :: Text -> GQLErrors
compileError x = globalErrorMessage $ "Unhandled Compile Time Error: \"" <> x <> "\" ;"
operationTypes :: DataTypeLib -> VariableDefinitions -> ValidOperation -> Validation ([TypeD], [TypeD])
operationTypes lib variables = genOperation
where
queryDataType = OutputObject $ snd $ query lib
typeByField :: Text -> DataFullType -> Validation DataFullType
typeByField key datatype = fst <$> lookupFieldType datatype key
lookupFieldType :: DataFullType -> Text -> Validation (DataFullType, TypeAlias)
lookupFieldType (OutputObject DataTyCon {typeData}) key =
case lookup key typeData of
Just DataField {fieldType = alias@TypeAlias {aliasTyCon}} -> trans <$> getType lib aliasTyCon
where trans x = (x, alias {aliasTyCon = typeFrom x, aliasArgs = Nothing})
Nothing -> Left (compileError key)
lookupFieldType _ key = Left (compileError key)
genOperation Operation {operationName, operationSelection} = do
argTypes <- rootArguments (operationName <> "Args")
queryTypes <- genRecordType operationName queryDataType operationSelection
pure (argTypes, queryTypes)
genInputType :: Text -> Validation [TypeD]
genInputType name = getType lib name >>= subTypes
where
subTypes (InputObject DataTyCon {typeName, typeData}) = do
types <- concat <$> mapM toInputTypeD typeData
fields <- traverse toFieldD typeData
pure $ typeD fields : types
where
typeD fields = TypeD {tName = unpack typeName, tCons = [ConsD {cName = unpack typeName, cFields = fields}]}
toInputTypeD :: (Text, DataField) -> Validation [TypeD]
toInputTypeD (_, DataField {fieldType}) = genInputType $ aliasTyCon fieldType
toFieldD :: (Text, DataField) -> Validation DataField
toFieldD (_, field@DataField {fieldType}) = do
aliasTyCon <- typeFrom <$> getType lib (aliasTyCon fieldType)
pure $ field {fieldType = fieldType {aliasTyCon}}
subTypes (Leaf x) = buildLeaf x
subTypes _ = pure []
rootArguments :: Text -> Validation [TypeD]
rootArguments name = do
types <- concat <$> mapM (genInputType . variableType . snd) variables
pure $ typeD : types
where
typeD :: TypeD
typeD = TypeD {tName = unpack name, tCons = [ConsD {cName = unpack name, cFields = map fieldD variables}]}
fieldD :: (Text, Variable ()) -> DataField
fieldD (key, Variable {variableType, variableTypeWrappers}) =
DataField
{ fieldName = key
, fieldArgs = []
, fieldArgsType = Nothing
, fieldType =
TypeAlias {aliasWrappers = variableTypeWrappers, aliasTyCon = variableType, aliasArgs = Nothing}
, fieldHidden = False
}
getCon name dataType selectionSet = do
cFields <- genFields dataType selectionSet
subTypes <- newFieldTypes dataType selectionSet
pure (ConsD {cName = unpack name, cFields}, subTypes)
where
genFields datatype = mapM typeNameFromField
where
typeNameFromField :: (Text, Selection) -> Validation DataField
typeNameFromField (fieldName, Selection {selectionRec = SelectionAlias {aliasFieldName}}) = do
fieldType <- snd <$> lookupFieldType datatype aliasFieldName
pure $ DataField {fieldName, fieldArgs = [], fieldArgsType = Nothing, fieldType, fieldHidden = False}
typeNameFromField (fieldName, _) = do
fieldType <- snd <$> lookupFieldType datatype fieldName
pure $ DataField {fieldName, fieldArgs = [], fieldArgsType = Nothing, fieldType, fieldHidden = False}
genRecordType name dataType selectionSet = do
(con, subTypes) <- getCon name dataType selectionSet
pure $ TypeD {tName = unpack name, tCons = [con]} : subTypes
newFieldTypes parentType = fmap concat <$> mapM validateSelection
where
validateSelection :: (Text, Selection) -> Validation [TypeD]
validateSelection (key, Selection {selectionRec = SelectionField}) =
key `typeByField` parentType >>= buildSelField
where
buildSelField (Leaf x) = buildLeaf x
buildSelField _ = Left $ compileError "Invalid schema Expected scalar"
validateSelection (key, Selection {selectionRec = SelectionSet selectionSet}) = do
datatype <- key `typeByField` parentType
genRecordType (typeFrom datatype) datatype selectionSet
validateSelection (_, selection@Selection {selectionRec = SelectionAlias {aliasFieldName, aliasSelection}}) =
validateSelection (aliasFieldName, selection {selectionRec = aliasSelection})
validateSelection (key, Selection {selectionRec = UnionSelection unionSelections}) = do
unionTypeName <- typeFrom <$> key `typeByField` parentType
(tCons, subTypes) <- unzip <$> mapM getUnionType unionSelections
pure $ TypeD {tName = unpack unionTypeName, tCons} : concat subTypes
where
getUnionType (typeKey, selSet) = do
conDatatype <- getType lib typeKey
getCon typeKey conDatatype selSet
buildLeaf :: DataLeaf -> Validation [TypeD]
buildLeaf (LeafEnum DataTyCon {typeName, typeData}) =
pure [TypeD {tName = unpack typeName, tCons = map enumOption typeData}]
where
enumOption name = ConsD {cName = unpack name, cFields = []}
buildLeaf _ = pure []
getType :: DataTypeLib -> Text -> Validation DataFullType
getType lib typename = lookupType (compileError typename) (allDataTypes lib) typename
isPrimitive :: Text -> Bool
isPrimitive "Boolean" = True
isPrimitive "Int" = True
isPrimitive "Float" = True
isPrimitive "String" = True
isPrimitive "ID" = True
isPrimitive _ = False
typeFrom :: DataFullType -> Text
typeFrom (Leaf (BaseScalar x)) = typeName x
typeFrom (Leaf (CustomScalar DataTyCon {typeName}))
| isPrimitive typeName = typeName
| otherwise = "ScalarValue"
typeFrom (Leaf (LeafEnum x)) = typeName x
typeFrom (InputObject x) = typeName x
typeFrom (OutputObject x) = typeName x
typeFrom (Union x) = typeName x
typeFrom (InputUnion x) = typeName x