{-# LANGUAGE StandaloneDeriving #-} {-# LANGUAGE NamedFieldPuns #-} {-# LANGUAGE DeriveLift #-} {-# LANGUAGE DataKinds #-} {-# LANGUAGE GADTs #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE TypeFamilies #-} {-# LANGUAGE FlexibleContexts #-} module Data.Morpheus.Types.Internal.AST.Selection ( Selection(..) , SelectionContent(..) , SelectionSet , UnionTag(..) , UnionSelection , Fragment(..) , Fragments , Operation(..) , Variable(..) , VariableDefinitions , DefaultValue , getOperationName , getOperationDataType ) where import Data.Maybe ( fromMaybe , isJust ) import Data.Semigroup ( (<>) ) import Language.Haskell.TH.Syntax ( Lift(..) ) import qualified Data.Text as T -- MORPHEUS import Data.Morpheus.Error.Operation ( mutationIsNotDefined , subscriptionIsNotDefined ) import Data.Morpheus.Types.Internal.AST.Base ( Key , Position , Ref(..) , Name , VALID , RAW , Stage , OperationType(..) , GQLError(..) , GQLErrors , Message ) import Data.Morpheus.Types.Internal.AST.Data ( Schema(..) , TypeDefinition(..) , Arguments ) import Data.Morpheus.Types.Internal.AST.Value ( Variable(..) , VariableDefinitions , ResolvedValue ) import Data.Morpheus.Types.Internal.AST.MergeSet ( MergeSet ) import Data.Morpheus.Types.Internal.AST.OrderedMap ( OrderedMap ) import Data.Morpheus.Types.Internal.Operation ( KeyOf(..) , Merge(..) , Failure(..) ) import Data.Morpheus.Error.NameCollision ( NameCollision(..) ) data Fragment = Fragment { fragmentName :: Name , fragmentType :: Name , fragmentPosition :: Position , fragmentSelection :: SelectionSet RAW } deriving ( Show, Eq, Lift) -- ERRORs instance NameCollision Fragment where nameCollision _ Fragment { fragmentName , fragmentPosition } = GQLError { message = "There can be only one fragment named \"" <> fragmentName <> "\"." , locations = [fragmentPosition] } instance KeyOf Fragment where keyOf = fragmentName type Fragments = OrderedMap Fragment data SelectionContent (s :: Stage) where SelectionField :: SelectionContent s SelectionSet :: SelectionSet s -> SelectionContent s UnionSelection :: UnionSelection -> SelectionContent VALID instance Merge (SelectionContent s) where merge path (SelectionSet s1) (SelectionSet s2) = SelectionSet <$> merge path s1 s2 merge path (UnionSelection u1) (UnionSelection u2) = UnionSelection <$> merge path u1 u2 merge path oldC currC | oldC == currC = pure oldC | otherwise = failure [ GQLError { message = T.concat $ map refName path, locations = map refPosition path } ] deriving instance Show (SelectionContent a) deriving instance Eq (SelectionContent a) deriving instance Lift (SelectionContent a) data UnionTag = UnionTag { unionTagName :: Name, unionTagSelection :: SelectionSet VALID } deriving (Show, Eq, Lift) mergeConflict :: [Ref] -> GQLError -> GQLErrors mergeConflict [] err = [err] mergeConflict refs@(rootField:xs) err = [ GQLError { message = renderSubfields <> message err, locations = map refPosition refs <> locations err } ] where fieldConflicts ref = "\"" <> refName ref <> "\" conflict because " renderSubfield ref txt = txt <> "subfields " <> fieldConflicts ref renderStart = "Fields " <> fieldConflicts rootField renderSubfields = foldr renderSubfield renderStart xs instance Merge UnionTag where merge path (UnionTag oldTag oldSel) (UnionTag _ currentSel) = UnionTag oldTag <$> merge path oldSel currentSel instance KeyOf UnionTag where keyOf = unionTagName type UnionSelection = MergeSet UnionTag type SelectionSet s = MergeSet (Selection s) data Selection (s :: Stage) where Selection :: { selectionName :: Name , selectionAlias :: Maybe Name , selectionPosition :: Position , selectionArguments :: Arguments s , selectionContent :: SelectionContent s } -> Selection s InlineFragment :: Fragment -> Selection RAW Spread :: Ref -> Selection RAW instance KeyOf (Selection s) where keyOf Selection { selectionName , selectionAlias } = fromMaybe selectionName selectionAlias keyOf InlineFragment {} = "" keyOf Spread {} = "" useDufferentAliases :: Message useDufferentAliases = "Use different aliases on the " <> "fields to fetch both if this was intentional." instance Merge (Selection a) where merge path old@Selection{ selectionPosition = pos1 } current@Selection{ selectionPosition = pos2 } = do selectionName <- mergeName let currentPath = path <> [Ref selectionName pos1] selectionArguments <- mergeArguments currentPath selectionContent <- merge currentPath (selectionContent old) (selectionContent current) pure $ Selection { selectionName, selectionAlias = mergeAlias, selectionPosition = pos1, selectionArguments, selectionContent } where -- passes if: -- * { user : user } -- * { user1: user -- user1: user -- } -- fails if: -- * { user1: user -- user1: product -- } mergeName | selectionName old == selectionName current = pure $ selectionName current | otherwise = failure $ mergeConflict path $ GQLError { message = "\"" <> selectionName old <> "\" and \"" <> selectionName current <> "\" are different fields. " <> useDufferentAliases, locations = [pos1, pos2] } --------------------- -- allias name is relevant only if they collide by allias like: -- { user1: user -- user1: user -- } mergeAlias | all (isJust . selectionAlias) [old,current] = selectionAlias old | otherwise = Nothing --- arguments must be equal mergeArguments currentPath | selectionArguments old == selectionArguments current = pure $ selectionArguments current | otherwise = failure $ mergeConflict currentPath $ GQLError { message = "they have differing arguments. " <> useDufferentAliases , locations = [pos1,pos2] } -- TODO: merge path old current = failure $ mergeConflict path $ GQLError { message = "can't merge. " <> useDufferentAliases , locations = map selectionPosition [old,current] } deriving instance Show (Selection a) deriving instance Lift (Selection a) deriving instance Eq (Selection a) type DefaultValue = Maybe ResolvedValue data Operation (s:: Stage) = Operation { operationName :: Maybe Key , operationType :: OperationType , operationArguments :: VariableDefinitions s , operationSelection :: SelectionSet s , operationPosition :: Position } deriving (Show,Lift) getOperationName :: Maybe Key -> Key getOperationName = fromMaybe "AnonymousOperation" getOperationDataType :: Failure GQLErrors m => Operation a -> Schema -> m TypeDefinition getOperationDataType Operation { operationType = Query } lib = pure (query lib) getOperationDataType Operation { operationType = Mutation, operationPosition } lib = case mutation lib of Just x -> pure x Nothing -> failure $ mutationIsNotDefined operationPosition getOperationDataType Operation { operationType = Subscription, operationPosition } lib = case subscription lib of Just x -> pure x Nothing -> failure $ subscriptionIsNotDefined operationPosition