graphql/src/Language/GraphQL/AST/Transform.hs
Eugen Wissner 73fc334bf8 Move related modules to Language.GraphQL.AST
Fixes #18.

- `Language.GraphQL.Encoder` moved to `Language.GraphQL.AST.Encoder`.
- `Language.GraphQL.Parser` moved to `Language.GraphQL.AST.Parser`.
- `Language.GraphQL.Lexer` moved to `Language.GraphQL.AST.Lexer`.
- All `Language.GraphQL.AST.Value` data constructor prefixes were removed. The
  module should be imported qualified.
- All `Language.GraphQL.AST.Core.Value` data constructor prefixes were removed.
  The module should be imported qualified.
- `Language.GraphQL.AST.Transform` is now isn't exposed publically anymore.
2019-11-03 11:00:18 +01:00

155 lines
5.8 KiB
Haskell

{-# LANGUAGE TupleSections #-}
-- | 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 Data.Foldable (fold)
import Data.HashMap.Strict (HashMap)
import qualified Data.HashMap.Strict as HashMap
import Data.List.NonEmpty (NonEmpty)
import qualified Data.List.NonEmpty as NonEmpty
import qualified Language.GraphQL.AST as Full
import qualified Language.GraphQL.AST.Core as Core
import qualified Language.GraphQL.Schema as Schema
-- | Associates a fragment name with a list of 'Core.Field's.
type Fragments = HashMap Core.Name (NonEmpty 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 =
case fragments of
Just fragments' -> operations subs fragments' operations'
Nothing -> Nothing
where
(fragments, operations') = foldr (defrag subs) (Just HashMap.empty, [])
$ NonEmpty.toList doc
-- * Operation
-- TODO: Replace Maybe by MonadThrow CustomError
operations ::
Schema.Subs ->
Fragments ->
[Full.OperationDefinition] ->
Maybe Core.Document
operations subs fragments operations' = do
coreOperations <- traverse (operation subs fragments) operations'
NonEmpty.nonEmpty coreOperations
operation ::
Schema.Subs ->
Fragments ->
Full.OperationDefinition ->
Maybe Core.Operation
operation subs fragments (Full.OperationSelectionSet sels) =
operation subs fragments $ Full.OperationDefinition Full.Query mempty mempty mempty sels
-- TODO: Validate Variable definitions with substituter
operation subs fragments (Full.OperationDefinition Full.Query name _vars _dirs sels) =
Core.Query name <$> appendSelection subs fragments sels
operation subs fragments (Full.OperationDefinition Full.Mutation name _vars _dirs sels) =
Core.Mutation name <$> appendSelection subs fragments sels
selection ::
Schema.Subs ->
Fragments ->
Full.Selection ->
Maybe (Either (NonEmpty Core.Selection) Core.Selection)
selection subs fragments (Full.SelectionField fld)
= Right . Core.SelectionField <$> field subs fragments fld
selection _ fragments (Full.SelectionFragmentSpread (Full.FragmentSpread name _))
= Left . fmap Core.SelectionField <$> HashMap.lookup name fragments
selection subs fragments (Full.SelectionInlineFragment fragment)
| (Full.InlineFragment (Just typeCondition) _ selectionSet) <- fragment
= Right
. Core.SelectionFragment
. Core.Fragment typeCondition
<$> appendSelection subs fragments selectionSet
| (Full.InlineFragment Nothing _ selectionSet) <- fragment
= Left <$> appendSelection subs fragments selectionSet
-- * Fragment replacement
-- | Extract fragments into a single 'HashMap' and operation definitions.
defrag ::
Schema.Subs ->
Full.Definition ->
(Maybe Fragments, [Full.OperationDefinition]) ->
(Maybe Fragments, [Full.OperationDefinition])
defrag _ (Full.DefinitionOperation op) (fragments, operations') =
(fragments, op : operations')
defrag subs (Full.DefinitionFragment fragDef) (Just fragments, operations') =
(fragmentDefinition subs fragments fragDef, operations')
defrag _ _ (Nothing, operations') =
(Nothing, operations')
fragmentDefinition ::
Schema.Subs ->
Fragments ->
Full.FragmentDefinition ->
Maybe Fragments
fragmentDefinition subs fragments (Full.FragmentDefinition name _tc _dirs sels) = do
emitted <- emitValue
newValue <- traverse extractField emitted
Just $ HashMap.insert name newValue fragments
where
emitValue :: Maybe (NonEmpty Core.Selection)
emitValue = do
selections <- traverse (selection subs fragments) sels
pure $ selections >>= either id pure
extractField :: Core.Selection -> Maybe Core.Field
extractField (Core.SelectionField field') = Just field'
extractField _ = Nothing -- Fragments within fragments are not supported yet
field :: Schema.Subs -> Fragments -> Full.Field -> Maybe Core.Field
field subs fragments (Full.Field a n args _dirs sels) =
Core.Field a n (fold $ argument subs `traverse` args)
<$> appendSelectionOpt subs fragments sels
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.Variable n) = subs n
value _ (Full.Int i) = pure $ Core.Int i
value _ (Full.Float f) = pure $ Core.Float f
value _ (Full.String x) = pure $ Core.String x
value _ (Full.Boolean b) = pure $ Core.Boolean b
value _ Full.Null = pure Core.Null
value _ (Full.Enum e) = pure $ Core.Enum e
value subs (Full.List l) =
Core.List <$> traverse (value subs) l
value subs (Full.Object o) =
Core.Object . HashMap.fromList <$> traverse (objectField subs) o
objectField :: Schema.Subs -> Full.ObjectField -> Maybe (Core.Name, Core.Value)
objectField subs (Full.ObjectField n v) = (n,) <$> value subs v
appendSelectionOpt ::
Traversable t =>
Schema.Subs ->
Fragments ->
t Full.Selection ->
Maybe [Core.Selection]
appendSelectionOpt subs fragments = foldr go (Just [])
where
go :: Full.Selection -> Maybe [Core.Selection] -> Maybe [Core.Selection]
go _ Nothing = Nothing
go sel (Just acc) = append acc <$> selection subs fragments sel
append acc (Left list) = NonEmpty.toList list <> acc
append acc (Right one) = one : acc
appendSelection ::
Schema.Subs ->
Fragments ->
NonEmpty Full.Selection ->
Maybe (NonEmpty Core.Selection)
appendSelection subs fragments fullSelection = do
coreSelection <-appendSelectionOpt subs fragments fullSelection
NonEmpty.nonEmpty coreSelection