{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE NamedFieldPuns #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TypeOperators #-}
module Data.Morpheus.Validation.Query.Selection
( validateSelectionSet
) where
import Data.Maybe (fromMaybe)
import Data.Text (Text)
import Data.Morpheus.Error.Selection (cannotQueryField, duplicateQuerySelections,
hasNoSubfields, subfieldsNotSelected)
import Data.Morpheus.Error.Variable (unknownType)
import Data.Morpheus.Types.Internal.AST.Operation (ValidVariables)
import Data.Morpheus.Types.Internal.AST.RawSelection (Fragment (..), FragmentLib, RawSelection (..),
RawSelectionSet)
import Data.Morpheus.Types.Internal.AST.Selection (Selection (..), SelectionRec (..), SelectionSet)
import Data.Morpheus.Types.Internal.Base (EnhancedKey (..))
import Data.Morpheus.Types.Internal.Data (DataField (..), DataType (..), DataObject,
DataTyCon (..), DataTypeLib (..), TypeAlias (..),
allDataTypes, isEntNode)
import Data.Morpheus.Types.Internal.Validation (Validation)
import Data.Morpheus.Validation.Internal.Utils (checkNameCollision, lookupType)
import Data.Morpheus.Validation.Query.Arguments (validateArguments)
import Data.Morpheus.Validation.Query.Fragment (castFragmentType, resolveSpread)
import Data.Morpheus.Validation.Query.Utils.Selection (lookupFieldAsSelectionSet, lookupSelectionField,
lookupUnionTypes)
checkDuplicatesOn :: DataObject -> SelectionSet -> Validation SelectionSet
checkDuplicatesOn DataTyCon {typeName = name'} keys = checkNameCollision enhancedKeys selError >> pure keys
where
selError = duplicateQuerySelections name'
enhancedKeys = map selToKey keys
selToKey (key, Selection {selectionPosition = position' , selectionAlias }) = EnhancedKey (fromMaybe key selectionAlias) position'
clusterUnionSelection ::
FragmentLib -> Text -> [DataObject] -> (Text, RawSelection) -> Validation ([Fragment], SelectionSet)
clusterUnionSelection fragments type' possibleTypes' = splitFrag
where
packFragment fragment = return ([fragment], [])
typeNames = map typeName possibleTypes'
splitFrag :: (Text, RawSelection) -> Validation ([Fragment], SelectionSet)
splitFrag (_, Spread ref) = resolveSpread fragments typeNames ref >>= packFragment
splitFrag ("__typename", RawSelectionField selection ) = pure ([] ,[
( "__typename",
selection {
selectionArguments = [] ,
selectionRec = SelectionField}
)
])
splitFrag (key, RawSelectionSet Selection {selectionPosition}) =
Left $ cannotQueryField key type' selectionPosition
splitFrag (key, RawSelectionField Selection {selectionPosition}) =
Left $ cannotQueryField key type' selectionPosition
splitFrag (_, InlineFragment fragment') =
castFragmentType Nothing (fragmentPosition fragment') typeNames fragment' >>= packFragment
categorizeTypes :: [DataObject] -> [Fragment] -> [(DataObject, [Fragment])]
categorizeTypes types fragments = filter notEmpty $ map categorizeType types
where
notEmpty = (0 /=) . length . snd
categorizeType :: DataObject -> (DataObject, [Fragment])
categorizeType datatype = (datatype, filter matches fragments)
where
matches fragment = fragmentType fragment == typeName datatype
flatTuple :: [([a], [b])] -> ([a], [b])
flatTuple list' = (concatMap fst list', concatMap snd list')
validateSelectionSet ::
DataTypeLib -> FragmentLib -> Text -> ValidVariables -> DataObject -> RawSelectionSet -> Validation SelectionSet
validateSelectionSet lib fragments' operatorName variables = __validate
where
__validate dataType'@DataTyCon {typeName = typeName'} selectionSet' =
concat <$> mapM validateSelection selectionSet' >>= checkDuplicatesOn dataType'
where
validateFragment Fragment {fragmentSelection = selection'} = __validate dataType' selection'
getValidationData key Selection {selectionArguments, selectionPosition} = do
selectionField <- lookupSelectionField selectionPosition key dataType'
arguments <-
validateArguments
lib
operatorName
variables
(key, selectionField)
selectionPosition
selectionArguments
fieldDataType <-
lookupType
(unknownType (aliasTyCon $fieldType selectionField) selectionPosition)
(allDataTypes lib)
(aliasTyCon $ fieldType selectionField)
return (selectionField, fieldDataType, arguments)
validateSelection :: (Text, RawSelection) -> Validation SelectionSet
validateSelection (key', RawSelectionSet fullRawSelection@Selection { selectionRec = rawSelection, selectionPosition }) = do
(dataField, dataType, arguments) <- getValidationData key' fullRawSelection
case dataType of
DataUnion _ -> do
(categories, __typename) <- clusterTypes
mapM (validateCluster __typename) categories >>= returnSelection arguments . UnionSelection
where clusterTypes = do
unionTypes <- lookupUnionTypes selectionPosition key' lib dataField
(spreads, __typename) <-
flatTuple <$> mapM (clusterUnionSelection fragments' typeName' unionTypes) rawSelection
return (categorizeTypes unionTypes spreads, __typename)
validateCluster :: SelectionSet -> (DataObject, [Fragment]) -> Validation (Text, SelectionSet)
validateCluster sysSelection' (type', frags') = do
selection' <- __validate type' (concatMap fragmentSelection frags')
return (typeName type', sysSelection' ++ selection')
DataObject _ -> do
fieldType' <- lookupFieldAsSelectionSet selectionPosition key' lib dataField
__validate fieldType' rawSelection >>= returnSelection arguments . SelectionSet
_ -> Left $ hasNoSubfields key' (aliasTyCon $fieldType dataField) selectionPosition
where
returnSelection selectionArguments selectionRec =
pure
[ ( key'
, fullRawSelection
{ selectionArguments,
selectionRec
}
)
]
validateSelection (key, RawSelectionField rawSelection@Selection{selectionPosition}) = do
(dataField, datatype, selectionArguments) <- getValidationData key rawSelection
isLeaf datatype dataField
pure [( key , rawSelection { selectionArguments , selectionRec = SelectionField })]
where
isLeaf dataType DataField {fieldType = TypeAlias {aliasTyCon}}
| isEntNode dataType = Right ()
| otherwise = Left $ subfieldsNotSelected key aliasTyCon selectionPosition
validateSelection (_, Spread reference') = resolveSpread fragments' [typeName'] reference' >>= validateFragment
validateSelection (_, InlineFragment fragment') =
castFragmentType Nothing (fragmentPosition fragment') [typeName'] fragment' >>= validateFragment