Split AST in 2
One AST is meant to be a target parser and tries to adhere as much as possible to the spec. The other is a simplified version of that AST meant for execution. Also newtypes have been replaced by type synonyms and NonEmpty lists are being used where it makes sense.
This commit is contained in:
parent
3e991adf4e
commit
5390c4ca1e
@ -1,11 +1,13 @@
|
|||||||
-- | This module defines an abstract syntax tree for the @GraphQL@ language based on
|
-- | This module defines an abstract syntax tree for the @GraphQL@ language based on
|
||||||
-- <https://facebook.github.io/graphql/ Facebook's GraphQL Specification>.
|
-- <https://facebook.github.io/graphql/ Facebook's GraphQL Specification>.
|
||||||
|
--
|
||||||
|
-- Target AST for Parser.
|
||||||
|
|
||||||
module Data.GraphQL.AST where
|
module Data.GraphQL.AST where
|
||||||
|
|
||||||
import Data.Int (Int32)
|
import Data.Int (Int32)
|
||||||
import Data.String (IsString(fromString))
|
import Data.List.NonEmpty (NonEmpty)
|
||||||
import Data.Text (Text, pack)
|
import Data.Text (Text)
|
||||||
|
|
||||||
-- * Name
|
-- * Name
|
||||||
|
|
||||||
@ -13,116 +15,117 @@ type Name = Text
|
|||||||
|
|
||||||
-- * Document
|
-- * Document
|
||||||
|
|
||||||
newtype Document = Document [Definition] deriving (Eq,Show)
|
type Document = NonEmpty Definition
|
||||||
|
|
||||||
|
-- * Operations
|
||||||
|
|
||||||
data Definition = DefinitionOperation OperationDefinition
|
data Definition = DefinitionOperation OperationDefinition
|
||||||
| DefinitionFragment FragmentDefinition
|
| DefinitionFragment FragmentDefinition
|
||||||
deriving (Eq,Show)
|
deriving (Eq,Show)
|
||||||
|
|
||||||
data OperationDefinition = Query Node
|
data OperationDefinition = OperationSelectionSet SelectionSet
|
||||||
| Mutation Node
|
| OperationDefinition OperationType
|
||||||
|
Name
|
||||||
|
VariableDefinitions
|
||||||
|
Directives
|
||||||
|
SelectionSet
|
||||||
deriving (Eq,Show)
|
deriving (Eq,Show)
|
||||||
|
|
||||||
data Node = Node Name [VariableDefinition] [Directive] SelectionSet
|
data OperationType = Query | Mutation deriving (Eq,Show)
|
||||||
deriving (Eq,Show)
|
|
||||||
|
|
||||||
data VariableDefinition = VariableDefinition Variable Type (Maybe DefaultValue)
|
-- * SelectionSet
|
||||||
deriving (Eq,Show)
|
|
||||||
|
|
||||||
newtype Variable = Variable Name deriving (Eq,Show)
|
type SelectionSet = NonEmpty Selection
|
||||||
|
|
||||||
instance IsString Variable where
|
type SelectionSetOpt = [Selection]
|
||||||
fromString = Variable . pack
|
|
||||||
|
|
||||||
type SelectionSet = [Selection]
|
|
||||||
|
|
||||||
data Selection = SelectionField Field
|
data Selection = SelectionField Field
|
||||||
| SelectionFragmentSpread FragmentSpread
|
| SelectionFragmentSpread FragmentSpread
|
||||||
| SelectionInlineFragment InlineFragment
|
| SelectionInlineFragment InlineFragment
|
||||||
deriving (Eq,Show)
|
deriving (Eq,Show)
|
||||||
|
|
||||||
-- | A 'SelectionSet' is primarily composed of 'Field's. A 'Field' describes one
|
-- * Field
|
||||||
-- discrete piece of information available to request within a 'SelectionSet'.
|
|
||||||
--
|
data Field = Field (Maybe Alias) Name Arguments Directives SelectionSetOpt
|
||||||
-- Some 'Field's describe complex data or relationships to other data. In
|
|
||||||
-- order to further explore this data, a 'Field' may itself contain a
|
|
||||||
-- 'SelectionSet', allowing for deeply nested requests. All @GraphQL@ operations
|
|
||||||
-- must specify their 'Selection's down to 'Field's which return scalar values to
|
|
||||||
-- ensure an unambiguously shaped response.
|
|
||||||
--
|
|
||||||
-- <https://facebook.github.io/graphql/#sec-Language.Query-Document.Fields Field Specification>
|
|
||||||
data Field = Field Alias Name [Argument] [Directive] SelectionSet
|
|
||||||
deriving (Eq,Show)
|
deriving (Eq,Show)
|
||||||
|
|
||||||
type Alias = Name
|
type Alias = Name
|
||||||
|
|
||||||
-- | 'Field's are conceptually functions which return values, and occasionally accept
|
-- * Arguments
|
||||||
-- 'Argument's which alter their behavior. These 'Argument's often map directly to
|
|
||||||
-- function arguments within a @GraphQL@ server’s implementation.
|
type Arguments = [Argument]
|
||||||
--
|
|
||||||
-- <https://facebook.github.io/graphql/#sec-Language.Query-Document.Arguments Argument Specification>
|
|
||||||
data Argument = Argument Name Value deriving (Eq,Show)
|
data Argument = Argument Name Value deriving (Eq,Show)
|
||||||
|
|
||||||
-- * Fragments
|
-- * Fragments
|
||||||
|
|
||||||
data FragmentSpread = FragmentSpread Name [Directive]
|
data FragmentSpread = FragmentSpread Name Directives deriving (Eq,Show)
|
||||||
deriving (Eq,Show)
|
|
||||||
|
|
||||||
data InlineFragment =
|
data InlineFragment = InlineFragment (Maybe TypeCondition) Directives SelectionSet
|
||||||
InlineFragment TypeCondition [Directive] SelectionSet
|
|
||||||
deriving (Eq,Show)
|
deriving (Eq,Show)
|
||||||
|
|
||||||
data FragmentDefinition =
|
data FragmentDefinition =
|
||||||
FragmentDefinition Name TypeCondition [Directive] SelectionSet
|
FragmentDefinition FragmentName TypeCondition Directives SelectionSet
|
||||||
deriving (Eq,Show)
|
deriving (Eq,Show)
|
||||||
|
|
||||||
type TypeCondition = NamedType
|
type FragmentName = Name
|
||||||
|
|
||||||
-- * Values
|
type TypeCondition = Name
|
||||||
|
|
||||||
|
-- Input Values
|
||||||
|
|
||||||
-- | 'Field' and 'Directive' 'Arguments' accept input values of various literal
|
|
||||||
-- primitives; input values can be scalars, enumeration values, lists, or input
|
|
||||||
-- objects.
|
|
||||||
--
|
|
||||||
-- If not defined as constant (for example, in 'DefaultValue'), input values
|
|
||||||
-- can be specified as a 'Variable'. List and inputs objects may also contain
|
|
||||||
-- 'Variable's (unless defined to be constant).
|
|
||||||
--
|
|
||||||
-- <https://facebook.github.io/graphql/#sec-Input-Values Input Value Specification>
|
|
||||||
data Value = ValueVariable Variable
|
data Value = ValueVariable Variable
|
||||||
| ValueInt Int32
|
| ValueInt IntValue
|
||||||
-- GraphQL Float is double precison
|
| ValueFloat FloatValue
|
||||||
| ValueFloat Double
|
| ValueString StringValue
|
||||||
| ValueBoolean Bool
|
| ValueBoolean BooleanValue
|
||||||
| ValueString Text
|
| ValueNull
|
||||||
| ValueEnum Name
|
| ValueEnum EnumValue
|
||||||
| ValueList ListValue
|
| ValueList ListValue
|
||||||
| ValueObject ObjectValue
|
| ValueObject ObjectValue
|
||||||
deriving (Eq,Show)
|
deriving (Eq,Show)
|
||||||
|
|
||||||
newtype ListValue = ListValue [Value] deriving (Eq,Show)
|
type IntValue = Int32
|
||||||
|
|
||||||
newtype ObjectValue = ObjectValue [ObjectField] deriving (Eq,Show)
|
-- GraphQL Float is double precison
|
||||||
|
type FloatValue = Double
|
||||||
|
|
||||||
|
type StringValue = Text
|
||||||
|
|
||||||
|
type BooleanValue = Bool
|
||||||
|
|
||||||
|
type EnumValue = Name
|
||||||
|
|
||||||
|
type ListValue = [Value]
|
||||||
|
|
||||||
|
type ObjectValue = [ObjectField]
|
||||||
|
|
||||||
data ObjectField = ObjectField Name Value deriving (Eq,Show)
|
data ObjectField = ObjectField Name Value deriving (Eq,Show)
|
||||||
|
|
||||||
|
-- * Variables
|
||||||
|
|
||||||
|
type VariableDefinitions = [VariableDefinition]
|
||||||
|
|
||||||
|
data VariableDefinition = VariableDefinition Variable Type (Maybe DefaultValue)
|
||||||
|
deriving (Eq,Show)
|
||||||
|
|
||||||
|
type Variable = Name
|
||||||
|
|
||||||
type DefaultValue = Value
|
type DefaultValue = Value
|
||||||
|
|
||||||
-- * Directives
|
-- * Input Types
|
||||||
|
|
||||||
data Directive = Directive Name [Argument] deriving (Eq,Show)
|
data Type = TypeNamed Name
|
||||||
|
| TypeList Type
|
||||||
-- * Type Reference
|
|
||||||
|
|
||||||
data Type = TypeNamed NamedType
|
|
||||||
| TypeList ListType
|
|
||||||
| TypeNonNull NonNullType
|
| TypeNonNull NonNullType
|
||||||
deriving (Eq,Show)
|
deriving (Eq,Show)
|
||||||
|
|
||||||
newtype NamedType = NamedType Name deriving (Eq,Show)
|
data NonNullType = NonNullTypeNamed Name
|
||||||
|
| NonNullTypeList Type
|
||||||
newtype ListType = ListType Type deriving (Eq,Show)
|
|
||||||
|
|
||||||
data NonNullType = NonNullTypeNamed NamedType
|
|
||||||
| NonNullTypeList ListType
|
|
||||||
deriving (Eq,Show)
|
deriving (Eq,Show)
|
||||||
|
|
||||||
|
-- * Directives
|
||||||
|
|
||||||
|
type Directives = [Directive]
|
||||||
|
|
||||||
|
data Directive = Directive Name [Argument] deriving (Eq,Show)
|
||||||
|
@ -6,15 +6,17 @@ import Data.List.NonEmpty (NonEmpty)
|
|||||||
|
|
||||||
import Data.Text (Text)
|
import Data.Text (Text)
|
||||||
|
|
||||||
newtype Name = Name Text deriving (Eq,Show)
|
type Name = Text
|
||||||
|
|
||||||
newtype Document = Document (NonEmpty Operation) deriving (Eq,Show)
|
type Document = NonEmpty Operation
|
||||||
|
|
||||||
data Operation = Query (NonEmpty Field)
|
data Operation = Query (NonEmpty Field)
|
||||||
| Mutation (NonEmpty Field)
|
| Mutation (NonEmpty Field)
|
||||||
deriving (Eq,Show)
|
deriving (Eq,Show)
|
||||||
|
|
||||||
data Field = Field Name [Argument] [Field] deriving (Eq,Show)
|
data Field = Field (Maybe Alias) Name [Argument] [Field] deriving (Eq,Show)
|
||||||
|
|
||||||
|
type Alias = Name
|
||||||
|
|
||||||
data Argument = Argument Name Value deriving (Eq,Show)
|
data Argument = Argument Name Value deriving (Eq,Show)
|
||||||
|
|
||||||
|
@ -2,7 +2,9 @@
|
|||||||
-- | This module defines a printer for the @GraphQL@ language.
|
-- | This module defines a printer for the @GraphQL@ language.
|
||||||
module Data.GraphQL.Encoder where
|
module Data.GraphQL.Encoder where
|
||||||
|
|
||||||
|
import Data.Foldable (fold)
|
||||||
import Data.Monoid ((<>))
|
import Data.Monoid ((<>))
|
||||||
|
import qualified Data.List.NonEmpty as NonEmpty (toList)
|
||||||
|
|
||||||
import Data.Text (Text, cons, intercalate, pack, snoc)
|
import Data.Text (Text, cons, intercalate, pack, snoc)
|
||||||
|
|
||||||
@ -10,24 +12,26 @@ import Data.GraphQL.AST
|
|||||||
|
|
||||||
-- * Document
|
-- * Document
|
||||||
|
|
||||||
-- TODO: Use query shorthand
|
|
||||||
document :: Document -> Text
|
document :: Document -> Text
|
||||||
document (Document defs) = (`snoc` '\n') . mconcat $ definition <$> defs
|
document defs = (`snoc` '\n') . mconcat . NonEmpty.toList $ definition <$> defs
|
||||||
|
|
||||||
definition :: Definition -> Text
|
definition :: Definition -> Text
|
||||||
definition (DefinitionOperation x) = operationDefinition x
|
definition (DefinitionOperation x) = operationDefinition x
|
||||||
definition (DefinitionFragment x) = fragmentDefinition x
|
definition (DefinitionFragment x) = fragmentDefinition x
|
||||||
|
|
||||||
operationDefinition :: OperationDefinition -> Text
|
operationDefinition :: OperationDefinition -> Text
|
||||||
operationDefinition (Query n) = "query " <> node n
|
operationDefinition (OperationSelectionSet sels) = selectionSet sels
|
||||||
operationDefinition (Mutation n) = "mutation " <> node n
|
operationDefinition (OperationDefinition Query name vars dirs sels) =
|
||||||
|
"query " <> node name vars dirs sels
|
||||||
|
operationDefinition (OperationDefinition Mutation name vars dirs sels) =
|
||||||
|
"mutation " <> node name vars dirs sels
|
||||||
|
|
||||||
node :: Node -> Text
|
node :: Name -> VariableDefinitions -> Directives -> SelectionSet -> Text
|
||||||
node (Node name vds ds ss) =
|
node name vars dirs sels =
|
||||||
name
|
name
|
||||||
<> optempty variableDefinitions vds
|
<> optempty variableDefinitions vars
|
||||||
<> optempty directives ds
|
<> optempty directives dirs
|
||||||
<> selectionSet ss
|
<> selectionSet sels
|
||||||
|
|
||||||
variableDefinitions :: [VariableDefinition] -> Text
|
variableDefinitions :: [VariableDefinition] -> Text
|
||||||
variableDefinitions = parensCommas variableDefinition
|
variableDefinitions = parensCommas variableDefinition
|
||||||
@ -40,10 +44,13 @@ defaultValue :: DefaultValue -> Text
|
|||||||
defaultValue val = "=" <> value val
|
defaultValue val = "=" <> value val
|
||||||
|
|
||||||
variable :: Variable -> Text
|
variable :: Variable -> Text
|
||||||
variable (Variable name) = "$" <> name
|
variable var = "$" <> var
|
||||||
|
|
||||||
selectionSet :: SelectionSet -> Text
|
selectionSet :: SelectionSet -> Text
|
||||||
selectionSet = bracesCommas selection
|
selectionSet = bracesCommas selection . NonEmpty.toList
|
||||||
|
|
||||||
|
selectionSetOpt :: SelectionSetOpt -> Text
|
||||||
|
selectionSetOpt = bracesCommas selection
|
||||||
|
|
||||||
selection :: Selection -> Text
|
selection :: Selection -> Text
|
||||||
selection (SelectionField x) = field x
|
selection (SelectionField x) = field x
|
||||||
@ -51,12 +58,12 @@ selection (SelectionInlineFragment x) = inlineFragment x
|
|||||||
selection (SelectionFragmentSpread x) = fragmentSpread x
|
selection (SelectionFragmentSpread x) = fragmentSpread x
|
||||||
|
|
||||||
field :: Field -> Text
|
field :: Field -> Text
|
||||||
field (Field alias name args ds ss) =
|
field (Field alias name args dirs selso) =
|
||||||
optempty (`snoc` ':') alias
|
optempty (`snoc` ':') (fold alias)
|
||||||
<> name
|
<> name
|
||||||
<> optempty arguments args
|
<> optempty arguments args
|
||||||
<> optempty directives ds
|
<> optempty directives dirs
|
||||||
<> optempty selectionSet ss
|
<> optempty selectionSetOpt selso
|
||||||
|
|
||||||
arguments :: [Argument] -> Text
|
arguments :: [Argument] -> Text
|
||||||
arguments = parensCommas argument
|
arguments = parensCommas argument
|
||||||
@ -71,26 +78,27 @@ fragmentSpread (FragmentSpread name ds) =
|
|||||||
"..." <> name <> optempty directives ds
|
"..." <> name <> optempty directives ds
|
||||||
|
|
||||||
inlineFragment :: InlineFragment -> Text
|
inlineFragment :: InlineFragment -> Text
|
||||||
inlineFragment (InlineFragment (NamedType tc) ds ss) =
|
inlineFragment (InlineFragment tc dirs sels) =
|
||||||
"... on " <> tc
|
"... on " <> fold tc
|
||||||
<> optempty directives ds
|
<> directives dirs
|
||||||
<> optempty selectionSet ss
|
<> selectionSet sels
|
||||||
|
|
||||||
fragmentDefinition :: FragmentDefinition -> Text
|
fragmentDefinition :: FragmentDefinition -> Text
|
||||||
fragmentDefinition (FragmentDefinition name (NamedType tc) ds ss) =
|
fragmentDefinition (FragmentDefinition name tc dirs sels) =
|
||||||
"fragment " <> name <> " on " <> tc
|
"fragment " <> name <> " on " <> tc
|
||||||
<> optempty directives ds
|
<> optempty directives dirs
|
||||||
<> selectionSet ss
|
<> selectionSet sels
|
||||||
|
|
||||||
-- * Values
|
-- * Values
|
||||||
|
|
||||||
value :: Value -> Text
|
value :: Value -> Text
|
||||||
value (ValueVariable x) = variable x
|
value (ValueVariable x) = variable x
|
||||||
-- TODO: This will be replaced with `decimal` Buidler
|
-- TODO: This will be replaced with `decimal` Builder
|
||||||
value (ValueInt x) = pack $ show x
|
value (ValueInt x) = pack $ show x
|
||||||
-- TODO: This will be replaced with `decimal` Buidler
|
-- TODO: This will be replaced with `decimal` Builder
|
||||||
value (ValueFloat x) = pack $ show x
|
value (ValueFloat x) = pack $ show x
|
||||||
value (ValueBoolean x) = booleanValue x
|
value (ValueBoolean x) = booleanValue x
|
||||||
|
value ValueNull = mempty
|
||||||
value (ValueString x) = stringValue x
|
value (ValueString x) = stringValue x
|
||||||
value (ValueEnum x) = x
|
value (ValueEnum x) = x
|
||||||
value (ValueList x) = listValue x
|
value (ValueList x) = listValue x
|
||||||
@ -105,10 +113,10 @@ stringValue :: Text -> Text
|
|||||||
stringValue = quotes
|
stringValue = quotes
|
||||||
|
|
||||||
listValue :: ListValue -> Text
|
listValue :: ListValue -> Text
|
||||||
listValue (ListValue vs) = bracketsCommas value vs
|
listValue = bracketsCommas value
|
||||||
|
|
||||||
objectValue :: ObjectValue -> Text
|
objectValue :: ObjectValue -> Text
|
||||||
objectValue (ObjectValue ofs) = bracesCommas objectField ofs
|
objectValue = bracesCommas objectField
|
||||||
|
|
||||||
objectField :: ObjectField -> Text
|
objectField :: ObjectField -> Text
|
||||||
objectField (ObjectField name v) = name <> ":" <> value v
|
objectField (ObjectField name v) = name <> ":" <> value v
|
||||||
@ -124,18 +132,15 @@ directive (Directive name args) = "@" <> name <> optempty arguments args
|
|||||||
-- * Type Reference
|
-- * Type Reference
|
||||||
|
|
||||||
type_ :: Type -> Text
|
type_ :: Type -> Text
|
||||||
type_ (TypeNamed (NamedType x)) = x
|
type_ (TypeNamed x) = x
|
||||||
type_ (TypeList x) = listType x
|
type_ (TypeList x) = listType x
|
||||||
type_ (TypeNonNull x) = nonNullType x
|
type_ (TypeNonNull x) = nonNullType x
|
||||||
|
|
||||||
namedType :: NamedType -> Text
|
listType :: Type -> Text
|
||||||
namedType (NamedType name) = name
|
listType x = brackets (type_ x)
|
||||||
|
|
||||||
listType :: ListType -> Text
|
|
||||||
listType (ListType ty) = brackets (type_ ty)
|
|
||||||
|
|
||||||
nonNullType :: NonNullType -> Text
|
nonNullType :: NonNullType -> Text
|
||||||
nonNullType (NonNullTypeNamed (NamedType x)) = x <> "!"
|
nonNullType (NonNullTypeNamed x) = x <> "!"
|
||||||
nonNullType (NonNullTypeList x) = listType x <> "!"
|
nonNullType (NonNullTypeList x) = listType x <> "!"
|
||||||
|
|
||||||
-- * Internal
|
-- * Internal
|
||||||
|
@ -21,38 +21,37 @@ import Data.GraphQL.Error
|
|||||||
-- errors wrapped in an /errors/ field.
|
-- errors wrapped in an /errors/ field.
|
||||||
execute :: Alternative f
|
execute :: Alternative f
|
||||||
=> Schema.Schema f -> Schema.Subs -> Document -> f Aeson.Value
|
=> Schema.Schema f -> Schema.Subs -> Document -> f Aeson.Value
|
||||||
execute (Schema resolvs) subs doc = runCollectErrs res
|
execute resolvers subs doc = undefined -- resolver resolvs $ rootFields subs doc
|
||||||
where res = Schema.resolvers resolvs $ rootFields subs doc
|
|
||||||
|
|
||||||
-- | Takes a variable substitution function and a @GraphQL@ document.
|
-- | Takes a variable substitution function and a @GraphQL@ document.
|
||||||
-- If the document contains one query (and no other definitions)
|
-- If the document contains one query (and no other definitions)
|
||||||
-- it applies the substitution to the query's set of selections
|
-- it applies the substitution to the query's set of selections
|
||||||
-- and then returns their fields.
|
-- and then returns their fields.
|
||||||
rootFields :: Schema.Subs -> Document -> [Field]
|
-- rootFields :: Schema.Subs -> Document -> [Field]
|
||||||
rootFields subs (Document [DefinitionOperation (Query (Node _varDefs _ _ sels))]) =
|
-- rootFields subs (Document [DefinitionOperation (Query (Node _varDefs _ _ sels))]) =
|
||||||
Schema.fields $ substitute subs <$> sels
|
-- Schema.fields $ substitute subs <$> sels
|
||||||
rootFields _ _ = []
|
-- rootFields _ _ = []
|
||||||
|
|
||||||
-- | Takes a variable substitution function and a selection. If the
|
-- | Takes a variable substitution function and a selection. If the
|
||||||
-- selection is a field it applies the substitution to the field's
|
-- selection is a field it applies the substitution to the field's
|
||||||
-- arguments using 'subsArg', and recursively applies the substitution to
|
-- arguments using 'subsArg', and recursively applies the substitution to
|
||||||
-- the arguments of fields nested in the primary field.
|
-- the arguments of fields nested in the primary field.
|
||||||
substitute :: Schema.Subs -> Selection -> Selection
|
-- substitute :: Schema.Subs -> Selection -> Selection
|
||||||
substitute subs (SelectionField (Field alias name args directives sels)) =
|
-- substitute subs (SelectionField (Field alias name args directives sels)) =
|
||||||
SelectionField $ Field
|
-- SelectionField $ Field
|
||||||
alias
|
-- alias
|
||||||
name
|
-- name
|
||||||
-- TODO: Get rid of `catMaybes`, invalid arguments should raise an error
|
-- -- TODO: Get rid of `catMaybes`, invalid arguments should raise an error
|
||||||
(catMaybes $ subsArg subs <$> args)
|
-- (catMaybes $ subsArg subs <$> args)
|
||||||
directives
|
-- directives
|
||||||
(substitute subs <$> sels)
|
-- (substitute subs <$> sels)
|
||||||
substitute _ sel = sel
|
-- substitute _ sel = sel
|
||||||
|
|
||||||
-- TODO: Support different value types
|
-- TODO: Support different value types
|
||||||
-- | Takes a variable substitution function and an argument. If the
|
-- | Takes a variable substitution function and an argument. If the
|
||||||
-- argument's value is a variable the substitution is applied to the
|
-- argument's value is a variable the substitution is applied to the
|
||||||
-- variable's name.
|
-- variable's name.
|
||||||
subsArg :: Schema.Subs -> Argument -> Maybe Argument
|
-- subsArg :: Schema.Subs -> Argument -> Maybe Argument
|
||||||
subsArg subs (Argument n (ValueVariable (Variable v))) =
|
-- subsArg subs (Argument n (ValueVariable (Variable v))) =
|
||||||
Argument n . ValueString <$> subs v
|
-- Argument n . ValueString <$> subs v
|
||||||
subsArg _ arg = Just arg
|
-- subsArg _ arg = Just arg
|
||||||
|
@ -1,27 +1,31 @@
|
|||||||
{-# LANGUAGE OverloadedStrings #-}
|
{-# LANGUAGE OverloadedStrings #-}
|
||||||
|
{-# LANGUAGE LambdaCase #-}
|
||||||
-- | This module defines a parser for @GraphQL@ request documents.
|
-- | This module defines a parser for @GraphQL@ request documents.
|
||||||
module Data.GraphQL.Parser where
|
module Data.GraphQL.Parser where
|
||||||
|
|
||||||
import Prelude hiding (takeWhile)
|
import Prelude hiding (takeWhile)
|
||||||
|
|
||||||
import Control.Applicative ((<|>), empty, many, optional)
|
import Control.Applicative ((<|>), Alternative, empty, many, optional)
|
||||||
import Control.Monad (when)
|
import Control.Monad (when)
|
||||||
import Data.Char (isDigit, isSpace)
|
import Data.Char (isDigit, isSpace)
|
||||||
import Data.Foldable (traverse_)
|
import Data.Foldable (traverse_)
|
||||||
|
import Data.Monoid ((<>))
|
||||||
|
import Data.List.NonEmpty (NonEmpty((:|)))
|
||||||
import Data.Scientific (floatingOrInteger)
|
import Data.Scientific (floatingOrInteger)
|
||||||
|
|
||||||
import Data.Text (Text, append)
|
import Data.Text (Text, append)
|
||||||
|
import Data.Attoparsec.Combinator (lookAhead)
|
||||||
import Data.Attoparsec.Text
|
import Data.Attoparsec.Text
|
||||||
( Parser
|
( Parser
|
||||||
, (<?>)
|
, (<?>)
|
||||||
, anyChar
|
, anyChar
|
||||||
, scientific
|
|
||||||
, endOfLine
|
, endOfLine
|
||||||
, inClass
|
, inClass
|
||||||
, many1
|
, many1
|
||||||
, manyTill
|
, manyTill
|
||||||
, option
|
, option
|
||||||
, peekChar
|
, peekChar
|
||||||
|
, scientific
|
||||||
, takeWhile
|
, takeWhile
|
||||||
, takeWhile1
|
, takeWhile1
|
||||||
)
|
)
|
||||||
@ -35,20 +39,12 @@ name = tok $ append <$> takeWhile1 isA_z
|
|||||||
<*> takeWhile ((||) <$> isDigit <*> isA_z)
|
<*> takeWhile ((||) <$> isDigit <*> isA_z)
|
||||||
where
|
where
|
||||||
-- `isAlpha` handles many more Unicode Chars
|
-- `isAlpha` handles many more Unicode Chars
|
||||||
isA_z = inClass $ '_' : ['A'..'Z'] ++ ['a'..'z']
|
isA_z = inClass $ '_' : ['A'..'Z'] <> ['a'..'z']
|
||||||
|
|
||||||
-- * Document
|
-- * Document
|
||||||
|
|
||||||
document :: Parser Document
|
document :: Parser Document
|
||||||
document = whiteSpace
|
document = whiteSpace *> manyNE definition
|
||||||
*> (Document <$> many1 definition)
|
|
||||||
-- Try SelectionSet when no definition
|
|
||||||
<|> (Document . pure
|
|
||||||
. DefinitionOperation
|
|
||||||
. Query
|
|
||||||
. Node mempty empty empty
|
|
||||||
<$> selectionSet)
|
|
||||||
<?> "document error!"
|
|
||||||
|
|
||||||
definition :: Parser Definition
|
definition :: Parser Definition
|
||||||
definition = DefinitionOperation <$> operationDefinition
|
definition = DefinitionOperation <$> operationDefinition
|
||||||
@ -56,54 +52,48 @@ definition = DefinitionOperation <$> operationDefinition
|
|||||||
<?> "definition error!"
|
<?> "definition error!"
|
||||||
|
|
||||||
operationDefinition :: Parser OperationDefinition
|
operationDefinition :: Parser OperationDefinition
|
||||||
operationDefinition =
|
operationDefinition = OperationSelectionSet <$> selectionSet
|
||||||
Query <$ tok "query" <*> node
|
<|> OperationDefinition <$> operationType
|
||||||
<|> Mutation <$ tok "mutation" <*> node
|
<*> name
|
||||||
<?> "operationDefinition error!"
|
<*> opt variableDefinitions
|
||||||
|
<*> opt directives
|
||||||
node :: Parser Node
|
|
||||||
node = Node <$> name
|
|
||||||
<*> optempty variableDefinitions
|
|
||||||
<*> optempty directives
|
|
||||||
<*> selectionSet
|
<*> selectionSet
|
||||||
|
<?> "operationDefinition error"
|
||||||
|
|
||||||
variableDefinitions :: Parser [VariableDefinition]
|
operationType :: Parser OperationType
|
||||||
variableDefinitions = parens (many1 variableDefinition)
|
operationType = Query <$ tok "query"
|
||||||
|
<|> Mutation <$ tok "mutation"
|
||||||
|
<?> "operationType error"
|
||||||
|
|
||||||
variableDefinition :: Parser VariableDefinition
|
-- * SelectionSet
|
||||||
variableDefinition =
|
|
||||||
VariableDefinition <$> variable
|
|
||||||
<* tok ":"
|
|
||||||
<*> type_
|
|
||||||
<*> optional defaultValue
|
|
||||||
|
|
||||||
defaultValue :: Parser DefaultValue
|
|
||||||
defaultValue = tok "=" *> value
|
|
||||||
|
|
||||||
variable :: Parser Variable
|
|
||||||
variable = Variable <$ tok "$" <*> name
|
|
||||||
|
|
||||||
selectionSet :: Parser SelectionSet
|
selectionSet :: Parser SelectionSet
|
||||||
selectionSet = braces $ many1 selection
|
selectionSet = braces $ manyNE selection
|
||||||
|
|
||||||
|
selectionSetOpt :: Parser SelectionSetOpt
|
||||||
|
selectionSetOpt = braces $ many1 selection
|
||||||
|
|
||||||
selection :: Parser Selection
|
selection :: Parser Selection
|
||||||
selection = SelectionField <$> field
|
selection = SelectionField <$> field
|
||||||
-- Inline first to catch `on` case
|
|
||||||
<|> SelectionInlineFragment <$> inlineFragment
|
|
||||||
<|> SelectionFragmentSpread <$> fragmentSpread
|
<|> SelectionFragmentSpread <$> fragmentSpread
|
||||||
|
<|> SelectionInlineFragment <$> inlineFragment
|
||||||
<?> "selection error!"
|
<?> "selection error!"
|
||||||
|
|
||||||
|
-- * Field
|
||||||
|
|
||||||
field :: Parser Field
|
field :: Parser Field
|
||||||
field = Field <$> optempty alias
|
field = Field <$> optional alias
|
||||||
<*> name
|
<*> name
|
||||||
<*> optempty arguments
|
<*> opt arguments
|
||||||
<*> optempty directives
|
<*> opt directives
|
||||||
<*> optempty selectionSet
|
<*> opt selectionSetOpt
|
||||||
|
|
||||||
alias :: Parser Alias
|
alias :: Parser Alias
|
||||||
alias = name <* tok ":"
|
alias = name <* tok ":"
|
||||||
|
|
||||||
arguments :: Parser [Argument]
|
-- * Arguments
|
||||||
|
|
||||||
|
arguments :: Parser Arguments
|
||||||
arguments = parens $ many1 argument
|
arguments = parens $ many1 argument
|
||||||
|
|
||||||
argument :: Parser Argument
|
argument :: Parser Argument
|
||||||
@ -112,50 +102,43 @@ argument = Argument <$> name <* tok ":" <*> value
|
|||||||
-- * Fragments
|
-- * Fragments
|
||||||
|
|
||||||
fragmentSpread :: Parser FragmentSpread
|
fragmentSpread :: Parser FragmentSpread
|
||||||
-- TODO: Make sure it fails when `... on`.
|
fragmentSpread = FragmentSpread <$ tok "..."
|
||||||
-- See https://facebook.github.io/graphql/#FragmentSpread
|
<*> fragmentName
|
||||||
fragmentSpread = FragmentSpread
|
<*> opt directives
|
||||||
<$ tok "..."
|
|
||||||
<*> name
|
|
||||||
<*> optempty directives
|
|
||||||
|
|
||||||
-- InlineFragment tried first in order to guard against 'on' keyword
|
|
||||||
inlineFragment :: Parser InlineFragment
|
inlineFragment :: Parser InlineFragment
|
||||||
inlineFragment = InlineFragment
|
inlineFragment = InlineFragment <$ tok "..."
|
||||||
<$ tok "..."
|
<*> optional typeCondition
|
||||||
<* tok "on"
|
<*> opt directives
|
||||||
<*> typeCondition
|
|
||||||
<*> optempty directives
|
|
||||||
<*> selectionSet
|
<*> selectionSet
|
||||||
|
|
||||||
fragmentDefinition :: Parser FragmentDefinition
|
fragmentDefinition :: Parser FragmentDefinition
|
||||||
fragmentDefinition = FragmentDefinition
|
fragmentDefinition = FragmentDefinition
|
||||||
<$ tok "fragment"
|
<$ tok "fragment"
|
||||||
<*> name
|
<*> name
|
||||||
<* tok "on"
|
|
||||||
<*> typeCondition
|
<*> typeCondition
|
||||||
<*> optempty directives
|
<*> opt directives
|
||||||
<*> selectionSet
|
<*> selectionSet
|
||||||
|
|
||||||
|
fragmentName :: Parser FragmentName
|
||||||
|
fragmentName = but (tok "on") *> name
|
||||||
|
|
||||||
typeCondition :: Parser TypeCondition
|
typeCondition :: Parser TypeCondition
|
||||||
typeCondition = namedType
|
typeCondition = tok "on" *> name
|
||||||
|
|
||||||
-- * Values
|
-- * Input Values
|
||||||
|
|
||||||
-- This will try to pick the first type it can parse. If you are working with
|
|
||||||
-- explicit types use the `typedValue` parser.
|
|
||||||
value :: Parser Value
|
value :: Parser Value
|
||||||
value = ValueVariable <$> variable
|
value = ValueVariable <$> variable
|
||||||
-- TODO: Handle maxBound, Int32 in spec.
|
|
||||||
<|> tok (either ValueFloat ValueInt . floatingOrInteger <$> scientific)
|
<|> tok (either ValueFloat ValueInt . floatingOrInteger <$> scientific)
|
||||||
<|> ValueBoolean <$> booleanValue
|
<|> ValueBoolean <$> booleanValue
|
||||||
|
<|> ValueNull <$ tok "null"
|
||||||
<|> ValueString <$> stringValue
|
<|> ValueString <$> stringValue
|
||||||
-- `true` and `false` have been tried before
|
<|> ValueEnum <$> enumValue
|
||||||
<|> ValueEnum <$> name
|
|
||||||
<|> ValueList <$> listValue
|
<|> ValueList <$> listValue
|
||||||
<|> ValueObject <$> objectValue
|
<|> ValueObject <$> objectValue
|
||||||
<?> "value error!"
|
<?> "value error!"
|
||||||
|
where
|
||||||
booleanValue :: Parser Bool
|
booleanValue :: Parser Bool
|
||||||
booleanValue = True <$ tok "true"
|
booleanValue = True <$ tok "true"
|
||||||
<|> False <$ tok "false"
|
<|> False <$ tok "false"
|
||||||
@ -164,46 +147,58 @@ booleanValue = True <$ tok "true"
|
|||||||
stringValue :: Parser Text
|
stringValue :: Parser Text
|
||||||
stringValue = quotes (takeWhile (/= '"'))
|
stringValue = quotes (takeWhile (/= '"'))
|
||||||
|
|
||||||
-- Notice it can be empty
|
enumValue :: Parser Name
|
||||||
listValue :: Parser ListValue
|
enumValue = but (tok "true") *> but (tok "false") *> but (tok "null") *> name
|
||||||
listValue = ListValue <$> brackets (many value)
|
|
||||||
|
|
||||||
-- Notice it can be empty
|
listValue :: Parser [Value]
|
||||||
objectValue :: Parser ObjectValue
|
listValue = brackets $ many1 value
|
||||||
objectValue = ObjectValue <$> braces (many objectField)
|
|
||||||
|
objectValue :: Parser [ObjectField]
|
||||||
|
objectValue = braces $ many1 objectField
|
||||||
|
|
||||||
objectField :: Parser ObjectField
|
objectField :: Parser ObjectField
|
||||||
objectField = ObjectField <$> name <* tok ":" <*> value
|
objectField = ObjectField <$> name <* tok ":" <*> value
|
||||||
|
|
||||||
|
-- * Variables
|
||||||
|
|
||||||
|
variableDefinitions :: Parser VariableDefinitions
|
||||||
|
variableDefinitions = parens $ many1 variableDefinition
|
||||||
|
|
||||||
|
variableDefinition :: Parser VariableDefinition
|
||||||
|
variableDefinition = VariableDefinition <$> variable
|
||||||
|
<* tok ":"
|
||||||
|
<*> type_
|
||||||
|
<*> optional defaultValue
|
||||||
|
|
||||||
|
variable :: Parser Variable
|
||||||
|
variable = tok "$" *> name
|
||||||
|
|
||||||
|
defaultValue :: Parser DefaultValue
|
||||||
|
defaultValue = tok "=" *> value
|
||||||
|
|
||||||
|
-- * Input Types
|
||||||
|
|
||||||
|
type_ :: Parser Type
|
||||||
|
type_ = TypeNamed <$> name
|
||||||
|
<|> TypeList <$> brackets type_
|
||||||
|
<|> TypeNonNull <$> nonNullType
|
||||||
|
<?> "type_ error!"
|
||||||
|
|
||||||
|
nonNullType :: Parser NonNullType
|
||||||
|
nonNullType = NonNullTypeNamed <$> name <* tok "!"
|
||||||
|
<|> NonNullTypeList <$> brackets type_ <* tok "!"
|
||||||
|
<?> "nonNullType error!"
|
||||||
|
|
||||||
-- * Directives
|
-- * Directives
|
||||||
|
|
||||||
directives :: Parser [Directive]
|
directives :: Parser Directives
|
||||||
directives = many1 directive
|
directives = many1 directive
|
||||||
|
|
||||||
directive :: Parser Directive
|
directive :: Parser Directive
|
||||||
directive = Directive
|
directive = Directive
|
||||||
<$ tok "@"
|
<$ tok "@"
|
||||||
<*> name
|
<*> name
|
||||||
<*> optempty arguments
|
<*> opt arguments
|
||||||
|
|
||||||
-- * Type Reference
|
|
||||||
|
|
||||||
type_ :: Parser Type
|
|
||||||
type_ = TypeList <$> listType
|
|
||||||
<|> TypeNonNull <$> nonNullType
|
|
||||||
<|> TypeNamed <$> namedType
|
|
||||||
<?> "type_ error!"
|
|
||||||
|
|
||||||
namedType :: Parser NamedType
|
|
||||||
namedType = NamedType <$> name
|
|
||||||
|
|
||||||
listType :: Parser ListType
|
|
||||||
listType = ListType <$> brackets type_
|
|
||||||
|
|
||||||
nonNullType :: Parser NonNullType
|
|
||||||
nonNullType = NonNullTypeNamed <$> namedType <* tok "!"
|
|
||||||
<|> NonNullTypeList <$> listType <* tok "!"
|
|
||||||
<?> "nonNullType error!"
|
|
||||||
|
|
||||||
-- * Internal
|
-- * Internal
|
||||||
|
|
||||||
@ -225,12 +220,18 @@ brackets = between "[" "]"
|
|||||||
between :: Parser Text -> Parser Text -> Parser a -> Parser a
|
between :: Parser Text -> Parser Text -> Parser a -> Parser a
|
||||||
between open close p = tok open *> p <* tok close
|
between open close p = tok open *> p <* tok close
|
||||||
|
|
||||||
-- `empty` /= `pure mempty` for `Parser`.
|
opt :: Monoid a => Parser a -> Parser a
|
||||||
optempty :: Monoid a => Parser a -> Parser a
|
opt = option mempty
|
||||||
optempty = option mempty
|
|
||||||
|
-- Hack to reverse parser success
|
||||||
|
but :: Parser a -> Parser ()
|
||||||
|
but pn = False <$ lookAhead pn <|> pure True >>= \case
|
||||||
|
False -> empty
|
||||||
|
True -> pure ()
|
||||||
|
|
||||||
|
manyNE :: Alternative f => f a -> f (NonEmpty a)
|
||||||
|
manyNE p = (:|) <$> p <*> many p
|
||||||
|
|
||||||
-- ** WhiteSpace
|
|
||||||
--
|
|
||||||
whiteSpace :: Parser ()
|
whiteSpace :: Parser ()
|
||||||
whiteSpace = peekChar >>= traverse_ (\c ->
|
whiteSpace = peekChar >>= traverse_ (\c ->
|
||||||
if isSpace c || c == ','
|
if isSpace c || c == ','
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
-- | This module provides a representation of a @GraphQL@ Schema in addition to
|
-- | This module provides a representation of a @GraphQL@ Schema in addition to
|
||||||
-- functions for defining and manipulating Schemas.
|
-- functions for defining and manipulating Schemas.
|
||||||
module Data.GraphQL.Schema
|
module Data.GraphQL.Schema
|
||||||
( Schema(..)
|
( Schema
|
||||||
, Resolver
|
, Resolver
|
||||||
, Subs
|
, Subs
|
||||||
, object
|
, object
|
||||||
@ -15,31 +15,31 @@ module Data.GraphQL.Schema
|
|||||||
, enum
|
, enum
|
||||||
, enumA
|
, enumA
|
||||||
, resolvers
|
, resolvers
|
||||||
, fields
|
|
||||||
-- * AST Reexports
|
-- * AST Reexports
|
||||||
, Field
|
, Field
|
||||||
, Argument(..)
|
, Argument(..)
|
||||||
, Value(..)
|
, Value(..)
|
||||||
) where
|
) where
|
||||||
|
|
||||||
import Data.Bifunctor (first)
|
|
||||||
import Data.Monoid (Alt(Alt,getAlt))
|
|
||||||
import Control.Applicative (Alternative((<|>), empty))
|
import Control.Applicative (Alternative((<|>), empty))
|
||||||
import Data.Maybe (catMaybes)
|
import Data.Bifunctor (first)
|
||||||
import Data.Foldable (fold)
|
import Data.Foldable (fold)
|
||||||
|
import Data.List.NonEmpty (NonEmpty)
|
||||||
|
import Data.Maybe (fromMaybe)
|
||||||
|
import Data.Monoid (Alt(Alt,getAlt))
|
||||||
|
|
||||||
import qualified Data.Aeson as Aeson
|
import qualified Data.Aeson as Aeson
|
||||||
import Data.HashMap.Strict (HashMap)
|
import Data.HashMap.Strict (HashMap)
|
||||||
import qualified Data.HashMap.Strict as HashMap
|
import qualified Data.HashMap.Strict as HashMap
|
||||||
import Data.Text (Text)
|
import Data.Text (Text)
|
||||||
import qualified Data.Text as T (null, unwords)
|
import qualified Data.Text as T (unwords)
|
||||||
|
|
||||||
import Data.GraphQL.AST
|
import Data.GraphQL.AST.Core
|
||||||
import Data.GraphQL.Error
|
import Data.GraphQL.Error
|
||||||
|
|
||||||
-- | A GraphQL schema.
|
-- | A GraphQL schema.
|
||||||
-- @f@ is usually expected to be an instance of 'Alternative'.
|
-- @f@ is usually expected to be an instance of 'Alternative'.
|
||||||
data Schema f = Schema [Resolver f]
|
type Schema f = NonEmpty (Resolver f)
|
||||||
|
|
||||||
-- | Resolves a 'Field' into an @Aeson.@'Aeson.Object' with error information
|
-- | Resolves a 'Field' into an @Aeson.@'Aeson.Object' with error information
|
||||||
-- (or 'empty'). @f@ is usually expected to be an instance of 'Alternative'.
|
-- (or 'empty'). @f@ is usually expected to be an instance of 'Alternative'.
|
||||||
@ -48,8 +48,7 @@ type Resolver f = Field -> CollectErrsT f Aeson.Object
|
|||||||
-- | Variable substitution function.
|
-- | Variable substitution function.
|
||||||
type Subs = Text -> Maybe Text
|
type Subs = Text -> Maybe Text
|
||||||
|
|
||||||
-- | Create a named 'Resolver' from a list of 'Resolver's.
|
object :: Alternative f => Name -> [Resolver f] -> Resolver f
|
||||||
object :: Alternative f => Text -> [Resolver f] -> Resolver f
|
|
||||||
object name resolvs = objectA name $ \case
|
object name resolvs = objectA name $ \case
|
||||||
[] -> resolvs
|
[] -> resolvs
|
||||||
_ -> empty
|
_ -> empty
|
||||||
@ -57,9 +56,8 @@ object name resolvs = objectA name $ \case
|
|||||||
-- | Like 'object' but also taking 'Argument's.
|
-- | Like 'object' but also taking 'Argument's.
|
||||||
objectA
|
objectA
|
||||||
:: Alternative f
|
:: Alternative f
|
||||||
=> Text -> ([Argument] -> [Resolver f]) -> Resolver f
|
=> Name -> ([Argument] -> [Resolver f]) -> Resolver f
|
||||||
objectA name f fld@(Field _ _ args _ sels) =
|
objectA name f fld@(Field _ _ args sels) = withField name (resolvers (f args) sels) fld
|
||||||
withField name (resolvers (f args) $ fields sels) fld
|
|
||||||
|
|
||||||
-- | A scalar represents a primitive value, like a string or an integer.
|
-- | A scalar represents a primitive value, like a string or an integer.
|
||||||
scalar :: (Alternative f, Aeson.ToJSON a) => Text -> a -> Resolver f
|
scalar :: (Alternative f, Aeson.ToJSON a) => Text -> a -> Resolver f
|
||||||
@ -70,11 +68,10 @@ scalar name s = scalarA name $ \case
|
|||||||
-- | Like 'scalar' but also taking 'Argument's.
|
-- | Like 'scalar' but also taking 'Argument's.
|
||||||
scalarA
|
scalarA
|
||||||
:: (Alternative f, Aeson.ToJSON a)
|
:: (Alternative f, Aeson.ToJSON a)
|
||||||
=> Text -> ([Argument] -> f a) -> Resolver f
|
=> Name -> ([Argument] -> f a) -> Resolver f
|
||||||
scalarA name f fld@(Field _ _ args _ []) = withField name (errWrap $ f args) fld
|
scalarA name f fld@(Field _ _ args []) = withField name (errWrap $ f args) fld
|
||||||
scalarA _ _ _ = empty
|
scalarA _ _ _ = empty
|
||||||
|
|
||||||
-- | Like 'object' but taking lists of 'Resolver's instead of a single list.
|
|
||||||
array :: Alternative f => Text -> [[Resolver f]] -> Resolver f
|
array :: Alternative f => Text -> [[Resolver f]] -> Resolver f
|
||||||
array name resolvs = arrayA name $ \case
|
array name resolvs = arrayA name $ \case
|
||||||
[] -> resolvs
|
[] -> resolvs
|
||||||
@ -84,8 +81,8 @@ array name resolvs = arrayA name $ \case
|
|||||||
arrayA
|
arrayA
|
||||||
:: Alternative f
|
:: Alternative f
|
||||||
=> Text -> ([Argument] -> [[Resolver f]]) -> Resolver f
|
=> Text -> ([Argument] -> [[Resolver f]]) -> Resolver f
|
||||||
arrayA name f fld@(Field _ _ args _ sels) =
|
arrayA name f fld@(Field _ _ args sels) =
|
||||||
withField name (joinErrs $ traverse (flip resolvers $ fields sels) $ f args) fld
|
withField name (joinErrs $ traverse (`resolvers` sels) $ f args) fld
|
||||||
|
|
||||||
-- | Represents one of a finite set of possible values.
|
-- | Represents one of a finite set of possible values.
|
||||||
-- Used in place of a 'scalar' when the possible responses are easily enumerable.
|
-- Used in place of a 'scalar' when the possible responses are easily enumerable.
|
||||||
@ -96,19 +93,19 @@ enum name enums = enumA name $ \case
|
|||||||
|
|
||||||
-- | Like 'enum' but also taking 'Argument's.
|
-- | Like 'enum' but also taking 'Argument's.
|
||||||
enumA :: Alternative f => Text -> ([Argument] -> f [Text]) -> Resolver f
|
enumA :: Alternative f => Text -> ([Argument] -> f [Text]) -> Resolver f
|
||||||
enumA name f fld@(Field _ _ args _ []) = withField name (errWrap $ f args) fld
|
enumA name f fld@(Field _ _ args []) = withField name (errWrap $ f args) fld
|
||||||
enumA _ _ _ = empty
|
enumA _ _ _ = empty
|
||||||
|
|
||||||
-- | Helper function to facilitate 'Argument' handling.
|
-- | Helper function to facilitate 'Argument' handling.
|
||||||
withField
|
withField
|
||||||
:: (Alternative f, Aeson.ToJSON a)
|
:: (Alternative f, Aeson.ToJSON a)
|
||||||
=> Text -> CollectErrsT f a -> Field -> CollectErrsT f (HashMap Text Aeson.Value)
|
=> Name -> CollectErrsT f a -> Field -> CollectErrsT f (HashMap Text Aeson.Value)
|
||||||
withField name f (Field alias name' _ _ _) =
|
withField name f (Field alias name' _ _) =
|
||||||
if name == name'
|
if name == name'
|
||||||
then fmap (first $ HashMap.singleton aliasOrName . Aeson.toJSON) f
|
then fmap (first $ HashMap.singleton aliasOrName . Aeson.toJSON) f
|
||||||
else empty
|
else empty
|
||||||
where
|
where
|
||||||
aliasOrName = if T.null alias then name' else alias
|
aliasOrName = fromMaybe name' alias
|
||||||
|
|
||||||
-- | Takes a list of 'Resolver's and a list of 'Field's and applies each
|
-- | Takes a list of 'Resolver's and a list of 'Field's and applies each
|
||||||
-- 'Resolver' to each 'Field'. Resolves into a value containing the
|
-- 'Resolver' to each 'Field'. Resolves into a value containing the
|
||||||
@ -118,18 +115,8 @@ resolvers resolvs =
|
|||||||
fmap (first Aeson.toJSON . fold)
|
fmap (first Aeson.toJSON . fold)
|
||||||
. traverse (\fld -> getAlt (foldMap (Alt . ($ fld)) resolvs) <|> errmsg fld)
|
. traverse (\fld -> getAlt (foldMap (Alt . ($ fld)) resolvs) <|> errmsg fld)
|
||||||
where
|
where
|
||||||
errmsg (Field alias name _ _ _) = addErrMsg msg $ (errWrap . pure) val
|
errmsg (Field alias name _ _) = addErrMsg msg $ (errWrap . pure) val
|
||||||
where
|
where
|
||||||
val = HashMap.singleton aliasOrName Aeson.Null
|
val = HashMap.singleton aliasOrName Aeson.Null
|
||||||
msg = T.unwords ["field", name, "not resolved."]
|
msg = T.unwords ["field", name, "not resolved."]
|
||||||
aliasOrName = if T.null alias then name else alias
|
aliasOrName = fromMaybe name alias
|
||||||
|
|
||||||
-- | Checks whether the given 'Selection' contains a 'Field' and
|
|
||||||
-- returns the 'Field' if so, else returns 'Nothing'.
|
|
||||||
field :: Selection -> Maybe Field
|
|
||||||
field (SelectionField x) = Just x
|
|
||||||
field _ = Nothing
|
|
||||||
|
|
||||||
-- | Returns a list of the 'Field's contained in the given 'SelectionSet'.
|
|
||||||
fields :: SelectionSet -> [Field]
|
|
||||||
fields = catMaybes . fmap field
|
|
||||||
|
@ -25,11 +25,12 @@ library
|
|||||||
ghc-options: -Wall
|
ghc-options: -Wall
|
||||||
exposed-modules: Data.GraphQL
|
exposed-modules: Data.GraphQL
|
||||||
Data.GraphQL.AST
|
Data.GraphQL.AST
|
||||||
Data.GraphQL.Encoder
|
Data.GraphQL.AST.Core
|
||||||
Data.GraphQL.Execute
|
Data.GraphQL.Execute
|
||||||
|
Data.GraphQL.Encoder
|
||||||
|
Data.GraphQL.Error
|
||||||
Data.GraphQL.Schema
|
Data.GraphQL.Schema
|
||||||
Data.GraphQL.Parser
|
Data.GraphQL.Parser
|
||||||
Data.GraphQL.Error
|
|
||||||
build-depends: aeson >= 0.7.0.3,
|
build-depends: aeson >= 0.7.0.3,
|
||||||
attoparsec >= 0.10.4.0,
|
attoparsec >= 0.10.4.0,
|
||||||
base >= 4.7 && < 5,
|
base >= 4.7 && < 5,
|
||||||
|
@ -1,15 +1,11 @@
|
|||||||
{-# LANGUAGE CPP #-}
|
|
||||||
{-# LANGUAGE LambdaCase #-}
|
{-# LANGUAGE LambdaCase #-}
|
||||||
{-# LANGUAGE OverloadedStrings #-}
|
{-# LANGUAGE OverloadedStrings #-}
|
||||||
module Test.StarWars.Schema where
|
module Test.StarWars.Schema where
|
||||||
|
|
||||||
import Control.Applicative (Alternative, empty)
|
import Control.Applicative (Alternative, empty)
|
||||||
|
import Data.List.NonEmpty (NonEmpty((:|)))
|
||||||
|
|
||||||
#if !MIN_VERSION_base(4,8,0)
|
import Data.GraphQL.Schema (Schema, Resolver, Argument(..), Value(..))
|
||||||
import Control.Applicative ((<$>))
|
|
||||||
import Data.Traversable (traverse)
|
|
||||||
#endif
|
|
||||||
import Data.GraphQL.Schema
|
|
||||||
import qualified Data.GraphQL.Schema as Schema
|
import qualified Data.GraphQL.Schema as Schema
|
||||||
|
|
||||||
import Test.StarWars.Data
|
import Test.StarWars.Data
|
||||||
@ -18,7 +14,7 @@ import Test.StarWars.Data
|
|||||||
-- See https://github.com/graphql/graphql-js/blob/master/src/__tests__/starWarsSchema.js
|
-- See https://github.com/graphql/graphql-js/blob/master/src/__tests__/starWarsSchema.js
|
||||||
|
|
||||||
schema :: Alternative f => Schema f
|
schema :: Alternative f => Schema f
|
||||||
schema = Schema [hero, human, droid]
|
schema = hero :| [human, droid]
|
||||||
|
|
||||||
hero :: Alternative f => Resolver f
|
hero :: Alternative f => Resolver f
|
||||||
hero = Schema.objectA "hero" $ \case
|
hero = Schema.objectA "hero" $ \case
|
||||||
|
@ -18,10 +18,10 @@ import qualified Test.StarWars.QueryTests as SW
|
|||||||
import Paths_graphql (getDataFileName)
|
import Paths_graphql (getDataFileName)
|
||||||
|
|
||||||
main :: IO ()
|
main :: IO ()
|
||||||
main = defaultMain . testGroup "Tests" . (: [SW.test]) =<< ksTest
|
main = defaultMain . testGroup "Tests" . (: [SW.test]) =<< kitchenTest
|
||||||
|
|
||||||
ksTest :: IO TestTree
|
kitchenTest :: IO TestTree
|
||||||
ksTest = testCase "Kitchen Sink"
|
kitchenTest = testCase "Kitchen Sink"
|
||||||
<$> (assertEqual "Encode" <$> expected <*> actual)
|
<$> (assertEqual "Encode" <$> expected <*> actual)
|
||||||
where
|
where
|
||||||
expected = Text.readFile
|
expected = Text.readFile
|
||||||
|
Loading…
Reference in New Issue
Block a user