{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE NamedFieldPuns #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TypeOperators #-}
module Data.Morpheus.Execution.Client.Selection
( operationTypes
)
where
import Data.Maybe ( fromMaybe )
import Data.Semigroup ( (<>) )
import Data.Text ( Text
, pack
, unpack
)
import Data.Morpheus.Error.Client.Client
( deprecatedField )
import Data.Morpheus.Error.Utils ( globalErrorMessage )
import Data.Morpheus.Execution.Internal.Utils
( nameSpaceType )
import Data.Morpheus.Types.Internal.AST
( DefaultValue
, Operation(..)
, ValidOperation
, Variable(..)
, VariableDefinitions
, getOperationName
, getOperationDataType
, Selection(..)
, SelectionRec(..)
, SelectionSet
, ValidSelection
, Ref(..)
, DataField(..)
, DataTyCon(..)
, DataType(..)
, DataTypeKind(..)
, DataTypeLib(..)
, Key
, TypeAlias(..)
, DataEnumValue(..)
, allDataTypes
, lookupType
, ConsD(..)
, ClientType(..)
, TypeD(..)
, lookupDeprecated
, lookupDeprecatedReason
)
import Data.Morpheus.Types.Internal.Resolving
( GQLErrors
, Validation
, Failure(..)
, Result(..)
, Position
, LibUpdater
, resolveUpdates
)
import Data.Set ( fromList
, toList
)
removeDuplicates :: [Text] -> [Text]
removeDuplicates = toList . fromList
compileError :: Text -> GQLErrors
compileError x =
globalErrorMessage $ "Unhandled Compile Time Error: \"" <> x <> "\" ;"
operationTypes
:: DataTypeLib
-> VariableDefinitions
-> ValidOperation
-> Validation (Maybe TypeD, [ClientType])
operationTypes lib variables = genOperation
where
genOperation operation@Operation { operationName, operationSelection } = do
datatype <- DataObject <$> getOperationDataType operation lib
(queryTypes, enums) <- genRecordType []
(getOperationName operationName)
datatype
operationSelection
inputTypeRequests <- resolveUpdates []
$ map (scanInputTypes lib . variableType . snd) variables
inputTypesAndEnums <- buildListedTypes (inputTypeRequests <> enums)
pure
( rootArguments (getOperationName operationName <> "Args")
, queryTypes <> inputTypesAndEnums
)
buildListedTypes =
fmap concat . traverse (buildInputType lib) . removeDuplicates
rootArguments :: Text -> Maybe TypeD
rootArguments argsName | null variables = Nothing
| otherwise = Just rootArgumentsType
where
rootArgumentsType :: TypeD
rootArgumentsType = TypeD
{ tName = unpack argsName
, tNamespace = []
, tCons = [ ConsD { cName = unpack argsName
, cFields = map fieldD variables
}
]
, tMeta = Nothing
}
where
fieldD :: (Text, Variable DefaultValue) -> DataField
fieldD (key, Variable { variableType, variableTypeWrappers }) = DataField
{ fieldName = key
, fieldArgs = []
, fieldArgsType = Nothing
, fieldType = TypeAlias { aliasWrappers = variableTypeWrappers
, aliasTyCon = variableType
, aliasArgs = Nothing
}
, fieldMeta = Nothing
}
genRecordType
:: [Key]
-> Key
-> DataType
-> SelectionSet
-> Validation ([ClientType], [Text])
genRecordType path name dataType recordSelSet = do
(con, subTypes, requests) <- genConsD (unpack name) dataType recordSelSet
pure
( ClientType
{ clientType = TypeD { tName
, tNamespace = map unpack path
, tCons = [con]
, tMeta = Nothing
}
, clientKind = KindObject Nothing
}
: subTypes
, requests
)
where
tName = unpack name
genConsD
:: String
-> DataType
-> SelectionSet
-> Validation (ConsD, [ClientType], [Text])
genConsD cName datatype selSet = do
(cFields, subTypes, requests) <- unzip3 <$> traverse genField selSet
pure (ConsD { cName, cFields }, concat subTypes, concat requests)
where
genField
:: (Text, ValidSelection)
-> Validation (DataField, [ClientType], [Text])
genField (fName, sel@Selection { selectionAlias, selectionPosition }) =
do
(fieldDataType, fieldType) <- lookupFieldType lib
fieldPath
datatype
selectionPosition
fName
(subTypes, requests) <- subTypesBySelection fieldDataType sel
pure
( DataField { fieldName
, fieldArgs = []
, fieldArgsType = Nothing
, fieldType
, fieldMeta = Nothing
}
, subTypes
, requests
)
where
fieldPath = path <> [fieldName]
fieldName = fromMaybe fName selectionAlias
subTypesBySelection
:: DataType -> ValidSelection -> Validation ([ClientType], [Text])
subTypesBySelection dType Selection { selectionRec = SelectionField } =
leafType dType
subTypesBySelection dType Selection { selectionRec = SelectionSet selectionSet }
= genRecordType fieldPath (typeFrom [] dType) dType selectionSet
subTypesBySelection dType Selection { selectionRec = UnionSelection unionSelections }
= do
(tCons, subTypes, requests) <-
unzip3 <$> mapM getUnionType unionSelections
pure
( ClientType
{ clientType = TypeD { tNamespace = map unpack fieldPath
, tName = unpack $ typeFrom [] dType
, tCons
, tMeta = Nothing
}
, clientKind = KindUnion
}
: concat subTypes
, concat requests
)
where
getUnionType (selectedTyName, selectionVariant) = do
conDatatype <- getType lib selectedTyName
genConsD (unpack selectedTyName) conDatatype selectionVariant
scanInputTypes :: DataTypeLib -> Key -> LibUpdater [Key]
scanInputTypes lib name collected | name `elem` collected = pure collected
| otherwise = getType lib name >>= scanType
where
scanType (DataInputObject DataTyCon { typeData }) = resolveUpdates
(name : collected)
(map toInputTypeD typeData)
where
toInputTypeD :: (Text, DataField) -> LibUpdater [Key]
toInputTypeD (_, DataField { fieldType = TypeAlias { aliasTyCon } }) =
scanInputTypes lib aliasTyCon
scanType (DataEnum DataTyCon { typeName }) = pure (collected <> [typeName])
scanType _ = pure collected
buildInputType :: DataTypeLib -> Text -> Validation [ClientType]
buildInputType lib name = getType lib name >>= subTypes
where
subTypes (DataInputObject DataTyCon { typeName, typeData }) = do
fields <- traverse toFieldD typeData
pure
[ ClientType
{ clientType =
TypeD
{ tName = unpack typeName
, tNamespace = []
, tCons = [ConsD { cName = unpack typeName, cFields = fields }]
, tMeta = Nothing
}
, clientKind = KindInputObject
}
]
where
toFieldD :: (Text, DataField) -> Validation DataField
toFieldD (_, field@DataField { fieldType }) = do
aliasTyCon <- typeFrom [] <$> getType lib (aliasTyCon fieldType)
pure $ field { fieldType = fieldType { aliasTyCon } }
subTypes (DataEnum DataTyCon { typeName, typeData }) = pure
[ ClientType
{ clientType = TypeD { tName = unpack typeName
, tNamespace = []
, tCons = map enumOption typeData
, tMeta = Nothing
}
, clientKind = KindEnum
}
]
where
enumOption DataEnumValue { enumName } =
ConsD { cName = unpack enumName, cFields = [] }
subTypes _ = pure []
lookupFieldType
:: DataTypeLib
-> [Key]
-> DataType
-> Position
-> Text
-> Validation (DataType, TypeAlias)
lookupFieldType lib path (DataObject DataTyCon { typeData, typeName }) refPosition key
= case lookup key typeData of
Just DataField { fieldType = alias@TypeAlias { aliasTyCon }, fieldMeta } ->
checkDeprecated >> (trans <$> getType lib aliasTyCon)
where
trans x =
(x, alias { aliasTyCon = typeFrom path x, aliasArgs = Nothing })
checkDeprecated :: Validation ()
checkDeprecated = case fieldMeta >>= lookupDeprecated of
Just deprecation -> Success { result = (), warnings, events = [] }
where
warnings = deprecatedField typeName
Ref { refName = key, refPosition }
(lookupDeprecatedReason deprecation)
Nothing -> pure ()
Nothing -> failure
(compileError $ "cant find field \"" <> pack (show typeData) <> "\"")
lookupFieldType _ _ dt _ _ =
failure (compileError $ "Type should be output Object \"" <> pack (show dt))
leafType :: DataType -> Validation ([ClientType], [Text])
leafType (DataEnum DataTyCon { typeName }) = pure ([], [typeName])
leafType DataScalar{} = pure ([], [])
leafType _ = failure $ compileError "Invalid schema Expected scalar"
getType :: DataTypeLib -> Text -> Validation DataType
getType lib typename =
lookupType (compileError typename) (allDataTypes lib) typename
typeFromScalar :: Text -> Text
typeFromScalar "Boolean" = "Bool"
typeFromScalar "Int" = "Int"
typeFromScalar "Float" = "Float"
typeFromScalar "String" = "Text"
typeFromScalar "ID" = "ID"
typeFromScalar _ = "ScalarValue"
typeFrom :: [Key] -> DataType -> Text
typeFrom _ (DataScalar DataTyCon { typeName }) = typeFromScalar typeName
typeFrom _ (DataEnum x) = typeName x
typeFrom _ (DataInputObject x) = typeName x
typeFrom path (DataObject x) = pack $ nameSpaceType path $ typeName x
typeFrom path (DataUnion x) = pack $ nameSpaceType path $ typeName x
typeFrom _ (DataInputUnion x) = typeName x