137 lines
5.1 KiB
Haskell
137 lines
5.1 KiB
Haskell
{-# LANGUAGE OverloadedStrings #-}
|
|
|
|
-- | After the document is parsed, before getting executed the AST is
|
|
-- transformed into a similar, simpler AST. This module is responsible for
|
|
-- this transformation.
|
|
module Language.GraphQL.AST.Transform
|
|
( document
|
|
) where
|
|
|
|
import Control.Applicative (empty)
|
|
import Data.Bifunctor (first)
|
|
import Data.Either (partitionEithers)
|
|
import Data.Foldable (fold, foldMap)
|
|
import Data.List.NonEmpty (NonEmpty)
|
|
import qualified Data.List.NonEmpty as NonEmpty
|
|
import Data.Monoid (Alt(Alt,getAlt), (<>))
|
|
import qualified Language.GraphQL.AST as Full
|
|
import qualified Language.GraphQL.AST.Core as Core
|
|
import qualified Language.GraphQL.Schema as Schema
|
|
|
|
-- | Replaces a fragment name by a list of 'Core.Field'. If the name doesn't
|
|
-- match an empty list is returned.
|
|
type Fragmenter = Core.Name -> [Core.Field]
|
|
|
|
-- | Rewrites the original syntax tree into an intermediate representation used
|
|
-- for query execution.
|
|
document :: Schema.Subs -> Full.Document -> Maybe Core.Document
|
|
document subs doc = operations subs fr ops
|
|
where
|
|
(fr, ops) = first foldFrags
|
|
. partitionEithers
|
|
. NonEmpty.toList
|
|
$ defrag subs
|
|
<$> doc
|
|
|
|
foldFrags :: [Fragmenter] -> Fragmenter
|
|
foldFrags fs n = getAlt $ foldMap (Alt . ($ n)) fs
|
|
|
|
-- * Operation
|
|
|
|
-- TODO: Replace Maybe by MonadThrow CustomError
|
|
operations
|
|
:: Schema.Subs
|
|
-> Fragmenter
|
|
-> [Full.OperationDefinition]
|
|
-> Maybe Core.Document
|
|
operations subs fr = NonEmpty.nonEmpty . fmap (operation subs fr)
|
|
|
|
operation
|
|
:: Schema.Subs
|
|
-> Fragmenter
|
|
-> Full.OperationDefinition
|
|
-> Core.Operation
|
|
operation subs fr (Full.OperationSelectionSet sels) =
|
|
operation subs fr $ Full.OperationDefinition Full.Query empty empty empty sels
|
|
-- TODO: Validate Variable definitions with substituter
|
|
operation subs fr (Full.OperationDefinition Full.Query name _vars _dirs sels) =
|
|
Core.Query name $ appendSelection subs fr sels
|
|
operation subs fr (Full.OperationDefinition Full.Mutation name _vars _dirs sels) =
|
|
Core.Mutation name $ appendSelection subs fr sels
|
|
|
|
selection
|
|
:: Schema.Subs
|
|
-> Fragmenter
|
|
-> Full.Selection
|
|
-> Either [Core.Selection] Core.Selection
|
|
selection subs fr (Full.SelectionField fld)
|
|
= Right $ Core.SelectionField $ field subs fr fld
|
|
selection _ fr (Full.SelectionFragmentSpread (Full.FragmentSpread name _))
|
|
= Left $ Core.SelectionField <$> fr name
|
|
selection subs fr (Full.SelectionInlineFragment fragment)
|
|
| (Full.InlineFragment (Just typeCondition) _ selectionSet) <- fragment
|
|
= Right
|
|
$ Core.SelectionFragment
|
|
$ Core.Fragment typeCondition
|
|
$ appendSelection subs fr selectionSet
|
|
| (Full.InlineFragment Nothing _ selectionSet) <- fragment
|
|
= Left $ NonEmpty.toList $ appendSelection subs fr selectionSet
|
|
|
|
-- * Fragment replacement
|
|
|
|
-- | Extract Fragments into a single Fragmenter function and a Operation
|
|
-- Definition.
|
|
defrag
|
|
:: Schema.Subs
|
|
-> Full.Definition
|
|
-> Either Fragmenter Full.OperationDefinition
|
|
defrag _ (Full.DefinitionOperation op) =
|
|
Right op
|
|
defrag subs (Full.DefinitionFragment fragDef) =
|
|
Left $ fragmentDefinition subs fragDef
|
|
|
|
fragmentDefinition :: Schema.Subs -> Full.FragmentDefinition -> Fragmenter
|
|
fragmentDefinition subs (Full.FragmentDefinition name _tc _dirs sels) name'
|
|
| name == name' = selection' <$> do
|
|
selections <- NonEmpty.toList $ selection subs mempty <$> sels
|
|
either id pure selections
|
|
| otherwise = empty
|
|
where
|
|
selection' (Core.SelectionField field') = field'
|
|
selection' _ = error "Fragments within fragments are not supported yet"
|
|
|
|
field :: Schema.Subs -> Fragmenter -> Full.Field -> Core.Field
|
|
field subs fr (Full.Field a n args _dirs sels) =
|
|
Core.Field a n (fold $ argument subs `traverse` args) (foldr go empty sels)
|
|
where
|
|
go :: Full.Selection -> [Core.Selection] -> [Core.Selection]
|
|
go (Full.SelectionFragmentSpread (Full.FragmentSpread name _dirs)) = ((Core.SelectionField <$> fr name) <>)
|
|
go sel = (either id pure (selection subs fr sel) <>)
|
|
|
|
argument :: Schema.Subs -> Full.Argument -> Maybe Core.Argument
|
|
argument subs (Full.Argument n v) = Core.Argument n <$> value subs v
|
|
|
|
value :: Schema.Subs -> Full.Value -> Maybe Core.Value
|
|
value subs (Full.ValueVariable n) = subs n
|
|
value _ (Full.ValueInt i) = pure $ Core.ValueInt i
|
|
value _ (Full.ValueFloat f) = pure $ Core.ValueFloat f
|
|
value _ (Full.ValueString x) = pure $ Core.ValueString x
|
|
value _ (Full.ValueBoolean b) = pure $ Core.ValueBoolean b
|
|
value _ Full.ValueNull = pure Core.ValueNull
|
|
value _ (Full.ValueEnum e) = pure $ Core.ValueEnum e
|
|
value subs (Full.ValueList l) =
|
|
Core.ValueList <$> traverse (value subs) l
|
|
value subs (Full.ValueObject o) =
|
|
Core.ValueObject <$> traverse (objectField subs) o
|
|
|
|
objectField :: Schema.Subs -> Full.ObjectField -> Maybe Core.ObjectField
|
|
objectField subs (Full.ObjectField n v) = Core.ObjectField n <$> value subs v
|
|
|
|
appendSelection ::
|
|
Schema.Subs ->
|
|
Fragmenter ->
|
|
NonEmpty Full.Selection ->
|
|
NonEmpty Core.Selection
|
|
appendSelection subs fr = NonEmpty.fromList
|
|
. foldr (either (++) (:) . selection subs fr) []
|