{-# LANGUAGE GADTs #-}
{-# LANGUAGE FlexibleContexts #-}
{-# 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
( ValidVariables
, Selection(..)
, SelectionContent(..)
, ValidSelection
, ValidSelectionSet
, Fragment(..)
, FragmentLib
, RawSelection
, RawSelectionSet
, DataField(..)
, Ref(..)
, DataObject
, DataTypeContent(..)
, DataType(..)
, DataTypeLib(..)
, TypeRef(..)
, Name
, allDataTypes
, isEntNode
, lookupFieldAsSelectionSet
, lookupSelectionField
, lookupType
, lookupUnionTypes
)
import Data.Morpheus.Types.Internal.Resolving
( Validation
, Failure(..)
)
import Data.Morpheus.Validation.Internal.Utils
( checkNameCollision )
import Data.Morpheus.Validation.Query.Arguments
( validateArguments )
import Data.Morpheus.Validation.Query.Fragment
( castFragmentType
, resolveSpread
)
checkDuplicatesOn :: Name -> ValidSelectionSet -> Validation ValidSelectionSet
checkDuplicatesOn typeName keys = checkNameCollision enhancedKeys selError
>> pure keys
where
selError = duplicateQuerySelections typeName
enhancedKeys = map selToKey keys
selToKey :: (Name, ValidSelection) -> Ref
selToKey (key, Selection { selectionPosition = position', selectionAlias }) =
Ref (fromMaybe key selectionAlias) position'
clusterUnionSelection
:: FragmentLib
-> Text
-> [Name]
-> (Text, RawSelection)
-> Validation ([Fragment], ValidSelectionSet)
clusterUnionSelection fragments type' typeNames = splitFrag
where
packFragment fragment = return ([fragment], [])
splitFrag
:: (Text, RawSelection) -> Validation ([Fragment], ValidSelectionSet)
splitFrag (_, Spread ref) =
resolveSpread fragments typeNames ref >>= packFragment
splitFrag ("__typename", selection@Selection { selectionContent = SelectionField })
= pure
( []
, [ ( "__typename"
, selection { selectionArguments = [], selectionContent = SelectionField }
)
]
)
splitFrag (key, Selection { selectionPosition }) =
failure $ cannotQueryField key type' selectionPosition
splitFrag (_, InlineFragment fragment') =
castFragmentType Nothing (fragmentPosition fragment') typeNames fragment'
>>= packFragment
categorizeTypes
:: [(Name, DataObject)] -> [Fragment] -> [((Name, DataObject), [Fragment])]
categorizeTypes types fragments = filter notEmpty $ map categorizeType types
where
notEmpty = (0 /=) . length . snd
categorizeType :: (Name, DataObject) -> ((Name, DataObject), [Fragment])
categorizeType datatype = (datatype, filter matches fragments)
where matches fragment = fragmentType fragment == fst datatype
flatTuple :: [([a], [b])] -> ([a], [b])
flatTuple list' = (concatMap fst list', concatMap snd list')
validateSelectionSet
:: DataTypeLib
-> FragmentLib
-> Text
-> ValidVariables
-> (Name, DataObject)
-> RawSelectionSet
-> Validation ValidSelectionSet
validateSelectionSet lib fragments' operatorName variables = __validate
where
__validate
:: (Name, DataObject) -> RawSelectionSet -> Validation ValidSelectionSet
__validate dataType@(typeName, objectFields) selectionSet =
concat
<$> mapM validateSelection selectionSet
>>= checkDuplicatesOn typeName
where
validateFragment Fragment { fragmentSelection = selection' } =
__validate dataType selection'
getValidationData key (selectionArguments, selectionPosition) = do
selectionField <- lookupSelectionField selectionPosition
key
typeName
objectFields
arguments <- validateArguments lib
operatorName
variables
(key, selectionField)
selectionPosition
selectionArguments
fieldDataType <- lookupType
(unknownType (typeConName $fieldType selectionField) selectionPosition)
(allDataTypes lib)
(typeConName $ fieldType selectionField)
return (selectionField, fieldDataType, arguments)
validateSelection :: (Text, RawSelection) -> Validation ValidSelectionSet
validateSelection (key', fullRawSelection@Selection { selectionArguments = selArgs, selectionContent = SelectionSet rawSelection, selectionPosition })
= do
(dataField, datatype, arguments) <- getValidationData
key'
(selArgs, selectionPosition)
case typeContent 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
$ fst
<$> unionTypes
)
rawSelection
return (categorizeTypes unionTypes spreads, __typename)
validateCluster
:: ValidSelectionSet
-> ((Name, DataObject), [Fragment])
-> Validation (Text, ValidSelectionSet)
validateCluster sysSelection' (type', frags') = do
selection' <- __validate type'
(concatMap fragmentSelection frags')
return (fst type', sysSelection' ++ selection')
DataObject _ -> do
fieldType' <- lookupFieldAsSelectionSet selectionPosition
key'
lib
dataField
__validate fieldType' rawSelection
>>= returnSelection arguments
. SelectionSet
_ -> failure $ hasNoSubfields key'
(typeConName $fieldType dataField)
selectionPosition
where
returnSelection selectionArguments selectionContent =
pure [(key', fullRawSelection { selectionArguments, selectionContent })]
validateSelection (key, rawSelection@Selection { selectionArguments = selArgs, selectionPosition, selectionContent = SelectionField })
= do
(dataField, datatype, selectionArguments) <- getValidationData
key
(selArgs, selectionPosition)
isLeaf (typeContent datatype) dataField
pure
[ ( key
, rawSelection { selectionArguments, selectionContent = SelectionField }
)
]
where
isLeaf datatype DataField { fieldType = TypeRef { typeConName } }
| isEntNode datatype = pure ()
| otherwise = failure
$ subfieldsNotSelected key typeConName selectionPosition
validateSelection (_, Spread reference') =
resolveSpread fragments' [typeName] reference' >>= validateFragment
validateSelection (_, InlineFragment fragment') =
castFragmentType Nothing (fragmentPosition fragment') [typeName] fragment'
>>= validateFragment