aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/Language/GraphQL.hs2
-rw-r--r--src/Language/GraphQL/AST.hs110
-rw-r--r--src/Language/GraphQL/AST/Core.hs104
-rw-r--r--src/Language/GraphQL/AST/Encoder.hs (renamed from src/Language/GraphQL/Encoder.hs)140
-rw-r--r--src/Language/GraphQL/AST/Lexer.hs (renamed from src/Language/GraphQL/Lexer.hs)10
-rw-r--r--src/Language/GraphQL/AST/Parser.hs (renamed from src/Language/GraphQL/Parser.hs)30
-rw-r--r--src/Language/GraphQL/AST/Transform.hs220
-rw-r--r--src/Language/GraphQL/Execute.hs5
-rw-r--r--src/Language/GraphQL/Schema.hs62
-rw-r--r--src/Language/GraphQL/Trans.hs16
-rw-r--r--src/Language/GraphQL/Type.hs6
11 files changed, 331 insertions, 374 deletions
diff --git a/src/Language/GraphQL.hs b/src/Language/GraphQL.hs
index c33eb95..afce8aa 100644
--- a/src/Language/GraphQL.hs
+++ b/src/Language/GraphQL.hs
@@ -10,7 +10,7 @@ import Data.List.NonEmpty (NonEmpty)
import qualified Data.Text as T
import Language.GraphQL.Error
import Language.GraphQL.Execute
-import Language.GraphQL.Parser
+import Language.GraphQL.AST.Parser
import qualified Language.GraphQL.Schema as Schema
import Text.Megaparsec (parse)
diff --git a/src/Language/GraphQL/AST.hs b/src/Language/GraphQL/AST.hs
index 6794ae3..44bf969 100644
--- a/src/Language/GraphQL/AST.hs
+++ b/src/Language/GraphQL/AST.hs
@@ -5,14 +5,11 @@
module Language.GraphQL.AST
( Alias
, Argument(..)
- , Arguments
, Definition(..)
, Directive(..)
- , Directives
, Document
, Field(..)
, FragmentDefinition(..)
- , FragmentName
, FragmentSpread(..)
, InlineFragment(..)
, Name
@@ -27,22 +24,23 @@ module Language.GraphQL.AST
, TypeCondition
, Value(..)
, VariableDefinition(..)
- , VariableDefinitions
) where
import Data.Int (Int32)
import Data.List.NonEmpty (NonEmpty)
import Data.Text (Text)
-import Language.GraphQL.AST.Core ( Alias
- , Name
- , TypeCondition
- )
-- * Document
-- | GraphQL document.
type Document = NonEmpty Definition
+-- | Name
+type Name = Text
+
+-- | Directive.
+data Directive = Directive Name [Argument] deriving (Eq, Show)
+
-- * Operations
-- | Top-level definition of a document, either an operation or a fragment.
@@ -68,7 +66,7 @@ data OperationType = Query | Mutation deriving (Eq, Show)
-- * Selections
--- | "Top-level" selection, selection on a operation.
+-- | "Top-level" selection, selection on an operation or fragment.
type SelectionSet = NonEmpty Selection
-- | Field selection.
@@ -83,18 +81,56 @@ data Selection
-- * Field
--- | GraphQL field.
+-- | Single GraphQL field.
+--
+-- The only required property of a field is its name. Optionally it can also
+-- have an alias, arguments or a list of subfields.
+--
+-- Given the following query:
+--
+-- @
+-- {
+-- zuck: user(id: 4) {
+-- id
+-- name
+-- }
+-- }
+-- @
+--
+-- * "user", "id" and "name" are field names.
+-- * "user" has two subfields, "id" and "name".
+-- * "zuck" is an alias for "user". "id" and "name" have no aliases.
+-- * "id: 4" is an argument for "user". "id" and "name" don't have any
+-- arguments.
data Field
= Field (Maybe Alias) Name [Argument] [Directive] SelectionSetOpt
deriving (Eq, Show)
--- * Arguments
-
--- | Argument list.
-{-# DEPRECATED Arguments "Use [Argument] instead" #-}
-type Arguments = [Argument]
+-- | Alternative field name.
+--
+-- @
+-- {
+-- smallPic: profilePic(size: 64)
+-- bigPic: profilePic(size: 1024)
+-- }
+-- @
+--
+-- Here "smallPic" and "bigPic" are aliases for the same field, "profilePic",
+-- used to distinquish between profile pictures with different arguments
+-- (sizes).
+type Alias = Name
--- | Argument.
+-- | Single argument.
+--
+-- @
+-- {
+-- user(id: 4) {
+-- name
+-- }
+-- }
+-- @
+--
+-- Here "id" is an argument for the field "user" and its value is 4.
data Argument = Argument Name Value deriving (Eq,Show)
-- * Fragments
@@ -111,21 +147,18 @@ data FragmentDefinition
= FragmentDefinition Name TypeCondition [Directive] SelectionSet
deriving (Eq, Show)
-{-# DEPRECATED FragmentName "Use Name instead" #-}
-type FragmentName = Name
-
--- * Input values
+-- * Inputs
-- | Input value.
-data Value = ValueVariable Name
- | ValueInt Int32
- | ValueFloat Double
- | ValueString Text
- | ValueBoolean Bool
- | ValueNull
- | ValueEnum Name
- | ValueList [Value]
- | ValueObject [ObjectField]
+data Value = Variable Name
+ | Int Int32
+ | Float Double
+ | String Text
+ | Boolean Bool
+ | Null
+ | Enum Name
+ | List [Value]
+ | Object [ObjectField]
deriving (Eq, Show)
-- | Key-value pair.
@@ -133,17 +166,12 @@ data Value = ValueVariable Name
-- A list of 'ObjectField's represents a GraphQL object type.
data ObjectField = ObjectField Name Value deriving (Eq, Show)
--- * Variables
-
--- | Variable definition list.
-{-# DEPRECATED VariableDefinitions "Use [VariableDefinition] instead" #-}
-type VariableDefinitions = [VariableDefinition]
-
-- | Variable definition.
data VariableDefinition = VariableDefinition Name Type (Maybe Value)
deriving (Eq, Show)
--- * Input types
+-- | Type condition.
+type TypeCondition = Name
-- | Type representation.
data Type = TypeNamed Name
@@ -151,17 +179,7 @@ data Type = TypeNamed Name
| TypeNonNull NonNullType
deriving (Eq, Show)
-
-- | Helper type to represent Non-Null types and lists of such types.
data NonNullType = NonNullTypeNamed Name
| NonNullTypeList Type
deriving (Eq, Show)
-
--- * Directives
-
--- | Directive list.
-{-# DEPRECATED Directives "Use [Directive] instead" #-}
-type Directives = [Directive]
-
--- | Directive.
-data Directive = Directive Name [Argument] deriving (Eq, Show)
diff --git a/src/Language/GraphQL/AST/Core.hs b/src/Language/GraphQL/AST/Core.hs
index a2a53be..f7a008f 100644
--- a/src/Language/GraphQL/AST/Core.hs
+++ b/src/Language/GraphQL/AST/Core.hs
@@ -6,7 +6,6 @@ module Language.GraphQL.AST.Core
, Field(..)
, Fragment(..)
, Name
- , ObjectField(..)
, Operation(..)
, Selection(..)
, TypeCondition
@@ -14,12 +13,12 @@ module Language.GraphQL.AST.Core
) where
import Data.Int (Int32)
+import Data.HashMap.Strict (HashMap)
import Data.List.NonEmpty (NonEmpty)
-import Data.String
+import Data.Sequence (Seq)
+import Data.String (IsString(..))
import Data.Text (Text)
-
--- | Name
-type Name = Text
+import Language.GraphQL.AST (Alias, Name, TypeCondition)
-- | GraphQL document is a non-empty list of operations.
type Document = NonEmpty Operation
@@ -28,87 +27,21 @@ type Document = NonEmpty Operation
--
-- Currently only queries and mutations are supported.
data Operation
- = Query (Maybe Text) (NonEmpty Selection)
- | Mutation (Maybe Text) (NonEmpty Selection)
+ = Query (Maybe Text) (Seq Selection)
+ | Mutation (Maybe Text) (Seq Selection)
deriving (Eq, Show)
--- | A single GraphQL field.
---
--- Only required property of a field, is its name. Optionally it can also have
--- an alias, arguments or a list of subfields.
---
--- Given the following query:
---
--- @
--- {
--- zuck: user(id: 4) {
--- id
--- name
--- }
--- }
--- @
---
--- * "user", "id" and "name" are field names.
--- * "user" has two subfields, "id" and "name".
--- * "zuck" is an alias for "user". "id" and "name" have no aliases.
--- * "id: 4" is an argument for "name". "id" and "name don't have any
--- arguments.
-data Field = Field (Maybe Alias) Name [Argument] [Selection] deriving (Eq, Show)
-
--- | Alternative field name.
---
--- @
--- {
--- smallPic: profilePic(size: 64)
--- bigPic: profilePic(size: 1024)
--- }
--- @
---
--- Here "smallPic" and "bigPic" are aliases for the same field, "profilePic",
--- used to distinquish between profile pictures with different arguments
--- (sizes).
-type Alias = Name
+-- | Single GraphQL field.
+data Field
+ = Field (Maybe Alias) Name [Argument] (Seq Selection)
+ deriving (Eq, Show)
-- | Single argument.
---
--- @
--- {
--- user(id: 4) {
--- name
--- }
--- }
--- @
---
--- Here "id" is an argument for the field "user" and its value is 4.
data Argument = Argument Name Value deriving (Eq, Show)
--- | Represents accordingly typed GraphQL values.
-data Value
- = ValueInt Int32
- -- GraphQL Float is double precision
- | ValueFloat Double
- | ValueString Text
- | ValueBoolean Bool
- | ValueNull
- | ValueEnum Name
- | ValueList [Value]
- | ValueObject [ObjectField]
- deriving (Eq, Show)
-
-instance IsString Value where
- fromString = ValueString . fromString
-
--- | Key-value pair.
---
--- A list of 'ObjectField's represents a GraphQL object type.
-data ObjectField = ObjectField Name Value deriving (Eq, Show)
-
--- | Type condition.
-type TypeCondition = Name
-
-- | Represents fragments and inline fragments.
data Fragment
- = Fragment TypeCondition (NonEmpty Selection)
+ = Fragment TypeCondition (Seq Selection)
deriving (Eq, Show)
-- | Single selection element.
@@ -116,3 +49,18 @@ data Selection
= SelectionFragment Fragment
| SelectionField Field
deriving (Eq, Show)
+
+-- | Represents accordingly typed GraphQL values.
+data Value
+ = Int Int32
+ | Float Double -- ^ GraphQL Float is double precision
+ | String Text
+ | Boolean Bool
+ | Null
+ | Enum Name
+ | List [Value]
+ | Object (HashMap Name Value)
+ deriving (Eq, Show)
+
+instance IsString Value where
+ fromString = String . fromString
diff --git a/src/Language/GraphQL/Encoder.hs b/src/Language/GraphQL/AST/Encoder.hs
index b3ec655..afc425f 100644
--- a/src/Language/GraphQL/Encoder.hs
+++ b/src/Language/GraphQL/AST/Encoder.hs
@@ -2,7 +2,7 @@
{-# LANGUAGE ExplicitForAll #-}
-- | This module defines a minifier and a printer for the @GraphQL@ language.
-module Language.GraphQL.Encoder
+module Language.GraphQL.AST.Encoder
( Formatter
, definition
, directive
@@ -21,12 +21,12 @@ import qualified Data.Text.Lazy as Text.Lazy
import Data.Text.Lazy.Builder (toLazyText)
import Data.Text.Lazy.Builder.Int (decimal)
import Data.Text.Lazy.Builder.RealFloat (realFloat)
-import Language.GraphQL.AST
+import qualified Language.GraphQL.AST as Full
--- | Instructs the encoder whether a GraphQL should be minified or pretty
--- printed.
---
--- Use 'pretty' and 'minified' to construct the formatter.
+-- | Instructs the encoder whether the GraphQL document should be minified or
+-- pretty printed.
+--
+-- Use 'pretty' or 'minified' to construct the formatter.
data Formatter
= Minified
| Pretty Word
@@ -39,38 +39,38 @@ pretty = Pretty 0
minified :: Formatter
minified = Minified
--- | Converts a 'Document' into a string.
-document :: Formatter -> Document -> Text
+-- | Converts a 'Full.Document' into a string.
+document :: Formatter -> Full.Document -> Text
document formatter defs
| Pretty _ <- formatter = Text.Lazy.intercalate "\n" encodeDocument
| Minified <-formatter = Text.Lazy.snoc (mconcat encodeDocument) '\n'
where
encodeDocument = NonEmpty.toList $ definition formatter <$> defs
--- | Converts a 'Definition' into a string.
-definition :: Formatter -> Definition -> Text
+-- | Converts a 'Full.Definition' into a string.
+definition :: Formatter -> Full.Definition -> Text
definition formatter x
| Pretty _ <- formatter = Text.Lazy.snoc (encodeDefinition x) '\n'
| Minified <- formatter = encodeDefinition x
where
- encodeDefinition (DefinitionOperation operation)
+ encodeDefinition (Full.DefinitionOperation operation)
= operationDefinition formatter operation
- encodeDefinition (DefinitionFragment fragment)
+ encodeDefinition (Full.DefinitionFragment fragment)
= fragmentDefinition formatter fragment
-operationDefinition :: Formatter -> OperationDefinition -> Text
-operationDefinition formatter (OperationSelectionSet sels)
+operationDefinition :: Formatter -> Full.OperationDefinition -> Text
+operationDefinition formatter (Full.OperationSelectionSet sels)
= selectionSet formatter sels
-operationDefinition formatter (OperationDefinition Query name vars dirs sels)
+operationDefinition formatter (Full.OperationDefinition Full.Query name vars dirs sels)
= "query " <> node formatter name vars dirs sels
-operationDefinition formatter (OperationDefinition Mutation name vars dirs sels)
+operationDefinition formatter (Full.OperationDefinition Full.Mutation name vars dirs sels)
= "mutation " <> node formatter name vars dirs sels
node :: Formatter
- -> Maybe Name
- -> [VariableDefinition]
- -> [Directive]
- -> SelectionSet
+ -> Maybe Full.Name
+ -> [Full.VariableDefinition]
+ -> [Full.Directive]
+ -> Full.SelectionSet
-> Text
node formatter name vars dirs sels
= Text.Lazy.fromStrict (fold name)
@@ -79,39 +79,39 @@ node formatter name vars dirs sels
<> eitherFormat formatter " " mempty
<> selectionSet formatter sels
-variableDefinitions :: Formatter -> [VariableDefinition] -> Text
+variableDefinitions :: Formatter -> [Full.VariableDefinition] -> Text
variableDefinitions formatter
= parensCommas formatter $ variableDefinition formatter
-variableDefinition :: Formatter -> VariableDefinition -> Text
-variableDefinition formatter (VariableDefinition var ty dv)
+variableDefinition :: Formatter -> Full.VariableDefinition -> Text
+variableDefinition formatter (Full.VariableDefinition var ty dv)
= variable var
<> eitherFormat formatter ": " ":"
<> type' ty
<> maybe mempty (defaultValue formatter) dv
-defaultValue :: Formatter -> Value -> Text
+defaultValue :: Formatter -> Full.Value -> Text
defaultValue formatter val
= eitherFormat formatter " = " "="
<> value formatter val
-variable :: Name -> Text
+variable :: Full.Name -> Text
variable var = "$" <> Text.Lazy.fromStrict var
-selectionSet :: Formatter -> SelectionSet -> Text
+selectionSet :: Formatter -> Full.SelectionSet -> Text
selectionSet formatter
= bracesList formatter (selection formatter)
. NonEmpty.toList
-selectionSetOpt :: Formatter -> SelectionSetOpt -> Text
+selectionSetOpt :: Formatter -> Full.SelectionSetOpt -> Text
selectionSetOpt formatter = bracesList formatter $ selection formatter
-selection :: Formatter -> Selection -> Text
+selection :: Formatter -> Full.Selection -> Text
selection formatter = Text.Lazy.append indent . f
where
- f (SelectionField x) = field incrementIndent x
- f (SelectionInlineFragment x) = inlineFragment incrementIndent x
- f (SelectionFragmentSpread x) = fragmentSpread incrementIndent x
+ f (Full.SelectionField x) = field incrementIndent x
+ f (Full.SelectionInlineFragment x) = inlineFragment incrementIndent x
+ f (Full.SelectionFragmentSpread x) = fragmentSpread incrementIndent x
incrementIndent
| Pretty n <- formatter = Pretty $ n + 1
| otherwise = Minified
@@ -119,8 +119,8 @@ selection formatter = Text.Lazy.append indent . f
| Pretty n <- formatter = Text.Lazy.replicate (fromIntegral $ n + 1) " "
| otherwise = mempty
-field :: Formatter -> Field -> Text
-field formatter (Field alias name args dirs selso)
+field :: Formatter -> Full.Field -> Text
+field formatter (Full.Field alias name args dirs selso)
= optempty (`Text.Lazy.append` colon) (Text.Lazy.fromStrict $ fold alias)
<> Text.Lazy.fromStrict name
<> optempty (arguments formatter) args
@@ -132,31 +132,31 @@ field formatter (Field alias name args dirs selso)
| null selso = mempty
| otherwise = eitherFormat formatter " " mempty <> selectionSetOpt formatter selso
-arguments :: Formatter -> [Argument] -> Text
+arguments :: Formatter -> [Full.Argument] -> Text
arguments formatter = parensCommas formatter $ argument formatter
-argument :: Formatter -> Argument -> Text
-argument formatter (Argument name v)
+argument :: Formatter -> Full.Argument -> Text
+argument formatter (Full.Argument name v)
= Text.Lazy.fromStrict name
<> eitherFormat formatter ": " ":"
<> value formatter v
-- * Fragments
-fragmentSpread :: Formatter -> FragmentSpread -> Text
-fragmentSpread formatter (FragmentSpread name ds)
+fragmentSpread :: Formatter -> Full.FragmentSpread -> Text
+fragmentSpread formatter (Full.FragmentSpread name ds)
= "..." <> Text.Lazy.fromStrict name <> optempty (directives formatter) ds
-inlineFragment :: Formatter -> InlineFragment -> Text
-inlineFragment formatter (InlineFragment tc dirs sels)
+inlineFragment :: Formatter -> Full.InlineFragment -> Text
+inlineFragment formatter (Full.InlineFragment tc dirs sels)
= "... on "
<> Text.Lazy.fromStrict (fold tc)
<> directives formatter dirs
<> eitherFormat formatter " " mempty
<> selectionSet formatter sels
-fragmentDefinition :: Formatter -> FragmentDefinition -> Text
-fragmentDefinition formatter (FragmentDefinition name tc dirs sels)
+fragmentDefinition :: Formatter -> Full.FragmentDefinition -> Text
+fragmentDefinition formatter (Full.FragmentDefinition name tc dirs sels)
= "fragment " <> Text.Lazy.fromStrict name
<> " on " <> Text.Lazy.fromStrict tc
<> optempty (directives formatter) dirs
@@ -165,26 +165,26 @@ fragmentDefinition formatter (FragmentDefinition name tc dirs sels)
-- * Miscellaneous
--- | Converts a 'Directive' into a string.
-directive :: Formatter -> Directive -> Text
-directive formatter (Directive name args)
+-- | Converts a 'Full.Directive' into a string.
+directive :: Formatter -> Full.Directive -> Text
+directive formatter (Full.Directive name args)
= "@" <> Text.Lazy.fromStrict name <> optempty (arguments formatter) args
-directives :: Formatter -> [Directive] -> Text
+directives :: Formatter -> [Full.Directive] -> Text
directives formatter@(Pretty _) = Text.Lazy.cons ' ' . spaces (directive formatter)
directives Minified = spaces (directive Minified)
--- | Converts a 'Value' into a string.
-value :: Formatter -> Value -> Text
-value _ (ValueVariable x) = variable x
-value _ (ValueInt x) = toLazyText $ decimal x
-value _ (ValueFloat x) = toLazyText $ realFloat x
-value _ (ValueBoolean x) = booleanValue x
-value _ ValueNull = mempty
-value _ (ValueString x) = stringValue $ Text.Lazy.fromStrict x
-value _ (ValueEnum x) = Text.Lazy.fromStrict x
-value formatter (ValueList x) = listValue formatter x
-value formatter (ValueObject x) = objectValue formatter x
+-- | Converts a 'Full.Value' into a string.
+value :: Formatter -> Full.Value -> Text
+value _ (Full.Variable x) = variable x
+value _ (Full.Int x) = toLazyText $ decimal x
+value _ (Full.Float x) = toLazyText $ realFloat x
+value _ (Full.Boolean x) = booleanValue x
+value _ Full.Null = mempty
+value _ (Full.String x) = stringValue $ Text.Lazy.fromStrict x
+value _ (Full.Enum x) = Text.Lazy.fromStrict x
+value formatter (Full.List x) = listValue formatter x
+value formatter (Full.Object x) = objectValue formatter x
booleanValue :: Bool -> Text
booleanValue True = "true"
@@ -196,10 +196,10 @@ stringValue
. Text.Lazy.replace "\"" "\\\""
. Text.Lazy.replace "\\" "\\\\"
-listValue :: Formatter -> [Value] -> Text
+listValue :: Formatter -> [Full.Value] -> Text
listValue formatter = bracketsCommas formatter $ value formatter
-objectValue :: Formatter -> [ObjectField] -> Text
+objectValue :: Formatter -> [Full.ObjectField] -> Text
objectValue formatter = intercalate $ objectField formatter
where
intercalate f
@@ -208,26 +208,26 @@ objectValue formatter = intercalate $ objectField formatter
. fmap f
-objectField :: Formatter -> ObjectField -> Text
-objectField formatter (ObjectField name v)
+objectField :: Formatter -> Full.ObjectField -> Text
+objectField formatter (Full.ObjectField name v)
= Text.Lazy.fromStrict name <> colon <> value formatter v
where
colon
| Pretty _ <- formatter = ": "
| Minified <- formatter = ":"
--- | Converts a 'Type' a type into a string.
-type' :: Type -> Text
-type' (TypeNamed x) = Text.Lazy.fromStrict x
-type' (TypeList x) = listType x
-type' (TypeNonNull x) = nonNullType x
+-- | Converts a 'Full.Type' a type into a string.
+type' :: Full.Type -> Text
+type' (Full.TypeNamed x) = Text.Lazy.fromStrict x
+type' (Full.TypeList x) = listType x
+type' (Full.TypeNonNull x) = nonNullType x
-listType :: Type -> Text
+listType :: Full.Type -> Text
listType x = brackets (type' x)
-nonNullType :: NonNullType -> Text
-nonNullType (NonNullTypeNamed x) = Text.Lazy.fromStrict x <> "!"
-nonNullType (NonNullTypeList x) = listType x <> "!"
+nonNullType :: Full.NonNullType -> Text
+nonNullType (Full.NonNullTypeNamed x) = Text.Lazy.fromStrict x <> "!"
+nonNullType (Full.NonNullTypeList x) = listType x <> "!"
-- * Internal
diff --git a/src/Language/GraphQL/Lexer.hs b/src/Language/GraphQL/AST/Lexer.hs
index dc000b5..e4d64ca 100644
--- a/src/Language/GraphQL/Lexer.hs
+++ b/src/Language/GraphQL/AST/Lexer.hs
@@ -3,7 +3,7 @@
-- | This module defines a bunch of small parsers used to parse individual
-- lexemes.
-module Language.GraphQL.Lexer
+module Language.GraphQL.AST.Lexer
( Parser
, amp
, at
@@ -89,12 +89,12 @@ symbol :: T.Text -> Parser T.Text
symbol = Lexer.symbol spaceConsumer
-- | Parser for "!".
-bang :: Parser Char
-bang = char '!'
+bang :: Parser T.Text
+bang = symbol "!"
-- | Parser for "$".
-dollar :: Parser Char
-dollar = char '$'
+dollar :: Parser T.Text
+dollar = symbol "$"
-- | Parser for "@".
at :: Parser Char
diff --git a/src/Language/GraphQL/Parser.hs b/src/Language/GraphQL/AST/Parser.hs
index bbe1de7..1505615 100644
--- a/src/Language/GraphQL/Parser.hs
+++ b/src/Language/GraphQL/AST/Parser.hs
@@ -2,7 +2,7 @@
{-# LANGUAGE OverloadedStrings #-}
-- | @GraphQL@ document parser.
-module Language.GraphQL.Parser
+module Language.GraphQL.AST.Parser
( document
) where
@@ -11,7 +11,7 @@ import Control.Applicative ( Alternative(..)
)
import Data.List.NonEmpty (NonEmpty(..))
import Language.GraphQL.AST
-import Language.GraphQL.Lexer
+import Language.GraphQL.AST.Lexer
import Text.Megaparsec ( lookAhead
, option
, try
@@ -105,16 +105,16 @@ typeCondition = symbol "on" *> name
-- * Input Values
value :: Parser Value
-value = ValueVariable <$> variable
- <|> ValueFloat <$> try float
- <|> ValueInt <$> integer
- <|> ValueBoolean <$> booleanValue
- <|> ValueNull <$ symbol "null"
- <|> ValueString <$> blockString
- <|> ValueString <$> string
- <|> ValueEnum <$> try enumValue
- <|> ValueList <$> listValue
- <|> ValueObject <$> objectValue
+value = Variable <$> variable
+ <|> Float <$> try float
+ <|> Int <$> integer
+ <|> Boolean <$> booleanValue
+ <|> Null <$ symbol "null"
+ <|> String <$> blockString
+ <|> String <$> string
+ <|> Enum <$> try enumValue
+ <|> List <$> listValue
+ <|> Object <$> objectValue
<?> "value error!"
where
booleanValue :: Parser Bool
@@ -152,9 +152,9 @@ defaultValue = equals *> value
-- * Input Types
type_ :: Parser Type
-type_ = try (TypeNamed <$> name <* but "!")
- <|> TypeList <$> brackets type_
- <|> TypeNonNull <$> nonNullType
+type_ = try (TypeNonNull <$> nonNullType)
+ <|> TypeList <$> brackets type_
+ <|> TypeNamed <$> name
<?> "type_ error!"
nonNullType :: Parser NonNullType
diff --git a/src/Language/GraphQL/AST/Transform.hs b/src/Language/GraphQL/AST/Transform.hs
index 3aa31b0..95cdfbb 100644
--- a/src/Language/GraphQL/AST/Transform.hs
+++ b/src/Language/GraphQL/AST/Transform.hs
@@ -1,4 +1,5 @@
-{-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE TupleSections #-}
+{-# LANGUAGE ExplicitForAll #-}
-- | After the document is parsed, before getting executed the AST is
-- transformed into a similar, simpler AST. This module is responsible for
@@ -7,130 +8,143 @@ 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 Control.Arrow (first)
+import Control.Monad (foldM, unless)
+import Control.Monad.Trans.Class (lift)
+import Control.Monad.Trans.Reader (ReaderT, ask, runReaderT)
+import Control.Monad.Trans.State (StateT, evalStateT, gets, modify)
+import Data.HashMap.Strict (HashMap)
+import qualified Data.HashMap.Strict as HashMap
import qualified Data.List.NonEmpty as NonEmpty
-import Data.Monoid (Alt(Alt,getAlt), (<>))
+import Data.Sequence (Seq, (<|), (><))
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]
+-- | Associates a fragment name with a list of 'Core.Field's.
+data Replacement = Replacement
+ { fragments :: HashMap Core.Name (Seq Core.Selection)
+ , fragmentDefinitions :: HashMap Full.Name Full.FragmentDefinition
+ }
+
+type TransformT a = StateT Replacement (ReaderT Schema.Subs Maybe) a
-- | 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
+document subs document' =
+ flip runReaderT subs
+ $ evalStateT (collectFragments >> operations operationDefinitions)
+ $ Replacement HashMap.empty fragmentTable
where
- (fr, ops) = first foldFrags
- . partitionEithers
- . NonEmpty.toList
- $ defrag subs
- <$> doc
-
- foldFrags :: [Fragmenter] -> Fragmenter
- foldFrags fs n = getAlt $ foldMap (Alt . ($ n)) fs
+ (fragmentTable, operationDefinitions) = foldr defragment mempty document'
+ defragment (Full.DefinitionOperation definition) acc =
+ (definition :) <$> acc
+ defragment (Full.DefinitionFragment definition) acc =
+ let (Full.FragmentDefinition name _ _ _) = definition
+ in first (HashMap.insert name definition) acc
-- * 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
+operations :: [Full.OperationDefinition] -> TransformT Core.Document
+operations operations' = do
+ coreOperations <- traverse operation operations'
+ lift . lift $ NonEmpty.nonEmpty coreOperations
+
+operation :: Full.OperationDefinition -> TransformT Core.Operation
+operation (Full.OperationSelectionSet sels) =
+ operation $ Full.OperationDefinition Full.Query mempty mempty mempty 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)
+operation (Full.OperationDefinition Full.Query name _vars _dirs sels) =
+ Core.Query name <$> appendSelection sels
+operation (Full.OperationDefinition Full.Mutation name _vars _dirs sels) =
+ Core.Mutation name <$> appendSelection sels
+
+selection ::
+ Full.Selection ->
+ TransformT (Either (Seq Core.Selection) Core.Selection)
+selection (Full.SelectionField fld) = Right . Core.SelectionField <$> field fld
+selection (Full.SelectionFragmentSpread (Full.FragmentSpread name _)) = do
+ fragments' <- gets fragments
+ Left <$> maybe lookupDefinition liftJust (HashMap.lookup name fragments')
+ where
+ lookupDefinition :: TransformT (Seq Core.Selection)
+ lookupDefinition = do
+ fragmentDefinitions' <- gets fragmentDefinitions
+ found <- lift . lift $ HashMap.lookup name fragmentDefinitions'
+ fragmentDefinition found
+selection (Full.SelectionInlineFragment fragment)
| (Full.InlineFragment (Just typeCondition) _ selectionSet) <- fragment
= Right
- $ Core.SelectionFragment
- $ Core.Fragment typeCondition
- $ appendSelection subs fr selectionSet
+ . Core.SelectionFragment
+ . Core.Fragment typeCondition
+ <$> appendSelection selectionSet
| (Full.InlineFragment Nothing _ selectionSet) <- fragment
- = Left $ NonEmpty.toList $ appendSelection subs fr selectionSet
+ = Left <$> appendSelection 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
+-- | Extract fragment definitions into a single 'HashMap'.
+collectFragments :: TransformT ()
+collectFragments = do
+ fragDefs <- gets fragmentDefinitions
+ let nextValue = head $ HashMap.elems fragDefs
+ unless (HashMap.null fragDefs) $ do
+ _ <- fragmentDefinition nextValue
+ collectFragments
+
+fragmentDefinition ::
+ Full.FragmentDefinition ->
+ TransformT (Seq Core.Selection)
+fragmentDefinition (Full.FragmentDefinition name _tc _dirs selections) = do
+ modify deleteFragmentDefinition
+ newValue <- appendSelection selections
+ modify $ insertFragment newValue
+ liftJust newValue
where
- selection' (Core.SelectionField field') = field'
- selection' _ = error "Fragments within fragments are not supported yet"
+ deleteFragmentDefinition (Replacement fragments' fragmentDefinitions') =
+ Replacement fragments' $ HashMap.delete name fragmentDefinitions'
+ insertFragment newValue (Replacement fragments' fragmentDefinitions') =
+ let newFragments = HashMap.insert name newValue fragments'
+ in Replacement newFragments fragmentDefinitions'
+
+field :: Full.Field -> TransformT Core.Field
+field (Full.Field a n args _dirs sels) = do
+ arguments <- traverse argument args
+ selection' <- appendSelection sels
+ return $ Core.Field a n arguments selection'
+
+argument :: Full.Argument -> TransformT Core.Argument
+argument (Full.Argument n v) = Core.Argument n <$> value v
+
+value :: Full.Value -> TransformT Core.Value
+value (Full.Variable n) = do
+ substitute' <- lift ask
+ lift . lift $ substitute' 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 (Full.List l) =
+ Core.List <$> traverse value l
+value (Full.Object o) =
+ Core.Object . HashMap.fromList <$> traverse objectField o
+
+objectField :: Full.ObjectField -> TransformT (Core.Name, Core.Value)
+objectField (Full.ObjectField n v) = (n,) <$> value v
-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)
+appendSelection ::
+ Traversable t =>
+ t Full.Selection ->
+ TransformT (Seq Core.Selection)
+appendSelection = foldM go mempty
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
+ go acc sel = append acc <$> selection sel
+ append acc (Left list) = list >< acc
+ append acc (Right one) = one <| acc
-appendSelection ::
- Schema.Subs ->
- Fragmenter ->
- NonEmpty Full.Selection ->
- NonEmpty Core.Selection
-appendSelection subs fr = NonEmpty.fromList
- . foldr (either (++) (:) . selection subs fr) []
+liftJust :: forall a. a -> TransformT a
+liftJust = lift . lift . Just
diff --git a/src/Language/GraphQL/Execute.hs b/src/Language/GraphQL/Execute.hs
index 9228dd5..59e85bf 100644
--- a/src/Language/GraphQL/Execute.hs
+++ b/src/Language/GraphQL/Execute.hs
@@ -8,6 +8,7 @@ module Language.GraphQL.Execute
import Control.Monad.IO.Class (MonadIO)
import qualified Data.Aeson as Aeson
+import Data.Foldable (toList)
import Data.List.NonEmpty (NonEmpty(..))
import qualified Data.List.NonEmpty as NE
import Data.Text (Text)
@@ -71,6 +72,6 @@ operation :: MonadIO m
-> AST.Core.Operation
-> m Aeson.Value
operation schema (AST.Core.Query _ flds)
- = runCollectErrs (Schema.resolve (NE.toList schema) (NE.toList flds))
+ = runCollectErrs (Schema.resolve (toList schema) flds)
operation schema (AST.Core.Mutation _ flds)
- = runCollectErrs (Schema.resolve (NE.toList schema) (NE.toList flds))
+ = runCollectErrs (Schema.resolve (toList schema) flds)
diff --git a/src/Language/GraphQL/Schema.hs b/src/Language/GraphQL/Schema.hs
index 112847f..afe068f 100644
--- a/src/Language/GraphQL/Schema.hs
+++ b/src/Language/GraphQL/Schema.hs
@@ -4,17 +4,12 @@
-- functions for defining and manipulating schemas.
module Language.GraphQL.Schema
( Resolver
- , Schema
, Subs
, object
, objectA
, scalar
, scalarA
- , enum
- , enumA
, resolve
- , wrappedEnum
- , wrappedEnumA
, wrappedObject
, wrappedObjectA
, wrappedScalar
@@ -28,23 +23,19 @@ module Language.GraphQL.Schema
import Control.Monad.IO.Class (MonadIO(..))
import Control.Monad.Trans.Class (lift)
import Control.Monad.Trans.Except (runExceptT)
+import Control.Monad.Trans.Reader (runReaderT)
import Data.Foldable (find, fold)
-import Data.List.NonEmpty (NonEmpty)
import Data.Maybe (fromMaybe)
import qualified Data.Aeson as Aeson
import Data.HashMap.Strict (HashMap)
import qualified Data.HashMap.Strict as HashMap
+import Data.Sequence (Seq)
import Data.Text (Text)
import qualified Data.Text as T
+import Language.GraphQL.AST.Core
import Language.GraphQL.Error
import Language.GraphQL.Trans
-import Language.GraphQL.Type
-import Language.GraphQL.AST.Core
-
-{-# DEPRECATED Schema "Use NonEmpty (Resolver m) instead" #-}
--- | A GraphQL schema.
--- @m@ is usually expected to be an instance of 'MonadIO'.
-type Schema m = NonEmpty (Resolver m)
+import qualified Language.GraphQL.Type as Type
-- | Resolves a 'Field' into an @Aeson.@'Data.Aeson.Types.Object' with error
-- information (if an error has occurred). @m@ is usually expected to be an
@@ -69,7 +60,7 @@ objectA name f = Resolver name $ resolveFieldValue f resolveRight
-- | Like 'object' but also taking 'Argument's and can be null or a list of objects.
wrappedObjectA :: MonadIO m
- => Name -> ([Argument] -> ActionT m (Wrapping [Resolver m])) -> Resolver m
+ => Name -> ([Argument] -> ActionT m (Type.Wrapping [Resolver m])) -> Resolver m
wrappedObjectA name f = Resolver name $ resolveFieldValue f resolveRight
where
resolveRight fld@(Field _ _ _ sels) resolver
@@ -77,7 +68,7 @@ wrappedObjectA name f = Resolver name $ resolveFieldValue f resolveRight
-- | Like 'object' but can be null or a list of objects.
wrappedObject :: MonadIO m
- => Name -> ActionT m (Wrapping [Resolver m]) -> Resolver m
+ => Name -> ActionT m (Type.Wrapping [Resolver m]) -> Resolver m
wrappedObject name = wrappedObjectA name . const
-- | A scalar represents a primitive value, like a string or an integer.
@@ -91,54 +82,31 @@ scalarA name f = Resolver name $ resolveFieldValue f resolveRight
where
resolveRight fld result = withField (return result) fld
--- | Lika 'scalar' but also taking 'Argument's and can be null or a list of scalars.
+-- | Like 'scalar' but also taking 'Argument's and can be null or a list of scalars.
wrappedScalarA :: (MonadIO m, Aeson.ToJSON a)
- => Name -> ([Argument] -> ActionT m (Wrapping a)) -> Resolver m
+ => Name -> ([Argument] -> ActionT m (Type.Wrapping a)) -> Resolver m
wrappedScalarA name f = Resolver name $ resolveFieldValue f resolveRight
where
- resolveRight fld (Named result) = withField (return result) fld
- resolveRight fld Null
+ resolveRight fld (Type.Named result) = withField (return result) fld
+ resolveRight fld Type.Null
= return $ HashMap.singleton (aliasOrName fld) Aeson.Null
- resolveRight fld (List result) = withField (return result) fld
+ resolveRight fld (Type.List result) = withField (return result) fld
-- | Like 'scalar' but can be null or a list of scalars.
wrappedScalar :: (MonadIO m, Aeson.ToJSON a)
- => Name -> ActionT m (Wrapping a) -> Resolver m
+ => Name -> ActionT m (Type.Wrapping a) -> Resolver m
wrappedScalar name = wrappedScalarA name . const
-{-# DEPRECATED enum "Use scalar instead" #-}
-enum :: MonadIO m => Name -> ActionT m [Text] -> Resolver m
-enum name = enumA name . const
-
-{-# DEPRECATED enumA "Use scalarA instead" #-}
-enumA :: MonadIO m => Name -> ([Argument] -> ActionT m [Text]) -> Resolver m
-enumA name f = Resolver name $ resolveFieldValue f resolveRight
- where
- resolveRight fld resolver = withField (return resolver) fld
-
-{-# DEPRECATED wrappedEnumA "Use wrappedScalarA instead" #-}
-wrappedEnumA :: MonadIO m
- => Name -> ([Argument] -> ActionT m (Wrapping [Text])) -> Resolver m
-wrappedEnumA name f = Resolver name $ resolveFieldValue f resolveRight
- where
- resolveRight fld (Named resolver) = withField (return resolver) fld
- resolveRight fld Null
- = return $ HashMap.singleton (aliasOrName fld) Aeson.Null
- resolveRight fld (List resolver) = withField (return resolver) fld
-
-{-# DEPRECATED wrappedEnum "Use wrappedScalar instead" #-}
-wrappedEnum :: MonadIO m => Name -> ActionT m (Wrapping [Text]) -> Resolver m
-wrappedEnum name = wrappedEnumA name . const
-
resolveFieldValue :: MonadIO m
=> ([Argument] -> ActionT m a)
-> (Field -> a -> CollectErrsT m (HashMap Text Aeson.Value))
-> Field
-> CollectErrsT m (HashMap Text Aeson.Value)
resolveFieldValue f resolveRight fld@(Field _ _ args _) = do
- result <- lift $ runExceptT . runActionT $ f args
+ result <- lift $ reader . runExceptT . runActionT $ f args
either resolveLeft (resolveRight fld) result
where
+ reader = flip runReaderT $ Context mempty
resolveLeft err = do
_ <- addErrMsg err
return $ HashMap.singleton (aliasOrName fld) Aeson.Null
@@ -153,7 +121,7 @@ withField v fld
-- 'Resolver' to each 'Field'. Resolves into a value containing the
-- resolved 'Field', or a null value and error information.
resolve :: MonadIO m
- => [Resolver m] -> [Selection] -> CollectErrsT m Aeson.Value
+ => [Resolver m] -> Seq Selection -> CollectErrsT m Aeson.Value
resolve resolvers = fmap (Aeson.toJSON . fold) . traverse tryResolvers
where
resolveTypeName (Resolver "__typename" f) = do
diff --git a/src/Language/GraphQL/Trans.hs b/src/Language/GraphQL/Trans.hs
index eb78911..4232e75 100644
--- a/src/Language/GraphQL/Trans.hs
+++ b/src/Language/GraphQL/Trans.hs
@@ -1,6 +1,7 @@
-- | Monad transformer stack used by the @GraphQL@ resolvers.
module Language.GraphQL.Trans
( ActionT(..)
+ , Context(Context)
) where
import Control.Applicative (Alternative(..))
@@ -8,10 +9,19 @@ import Control.Monad (MonadPlus(..))
import Control.Monad.IO.Class (MonadIO(..))
import Control.Monad.Trans.Class (MonadTrans(..))
import Control.Monad.Trans.Except (ExceptT)
+import Control.Monad.Trans.Reader (ReaderT)
+import Data.HashMap.Strict (HashMap)
import Data.Text (Text)
+import Language.GraphQL.AST.Core (Name, Value)
--- | Monad transformer stack used by the resolvers to provide error handling.
-newtype ActionT m a = ActionT { runActionT :: ExceptT Text m a }
+-- | Resolution context holds resolver arguments.
+newtype Context = Context (HashMap Name Value)
+
+-- | Monad transformer stack used by the resolvers to provide error handling
+-- and resolution context (resolver arguments).
+newtype ActionT m a = ActionT
+ { runActionT :: ExceptT Text (ReaderT Context m) a
+ }
instance Functor m => Functor (ActionT m) where
fmap f = ActionT . fmap f . runActionT
@@ -25,7 +35,7 @@ instance Monad m => Monad (ActionT m) where
(ActionT action) >>= f = ActionT $ action >>= runActionT . f
instance MonadTrans ActionT where
- lift = ActionT . lift
+ lift = ActionT . lift . lift
instance MonadIO m => MonadIO (ActionT m) where
liftIO = lift . liftIO
diff --git a/src/Language/GraphQL/Type.hs b/src/Language/GraphQL/Type.hs
index 3f91e50..c8a9997 100644
--- a/src/Language/GraphQL/Type.hs
+++ b/src/Language/GraphQL/Type.hs
@@ -1,11 +1,9 @@
--- | Definitions for @GraphQL@ type system.
+-- | Definitions for @GraphQL@ input types.
module Language.GraphQL.Type
( Wrapping(..)
) where
-import Data.Aeson as Aeson ( ToJSON
- , toJSON
- )
+import Data.Aeson as Aeson (ToJSON, toJSON)
import qualified Data.Aeson as Aeson
-- | GraphQL distinguishes between "wrapping" and "named" types. Each wrapping