graphql 0.3 → 0.4.0.0
raw patch · 30 files changed
+2372/−817 lines, 30 filesdep +aesondep +hspecdep +hspec-expectationsdep −attoparsecdep −tastydep −tasty-hunitdep ~basedep ~textnew-uploaderPVP ok
version bump matches the API change (PVP)
Dependencies added: aeson, hspec, hspec-expectations, megaparsec, raw-strings-qq, transformers, unordered-containers
Dependencies removed: attoparsec, tasty, tasty-hunit
Dependency ranges changed: base, text
API changes (from Hackage documentation)
- Data.GraphQL.AST: Argument :: Name -> Value -> Argument
- Data.GraphQL.AST: DefinitionFragment :: FragmentDefinition -> Definition
- Data.GraphQL.AST: DefinitionOperation :: OperationDefinition -> Definition
- Data.GraphQL.AST: DefinitionType :: TypeDefinition -> Definition
- Data.GraphQL.AST: Directive :: Name -> [Argument] -> Directive
- Data.GraphQL.AST: Document :: [Definition] -> Document
- Data.GraphQL.AST: EnumTypeDefinition :: Name -> [EnumValueDefinition] -> EnumTypeDefinition
- Data.GraphQL.AST: EnumValueDefinition :: Name -> EnumValueDefinition
- Data.GraphQL.AST: Field :: Alias -> Name -> [Argument] -> [Directive] -> SelectionSet -> Field
- Data.GraphQL.AST: FieldDefinition :: Name -> ArgumentsDefinition -> Type -> FieldDefinition
- Data.GraphQL.AST: FragmentDefinition :: Name -> TypeCondition -> [Directive] -> SelectionSet -> FragmentDefinition
- Data.GraphQL.AST: FragmentSpread :: Name -> [Directive] -> FragmentSpread
- Data.GraphQL.AST: InlineFragment :: TypeCondition -> [Directive] -> SelectionSet -> InlineFragment
- Data.GraphQL.AST: InputObjectTypeDefinition :: Name -> [InputValueDefinition] -> InputObjectTypeDefinition
- Data.GraphQL.AST: InputValueDefinition :: Name -> Type -> (Maybe DefaultValue) -> InputValueDefinition
- Data.GraphQL.AST: InterfaceTypeDefinition :: Name -> [FieldDefinition] -> InterfaceTypeDefinition
- Data.GraphQL.AST: ListType :: Type -> ListType
- Data.GraphQL.AST: ListValue :: [Value] -> ListValue
- Data.GraphQL.AST: Mutation :: Node -> OperationDefinition
- Data.GraphQL.AST: NamedType :: Name -> NamedType
- Data.GraphQL.AST: Node :: Name -> [VariableDefinition] -> [Directive] -> SelectionSet -> Node
- Data.GraphQL.AST: NonNullTypeList :: ListType -> NonNullType
- Data.GraphQL.AST: NonNullTypeNamed :: NamedType -> NonNullType
- Data.GraphQL.AST: ObjectField :: Name -> Value -> ObjectField
- Data.GraphQL.AST: ObjectTypeDefinition :: Name -> Interfaces -> [FieldDefinition] -> ObjectTypeDefinition
- Data.GraphQL.AST: ObjectValue :: [ObjectField] -> ObjectValue
- Data.GraphQL.AST: Query :: Node -> OperationDefinition
- Data.GraphQL.AST: ScalarTypeDefinition :: Name -> ScalarTypeDefinition
- Data.GraphQL.AST: SelectionField :: Field -> Selection
- Data.GraphQL.AST: SelectionFragmentSpread :: FragmentSpread -> Selection
- Data.GraphQL.AST: SelectionInlineFragment :: InlineFragment -> Selection
- Data.GraphQL.AST: StringValue :: Text -> StringValue
- Data.GraphQL.AST: TypeDefinitionEnum :: EnumTypeDefinition -> TypeDefinition
- Data.GraphQL.AST: TypeDefinitionInputObject :: InputObjectTypeDefinition -> TypeDefinition
- Data.GraphQL.AST: TypeDefinitionInterface :: InterfaceTypeDefinition -> TypeDefinition
- Data.GraphQL.AST: TypeDefinitionObject :: ObjectTypeDefinition -> TypeDefinition
- Data.GraphQL.AST: TypeDefinitionScalar :: ScalarTypeDefinition -> TypeDefinition
- Data.GraphQL.AST: TypeDefinitionTypeExtension :: TypeExtensionDefinition -> TypeDefinition
- Data.GraphQL.AST: TypeDefinitionUnion :: UnionTypeDefinition -> TypeDefinition
- Data.GraphQL.AST: TypeExtensionDefinition :: ObjectTypeDefinition -> TypeExtensionDefinition
- Data.GraphQL.AST: TypeList :: ListType -> Type
- Data.GraphQL.AST: TypeNamed :: NamedType -> Type
- Data.GraphQL.AST: TypeNonNull :: NonNullType -> Type
- Data.GraphQL.AST: UnionTypeDefinition :: Name -> [NamedType] -> UnionTypeDefinition
- Data.GraphQL.AST: ValueBoolean :: Bool -> Value
- Data.GraphQL.AST: ValueEnum :: Name -> Value
- Data.GraphQL.AST: ValueFloat :: Double -> Value
- Data.GraphQL.AST: ValueInt :: Int32 -> Value
- Data.GraphQL.AST: ValueList :: ListValue -> Value
- Data.GraphQL.AST: ValueObject :: ObjectValue -> Value
- Data.GraphQL.AST: ValueString :: StringValue -> Value
- Data.GraphQL.AST: ValueVariable :: Variable -> Value
- Data.GraphQL.AST: Variable :: Name -> Variable
- Data.GraphQL.AST: VariableDefinition :: Variable -> Type -> (Maybe DefaultValue) -> VariableDefinition
- Data.GraphQL.AST: data Argument
- Data.GraphQL.AST: data Definition
- Data.GraphQL.AST: data Directive
- Data.GraphQL.AST: data EnumTypeDefinition
- Data.GraphQL.AST: data Field
- Data.GraphQL.AST: data FieldDefinition
- Data.GraphQL.AST: data FragmentDefinition
- Data.GraphQL.AST: data FragmentSpread
- Data.GraphQL.AST: data InlineFragment
- Data.GraphQL.AST: data InputObjectTypeDefinition
- Data.GraphQL.AST: data InputValueDefinition
- Data.GraphQL.AST: data InterfaceTypeDefinition
- Data.GraphQL.AST: data Node
- Data.GraphQL.AST: data NonNullType
- Data.GraphQL.AST: data ObjectField
- Data.GraphQL.AST: data ObjectTypeDefinition
- Data.GraphQL.AST: data OperationDefinition
- Data.GraphQL.AST: data ScalarTypeDefinition
- Data.GraphQL.AST: data Selection
- Data.GraphQL.AST: data Type
- Data.GraphQL.AST: data TypeDefinition
- Data.GraphQL.AST: data UnionTypeDefinition
- Data.GraphQL.AST: data Value
- Data.GraphQL.AST: data VariableDefinition
- Data.GraphQL.AST: instance Eq Argument
- Data.GraphQL.AST: instance Eq Definition
- Data.GraphQL.AST: instance Eq Directive
- Data.GraphQL.AST: instance Eq Document
- Data.GraphQL.AST: instance Eq EnumTypeDefinition
- Data.GraphQL.AST: instance Eq EnumValueDefinition
- Data.GraphQL.AST: instance Eq Field
- Data.GraphQL.AST: instance Eq FieldDefinition
- Data.GraphQL.AST: instance Eq FragmentDefinition
- Data.GraphQL.AST: instance Eq FragmentSpread
- Data.GraphQL.AST: instance Eq InlineFragment
- Data.GraphQL.AST: instance Eq InputObjectTypeDefinition
- Data.GraphQL.AST: instance Eq InputValueDefinition
- Data.GraphQL.AST: instance Eq InterfaceTypeDefinition
- Data.GraphQL.AST: instance Eq ListType
- Data.GraphQL.AST: instance Eq ListValue
- Data.GraphQL.AST: instance Eq NamedType
- Data.GraphQL.AST: instance Eq Node
- Data.GraphQL.AST: instance Eq NonNullType
- Data.GraphQL.AST: instance Eq ObjectField
- Data.GraphQL.AST: instance Eq ObjectTypeDefinition
- Data.GraphQL.AST: instance Eq ObjectValue
- Data.GraphQL.AST: instance Eq OperationDefinition
- Data.GraphQL.AST: instance Eq ScalarTypeDefinition
- Data.GraphQL.AST: instance Eq Selection
- Data.GraphQL.AST: instance Eq StringValue
- Data.GraphQL.AST: instance Eq Type
- Data.GraphQL.AST: instance Eq TypeDefinition
- Data.GraphQL.AST: instance Eq TypeExtensionDefinition
- Data.GraphQL.AST: instance Eq UnionTypeDefinition
- Data.GraphQL.AST: instance Eq Value
- Data.GraphQL.AST: instance Eq Variable
- Data.GraphQL.AST: instance Eq VariableDefinition
- Data.GraphQL.AST: instance Show Argument
- Data.GraphQL.AST: instance Show Definition
- Data.GraphQL.AST: instance Show Directive
- Data.GraphQL.AST: instance Show Document
- Data.GraphQL.AST: instance Show EnumTypeDefinition
- Data.GraphQL.AST: instance Show EnumValueDefinition
- Data.GraphQL.AST: instance Show Field
- Data.GraphQL.AST: instance Show FieldDefinition
- Data.GraphQL.AST: instance Show FragmentDefinition
- Data.GraphQL.AST: instance Show FragmentSpread
- Data.GraphQL.AST: instance Show InlineFragment
- Data.GraphQL.AST: instance Show InputObjectTypeDefinition
- Data.GraphQL.AST: instance Show InputValueDefinition
- Data.GraphQL.AST: instance Show InterfaceTypeDefinition
- Data.GraphQL.AST: instance Show ListType
- Data.GraphQL.AST: instance Show ListValue
- Data.GraphQL.AST: instance Show NamedType
- Data.GraphQL.AST: instance Show Node
- Data.GraphQL.AST: instance Show NonNullType
- Data.GraphQL.AST: instance Show ObjectField
- Data.GraphQL.AST: instance Show ObjectTypeDefinition
- Data.GraphQL.AST: instance Show ObjectValue
- Data.GraphQL.AST: instance Show OperationDefinition
- Data.GraphQL.AST: instance Show ScalarTypeDefinition
- Data.GraphQL.AST: instance Show Selection
- Data.GraphQL.AST: instance Show StringValue
- Data.GraphQL.AST: instance Show Type
- Data.GraphQL.AST: instance Show TypeDefinition
- Data.GraphQL.AST: instance Show TypeExtensionDefinition
- Data.GraphQL.AST: instance Show UnionTypeDefinition
- Data.GraphQL.AST: instance Show Value
- Data.GraphQL.AST: instance Show Variable
- Data.GraphQL.AST: instance Show VariableDefinition
- Data.GraphQL.AST: newtype Document
- Data.GraphQL.AST: newtype EnumValueDefinition
- Data.GraphQL.AST: newtype ListType
- Data.GraphQL.AST: newtype ListValue
- Data.GraphQL.AST: newtype NamedType
- Data.GraphQL.AST: newtype ObjectValue
- Data.GraphQL.AST: newtype StringValue
- Data.GraphQL.AST: newtype TypeExtensionDefinition
- Data.GraphQL.AST: newtype Variable
- Data.GraphQL.AST: type Alias = Name
- Data.GraphQL.AST: type ArgumentsDefinition = [InputValueDefinition]
- Data.GraphQL.AST: type DefaultValue = Value
- Data.GraphQL.AST: type Interfaces = [NamedType]
- Data.GraphQL.AST: type Name = Text
- Data.GraphQL.AST: type SelectionSet = [Selection]
- Data.GraphQL.AST: type TypeCondition = NamedType
- Data.GraphQL.Encoder: argument :: Argument -> Text
- Data.GraphQL.Encoder: arguments :: [Argument] -> Text
- Data.GraphQL.Encoder: argumentsDefinition :: ArgumentsDefinition -> Text
- Data.GraphQL.Encoder: between :: Char -> Char -> Text -> Text
- Data.GraphQL.Encoder: booleanValue :: Bool -> Text
- Data.GraphQL.Encoder: braces :: Text -> Text
- Data.GraphQL.Encoder: bracesCommas :: (a -> Text) -> [a] -> Text
- Data.GraphQL.Encoder: brackets :: Text -> Text
- Data.GraphQL.Encoder: bracketsCommas :: (a -> Text) -> [a] -> Text
- Data.GraphQL.Encoder: defaultValue :: DefaultValue -> Text
- Data.GraphQL.Encoder: definition :: Definition -> Text
- Data.GraphQL.Encoder: directive :: Directive -> Text
- Data.GraphQL.Encoder: directives :: [Directive] -> Text
- Data.GraphQL.Encoder: document :: Document -> Text
- Data.GraphQL.Encoder: enumTypeDefinition :: EnumTypeDefinition -> Text
- Data.GraphQL.Encoder: enumValueDefinition :: EnumValueDefinition -> Text
- Data.GraphQL.Encoder: field :: Field -> Text
- Data.GraphQL.Encoder: fieldDefinition :: FieldDefinition -> Text
- Data.GraphQL.Encoder: fieldDefinitions :: [FieldDefinition] -> Text
- Data.GraphQL.Encoder: fragmentDefinition :: FragmentDefinition -> Text
- Data.GraphQL.Encoder: fragmentSpread :: FragmentSpread -> Text
- Data.GraphQL.Encoder: inlineFragment :: InlineFragment -> Text
- Data.GraphQL.Encoder: inputObjectTypeDefinition :: InputObjectTypeDefinition -> Text
- Data.GraphQL.Encoder: inputValueDefinition :: InputValueDefinition -> Text
- Data.GraphQL.Encoder: inputValueDefinitions :: [InputValueDefinition] -> Text
- Data.GraphQL.Encoder: interfaceTypeDefinition :: InterfaceTypeDefinition -> Text
- Data.GraphQL.Encoder: interfaces :: Interfaces -> Text
- Data.GraphQL.Encoder: listType :: ListType -> Text
- Data.GraphQL.Encoder: listValue :: ListValue -> Text
- Data.GraphQL.Encoder: namedType :: NamedType -> Text
- Data.GraphQL.Encoder: node :: Node -> Text
- Data.GraphQL.Encoder: nonNullType :: NonNullType -> Text
- Data.GraphQL.Encoder: objectField :: ObjectField -> Text
- Data.GraphQL.Encoder: objectTypeDefinition :: ObjectTypeDefinition -> Text
- Data.GraphQL.Encoder: objectValue :: ObjectValue -> Text
- Data.GraphQL.Encoder: operationDefinition :: OperationDefinition -> Text
- Data.GraphQL.Encoder: optempty :: (Eq a, Monoid a, Monoid b) => (a -> b) -> a -> b
- Data.GraphQL.Encoder: parens :: Text -> Text
- Data.GraphQL.Encoder: parensCommas :: (a -> Text) -> [a] -> Text
- Data.GraphQL.Encoder: quotes :: Text -> Text
- Data.GraphQL.Encoder: scalarTypeDefinition :: ScalarTypeDefinition -> Text
- Data.GraphQL.Encoder: selection :: Selection -> Text
- Data.GraphQL.Encoder: selectionSet :: SelectionSet -> Text
- Data.GraphQL.Encoder: spaced :: Text -> Text
- Data.GraphQL.Encoder: spaces :: (a -> Text) -> [a] -> Text
- Data.GraphQL.Encoder: stringValue :: StringValue -> Text
- Data.GraphQL.Encoder: typeDefinition :: TypeDefinition -> Text
- Data.GraphQL.Encoder: typeExtensionDefinition :: TypeExtensionDefinition -> Text
- Data.GraphQL.Encoder: type_ :: Type -> Text
- Data.GraphQL.Encoder: unionMembers :: [NamedType] -> Text
- Data.GraphQL.Encoder: unionTypeDefinition :: UnionTypeDefinition -> Text
- Data.GraphQL.Encoder: value :: Value -> Text
- Data.GraphQL.Encoder: variable :: Variable -> Text
- Data.GraphQL.Encoder: variableDefinition :: VariableDefinition -> Text
- Data.GraphQL.Encoder: variableDefinitions :: [VariableDefinition] -> Text
- Data.GraphQL.Parser: alias :: Parser Alias
- Data.GraphQL.Parser: argument :: Parser Argument
- Data.GraphQL.Parser: arguments :: Parser [Argument]
- Data.GraphQL.Parser: argumentsDefinition :: Parser ArgumentsDefinition
- Data.GraphQL.Parser: between :: Parser Text -> Parser Text -> Parser a -> Parser a
- Data.GraphQL.Parser: booleanValue :: Parser Bool
- Data.GraphQL.Parser: braces :: Parser a -> Parser a
- Data.GraphQL.Parser: brackets :: Parser a -> Parser a
- Data.GraphQL.Parser: defaultValue :: Parser DefaultValue
- Data.GraphQL.Parser: definition :: Parser Definition
- Data.GraphQL.Parser: directive :: Parser Directive
- Data.GraphQL.Parser: directives :: Parser [Directive]
- Data.GraphQL.Parser: document :: Parser Document
- Data.GraphQL.Parser: enumTypeDefinition :: Parser EnumTypeDefinition
- Data.GraphQL.Parser: enumValueDefinition :: Parser EnumValueDefinition
- Data.GraphQL.Parser: enumValueDefinitions :: Parser [EnumValueDefinition]
- Data.GraphQL.Parser: field :: Parser Field
- Data.GraphQL.Parser: fieldDefinition :: Parser FieldDefinition
- Data.GraphQL.Parser: fieldDefinitions :: Parser [FieldDefinition]
- Data.GraphQL.Parser: fragmentDefinition :: Parser FragmentDefinition
- Data.GraphQL.Parser: fragmentSpread :: Parser FragmentSpread
- Data.GraphQL.Parser: inlineFragment :: Parser InlineFragment
- Data.GraphQL.Parser: inputObjectTypeDefinition :: Parser InputObjectTypeDefinition
- Data.GraphQL.Parser: inputValueDefinition :: Parser InputValueDefinition
- Data.GraphQL.Parser: inputValueDefinitions :: Parser [InputValueDefinition]
- Data.GraphQL.Parser: interfaceTypeDefinition :: Parser InterfaceTypeDefinition
- Data.GraphQL.Parser: interfaces :: Parser Interfaces
- Data.GraphQL.Parser: listType :: Parser ListType
- Data.GraphQL.Parser: listValue :: Parser ListValue
- Data.GraphQL.Parser: name :: Parser Name
- Data.GraphQL.Parser: namedType :: Parser NamedType
- Data.GraphQL.Parser: node :: Parser Node
- Data.GraphQL.Parser: nonNullType :: Parser NonNullType
- Data.GraphQL.Parser: objectField :: Parser ObjectField
- Data.GraphQL.Parser: objectTypeDefinition :: Parser ObjectTypeDefinition
- Data.GraphQL.Parser: objectValue :: Parser ObjectValue
- Data.GraphQL.Parser: operationDefinition :: Parser OperationDefinition
- Data.GraphQL.Parser: optempty :: Monoid a => Parser a -> Parser a
- Data.GraphQL.Parser: parens :: Parser a -> Parser a
- Data.GraphQL.Parser: quotes :: Parser a -> Parser a
- Data.GraphQL.Parser: scalarTypeDefinition :: Parser ScalarTypeDefinition
- Data.GraphQL.Parser: selection :: Parser Selection
- Data.GraphQL.Parser: selectionSet :: Parser SelectionSet
- Data.GraphQL.Parser: stringValue :: Parser StringValue
- Data.GraphQL.Parser: tok :: Parser a -> Parser a
- Data.GraphQL.Parser: typeCondition :: Parser TypeCondition
- Data.GraphQL.Parser: typeDefinition :: Parser TypeDefinition
- Data.GraphQL.Parser: typeExtensionDefinition :: Parser TypeExtensionDefinition
- Data.GraphQL.Parser: type_ :: Parser Type
- Data.GraphQL.Parser: unionMembers :: Parser [NamedType]
- Data.GraphQL.Parser: unionTypeDefinition :: Parser UnionTypeDefinition
- Data.GraphQL.Parser: value :: Parser Value
- Data.GraphQL.Parser: variable :: Parser Variable
- Data.GraphQL.Parser: variableDefinition :: Parser VariableDefinition
- Data.GraphQL.Parser: variableDefinitions :: Parser [VariableDefinition]
- Data.GraphQL.Parser: whiteSpace :: Parser ()
+ Language.GraphQL: graphql :: MonadIO m => Schema m -> Text -> m Value
+ Language.GraphQL: graphqlSubs :: MonadIO m => Schema m -> Subs -> Text -> m Value
+ Language.GraphQL.AST: Argument :: Name -> Value -> Argument
+ Language.GraphQL.AST: DefinitionFragment :: FragmentDefinition -> Definition
+ Language.GraphQL.AST: DefinitionOperation :: OperationDefinition -> Definition
+ Language.GraphQL.AST: Directive :: Name -> [Argument] -> Directive
+ Language.GraphQL.AST: Field :: Maybe Alias -> Name -> Arguments -> Directives -> SelectionSetOpt -> Field
+ Language.GraphQL.AST: FragmentDefinition :: FragmentName -> TypeCondition -> Directives -> SelectionSet -> FragmentDefinition
+ Language.GraphQL.AST: FragmentSpread :: Name -> Directives -> FragmentSpread
+ Language.GraphQL.AST: InlineFragment :: Maybe TypeCondition -> Directives -> SelectionSet -> InlineFragment
+ Language.GraphQL.AST: Mutation :: OperationType
+ Language.GraphQL.AST: NonNullTypeList :: Type -> NonNullType
+ Language.GraphQL.AST: NonNullTypeNamed :: Name -> NonNullType
+ Language.GraphQL.AST: ObjectField :: Name -> Value -> ObjectField
+ Language.GraphQL.AST: OperationDefinition :: OperationType -> Maybe Name -> VariableDefinitions -> Directives -> SelectionSet -> OperationDefinition
+ Language.GraphQL.AST: OperationSelectionSet :: SelectionSet -> OperationDefinition
+ Language.GraphQL.AST: Query :: OperationType
+ Language.GraphQL.AST: SelectionField :: Field -> Selection
+ Language.GraphQL.AST: SelectionFragmentSpread :: FragmentSpread -> Selection
+ Language.GraphQL.AST: SelectionInlineFragment :: InlineFragment -> Selection
+ Language.GraphQL.AST: TypeList :: Type -> Type
+ Language.GraphQL.AST: TypeNamed :: Name -> Type
+ Language.GraphQL.AST: TypeNonNull :: NonNullType -> Type
+ Language.GraphQL.AST: ValueBoolean :: Bool -> Value
+ Language.GraphQL.AST: ValueEnum :: Name -> Value
+ Language.GraphQL.AST: ValueFloat :: Double -> Value
+ Language.GraphQL.AST: ValueInt :: Int32 -> Value
+ Language.GraphQL.AST: ValueList :: [Value] -> Value
+ Language.GraphQL.AST: ValueNull :: Value
+ Language.GraphQL.AST: ValueObject :: [ObjectField] -> Value
+ Language.GraphQL.AST: ValueString :: Text -> Value
+ Language.GraphQL.AST: ValueVariable :: Name -> Value
+ Language.GraphQL.AST: VariableDefinition :: Name -> Type -> Maybe Value -> VariableDefinition
+ Language.GraphQL.AST: data Argument
+ Language.GraphQL.AST: data Definition
+ Language.GraphQL.AST: data Directive
+ Language.GraphQL.AST: data Field
+ Language.GraphQL.AST: data FragmentDefinition
+ Language.GraphQL.AST: data FragmentSpread
+ Language.GraphQL.AST: data InlineFragment
+ Language.GraphQL.AST: data NonNullType
+ Language.GraphQL.AST: data ObjectField
+ Language.GraphQL.AST: data OperationDefinition
+ Language.GraphQL.AST: data OperationType
+ Language.GraphQL.AST: data Selection
+ Language.GraphQL.AST: data Type
+ Language.GraphQL.AST: data Value
+ Language.GraphQL.AST: data VariableDefinition
+ Language.GraphQL.AST: instance GHC.Classes.Eq Language.GraphQL.AST.Argument
+ Language.GraphQL.AST: instance GHC.Classes.Eq Language.GraphQL.AST.Definition
+ Language.GraphQL.AST: instance GHC.Classes.Eq Language.GraphQL.AST.Directive
+ Language.GraphQL.AST: instance GHC.Classes.Eq Language.GraphQL.AST.Field
+ Language.GraphQL.AST: instance GHC.Classes.Eq Language.GraphQL.AST.FragmentDefinition
+ Language.GraphQL.AST: instance GHC.Classes.Eq Language.GraphQL.AST.FragmentSpread
+ Language.GraphQL.AST: instance GHC.Classes.Eq Language.GraphQL.AST.InlineFragment
+ Language.GraphQL.AST: instance GHC.Classes.Eq Language.GraphQL.AST.NonNullType
+ Language.GraphQL.AST: instance GHC.Classes.Eq Language.GraphQL.AST.ObjectField
+ Language.GraphQL.AST: instance GHC.Classes.Eq Language.GraphQL.AST.OperationDefinition
+ Language.GraphQL.AST: instance GHC.Classes.Eq Language.GraphQL.AST.OperationType
+ Language.GraphQL.AST: instance GHC.Classes.Eq Language.GraphQL.AST.Selection
+ Language.GraphQL.AST: instance GHC.Classes.Eq Language.GraphQL.AST.Type
+ Language.GraphQL.AST: instance GHC.Classes.Eq Language.GraphQL.AST.Value
+ Language.GraphQL.AST: instance GHC.Classes.Eq Language.GraphQL.AST.VariableDefinition
+ Language.GraphQL.AST: instance GHC.Show.Show Language.GraphQL.AST.Argument
+ Language.GraphQL.AST: instance GHC.Show.Show Language.GraphQL.AST.Definition
+ Language.GraphQL.AST: instance GHC.Show.Show Language.GraphQL.AST.Directive
+ Language.GraphQL.AST: instance GHC.Show.Show Language.GraphQL.AST.Field
+ Language.GraphQL.AST: instance GHC.Show.Show Language.GraphQL.AST.FragmentDefinition
+ Language.GraphQL.AST: instance GHC.Show.Show Language.GraphQL.AST.FragmentSpread
+ Language.GraphQL.AST: instance GHC.Show.Show Language.GraphQL.AST.InlineFragment
+ Language.GraphQL.AST: instance GHC.Show.Show Language.GraphQL.AST.NonNullType
+ Language.GraphQL.AST: instance GHC.Show.Show Language.GraphQL.AST.ObjectField
+ Language.GraphQL.AST: instance GHC.Show.Show Language.GraphQL.AST.OperationDefinition
+ Language.GraphQL.AST: instance GHC.Show.Show Language.GraphQL.AST.OperationType
+ Language.GraphQL.AST: instance GHC.Show.Show Language.GraphQL.AST.Selection
+ Language.GraphQL.AST: instance GHC.Show.Show Language.GraphQL.AST.Type
+ Language.GraphQL.AST: instance GHC.Show.Show Language.GraphQL.AST.Value
+ Language.GraphQL.AST: instance GHC.Show.Show Language.GraphQL.AST.VariableDefinition
+ Language.GraphQL.AST: type Alias = Name
+ Language.GraphQL.AST: type Arguments = [Argument]
+ Language.GraphQL.AST: type Directives = [Directive]
+ Language.GraphQL.AST: type Document = NonEmpty Definition
+ Language.GraphQL.AST: type FragmentName = Name
+ Language.GraphQL.AST: type Name = Text
+ Language.GraphQL.AST: type SelectionSet = NonEmpty Selection
+ Language.GraphQL.AST: type SelectionSetOpt = [Selection]
+ Language.GraphQL.AST: type TypeCondition = Name
+ Language.GraphQL.AST: type VariableDefinitions = [VariableDefinition]
+ Language.GraphQL.AST.Core: Argument :: Name -> Value -> Argument
+ Language.GraphQL.AST.Core: Field :: Maybe Alias -> Name -> [Argument] -> [Field] -> Field
+ Language.GraphQL.AST.Core: Mutation :: NonEmpty Field -> Operation
+ Language.GraphQL.AST.Core: ObjectField :: Name -> Value -> ObjectField
+ Language.GraphQL.AST.Core: Query :: NonEmpty Field -> Operation
+ Language.GraphQL.AST.Core: ValueBoolean :: Bool -> Value
+ Language.GraphQL.AST.Core: ValueEnum :: Name -> Value
+ Language.GraphQL.AST.Core: ValueFloat :: Double -> Value
+ Language.GraphQL.AST.Core: ValueInt :: Int32 -> Value
+ Language.GraphQL.AST.Core: ValueList :: [Value] -> Value
+ Language.GraphQL.AST.Core: ValueNull :: Value
+ Language.GraphQL.AST.Core: ValueObject :: [ObjectField] -> Value
+ Language.GraphQL.AST.Core: ValueString :: Text -> Value
+ Language.GraphQL.AST.Core: data Argument
+ Language.GraphQL.AST.Core: data Field
+ Language.GraphQL.AST.Core: data ObjectField
+ Language.GraphQL.AST.Core: data Operation
+ Language.GraphQL.AST.Core: data Value
+ Language.GraphQL.AST.Core: instance Data.String.IsString Language.GraphQL.AST.Core.Value
+ Language.GraphQL.AST.Core: instance GHC.Classes.Eq Language.GraphQL.AST.Core.Argument
+ Language.GraphQL.AST.Core: instance GHC.Classes.Eq Language.GraphQL.AST.Core.Field
+ Language.GraphQL.AST.Core: instance GHC.Classes.Eq Language.GraphQL.AST.Core.ObjectField
+ Language.GraphQL.AST.Core: instance GHC.Classes.Eq Language.GraphQL.AST.Core.Operation
+ Language.GraphQL.AST.Core: instance GHC.Classes.Eq Language.GraphQL.AST.Core.Value
+ Language.GraphQL.AST.Core: instance GHC.Show.Show Language.GraphQL.AST.Core.Argument
+ Language.GraphQL.AST.Core: instance GHC.Show.Show Language.GraphQL.AST.Core.Field
+ Language.GraphQL.AST.Core: instance GHC.Show.Show Language.GraphQL.AST.Core.ObjectField
+ Language.GraphQL.AST.Core: instance GHC.Show.Show Language.GraphQL.AST.Core.Operation
+ Language.GraphQL.AST.Core: instance GHC.Show.Show Language.GraphQL.AST.Core.Value
+ Language.GraphQL.AST.Core: type Alias = Name
+ Language.GraphQL.AST.Core: type Document = NonEmpty Operation
+ Language.GraphQL.AST.Core: type Name = Text
+ Language.GraphQL.AST.Transform: document :: Subs -> Document -> Maybe Document
+ Language.GraphQL.Encoder: document :: Document -> Text
+ Language.GraphQL.Encoder: spaced :: Text -> Text
+ Language.GraphQL.Error: addErr :: Monad m => Value -> CollectErrsT m ()
+ Language.GraphQL.Error: addErrMsg :: Monad m => Text -> CollectErrsT m ()
+ Language.GraphQL.Error: parseError :: Applicative f => ParseErrorBundle Text Void -> f Value
+ Language.GraphQL.Error: runAppendErrs :: Monad m => CollectErrsT m a -> CollectErrsT m a
+ Language.GraphQL.Error: runCollectErrs :: Monad m => CollectErrsT m Value -> m Value
+ Language.GraphQL.Error: singleError :: Text -> Value
+ Language.GraphQL.Error: type CollectErrsT m = StateT [Value] m
+ Language.GraphQL.Execute: execute :: MonadIO m => Schema m -> Subs -> Document -> m Value
+ Language.GraphQL.Lexer: amp :: Parser Text
+ Language.GraphQL.Lexer: at :: Parser Char
+ Language.GraphQL.Lexer: bang :: Parser Char
+ Language.GraphQL.Lexer: blockString :: Parser Text
+ Language.GraphQL.Lexer: braces :: forall a. Parser a -> Parser a
+ Language.GraphQL.Lexer: brackets :: forall a. Parser a -> Parser a
+ Language.GraphQL.Lexer: colon :: Parser Text
+ Language.GraphQL.Lexer: comment :: Parser ()
+ Language.GraphQL.Lexer: dollar :: Parser Char
+ Language.GraphQL.Lexer: equals :: Parser Text
+ Language.GraphQL.Lexer: float :: Parser Double
+ Language.GraphQL.Lexer: integer :: Integral a => Parser a
+ Language.GraphQL.Lexer: lexeme :: forall a. Parser a -> Parser a
+ Language.GraphQL.Lexer: name :: Parser Text
+ Language.GraphQL.Lexer: parens :: forall a. Parser a -> Parser a
+ Language.GraphQL.Lexer: pipe :: Parser Text
+ Language.GraphQL.Lexer: spaceConsumer :: Parser ()
+ Language.GraphQL.Lexer: spread :: Parser Text
+ Language.GraphQL.Lexer: string :: Parser Text
+ Language.GraphQL.Lexer: symbol :: Text -> Parser Text
+ Language.GraphQL.Lexer: type Parser = Parsec Void Text
+ Language.GraphQL.Lexer: unicodeBOM :: Parser ()
+ Language.GraphQL.Parser: document :: Parser Document
+ Language.GraphQL.Schema: Argument :: Name -> Value -> Argument
+ Language.GraphQL.Schema: ValueBoolean :: Bool -> Value
+ Language.GraphQL.Schema: ValueEnum :: Name -> Value
+ Language.GraphQL.Schema: ValueFloat :: Double -> Value
+ Language.GraphQL.Schema: ValueInt :: Int32 -> Value
+ Language.GraphQL.Schema: ValueList :: [Value] -> Value
+ Language.GraphQL.Schema: ValueNull :: Value
+ Language.GraphQL.Schema: ValueObject :: [ObjectField] -> Value
+ Language.GraphQL.Schema: ValueString :: Text -> Value
+ Language.GraphQL.Schema: data Argument
+ Language.GraphQL.Schema: data Field
+ Language.GraphQL.Schema: data Resolver m
+ Language.GraphQL.Schema: data Value
+ Language.GraphQL.Schema: enum :: MonadIO m => Name -> ActionT m [Text] -> Resolver m
+ Language.GraphQL.Schema: enumA :: MonadIO m => Name -> (Arguments -> ActionT m [Text]) -> Resolver m
+ Language.GraphQL.Schema: object :: MonadIO m => Name -> ActionT m [Resolver m] -> Resolver m
+ Language.GraphQL.Schema: objectA :: MonadIO m => Name -> (Arguments -> ActionT m [Resolver m]) -> Resolver m
+ Language.GraphQL.Schema: resolve :: MonadIO m => [Resolver m] -> Fields -> CollectErrsT m Value
+ Language.GraphQL.Schema: scalar :: (MonadIO m, ToJSON a) => Name -> ActionT m a -> Resolver m
+ Language.GraphQL.Schema: scalarA :: (MonadIO m, ToJSON a) => Name -> (Arguments -> ActionT m a) -> Resolver m
+ Language.GraphQL.Schema: type Schema m = NonEmpty (Resolver m)
+ Language.GraphQL.Schema: type Subs = Name -> Maybe Value
+ Language.GraphQL.Schema: wrappedEnum :: MonadIO m => Name -> ActionT m (Wrapping [Text]) -> Resolver m
+ Language.GraphQL.Schema: wrappedEnumA :: MonadIO m => Name -> (Arguments -> ActionT m (Wrapping [Text])) -> Resolver m
+ Language.GraphQL.Schema: wrappedObject :: MonadIO m => Name -> ActionT m (Wrapping [Resolver m]) -> Resolver m
+ Language.GraphQL.Schema: wrappedObjectA :: MonadIO m => Name -> (Arguments -> ActionT m (Wrapping [Resolver m])) -> Resolver m
+ Language.GraphQL.Schema: wrappedScalar :: (MonadIO m, ToJSON a) => Name -> ActionT m (Wrapping a) -> Resolver m
+ Language.GraphQL.Schema: wrappedScalarA :: (MonadIO m, ToJSON a) => Name -> (Arguments -> ActionT m (Wrapping a)) -> Resolver m
+ Language.GraphQL.Trans: ActionT :: ExceptT Text m a -> ActionT m a
+ Language.GraphQL.Trans: [runActionT] :: ActionT m a -> ExceptT Text m a
+ Language.GraphQL.Trans: instance Control.Monad.IO.Class.MonadIO m => Control.Monad.IO.Class.MonadIO (Language.GraphQL.Trans.ActionT m)
+ Language.GraphQL.Trans: instance Control.Monad.Trans.Class.MonadTrans Language.GraphQL.Trans.ActionT
+ Language.GraphQL.Trans: instance GHC.Base.Functor m => GHC.Base.Functor (Language.GraphQL.Trans.ActionT m)
+ Language.GraphQL.Trans: instance GHC.Base.Monad m => GHC.Base.Alternative (Language.GraphQL.Trans.ActionT m)
+ Language.GraphQL.Trans: instance GHC.Base.Monad m => GHC.Base.Applicative (Language.GraphQL.Trans.ActionT m)
+ Language.GraphQL.Trans: instance GHC.Base.Monad m => GHC.Base.Monad (Language.GraphQL.Trans.ActionT m)
+ Language.GraphQL.Trans: instance GHC.Base.Monad m => GHC.Base.MonadPlus (Language.GraphQL.Trans.ActionT m)
+ Language.GraphQL.Trans: newtype ActionT m a
+ Language.GraphQL.Type: List :: [Wrapping a] -> Wrapping a
+ Language.GraphQL.Type: Named :: a -> Wrapping a
+ Language.GraphQL.Type: Null :: Wrapping a
+ Language.GraphQL.Type: data Wrapping a
+ Language.GraphQL.Type: instance Data.Aeson.Types.ToJSON.ToJSON a => Data.Aeson.Types.ToJSON.ToJSON (Language.GraphQL.Type.Wrapping a)
+ Language.GraphQL.Type: instance Data.Foldable.Foldable Language.GraphQL.Type.Wrapping
+ Language.GraphQL.Type: instance Data.Traversable.Traversable Language.GraphQL.Type.Wrapping
+ Language.GraphQL.Type: instance GHC.Base.Applicative Language.GraphQL.Type.Wrapping
+ Language.GraphQL.Type: instance GHC.Base.Functor Language.GraphQL.Type.Wrapping
+ Language.GraphQL.Type: instance GHC.Base.Monad Language.GraphQL.Type.Wrapping
+ Language.GraphQL.Type: instance GHC.Classes.Eq a => GHC.Classes.Eq (Language.GraphQL.Type.Wrapping a)
+ Language.GraphQL.Type: instance GHC.Show.Show a => GHC.Show.Show (Language.GraphQL.Type.Wrapping a)
Files
- CHANGELOG.md +24/−3
- Data/GraphQL/AST.hs +0/−147
- Data/GraphQL/Encoder.hs +0/−246
- Data/GraphQL/Parser.hs +0/−336
- LICENSE +2/−1
- README.md +8/−8
- docs/tutorial/tutorial.lhs +154/−0
- graphql.cabal +77/−43
- src/Language/GraphQL.hs +36/−0
- src/Language/GraphQL/AST.hs +134/−0
- src/Language/GraphQL/AST/Core.hs +48/−0
- src/Language/GraphQL/AST/Transform.hs +120/−0
- src/Language/GraphQL/Encoder.hs +180/−0
- src/Language/GraphQL/Error.hs +83/−0
- src/Language/GraphQL/Execute.hs +42/−0
- src/Language/GraphQL/Lexer.hs +226/−0
- src/Language/GraphQL/Parser.hs +185/−0
- src/Language/GraphQL/Schema.hs +172/−0
- src/Language/GraphQL/Trans.hs +37/−0
- src/Language/GraphQL/Type.hs +57/−0
- stack.yaml +0/−5
- tests/Language/GraphQL/ErrorSpec.hs +24/−0
- tests/Language/GraphQL/LexerSpec.hs +104/−0
- tests/Language/GraphQL/ParserSpec.hs +18/−0
- tests/Spec.hs +1/−0
- tests/Test/KitchenSinkSpec.hs +29/−0
- tests/Test/StarWars/Data.hs +197/−0
- tests/Test/StarWars/QuerySpec.hs +351/−0
- tests/Test/StarWars/Schema.hs +63/−0
- tests/tasty.hs +0/−28
CHANGELOG.md view
@@ -1,6 +1,26 @@ # Change Log All notable changes to this project will be documented in this file. +## [0.4.0.0] - 2019-07-23+### Added+- Support for mutations.+- Error handling (with monad transformers).+- Nullable types.+- Arbitrary nested lists support.+- Potential BOM header parsing.++### Changed+- attoparsec is replaced with megaparsec.+- The library is now under `Language.GraphQL` (instead of `Data.GraphQL`).+- HUnit and tasty are replaced with Hspec.+- `Alternative`/`MonadPlus` resolver constraints are replaced with `MonadIO`.++### Removed+- Duplicates from `Language.GraphQL.AST` already available in+ `Language.GraphQL.AST.Core`.+- All module exports are now explicit, so private and help functions aren't+ exported anymore.+ ## [0.3] - 2015-09-22 ### Changed - Exact match numeric types to spec.@@ -33,6 +53,7 @@ ### Added - Data types for the GraphQL language. -[0.3]: https://github.com/jdnavarro/graphql-haskell/compare/v0.2.1...v0.3-[0.2.1]: https://github.com/jdnavarro/graphql-haskell/compare/v0.2...v0.2.1-[0.2]: https://github.com/jdnavarro/graphql-haskell/compare/v0.1...v0.2+[0.4.0.0]: https://github.com/caraus-ecms/graphql/compare/v0.3...v0.4.0.0+[0.3]: https://github.com/caraus-ecms/graphql/compare/v0.2.1...v0.3+[0.2.1]: https://github.com/caraus-ecms/graphql/compare/v0.2...v0.2.1+[0.2]: https://github.com/caraus-ecms/graphql/compare/v0.1...v0.2
− Data/GraphQL/AST.hs
@@ -1,147 +0,0 @@-module Data.GraphQL.AST where--import Data.Int (Int32)-import Data.Text (Text)---- * Name--type Name = Text---- * Document--newtype Document = Document [Definition] deriving (Eq,Show)--data Definition = DefinitionOperation OperationDefinition- | DefinitionFragment FragmentDefinition- | DefinitionType TypeDefinition- deriving (Eq,Show)--data OperationDefinition = Query Node- | Mutation Node- deriving (Eq,Show)--data Node = Node Name [VariableDefinition] [Directive] SelectionSet- deriving (Eq,Show)--data VariableDefinition = VariableDefinition Variable Type (Maybe DefaultValue)- deriving (Eq,Show)--newtype Variable = Variable Name deriving (Eq,Show)--type SelectionSet = [Selection]--data Selection = SelectionField Field- | SelectionFragmentSpread FragmentSpread- | SelectionInlineFragment InlineFragment- deriving (Eq,Show)--data Field = Field Alias Name [Argument]- [Directive]- SelectionSet- deriving (Eq,Show)--type Alias = Name--data Argument = Argument Name Value deriving (Eq,Show)---- * Fragments--data FragmentSpread = FragmentSpread Name [Directive]- deriving (Eq,Show)--data InlineFragment =- InlineFragment TypeCondition [Directive] SelectionSet- deriving (Eq,Show)--data FragmentDefinition =- FragmentDefinition Name TypeCondition [Directive] SelectionSet- deriving (Eq,Show)--type TypeCondition = NamedType---- * Values--data Value = ValueVariable Variable- | ValueInt Int32- -- GraphQL Float is double precison- | ValueFloat Double- | ValueBoolean Bool- | ValueString StringValue- | ValueEnum Name- | ValueList ListValue- | ValueObject ObjectValue- deriving (Eq,Show)--newtype StringValue = StringValue Text deriving (Eq,Show)--newtype ListValue = ListValue [Value] deriving (Eq,Show)--newtype ObjectValue = ObjectValue [ObjectField] deriving (Eq,Show)--data ObjectField = ObjectField Name Value deriving (Eq,Show)--type DefaultValue = Value---- * Directives--data Directive = Directive Name [Argument] deriving (Eq,Show)---- * Type Reference--data Type = TypeNamed NamedType- | TypeList ListType- | TypeNonNull NonNullType- deriving (Eq,Show)--newtype NamedType = NamedType Name deriving (Eq,Show)--newtype ListType = ListType Type deriving (Eq,Show)--data NonNullType = NonNullTypeNamed NamedType- | NonNullTypeList ListType- deriving (Eq,Show)---- * Type definition--data TypeDefinition = TypeDefinitionObject ObjectTypeDefinition- | TypeDefinitionInterface InterfaceTypeDefinition- | TypeDefinitionUnion UnionTypeDefinition- | TypeDefinitionScalar ScalarTypeDefinition- | TypeDefinitionEnum EnumTypeDefinition- | TypeDefinitionInputObject InputObjectTypeDefinition- | TypeDefinitionTypeExtension TypeExtensionDefinition- deriving (Eq,Show)--data ObjectTypeDefinition = ObjectTypeDefinition Name Interfaces [FieldDefinition]- deriving (Eq,Show)--type Interfaces = [NamedType]--data FieldDefinition = FieldDefinition Name ArgumentsDefinition Type- deriving (Eq,Show)--type ArgumentsDefinition = [InputValueDefinition]--data InputValueDefinition = InputValueDefinition Name Type (Maybe DefaultValue)- deriving (Eq,Show)--data InterfaceTypeDefinition = InterfaceTypeDefinition Name [FieldDefinition]- deriving (Eq,Show)--data UnionTypeDefinition = UnionTypeDefinition Name [NamedType]- deriving (Eq,Show)--data ScalarTypeDefinition = ScalarTypeDefinition Name- deriving (Eq,Show)--data EnumTypeDefinition = EnumTypeDefinition Name [EnumValueDefinition]- deriving (Eq,Show)--newtype EnumValueDefinition = EnumValueDefinition Name- deriving (Eq,Show)--data InputObjectTypeDefinition = InputObjectTypeDefinition Name [InputValueDefinition]- deriving (Eq,Show)--newtype TypeExtensionDefinition = TypeExtensionDefinition ObjectTypeDefinition- deriving (Eq,Show)
− Data/GraphQL/Encoder.hs
@@ -1,246 +0,0 @@-{-# LANGUAGE CPP #-}-{-# LANGUAGE OverloadedStrings #-}-module Data.GraphQL.Encoder where--#if !MIN_VERSION_base(4,8,0)-import Control.Applicative ((<$>))-import Data.Monoid (Monoid, mconcat, mempty)-#endif-import Data.Monoid ((<>))--import Data.Text (Text, cons, intercalate, pack, snoc)--import Data.GraphQL.AST---- * Document---- TODO: Use query shorthand-document :: Document -> Text-document (Document defs) = (`snoc` '\n') . mconcat $ definition <$> defs--definition :: Definition -> Text-definition (DefinitionOperation x) = operationDefinition x-definition (DefinitionFragment x) = fragmentDefinition x-definition (DefinitionType x) = typeDefinition x--operationDefinition :: OperationDefinition -> Text-operationDefinition (Query n) = "query " <> node n-operationDefinition (Mutation n) = "mutation " <> node n--node :: Node -> Text-node (Node name vds ds ss) =- name- <> optempty variableDefinitions vds- <> optempty directives ds- <> selectionSet ss--variableDefinitions :: [VariableDefinition] -> Text-variableDefinitions = parensCommas variableDefinition--variableDefinition :: VariableDefinition -> Text-variableDefinition (VariableDefinition var ty dv) =- variable var <> ":" <> type_ ty <> maybe mempty defaultValue dv--defaultValue :: DefaultValue -> Text-defaultValue val = "=" <> value val--variable :: Variable -> Text-variable (Variable name) = "$" <> name--selectionSet :: SelectionSet -> Text-selectionSet = bracesCommas selection--selection :: Selection -> Text-selection (SelectionField x) = field x-selection (SelectionInlineFragment x) = inlineFragment x-selection (SelectionFragmentSpread x) = fragmentSpread x--field :: Field -> Text-field (Field alias name args ds ss) =- optempty (`snoc` ':') alias- <> name- <> optempty arguments args- <> optempty directives ds- <> optempty selectionSet ss--arguments :: [Argument] -> Text-arguments = parensCommas argument--argument :: Argument -> Text-argument (Argument name v) = name <> ":" <> value v---- * Fragments--fragmentSpread :: FragmentSpread -> Text-fragmentSpread (FragmentSpread name ds) =- "..." <> name <> optempty directives ds--inlineFragment :: InlineFragment -> Text-inlineFragment (InlineFragment (NamedType tc) ds ss) =- "... on " <> tc- <> optempty directives ds- <> optempty selectionSet ss--fragmentDefinition :: FragmentDefinition -> Text-fragmentDefinition (FragmentDefinition name (NamedType tc) ds ss) =- "fragment " <> name <> " on " <> tc- <> optempty directives ds- <> selectionSet ss---- * Values--value :: Value -> Text-value (ValueVariable x) = variable x--- TODO: This will be replaced with `decimal` Buidler-value (ValueInt x) = pack $ show x--- TODO: This will be replaced with `decimal` Buidler-value (ValueFloat x) = pack $ show x-value (ValueBoolean x) = booleanValue x-value (ValueString x) = stringValue x-value (ValueEnum x) = x-value (ValueList x) = listValue x-value (ValueObject x) = objectValue x--booleanValue :: Bool -> Text-booleanValue True = "true"-booleanValue False = "false"---- TODO: Escape characters-stringValue :: StringValue -> Text-stringValue (StringValue v) = quotes v--listValue :: ListValue -> Text-listValue (ListValue vs) = bracketsCommas value vs--objectValue :: ObjectValue -> Text-objectValue (ObjectValue ofs) = bracesCommas objectField ofs--objectField :: ObjectField -> Text-objectField (ObjectField name v) = name <> ":" <> value v---- * Directives--directives :: [Directive] -> Text-directives = spaces directive--directive :: Directive -> Text-directive (Directive name args) = "@" <> name <> optempty arguments args---- * Type Reference--type_ :: Type -> Text-type_ (TypeNamed (NamedType x)) = x-type_ (TypeList x) = listType x-type_ (TypeNonNull x) = nonNullType x--namedType :: NamedType -> Text-namedType (NamedType name) = name--listType :: ListType -> Text-listType (ListType ty) = brackets (type_ ty)--nonNullType :: NonNullType -> Text-nonNullType (NonNullTypeNamed (NamedType x)) = x <> "!"-nonNullType (NonNullTypeList x) = listType x <> "!"--typeDefinition :: TypeDefinition -> Text-typeDefinition (TypeDefinitionObject x) = objectTypeDefinition x-typeDefinition (TypeDefinitionInterface x) = interfaceTypeDefinition x-typeDefinition (TypeDefinitionUnion x) = unionTypeDefinition x-typeDefinition (TypeDefinitionScalar x) = scalarTypeDefinition x-typeDefinition (TypeDefinitionEnum x) = enumTypeDefinition x-typeDefinition (TypeDefinitionInputObject x) = inputObjectTypeDefinition x-typeDefinition (TypeDefinitionTypeExtension x) = typeExtensionDefinition x--objectTypeDefinition :: ObjectTypeDefinition -> Text-objectTypeDefinition (ObjectTypeDefinition name ifaces fds) =- "type " <> name- <> optempty (spaced . interfaces) ifaces- <> optempty fieldDefinitions fds--interfaces :: Interfaces -> Text-interfaces = ("implements " <>) . spaces namedType--fieldDefinitions :: [FieldDefinition] -> Text-fieldDefinitions = bracesCommas fieldDefinition--fieldDefinition :: FieldDefinition -> Text-fieldDefinition (FieldDefinition name args ty) =- name <> optempty argumentsDefinition args- <> ":"- <> type_ ty--argumentsDefinition :: ArgumentsDefinition -> Text-argumentsDefinition = parensCommas inputValueDefinition--interfaceTypeDefinition :: InterfaceTypeDefinition -> Text-interfaceTypeDefinition (InterfaceTypeDefinition name fds) =- "interface " <> name <> fieldDefinitions fds--unionTypeDefinition :: UnionTypeDefinition -> Text-unionTypeDefinition (UnionTypeDefinition name ums) =- "union " <> name <> "=" <> unionMembers ums--unionMembers :: [NamedType] -> Text-unionMembers = intercalate "|" . fmap namedType--scalarTypeDefinition :: ScalarTypeDefinition -> Text-scalarTypeDefinition (ScalarTypeDefinition name) = "scalar " <> name--enumTypeDefinition :: EnumTypeDefinition -> Text-enumTypeDefinition (EnumTypeDefinition name evds) =- "enum " <> name- <> bracesCommas enumValueDefinition evds--enumValueDefinition :: EnumValueDefinition -> Text-enumValueDefinition (EnumValueDefinition name) = name--inputObjectTypeDefinition :: InputObjectTypeDefinition -> Text-inputObjectTypeDefinition (InputObjectTypeDefinition name ivds) =- "input " <> name <> inputValueDefinitions ivds--inputValueDefinitions :: [InputValueDefinition] -> Text-inputValueDefinitions = bracesCommas inputValueDefinition--inputValueDefinition :: InputValueDefinition -> Text-inputValueDefinition (InputValueDefinition name ty dv) =- name <> ":" <> type_ ty <> maybe mempty defaultValue dv--typeExtensionDefinition :: TypeExtensionDefinition -> Text-typeExtensionDefinition (TypeExtensionDefinition otd) =- "extend " <> objectTypeDefinition otd---- * Internal--spaced :: Text -> Text-spaced = cons '\SP'--between :: Char -> Char -> Text -> Text-between open close = cons open . (`snoc` close)--parens :: Text -> Text-parens = between '(' ')'--brackets :: Text -> Text-brackets = between '[' ']'--braces :: Text -> Text-braces = between '{' '}'--quotes :: Text -> Text-quotes = between '"' '"'--spaces :: (a -> Text) -> [a] -> Text-spaces f = intercalate "\SP" . fmap f--parensCommas :: (a -> Text) -> [a] -> Text-parensCommas f = parens . intercalate "," . fmap f--bracketsCommas :: (a -> Text) -> [a] -> Text-bracketsCommas f = brackets . intercalate "," . fmap f--bracesCommas :: (a -> Text) -> [a] -> Text-bracesCommas f = braces . intercalate "," . fmap f--optempty :: (Eq a, Monoid a, Monoid b) => (a -> b) -> a -> b-optempty f xs = if xs == mempty then mempty else f xs
− Data/GraphQL/Parser.hs
@@ -1,336 +0,0 @@-{-# LANGUAGE CPP #-}-{-# LANGUAGE OverloadedStrings #-}-module Data.GraphQL.Parser where--import Prelude hiding (takeWhile)--#if !MIN_VERSION_base(4,8,0)-import Control.Applicative ((<$>), (<*>), (*>), (<*), (<$), pure)-import Data.Monoid (Monoid, mempty)-#endif-import Control.Applicative ((<|>), empty, many, optional)-import Control.Monad (when)-import Data.Char (isDigit, isSpace)-import Data.Foldable (traverse_)--import Data.Text (Text, append)-import Data.Attoparsec.Text- ( Parser- , (<?>)- , anyChar- , decimal- , double- , endOfLine- , inClass- , many1- , manyTill- , option- , peekChar- , sepBy1- , signed- , takeWhile- , takeWhile1- )--import Data.GraphQL.AST---- * Name--name :: Parser Name-name = tok $ append <$> takeWhile1 isA_z- <*> takeWhile ((||) <$> isDigit <*> isA_z)- where- -- `isAlpha` handles many more Unicode Chars- isA_z = inClass $ '_' : ['A'..'Z'] ++ ['a'..'z']---- * Document--document :: Parser Document-document = whiteSpace- *> (Document <$> many1 definition)- -- Try SelectionSet when no definition- <|> (Document . pure- . DefinitionOperation- . Query- . Node mempty empty empty- <$> selectionSet)- <?> "document error!"--definition :: Parser Definition-definition = DefinitionOperation <$> operationDefinition- <|> DefinitionFragment <$> fragmentDefinition- <|> DefinitionType <$> typeDefinition- <?> "definition error!"--operationDefinition :: Parser OperationDefinition-operationDefinition =- Query <$ tok "query" <*> node- <|> Mutation <$ tok "mutation" <*> node- <?> "operationDefinition error!"--node :: Parser Node-node = Node <$> name- <*> optempty variableDefinitions- <*> optempty directives- <*> selectionSet--variableDefinitions :: Parser [VariableDefinition]-variableDefinitions = parens (many1 variableDefinition)--variableDefinition :: Parser VariableDefinition-variableDefinition =- VariableDefinition <$> variable- <* tok ":"- <*> type_- <*> optional defaultValue--defaultValue :: Parser DefaultValue-defaultValue = tok "=" *> value--variable :: Parser Variable-variable = Variable <$ tok "$" <*> name--selectionSet :: Parser SelectionSet-selectionSet = braces $ many1 selection--selection :: Parser Selection-selection = SelectionField <$> field- -- Inline first to catch `on` case- <|> SelectionInlineFragment <$> inlineFragment- <|> SelectionFragmentSpread <$> fragmentSpread- <?> "selection error!"--field :: Parser Field-field = Field <$> optempty alias- <*> name- <*> optempty arguments- <*> optempty directives- <*> optempty selectionSet--alias :: Parser Alias-alias = name <* tok ":"--arguments :: Parser [Argument]-arguments = parens $ many1 argument--argument :: Parser Argument-argument = Argument <$> name <* tok ":" <*> value---- * Fragments--fragmentSpread :: Parser FragmentSpread--- TODO: Make sure it fails when `... on`.--- See https://facebook.github.io/graphql/#FragmentSpread-fragmentSpread = FragmentSpread- <$ tok "..."- <*> name- <*> optempty directives---- InlineFragment tried first in order to guard against 'on' keyword-inlineFragment :: Parser InlineFragment-inlineFragment = InlineFragment- <$ tok "..."- <* tok "on"- <*> typeCondition- <*> optempty directives- <*> selectionSet--fragmentDefinition :: Parser FragmentDefinition-fragmentDefinition = FragmentDefinition- <$ tok "fragment"- <*> name- <* tok "on"- <*> typeCondition- <*> optempty directives- <*> selectionSet--typeCondition :: Parser TypeCondition-typeCondition = namedType---- * 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 = ValueVariable <$> variable- -- TODO: Handle maxBound, Int32 in spec.- <|> ValueInt <$> tok (signed decimal)- <|> ValueFloat <$> tok (signed double)- <|> ValueBoolean <$> booleanValue- <|> ValueString <$> stringValue- -- `true` and `false` have been tried before- <|> ValueEnum <$> name- <|> ValueList <$> listValue- <|> ValueObject <$> objectValue- <?> "value error!"--booleanValue :: Parser Bool-booleanValue = True <$ tok "true"- <|> False <$ tok "false"---- TODO: Escape characters. Look at `jsstring_` in aeson package.-stringValue :: Parser StringValue-stringValue = StringValue <$> quotes (takeWhile (/= '"'))---- Notice it can be empty-listValue :: Parser ListValue-listValue = ListValue <$> brackets (many value)---- Notice it can be empty-objectValue :: Parser ObjectValue-objectValue = ObjectValue <$> braces (many objectField)--objectField :: Parser ObjectField-objectField = ObjectField <$> name <* tok ":" <*> value---- * Directives--directives :: Parser [Directive]-directives = many1 directive--directive :: Parser Directive-directive = Directive- <$ tok "@"- <*> name- <*> optempty 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!"---- * Type Definition--typeDefinition :: Parser TypeDefinition-typeDefinition =- TypeDefinitionObject <$> objectTypeDefinition- <|> TypeDefinitionInterface <$> interfaceTypeDefinition- <|> TypeDefinitionUnion <$> unionTypeDefinition- <|> TypeDefinitionScalar <$> scalarTypeDefinition- <|> TypeDefinitionEnum <$> enumTypeDefinition- <|> TypeDefinitionInputObject <$> inputObjectTypeDefinition- <|> TypeDefinitionTypeExtension <$> typeExtensionDefinition- <?> "typeDefinition error!"--objectTypeDefinition :: Parser ObjectTypeDefinition-objectTypeDefinition = ObjectTypeDefinition- <$ tok "type"- <*> name- <*> optempty interfaces- <*> fieldDefinitions--interfaces :: Parser Interfaces-interfaces = tok "implements" *> many1 namedType--fieldDefinitions :: Parser [FieldDefinition]-fieldDefinitions = braces $ many1 fieldDefinition--fieldDefinition :: Parser FieldDefinition-fieldDefinition = FieldDefinition- <$> name- <*> optempty argumentsDefinition- <* tok ":"- <*> type_--argumentsDefinition :: Parser ArgumentsDefinition-argumentsDefinition = parens $ many1 inputValueDefinition--interfaceTypeDefinition :: Parser InterfaceTypeDefinition-interfaceTypeDefinition = InterfaceTypeDefinition- <$ tok "interface"- <*> name- <*> fieldDefinitions--unionTypeDefinition :: Parser UnionTypeDefinition-unionTypeDefinition = UnionTypeDefinition- <$ tok "union"- <*> name- <* tok "="- <*> unionMembers--unionMembers :: Parser [NamedType]-unionMembers = namedType `sepBy1` tok "|"--scalarTypeDefinition :: Parser ScalarTypeDefinition-scalarTypeDefinition = ScalarTypeDefinition- <$ tok "scalar"- <*> name--enumTypeDefinition :: Parser EnumTypeDefinition-enumTypeDefinition = EnumTypeDefinition- <$ tok "enum"- <*> name- <*> enumValueDefinitions--enumValueDefinitions :: Parser [EnumValueDefinition]-enumValueDefinitions = braces $ many1 enumValueDefinition--enumValueDefinition :: Parser EnumValueDefinition-enumValueDefinition = EnumValueDefinition <$> name--inputObjectTypeDefinition :: Parser InputObjectTypeDefinition-inputObjectTypeDefinition = InputObjectTypeDefinition- <$ tok "input"- <*> name- <*> inputValueDefinitions--inputValueDefinitions :: Parser [InputValueDefinition]-inputValueDefinitions = braces $ many1 inputValueDefinition--inputValueDefinition :: Parser InputValueDefinition-inputValueDefinition = InputValueDefinition- <$> name- <* tok ":"- <*> type_- <*> optional defaultValue--typeExtensionDefinition :: Parser TypeExtensionDefinition-typeExtensionDefinition = TypeExtensionDefinition- <$ tok "extend"- <*> objectTypeDefinition---- * Internal--tok :: Parser a -> Parser a-tok p = p <* whiteSpace--parens :: Parser a -> Parser a-parens = between "(" ")"--braces :: Parser a -> Parser a-braces = between "{" "}"--quotes :: Parser a -> Parser a-quotes = between "\"" "\""--brackets :: Parser a -> Parser a-brackets = between "[" "]"--between :: Parser Text -> Parser Text -> Parser a -> Parser a-between open close p = tok open *> p <* tok close---- `empty` /= `pure mempty` for `Parser`.-optempty :: Monoid a => Parser a -> Parser a-optempty = option mempty---- ** WhiteSpace----whiteSpace :: Parser ()-whiteSpace = peekChar >>= traverse_ (\c ->- if isSpace c || c == ','- then anyChar *> whiteSpace- else when (c == '#') $ manyTill anyChar endOfLine *> whiteSpace)
LICENSE view
@@ -1,4 +1,5 @@-Copyright J. Daniel Navarro (c) 2015+Copyright 2019 Eugen Wissner, Germany+Copyright 2015-2017 J. Daniel Navarro All rights reserved.
README.md view
@@ -1,23 +1,23 @@ # Haskell GraphQL [](https://hackage.haskell.org/package/graphql)-[](https://travis-ci.org/jdnavarro/graphql-haskell)+[](https://semaphoreci.com/belka-ew/graphql)+[](https://raw.githubusercontent.com/caraus-ecms/graphql/master/LICENSE) -For now this only provides the data types to represent the GraphQL AST,-but the idea is to be a Haskell port of-[`graphql-js`](https://github.com/graphql/graphql-js). Next releases-should include:+For now this only provides a parser for the GraphQL query language and allows+to execute queries and mutations without the schema validation step.+But the idea is to be a Haskell port of+[`graphql-js`](https://github.com/graphql/graphql-js). Next releases should+include: - [x] GraphQL AST-- [x] Parser for the GraphQL language. See TODO for limitations.+- [x] Parser for the GraphQL language. - [x] Printer for GraphQL. This is not pretty yet. - [ ] GraphQL Schema AST. - [ ] Parser for the GraphQL Schema language. - [ ] Printer for the GraphQL Schema language. - [ ] Interpreter of GraphQL requests. - [ ] Utilities to define GraphQL types and schema.--See the TODO file for more concrete tasks. ## Contact
+ docs/tutorial/tutorial.lhs view
@@ -0,0 +1,154 @@+---+title: GraphQL Haskell Tutorial+---+++== Getting started ==++Welcome to graphql-haskell!++We have written a small tutorial to help you (and ourselves) understand the graphql package.++Since this file is a literate haskell file, we start by importing some dependencies.++> {-# LANGUAGE OverloadedStrings #-}+> {-# LANGUAGE LambdaCase #-}+> module Main where+>+> import Control.Monad.IO.Class (liftIO)+> import Control.Monad.Trans.Except (throwE)+> import Data.Aeson (encode)+> import Data.ByteString.Lazy.Char8 (putStrLn)+> import Data.List.NonEmpty (NonEmpty(..))+> import Data.Text (Text)+> import Data.Time (getCurrentTime)+>+> import Language.GraphQL+> import Language.GraphQL.Schema (Schema)+> import qualified Language.GraphQL.Schema as Schema+> import Language.GraphQL.Trans (ActionT(..))+>+> import Prelude hiding (putStrLn)++=== First example ===++Now, as our first example, we are going to look at the+example from [graphql.js](https://github.com/graphql/graphql-js).++First we build a GraphQL schema.++> schema1 :: Schema IO+> schema1 = hello :| []+>+> hello :: Schema.Resolver IO+> hello = Schema.scalar "hello" (return ("it's me" :: Text))++This defines a simple schema with one type and one field, that resolves to a fixed value.++Next we define our query.++> query1 :: Text+> query1 = "{ hello }"+++To run the query, we call the `graphql` with the schema and the query.++> main1 :: IO ()+> main1 = putStrLn =<< encode <$> graphql schema1 query1++This runs the query by fetching the one field defined,+returning++```{"data" : {"hello":"it's me"}}```++++=== Monadic actions ===++For this example, we're going to be using time.++> schema2 :: Schema IO+> schema2 = time :| []+>+> time :: Schema.Resolver IO+> time = Schema.scalarA "time" $ \case+> [] -> do t <- liftIO getCurrentTime+> return $ show t+> _ -> ActionT $ throwE "Invalid arguments."++This defines a simple schema with one type and one field,+which resolves to the current time.++Next we define our query.++> query2 :: Text+> query2 = "{ time }"+>+> main2 :: IO ()+> main2 = putStrLn =<< encode <$> graphql schema2 query2++This runs the query, returning the current time++```{"data": {"time":"2016-03-08 23:28:14.546899 UTC"}}```+++=== Errors ===++Errors are handled according to the spec,+with fields that cause erros being resolved to `null`,+and an error being added to the error list.++An example of this is the following query:++> queryShouldFail :: Text+> queryShouldFail = "{ boyhowdy }"++Since there is no `boyhowdy` field in our schema, it will not resolve,+and the query will fail, as we can see in the following example.++> mainShouldFail :: IO ()+> mainShouldFail = do+> success <- graphql schema1 query1+> putStrLn $ encode success+> putStrLn "This will fail"+> failure <- graphql schema1 queryShouldFail+> putStrLn $ encode failure+>++This outputs:++```+{"data": {"hello": "it's me"}}+This will fail+{"data": {"boyhowdy": null}, "errors":[{"message": "the field boyhowdy did not resolve."}]}+```++=== Combining resolvers ===++Now that we have two resolvers, we can define a schema which uses them both.++> schema3 :: Schema IO+> schema3 = hello :| [time]+>+> query3 :: Text+> query3 = "query timeAndHello { time hello }"+>+> main3 :: IO ()+> main3 = putStrLn =<< encode <$> graphql schema3 query3++This queries for both time and hello, returning++```{ "data": {"hello":"it's me","time":"2016-03-08 23:29:11.62108 UTC"}}```++Notice that we can name our queries, as we did with `timeAndHello`. Since we have only been using single queries, we can use the shorthand `{ time hello}`, as we have been doing in the previous examples.++In GraphQL there can only be one operation per query.+++== Further examples ==++More examples on queries and a more complex schema can be found in the test directory,+in the [Test.StarWars](../../tests/Test/StarWars) module. This includes a more complex schema, and more complex queries.++> main :: IO ()+> main = main1 >> main2 >> mainShouldFail >> main3
graphql.cabal view
@@ -1,48 +1,82 @@-name: graphql-version: 0.3-synopsis: Haskell GraphQL implementation+cabal-version: 1.12+name: graphql+version: 0.4.0.0+license: BSD3+license-file: LICENSE+copyright: (c) 2019 Eugen Wissner,+ (c) 2015-2017 J. Daniel Navarro+maintainer: belka@caraus.de+author: Danny Navarro <j@dannynavarro.net>,+ Matthías Páll Gissurarson <mpg@mpg.is>,+ Sólrún Halla Einarsdóttir <she@mpg.is>+homepage: https://github.com/caraus-ecms/graphql#readme+bug-reports: https://github.com/caraus-ecms/graphql/issues+synopsis: Haskell GraphQL implementation description:- This package provides a rudimentary parser for the- <https://facebook.github.io/graphql/ GraphQL> language.-homepage: https://github.com/jdnavarro/graphql-haskell-bug-reports: https://github.com/jdnavarro/graphql-haskell/issues-license: BSD3-license-file: LICENSE-author: Danny Navarro-maintainer: j@dannynavarro.net-copyright: Copyright (C) 2015 J. Daniel Navarro-category: Web-build-type: Simple-cabal-version: >=1.10-tested-with: GHC == 7.8.4, GHC == 7.10.2-extra-source-files: README.md CHANGELOG.md stack.yaml-data-files: tests/data/*.graphql- tests/data/*.min.graphql+ This package provides a rudimentary parser for the <https://graphql.github.io/graphql-spec/June2018/ GraphQL> language.+category: Language+build-type: Simple+data-files:+ tests/data/kitchen-sink.graphql+ tests/data/kitchen-sink.min.graphql+extra-source-files:+ CHANGELOG.md+ README.md+ LICENSE+ docs/tutorial/tutorial.lhs +source-repository head+ type: git+ location: https://github.com/caraus-ecms/graphql+ library- default-language: Haskell2010- ghc-options: -Wall- exposed-modules: Data.GraphQL.AST- Data.GraphQL.Encoder- Data.GraphQL.Parser- build-depends: base >=4.7 && < 5,- text >=0.11.3.1,- attoparsec >=0.10.4.0+ exposed-modules:+ Language.GraphQL+ Language.GraphQL.AST+ Language.GraphQL.AST.Core+ Language.GraphQL.AST.Transform+ Language.GraphQL.Encoder+ Language.GraphQL.Error+ Language.GraphQL.Execute+ Language.GraphQL.Lexer+ Language.GraphQL.Parser+ Language.GraphQL.Schema+ Language.GraphQL.Trans+ Language.GraphQL.Type+ hs-source-dirs: src+ other-modules:+ Paths_graphql+ default-language: Haskell2010+ build-depends:+ aeson >=1.4.2.0 && <1.5,+ base >=4.7 && <5,+ megaparsec >=7.0.5 && <7.1,+ text >=1.2.3.1 && <1.3,+ transformers >=0.5.6.2 && <0.6,+ unordered-containers >=0.2.10.0 && <0.3 test-suite tasty- default-language: Haskell2010- type: exitcode-stdio-1.0- hs-source-dirs: tests- main-is: tasty.hs- ghc-options: -Wall- other-modules: Paths_graphql- build-depends: base >=4.6 && <5,- text >=0.11.3.1,- attoparsec >=0.10.4.0,- tasty >=0.10,- tasty-hunit >=0.9,- graphql--source-repository head- type: git- location: git://github.com/jdnavarro/graphql-haskell.git+ type: exitcode-stdio-1.0+ main-is: Spec.hs+ hs-source-dirs: tests+ other-modules:+ Language.GraphQL.ErrorSpec+ Language.GraphQL.LexerSpec+ Language.GraphQL.ParserSpec+ Test.KitchenSinkSpec+ Test.StarWars.Data+ Test.StarWars.QuerySpec+ Test.StarWars.Schema+ Paths_graphql+ default-language: Haskell2010+ ghc-options: -threaded -rtsopts -with-rtsopts=-N+ build-depends:+ aeson >=1.4.2.0 && <1.5,+ base >=4.7 && <5,+ graphql -any,+ hspec >=2.6.1 && <2.7,+ hspec-expectations >=0.8.2 && <0.9,+ megaparsec >=7.0.5 && <7.1,+ raw-strings-qq ==1.1.*,+ text >=1.2.3.1 && <1.3,+ transformers >=0.5.6.2 && <0.6
+ src/Language/GraphQL.hs view
@@ -0,0 +1,36 @@+-- | This module provides the functions to parse and execute @GraphQL@ queries.+module Language.GraphQL+ ( graphql+ , graphqlSubs+ ) where++import Control.Monad.IO.Class (MonadIO)+import qualified Data.Text as T++import qualified Data.Aeson as Aeson+import Text.Megaparsec (parse)++import Language.GraphQL.Execute+import Language.GraphQL.Parser+import Language.GraphQL.Schema++import Language.GraphQL.Error++-- | Takes a 'Schema' and text representing a @GraphQL@ request document.+-- If the text parses correctly as a @GraphQL@ query the query is+-- executed according to the given 'Schema'.+--+-- Returns the response as an @Aeson.@'Aeson.Value'.+graphql :: MonadIO m => Schema m -> T.Text -> m Aeson.Value+graphql = flip graphqlSubs $ const Nothing++-- | Takes a 'Schema', a variable substitution function and text+-- representing a @GraphQL@ request document. If the text parses+-- correctly as a @GraphQL@ query the substitution is applied to the+-- query and the query is then executed according to the given 'Schema'.+--+-- Returns the response as an @Aeson.@'Aeson.Value'.+graphqlSubs :: MonadIO m => Schema m -> Subs -> T.Text -> m Aeson.Value+graphqlSubs schema f =+ either parseError (execute schema f)+ . parse document ""
+ src/Language/GraphQL/AST.hs view
@@ -0,0 +1,134 @@+-- | This module defines an abstract syntax tree for the @GraphQL@ language based on+-- <https://facebook.github.io/graphql/ Facebook's GraphQL Specification>.+--+-- Target AST for Parser.+module Language.GraphQL.AST+ ( Alias+ , Argument(..)+ , Arguments+ , Definition(..)+ , Directive(..)+ , Directives+ , Document+ , Field(..)+ , FragmentDefinition(..)+ , FragmentName+ , FragmentSpread(..)+ , InlineFragment(..)+ , Name+ , NonNullType(..)+ , ObjectField(..)+ , OperationDefinition(..)+ , OperationType(..)+ , Selection(..)+ , SelectionSet+ , SelectionSetOpt+ , Type(..)+ , 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+ )++-- * Document++type Document = NonEmpty Definition++-- * Operations++data Definition = DefinitionOperation OperationDefinition+ | DefinitionFragment FragmentDefinition+ deriving (Eq,Show)++data OperationDefinition = OperationSelectionSet SelectionSet+ | OperationDefinition OperationType+ (Maybe Name)+ VariableDefinitions+ Directives+ SelectionSet+ deriving (Eq,Show)++data OperationType = Query | Mutation deriving (Eq,Show)++-- * SelectionSet++type SelectionSet = NonEmpty Selection++type SelectionSetOpt = [Selection]++data Selection = SelectionField Field+ | SelectionFragmentSpread FragmentSpread+ | SelectionInlineFragment InlineFragment+ deriving (Eq,Show)++-- * Field++data Field = Field (Maybe Alias) Name Arguments Directives SelectionSetOpt+ deriving (Eq,Show)++-- * Arguments++type Arguments = [Argument]++data Argument = Argument Name Value deriving (Eq,Show)++-- * Fragments++data FragmentSpread = FragmentSpread Name Directives deriving (Eq,Show)++data InlineFragment = InlineFragment (Maybe TypeCondition) Directives SelectionSet+ deriving (Eq,Show)++data FragmentDefinition =+ FragmentDefinition FragmentName TypeCondition Directives SelectionSet+ deriving (Eq,Show)++type FragmentName = Name++type TypeCondition = Name++-- * Input values++data Value = ValueVariable Name+ | ValueInt Int32+ | ValueFloat Double+ | ValueString Text+ | ValueBoolean Bool+ | ValueNull+ | ValueEnum Name+ | ValueList [Value]+ | ValueObject [ObjectField]+ deriving (Eq, Show)++data ObjectField = ObjectField Name Value deriving (Eq, Show)++-- * Variables++type VariableDefinitions = [VariableDefinition]++data VariableDefinition = VariableDefinition Name Type (Maybe Value)+ deriving (Eq,Show)++-- * Input types++data Type = TypeNamed Name+ | TypeList Type+ | TypeNonNull NonNullType+ deriving (Eq,Show)++data NonNullType = NonNullTypeNamed Name+ | NonNullTypeList Type+ deriving (Eq,Show)++-- * Directives++type Directives = [Directive]++data Directive = Directive Name [Argument] deriving (Eq,Show)
+ src/Language/GraphQL/AST/Core.hs view
@@ -0,0 +1,48 @@+-- | This is the AST meant to be executed.+module Language.GraphQL.AST.Core+ ( Alias+ , Argument(..)+ , Document+ , Field(..)+ , Name+ , ObjectField(..)+ , Operation(..)+ , Value(..)+ ) where++import Data.Int (Int32)+import Data.List.NonEmpty (NonEmpty)+import Data.String++import Data.Text (Text)++-- | Name+type Name = Text++type Document = NonEmpty Operation++data Operation = Query (NonEmpty Field)+ | Mutation (NonEmpty 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 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++data ObjectField = ObjectField Name Value deriving (Eq,Show)
+ src/Language/GraphQL/AST/Transform.hs view
@@ -0,0 +1,120 @@+{-# LANGUAGE OverloadedStrings #-}+module Language.GraphQL.AST.Transform+ ( document+ ) where++import Control.Applicative (empty)+import Control.Monad ((<=<))+import Data.Bifunctor (first)+import Data.Either (partitionEithers)+import Data.Foldable (fold, foldMap)+import qualified Data.List.NonEmpty as NonEmpty+import Data.Monoid (Alt(Alt,getAlt), (<>))+import qualified Language.GraphQL.AST as Full+import qualified Language.GraphQL.AST.Core as Core+import qualified Language.GraphQL.Schema as Schema++-- | Replaces a fragment name by a list of 'Field'. If the name doesn't match an+-- empty list is returned.+type Fragmenter = Core.Name -> [Core.Field]++-- TODO: Replace Maybe by MonadThrow with CustomError+document :: Schema.Subs -> Full.Document -> Maybe Core.Document+document subs doc = operations subs fr ops+ where+ (fr, ops) = first foldFrags+ . partitionEithers+ . NonEmpty.toList+ $ defrag subs+ <$> doc++ foldFrags :: [Fragmenter] -> Fragmenter+ foldFrags fs n = getAlt $ foldMap (Alt . ($ n)) fs++-- * Operation++-- TODO: Replace Maybe by MonadThrow CustomError+operations+ :: Schema.Subs+ -> Fragmenter+ -> [Full.OperationDefinition]+ -> Maybe Core.Document+operations subs fr = NonEmpty.nonEmpty <=< traverse (operation subs fr)++-- TODO: Replace Maybe by MonadThrow CustomError+operation+ :: Schema.Subs+ -> Fragmenter+ -> Full.OperationDefinition+ -> Maybe Core.Operation+operation subs fr (Full.OperationSelectionSet sels) =+ operation subs fr $ Full.OperationDefinition Full.Query empty empty empty sels+-- TODO: Validate Variable definitions with substituter+operation subs fr (Full.OperationDefinition ot _n _vars _dirs sels) =+ case ot of+ Full.Query -> Core.Query <$> node+ Full.Mutation -> Core.Mutation <$> node+ where+ node = traverse (hush . selection subs fr) sels++selection+ :: Schema.Subs+ -> Fragmenter+ -> Full.Selection+ -> Either [Core.Field] Core.Field+selection subs fr (Full.SelectionField fld) =+ Right $ field subs fr fld+selection _ fr (Full.SelectionFragmentSpread (Full.FragmentSpread n _dirs)) =+ Left $ fr n+selection _ _ (Full.SelectionInlineFragment _) =+ error "Inline fragments not supported yet"++-- * 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' =+ -- TODO: Support fragments within fragments. Fold instead of map.+ if name == name'+ then either id pure =<< NonEmpty.toList (selection subs mempty <$> sels)+ else empty++field :: Schema.Subs -> Fragmenter -> Full.Field -> Core.Field+field subs fr (Full.Field a n args _dirs sels) =+ Core.Field a n (fold $ argument subs `traverse` args) (foldr go empty sels)+ where+ go :: Full.Selection -> [Core.Field] -> [Core.Field]+ go (Full.SelectionFragmentSpread (Full.FragmentSpread name _dirs)) = (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++hush :: Either a b -> Maybe b+hush = either (const Nothing) Just
+ src/Language/GraphQL/Encoder.hs view
@@ -0,0 +1,180 @@+{-# LANGUAGE OverloadedStrings #-}+-- | This module defines a printer for the @GraphQL@ language.+module Language.GraphQL.Encoder+ ( document+ , spaced+ ) where++import Data.Foldable (fold)+import Data.Monoid ((<>))+import qualified Data.List.NonEmpty as NonEmpty (toList)+import Data.Text (Text, cons, intercalate, pack, snoc)+import Language.GraphQL.AST++-- * Document++document :: Document -> Text+document defs = (`snoc` '\n') . mconcat . NonEmpty.toList $ definition <$> defs++definition :: Definition -> Text+definition (DefinitionOperation x) = operationDefinition x+definition (DefinitionFragment x) = fragmentDefinition x++operationDefinition :: OperationDefinition -> Text+operationDefinition (OperationSelectionSet sels) = selectionSet sels+operationDefinition (OperationDefinition Query name vars dirs sels) =+ "query " <> node (fold name) vars dirs sels+operationDefinition (OperationDefinition Mutation name vars dirs sels) =+ "mutation " <> node (fold name) vars dirs sels++node :: Name -> VariableDefinitions -> Directives -> SelectionSet -> Text+node name vars dirs sels =+ name+ <> optempty variableDefinitions vars+ <> optempty directives dirs+ <> selectionSet sels++variableDefinitions :: [VariableDefinition] -> Text+variableDefinitions = parensCommas variableDefinition++variableDefinition :: VariableDefinition -> Text+variableDefinition (VariableDefinition var ty dv) =+ variable var <> ":" <> type_ ty <> maybe mempty defaultValue dv++defaultValue :: Value -> Text+defaultValue val = "=" <> value val++variable :: Name -> Text+variable var = "$" <> var++selectionSet :: SelectionSet -> Text+selectionSet = bracesCommas selection . NonEmpty.toList++selectionSetOpt :: SelectionSetOpt -> Text+selectionSetOpt = bracesCommas selection++selection :: Selection -> Text+selection (SelectionField x) = field x+selection (SelectionInlineFragment x) = inlineFragment x+selection (SelectionFragmentSpread x) = fragmentSpread x++field :: Field -> Text+field (Field alias name args dirs selso) =+ optempty (`snoc` ':') (fold alias)+ <> name+ <> optempty arguments args+ <> optempty directives dirs+ <> optempty selectionSetOpt selso++arguments :: [Argument] -> Text+arguments = parensCommas argument++argument :: Argument -> Text+argument (Argument name v) = name <> ":" <> value v++-- * Fragments++fragmentSpread :: FragmentSpread -> Text+fragmentSpread (FragmentSpread name ds) =+ "..." <> name <> optempty directives ds++inlineFragment :: InlineFragment -> Text+inlineFragment (InlineFragment tc dirs sels) =+ "... on " <> fold tc+ <> directives dirs+ <> selectionSet sels++fragmentDefinition :: FragmentDefinition -> Text+fragmentDefinition (FragmentDefinition name tc dirs sels) =+ "fragment " <> name <> " on " <> tc+ <> optempty directives dirs+ <> selectionSet sels++-- * Values++value :: Value -> Text+value (ValueVariable x) = variable x+-- TODO: This will be replaced with `decimal` Builder+value (ValueInt x) = pack $ show x+-- TODO: This will be replaced with `decimal` Builder+value (ValueFloat x) = pack $ show x+value (ValueBoolean x) = booleanValue x+value ValueNull = mempty+value (ValueString x) = stringValue x+value (ValueEnum x) = x+value (ValueList x) = listValue x+value (ValueObject x) = objectValue x++booleanValue :: Bool -> Text+booleanValue True = "true"+booleanValue False = "false"++-- TODO: Escape characters+stringValue :: Text -> Text+stringValue = quotes++listValue :: [Value] -> Text+listValue = bracketsCommas value++objectValue :: [ObjectField] -> Text+objectValue = bracesCommas objectField++objectField :: ObjectField -> Text+objectField (ObjectField name v) = name <> ":" <> value v++-- * Directives++directives :: [Directive] -> Text+directives = spaces directive++directive :: Directive -> Text+directive (Directive name args) = "@" <> name <> optempty arguments args++-- * Type Reference++type_ :: Type -> Text+type_ (TypeNamed x) = x+type_ (TypeList x) = listType x+type_ (TypeNonNull x) = nonNullType x++listType :: Type -> Text+listType x = brackets (type_ x)++nonNullType :: NonNullType -> Text+nonNullType (NonNullTypeNamed x) = x <> "!"+nonNullType (NonNullTypeList x) = listType x <> "!"++-- * Internal++spaced :: Text -> Text+spaced = cons '\SP'++between :: Char -> Char -> Text -> Text+between open close = cons open . (`snoc` close)++parens :: Text -> Text+parens = between '(' ')'++brackets :: Text -> Text+brackets = between '[' ']'++braces :: Text -> Text+braces = between '{' '}'++quotes :: Text -> Text+quotes = between '"' '"'++spaces :: (a -> Text) -> [a] -> Text+spaces f = intercalate "\SP" . fmap f++parensCommas :: (a -> Text) -> [a] -> Text+parensCommas f = parens . intercalate "," . fmap f++bracketsCommas :: (a -> Text) -> [a] -> Text+bracketsCommas f = brackets . intercalate "," . fmap f++bracesCommas :: (a -> Text) -> [a] -> Text+bracesCommas f = braces . intercalate "," . fmap f++optempty :: (Eq a, Monoid a, Monoid b) => (a -> b) -> a -> b+optempty f xs = if xs == mempty then mempty else f xs
+ src/Language/GraphQL/Error.hs view
@@ -0,0 +1,83 @@+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE RecordWildCards #-}+module Language.GraphQL.Error+ ( parseError+ , CollectErrsT+ , addErr+ , addErrMsg+ , runCollectErrs+ , runAppendErrs+ , singleError+ ) where++import qualified Data.Aeson as Aeson+import Data.Text (Text)+import Data.Void (Void)+import Control.Monad.Trans.Class (lift)+import Control.Monad.Trans.State ( StateT+ , modify+ , runStateT+ )+import Text.Megaparsec ( ParseErrorBundle(..)+ , SourcePos(..)+ , errorOffset+ , parseErrorTextPretty+ , reachOffset+ , unPos+ )++-- | Wraps a parse error into a list of errors.+parseError :: Applicative f => ParseErrorBundle Text Void -> f Aeson.Value+parseError ParseErrorBundle{..} =+ pure $ Aeson.object [("errors", Aeson.toJSON $ fst $ foldl go ([], bundlePosState) bundleErrors)]+ where+ errorObject s SourcePos{..} = Aeson.object+ [ ("message", Aeson.toJSON $ init $ parseErrorTextPretty s)+ , ("line", Aeson.toJSON $ unPos sourceLine)+ , ("column", Aeson.toJSON $ unPos sourceColumn)+ ]+ go (result, state) x =+ let (sourcePosition, _, newState) = reachOffset (errorOffset x) state+ in (errorObject x sourcePosition : result, newState)++-- | A wrapper to pass error messages around.+type CollectErrsT m = StateT [Aeson.Value] m++-- | Adds an error to the list of errors.+addErr :: Monad m => Aeson.Value -> CollectErrsT m ()+addErr v = modify (v :)++makeErrorMessage :: Text -> Aeson.Value+makeErrorMessage s = Aeson.object [("message", Aeson.toJSON s)]++-- | Constructs a response object containing only the error with the given+-- message.+singleError :: Text -> Aeson.Value+singleError message = Aeson.object+ [ ("errors", Aeson.toJSON [makeErrorMessage message])+ ]++-- | Convenience function for just wrapping an error message.+addErrMsg :: Monad m => Text -> CollectErrsT m ()+addErrMsg = addErr . makeErrorMessage++-- | Appends the given list of errors to the current list of errors.+appendErrs :: Monad m => [Aeson.Value] -> CollectErrsT m ()+appendErrs errs = modify (errs ++)++-- | Runs the given query computation, but collects the errors into an error+-- list, which is then sent back with the data.+runCollectErrs :: Monad m => CollectErrsT m Aeson.Value -> m Aeson.Value+runCollectErrs res = do+ (dat, errs) <- runStateT res []+ if null errs+ then return $ Aeson.object [("data", dat)]+ else return $ Aeson.object [("data", dat), ("errors", Aeson.toJSON $ reverse errs)]++-- | Runs the given computation, collecting the errors and appending them+-- to the previous list of errors.+runAppendErrs :: Monad m => CollectErrsT m a -> CollectErrsT m a+runAppendErrs f = do+ (v, errs) <- lift $ runStateT f []+ appendErrs errs+ return v
+ src/Language/GraphQL/Execute.hs view
@@ -0,0 +1,42 @@+{-# LANGUAGE OverloadedStrings #-}++-- | This module provides the function to execute a @GraphQL@ request --+-- according to a 'Schema'.+module Language.GraphQL.Execute+ ( execute+ ) where++import Control.Monad.IO.Class (MonadIO)+import qualified Data.List.NonEmpty as NE+import Data.List.NonEmpty (NonEmpty((:|)))+import qualified Data.Aeson as Aeson+import qualified Language.GraphQL.AST as AST+import qualified Language.GraphQL.AST.Core as AST.Core+import qualified Language.GraphQL.AST.Transform as Transform+import Language.GraphQL.Error+import Language.GraphQL.Schema (Schema)+import qualified Language.GraphQL.Schema as Schema++-- | Takes a 'Schema', a variable substitution function ('Schema.Subs'), and a+-- @GraphQL@ 'document'. The substitution is applied to the document using+-- 'rootFields', and the 'Schema''s resolvers are applied to the resulting fields.+--+-- Returns the result of the query against the 'Schema' wrapped in a /data/ field, or+-- errors wrapped in an /errors/ field.+execute+ :: MonadIO m+ => Schema m -> Schema.Subs -> AST.Document -> m Aeson.Value+execute schema subs doc =+ maybe transformError (document schema) $ Transform.document subs doc+ where+ transformError = return $ singleError "Schema transformation error."++document :: MonadIO m => Schema m -> AST.Core.Document -> m Aeson.Value+document schema (op :| []) = operation schema op+document _ _ = return $ singleError "Multiple operations not supported yet."++operation :: MonadIO m => Schema m -> AST.Core.Operation -> m Aeson.Value+operation schema (AST.Core.Query flds)+ = runCollectErrs (Schema.resolve (NE.toList schema) (NE.toList flds))+operation schema (AST.Core.Mutation flds)+ = runCollectErrs (Schema.resolve (NE.toList schema) (NE.toList flds))
+ src/Language/GraphQL/Lexer.hs view
@@ -0,0 +1,226 @@+{-# LANGUAGE ExplicitForAll #-}+{-# LANGUAGE OverloadedStrings #-}++-- | This module defines a bunch of small parsers used to parse individual+-- lexemes.+module Language.GraphQL.Lexer + ( Parser+ , amp+ , at+ , bang+ , blockString+ , braces+ , brackets+ , colon+ , dollar+ , comment+ , equals+ , integer+ , float+ , lexeme+ , name+ , parens+ , pipe+ , spaceConsumer+ , spread+ , string+ , symbol+ , unicodeBOM+ ) where++import Control.Applicative ( Alternative(..)+ , liftA2+ )+import Data.Char ( chr+ , digitToInt+ , isAsciiLower+ , isAsciiUpper+ , ord+ )+import Data.Foldable (foldl')+import Data.List (dropWhileEnd)+import Data.Proxy (Proxy(..))+import Data.Void (Void)+import Text.Megaparsec ( Parsec+ , between+ , chunk+ , chunkToTokens+ , notFollowedBy+ , oneOf+ , option+ , optional+ , satisfy+ , sepBy+ , skipSome+ , takeP+ , takeWhile1P+ , try+ )+import Text.Megaparsec.Char ( char+ , digitChar+ , space1+ )+import qualified Text.Megaparsec.Char.Lexer as Lexer+import qualified Data.Text as T+import qualified Data.Text.Lazy as TL++-- | Standard parser.+-- Accepts the type of the parsed token.+type Parser = Parsec Void T.Text++ignoredCharacters :: Parser ()+ignoredCharacters = space1 <|> skipSome (char ',')++spaceConsumer :: Parser ()+spaceConsumer = Lexer.space ignoredCharacters comment empty++-- | Parser for comments.+comment :: Parser ()+comment = Lexer.skipLineComment "#"++-- | Lexeme definition which ignores whitespaces and commas.+lexeme :: forall a. Parser a -> Parser a+lexeme = Lexer.lexeme spaceConsumer++-- | Symbol definition which ignores whitespaces and commas.+symbol :: T.Text -> Parser T.Text+symbol = Lexer.symbol spaceConsumer++-- | Parser for "!".+bang :: Parser Char+bang = char '!'++-- | Parser for "$".+dollar :: Parser Char+dollar = char '$'++-- | Parser for "@".+at :: Parser Char+at = char '@'++-- | Parser for "&".+amp :: Parser T.Text+amp = symbol "&"++-- | Parser for ":".+colon :: Parser T.Text+colon = symbol ":"++-- | Parser for "=".+equals :: Parser T.Text+equals = symbol "="++-- | Parser for the spread operator (...).+spread :: Parser T.Text+spread = symbol "..."++-- | Parser for "|".+pipe :: Parser T.Text+pipe = symbol "|"++-- | Parser for an expression between "(" and ")".+parens :: forall a. Parser a -> Parser a+parens = between (symbol "(") (symbol ")")++-- | Parser for an expression between "[" and "]".+brackets :: forall a. Parser a -> Parser a+brackets = between (symbol "[") (symbol "]")++-- | Parser for an expression between "{" and "}".+braces :: forall a. Parser a -> Parser a+braces = between (symbol "{") (symbol "}")++-- | Parser for strings.+string :: Parser T.Text+string = between "\"" "\"" stringValue+ where+ stringValue = T.pack <$> many stringCharacter+ stringCharacter = satisfy isStringCharacter1+ <|> escapeSequence+ isStringCharacter1 = liftA2 (&&) isSourceCharacter isChunkDelimiter++-- | Parser for block strings.+blockString :: Parser T.Text+blockString = between "\"\"\"" "\"\"\"" stringValue+ where+ stringValue = do+ byLine <- sepBy (many blockStringCharacter) lineTerminator+ let indentSize = foldr countIndent 0 $ tail byLine+ withoutIndent = head byLine : (removeIndent indentSize <$> tail byLine)+ withoutEmptyLines = liftA2 (.) dropWhile dropWhileEnd removeEmptyLine withoutIndent++ return $ T.intercalate "\n" $ T.concat <$> withoutEmptyLines+ removeEmptyLine [] = True+ removeEmptyLine [x] = T.null x || isWhiteSpace (T.head x)+ removeEmptyLine _ = False+ blockStringCharacter+ = takeWhile1P Nothing isWhiteSpace+ <|> takeWhile1P Nothing isBlockStringCharacter1+ <|> escapeTripleQuote+ <|> try (chunk "\"" <* notFollowedBy (chunk "\"\""))+ escapeTripleQuote = chunk "\\" >>= flip option (chunk "\"\"")+ isBlockStringCharacter1 = liftA2 (&&) isSourceCharacter isChunkDelimiter+ countIndent [] acc = acc+ countIndent (x:_) acc+ | T.null x = acc+ | not (isWhiteSpace $ T.head x) = acc+ | acc == 0 = T.length x+ | otherwise = min acc $ T.length x+ removeIndent _ [] = []+ removeIndent n (x:chunks) = T.drop n x : chunks++-- | Parser for integers.+integer :: Integral a => Parser a+integer = Lexer.signed (pure ()) $ lexeme Lexer.decimal++-- | Parser for floating-point numbers.+float :: Parser Double+float = Lexer.signed (pure ()) $ lexeme Lexer.float++-- | Parser for names (/[_A-Za-z][_0-9A-Za-z]*/).+name :: Parser T.Text+name = do+ firstLetter <- nameFirstLetter+ rest <- many $ nameFirstLetter <|> digitChar+ _ <- spaceConsumer+ return $ TL.toStrict $ TL.cons firstLetter $ TL.pack rest+ where+ nameFirstLetter = satisfy isAsciiUpper <|> satisfy isAsciiLower <|> char '_'++isChunkDelimiter :: Char -> Bool+isChunkDelimiter = flip notElem ['"', '\\', '\n', '\r']++isWhiteSpace :: Char -> Bool+isWhiteSpace = liftA2 (||) (== ' ') (== '\t')++lineTerminator :: Parser T.Text+lineTerminator = chunk "\r\n" <|> chunk "\n" <|> chunk "\r"++isSourceCharacter :: Char -> Bool+isSourceCharacter = isSourceCharacter' . ord+ where+ isSourceCharacter' code = code >= 0x0020+ || code == 0x0009+ || code == 0x000a+ || code == 0x000d++escapeSequence :: Parser Char+escapeSequence = do+ _ <- char '\\'+ escaped <- oneOf ['"', '\\', '/', 'b', 'f', 'n', 'r', 't', 'u']+ case escaped of+ 'b' -> return '\b'+ 'f' -> return '\f'+ 'n' -> return '\n'+ 'r' -> return '\r'+ 't' -> return '\t'+ 'u' -> chr . foldl' step 0+ . chunkToTokens (Proxy :: Proxy T.Text)+ <$> takeP Nothing 4+ _ -> return escaped+ where+ step accumulator = (accumulator * 16 +) . digitToInt++-- | Parser for the "Byte Order Mark".+unicodeBOM :: Parser ()+unicodeBOM = optional (char '\xfeff') >> pure ()
+ src/Language/GraphQL/Parser.hs view
@@ -0,0 +1,185 @@+{-# LANGUAGE LambdaCase #-}+{-# LANGUAGE OverloadedStrings #-}+module Language.GraphQL.Parser+ ( document+ ) where++import Control.Applicative ( Alternative(..)+ , optional+ )+import Data.List.NonEmpty (NonEmpty(..))+import Language.GraphQL.AST+import Language.GraphQL.Lexer+import Text.Megaparsec ( lookAhead+ , option+ , try+ , (<?>)+ )++document :: Parser Document+document = unicodeBOM >> spaceConsumer >> lexeme (manyNE definition)++definition :: Parser Definition+definition = DefinitionOperation <$> operationDefinition+ <|> DefinitionFragment <$> fragmentDefinition+ <?> "definition error!"++operationDefinition :: Parser OperationDefinition+operationDefinition = OperationSelectionSet <$> selectionSet+ <|> OperationDefinition <$> operationType+ <*> optional name+ <*> opt variableDefinitions+ <*> opt directives+ <*> selectionSet+ <?> "operationDefinition error"++operationType :: Parser OperationType+operationType = Query <$ symbol "query"+ <|> Mutation <$ symbol "mutation"+ <?> "operationType error"++-- * SelectionSet++selectionSet :: Parser SelectionSet+selectionSet = braces $ manyNE selection++selectionSetOpt :: Parser SelectionSetOpt+selectionSetOpt = braces $ some selection++selection :: Parser Selection+selection = SelectionField <$> field+ <|> try (SelectionFragmentSpread <$> fragmentSpread)+ <|> SelectionInlineFragment <$> inlineFragment+ <?> "selection error!"++-- * Field++field :: Parser Field+field = Field <$> optional alias+ <*> name+ <*> opt arguments+ <*> opt directives+ <*> opt selectionSetOpt++alias :: Parser Alias+alias = try $ name <* colon++-- * Arguments++arguments :: Parser Arguments+arguments = parens $ some argument++argument :: Parser Argument+argument = Argument <$> name <* colon <*> value++-- * Fragments++fragmentSpread :: Parser FragmentSpread+fragmentSpread = FragmentSpread <$ spread+ <*> fragmentName+ <*> opt directives++inlineFragment :: Parser InlineFragment+inlineFragment = InlineFragment <$ spread+ <*> optional typeCondition+ <*> opt directives+ <*> selectionSet++fragmentDefinition :: Parser FragmentDefinition+fragmentDefinition = FragmentDefinition+ <$ symbol "fragment"+ <*> name+ <*> typeCondition+ <*> opt directives+ <*> selectionSet++fragmentName :: Parser FragmentName+fragmentName = but (symbol "on") *> name++typeCondition :: Parser TypeCondition+typeCondition = symbol "on" *> name++-- * Input Values++value :: Parser Value+value = ValueVariable <$> variable+ <|> ValueFloat <$> try float+ <|> ValueInt <$> integer+ <|> ValueBoolean <$> booleanValue+ <|> ValueNull <$ symbol "null"+ <|> ValueString <$> string+ <|> ValueString <$> blockString+ <|> ValueEnum <$> try enumValue+ <|> ValueList <$> listValue+ <|> ValueObject <$> objectValue+ <?> "value error!"+ where+ booleanValue :: Parser Bool+ booleanValue = True <$ symbol "true"+ <|> False <$ symbol "false"++ enumValue :: Parser Name+ enumValue = but (symbol "true") *> but (symbol "false") *> but (symbol "null") *> name++ listValue :: Parser [Value]+ listValue = brackets $ some value++ objectValue :: Parser [ObjectField]+ objectValue = braces $ some objectField++objectField :: Parser ObjectField+objectField = ObjectField <$> name <* symbol ":" <*> value++-- * Variables++variableDefinitions :: Parser VariableDefinitions+variableDefinitions = parens $ some variableDefinition++variableDefinition :: Parser VariableDefinition+variableDefinition = VariableDefinition <$> variable+ <* colon+ <*> type_+ <*> optional defaultValue+variable :: Parser Name+variable = dollar *> name++defaultValue :: Parser Value+defaultValue = equals *> value++-- * Input Types++type_ :: Parser Type+type_ = try (TypeNamed <$> name <* but "!")+ <|> TypeList <$> brackets type_+ <|> TypeNonNull <$> nonNullType+ <?> "type_ error!"++nonNullType :: Parser NonNullType+nonNullType = NonNullTypeNamed <$> name <* bang+ <|> NonNullTypeList <$> brackets type_ <* bang+ <?> "nonNullType error!"++-- * Directives++directives :: Parser Directives+directives = some directive++directive :: Parser Directive+directive = Directive+ <$ at+ <*> name+ <*> opt arguments++-- * Internal++opt :: Monoid a => Parser a -> Parser a+opt = 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
+ src/Language/GraphQL/Schema.hs view
@@ -0,0 +1,172 @@+{-# LANGUAGE OverloadedStrings #-}++-- | This module provides a representation of a @GraphQL@ Schema in addition to+-- 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+ , wrappedScalarA+ -- * AST Reexports+ , Field+ , Argument(..)+ , Value(..)+ ) where++import Control.Monad.IO.Class (MonadIO(..))+import Control.Monad.Trans.Class (lift)+import Control.Monad.Trans.Except (runExceptT)+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.Text (Text)+import qualified Data.Text as T+import Language.GraphQL.Error+import Language.GraphQL.Trans+import Language.GraphQL.Type+import Language.GraphQL.AST.Core++-- | A GraphQL schema.+-- @m@ is usually expected to be an instance of 'MonadIO'.+type Schema m = NonEmpty (Resolver m)++-- | Resolves a 'Field' into an @Aeson.@'Aeson.Object' with error information+-- (or 'empty'). @m@ is usually expected to be an instance of 'MonadIO.+data Resolver m = Resolver+ Text -- ^ Name+ (Field -> CollectErrsT m Aeson.Object) -- ^ Resolver++type Fields = [Field]++type Arguments = [Argument]++-- | Variable substitution function.+type Subs = Name -> Maybe Value++-- | Create a new 'Resolver' with the given 'Name' from the given 'Resolver's.+object :: MonadIO m => Name -> ActionT m [Resolver m] -> Resolver m+object name = objectA name . const++-- | Like 'object' but also taking 'Argument's.+objectA :: MonadIO m+ => Name -> (Arguments -> ActionT m [Resolver m]) -> Resolver m+objectA name f = Resolver name $ resolveFieldValue f resolveRight+ where+ resolveRight fld@(Field _ _ _ flds) resolver = withField (resolve resolver flds) fld++-- | Like 'object' but also taking 'Argument's and can be null or a list of objects.+wrappedObjectA :: MonadIO m+ => Name -> (Arguments -> ActionT m (Wrapping [Resolver m])) -> Resolver m+wrappedObjectA name f = Resolver name $ resolveFieldValue f resolveRight+ where+ resolveRight fld@(Field _ _ _ sels) resolver+ = withField (traverse (`resolve` sels) resolver) fld++-- | Like 'object' but can be null or a list of objects.+wrappedObject :: MonadIO m+ => Name -> ActionT m (Wrapping [Resolver m]) -> Resolver m+wrappedObject name = wrappedObjectA name . const++-- | A scalar represents a primitive value, like a string or an integer.+scalar :: (MonadIO m, Aeson.ToJSON a) => Name -> ActionT m a -> Resolver m+scalar name = scalarA name . const++-- | Like 'scalar' but also taking 'Argument's.+scalarA :: (MonadIO m, Aeson.ToJSON a)+ => Name -> (Arguments -> ActionT m a) -> Resolver m+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.+wrappedScalarA :: (MonadIO m, Aeson.ToJSON a)+ => Name -> (Arguments -> ActionT m (Wrapping a)) -> Resolver m+wrappedScalarA name f = Resolver name $ resolveFieldValue f resolveRight+ where+ resolveRight fld (Named result) = withField (return result) fld+ resolveRight fld Null+ = return $ HashMap.singleton (aliasOrName fld) Aeson.Null+ resolveRight fld (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+wrappedScalar name = wrappedScalarA name . const++-- | Represents one of a finite set of possible values.+-- Used in place of a 'scalar' when the possible responses are easily enumerable.+enum :: MonadIO m => Name -> ActionT m [Text] -> Resolver m+enum name = enumA name . const++-- | Like 'enum' but also taking 'Argument's.+enumA :: MonadIO m => Name -> (Arguments -> ActionT m [Text]) -> Resolver m+enumA name f = Resolver name $ resolveFieldValue f resolveRight+ where+ resolveRight fld resolver = withField (return resolver) fld++-- | Like 'enum' but also taking 'Argument's and can be null or a list of enums.+wrappedEnumA :: MonadIO m+ => Name -> (Arguments -> 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++-- | Like 'enum' but can be null or a list of enums.+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+ either resolveLeft (resolveRight fld) result+ where+ resolveLeft err = do+ _ <- addErrMsg err+ return $ HashMap.singleton (aliasOrName fld) Aeson.Null++-- | Helper function to facilitate 'Argument' handling.+withField :: (MonadIO m, Aeson.ToJSON a)+ => CollectErrsT m a -> Field -> CollectErrsT m (HashMap Text Aeson.Value)+withField v fld+ = HashMap.singleton (aliasOrName fld) . Aeson.toJSON <$> runAppendErrs v++-- | 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+-- resolved 'Field', or a null value and error information.+resolve :: MonadIO m+ => [Resolver m] -> Fields -> CollectErrsT m Aeson.Value+resolve resolvers = fmap (Aeson.toJSON . fold) . traverse tryResolvers+ where+ tryResolvers fld = maybe (errmsg fld) (tryResolver fld) $ find (compareResolvers fld) resolvers+ compareResolvers (Field _ name _ _) (Resolver name' _) = name == name'+ tryResolver fld (Resolver _ resolver) = resolver fld+ errmsg fld@(Field _ name _ _) = do+ addErrMsg $ T.unwords ["field", name, "not resolved."]+ return $ HashMap.singleton (aliasOrName fld) Aeson.Null++aliasOrName :: Field -> Text+aliasOrName (Field alias name _ _) = fromMaybe name alias
+ src/Language/GraphQL/Trans.hs view
@@ -0,0 +1,37 @@+module Language.GraphQL.Trans+ ( ActionT(..)+ ) where++import Control.Applicative (Alternative(..))+import Control.Monad (MonadPlus(..))+import Control.Monad.IO.Class (MonadIO(..))+import Control.Monad.Trans.Class (MonadTrans(..))+import Control.Monad.Trans.Except (ExceptT)+import Data.Text (Text)++newtype ActionT m a = ActionT { runActionT :: ExceptT Text m a }++instance Functor m => Functor (ActionT m) where+ fmap f = ActionT . fmap f . runActionT++instance Monad m => Applicative (ActionT m) where+ pure = ActionT . pure+ (ActionT f) <*> (ActionT x) = ActionT $ f <*> x++instance Monad m => Monad (ActionT m) where+ return = pure+ (ActionT action) >>= f = ActionT $ action >>= runActionT . f++instance MonadTrans ActionT where+ lift = ActionT . lift++instance MonadIO m => MonadIO (ActionT m) where+ liftIO = lift . liftIO++instance Monad m => Alternative (ActionT m) where+ empty = ActionT empty+ (ActionT x) <|> (ActionT y) = ActionT $ x <|> y++instance Monad m => MonadPlus (ActionT m) where+ mzero = empty+ mplus = (<|>)
+ src/Language/GraphQL/Type.hs view
@@ -0,0 +1,57 @@+-- | Definitions for @GraphQL@ type system.+module Language.GraphQL.Type+ ( Wrapping(..)+ ) where++import Data.Aeson as Aeson ( ToJSON+ , toJSON+ )+import qualified Data.Aeson as Aeson++-- | GraphQL distinguishes between "wrapping" and "named" types. Each wrapping+-- type can wrap other wrapping or named types. Wrapping types are lists and+-- Non-Null types (named types are nullable by default).+--+-- This 'Wrapping' type doesn\'t reflect this distinction exactly but it is+-- used in the resolvers to take into account that the returned value can be+-- nullable or an (arbitrary nested) list.+data Wrapping a+ = List [Wrapping a] -- ^ Arbitrary nested list+ | Named a -- ^ Named type without further wrapping+ | Null -- ^ Null+ deriving (Eq, Show)++instance Functor Wrapping where+ fmap f (List list) = List $ fmap (fmap f) list+ fmap f (Named named) = Named $ f named+ fmap _ Null = Null++instance Foldable Wrapping where+ foldr f acc (List list) = foldr (flip $ foldr f) acc list+ foldr f acc (Named named) = f named acc+ foldr _ acc Null = acc++instance Traversable Wrapping where+ traverse f (List list) = List <$> traverse (traverse f) list+ traverse f (Named named) = Named <$> f named+ traverse _ Null = pure Null++instance Applicative Wrapping where+ pure = Named+ Null <*> _ = Null+ _ <*> Null = Null+ (Named f) <*> (Named x) = Named $ f x+ (List fs) <*> (List xs) = List $ (<*>) <$> fs <*> xs+ (Named f) <*> list = f <$> list+ (List fs) <*> named = List $ (<*> named) <$> fs++instance Monad Wrapping where+ return = pure+ Null >>= _ = Null+ (Named x) >>= f = f x+ (List xs) >>= f = List $ fmap (>>= f) xs++instance ToJSON a => ToJSON (Wrapping a) where+ toJSON (List list) = toJSON list+ toJSON (Named named) = toJSON named+ toJSON Null = Aeson.Null
− stack.yaml
@@ -1,5 +0,0 @@-flags: {}-packages:-- '.'-extra-deps: []-resolver: lts-3.4
+ tests/Language/GraphQL/ErrorSpec.hs view
@@ -0,0 +1,24 @@+{-# LANGUAGE OverloadedStrings #-}+module Language.GraphQL.ErrorSpec+ ( spec+ ) where++import qualified Data.Aeson as Aeson+import Language.GraphQL.Error+import Test.Hspec ( Spec+ , describe+ , it+ , shouldBe+ )++spec :: Spec+spec = describe "singleError" $+ it "constructs an error with the given message" $+ let expected = Aeson.object+ [+ ("errors", Aeson.toJSON+ [ Aeson.object [("message", "Message.")]+ ]+ )+ ]+ in singleError "Message." `shouldBe` expected
+ tests/Language/GraphQL/LexerSpec.hs view
@@ -0,0 +1,104 @@+{-# LANGUAGE ExplicitForAll #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE QuasiQuotes #-}+module Language.GraphQL.LexerSpec+ ( spec+ ) where++import Data.Either (isRight)+import Data.Text (Text)+import Data.Void (Void)+import Language.GraphQL.Lexer+import Test.Hspec ( Spec+ , context+ , describe+ , it+ , shouldBe+ , shouldSatisfy+ )+import Text.Megaparsec ( ParseErrorBundle+ , parse+ )+import Text.RawString.QQ (r)++spec :: Spec+spec = describe "Lexer" $ do+ context "Reference tests" $ do+ it "accepts BOM header" $+ runParser unicodeBOM "\xfeff" `shouldSatisfy` isRight++ it "lexes strings" $ do+ runParser string [r|"simple"|] `shouldBe` Right "simple"+ runParser string [r|" white space "|] `shouldBe` Right " white space "+ runParser string [r|"quote \""|] `shouldBe` Right [r|quote "|]+ runParser string [r|"escaped \n"|] `shouldBe` Right "escaped \n"+ runParser string [r|"slashes \\ \/"|] `shouldBe` Right [r|slashes \ /|]+ runParser string [r|"unicode \u1234\u5678\u90AB\uCDEF"|]+ `shouldBe` Right "unicode ሴ噸邫췯"++ it "lexes block string" $ do+ runParser blockString [r|"""simple"""|] `shouldBe` Right "simple"+ runParser blockString [r|""" white space """|]+ `shouldBe` Right " white space "+ runParser blockString [r|"""contains " quote"""|]+ `shouldBe` Right [r|contains " quote|]+ runParser blockString [r|"""contains \""" triplequote"""|]+ `shouldBe` Right [r|contains """ triplequote|]+ runParser blockString "\"\"\"multi\nline\"\"\"" `shouldBe` Right "multi\nline"+ runParser blockString "\"\"\"multi\rline\r\nnormalized\"\"\""+ `shouldBe` Right "multi\nline\nnormalized"+ runParser blockString "\"\"\"multi\rline\r\nnormalized\"\"\""+ `shouldBe` Right "multi\nline\nnormalized"+ runParser blockString [r|"""unescaped \n\r\b\t\f\u1234"""|]+ `shouldBe` Right [r|unescaped \n\r\b\t\f\u1234|]+ runParser blockString [r|"""slashes \\ \/"""|]+ `shouldBe` Right [r|slashes \\ \/|]+ runParser blockString [r|"""++ spans+ multiple+ lines++ """|] `shouldBe` Right "spans\n multiple\n lines"++ it "lexes numbers" $ do+ runParser integer "4" `shouldBe` Right (4 :: Int)+ runParser float "4.123" `shouldBe` Right 4.123+ runParser integer "-4" `shouldBe` Right (-4 :: Int)+ runParser integer "9" `shouldBe` Right (9 :: Int)+ runParser integer "0" `shouldBe` Right (0 :: Int)+ runParser float "-4.123" `shouldBe` Right (-4.123)+ runParser float "0.123" `shouldBe` Right 0.123+ runParser float "123e4" `shouldBe` Right 123e4+ runParser float "123E4" `shouldBe` Right 123E4+ runParser float "123e-4" `shouldBe` Right 123e-4+ runParser float "123e+4" `shouldBe` Right 123e+4+ runParser float "-1.123e4" `shouldBe` Right (-1.123e4)+ runParser float "-1.123E4" `shouldBe` Right (-1.123E4)+ runParser float "-1.123e-4" `shouldBe` Right (-1.123e-4)+ runParser float "-1.123e+4" `shouldBe` Right (-1.123e+4)+ runParser float "-1.123e4567" `shouldBe` Right (-1.123e4567)++ it "lexes punctuation" $ do+ runParser bang "!" `shouldBe` Right '!'+ runParser dollar "$" `shouldBe` Right '$'+ runBetween parens "()" `shouldSatisfy` isRight+ runParser spread "..." `shouldBe` Right "..."+ runParser colon ":" `shouldBe` Right ":"+ runParser equals "=" `shouldBe` Right "="+ runParser at "@" `shouldBe` Right '@'+ runBetween brackets "[]" `shouldSatisfy` isRight+ runBetween braces "{}" `shouldSatisfy` isRight+ runParser pipe "|" `shouldBe` Right "|"++ context "Implementation tests" $ do+ it "lexes empty block strings" $+ runParser blockString [r|""""""|] `shouldBe` Right ""+ it "lexes ampersand" $+ runParser amp "&" `shouldBe` Right "&"++runParser :: forall a. Parser a -> Text -> Either (ParseErrorBundle Text Void) a+runParser = flip parse ""++runBetween :: (Parser () -> Parser ()) -> Text -> Either (ParseErrorBundle Text Void) ()+runBetween parser = parse (parser $ pure ()) ""
+ tests/Language/GraphQL/ParserSpec.hs view
@@ -0,0 +1,18 @@+{-# LANGUAGE OverloadedStrings #-}+module Language.GraphQL.ParserSpec+ ( spec+ ) where++import Data.Either (isRight)+import Language.GraphQL.Parser (document)+import Test.Hspec ( Spec+ , describe+ , it+ , shouldSatisfy+ )+import Text.Megaparsec (parse)++spec :: Spec+spec = describe "Parser" $+ it "accepts BOM header" $+ parse document "" "\xfeff{foo}" `shouldSatisfy` isRight
+ tests/Spec.hs view
@@ -0,0 +1,1 @@+{-# OPTIONS_GHC -F -pgmF hspec-discover #-}
+ tests/Test/KitchenSinkSpec.hs view
@@ -0,0 +1,29 @@+module Test.KitchenSinkSpec+ ( spec+ ) where++import qualified Data.Text.IO as Text.IO+import qualified Language.GraphQL.Encoder as Encoder+import qualified Language.GraphQL.Parser as Parser+import Paths_graphql (getDataFileName)+import Test.Hspec ( Spec+ , describe+ , it+ )+import Test.Hspec.Expectations ( expectationFailure+ , shouldBe+ )+import Text.Megaparsec ( errorBundlePretty+ , parse+ )++spec :: Spec+spec = describe "Kitchen Sink" $+ it "prints the query" $ do+ dataFileName <- getDataFileName "tests/data/kitchen-sink.min.graphql"+ expected <- Text.IO.readFile dataFileName++ either+ (expectationFailure . errorBundlePretty)+ (flip shouldBe expected . Encoder.document)+ $ parse Parser.document dataFileName expected
+ tests/Test/StarWars/Data.hs view
@@ -0,0 +1,197 @@+{-# LANGUAGE OverloadedStrings #-}+module Test.StarWars.Data+ ( Character+ , appearsIn+ , artoo+ , getDroid+ , getDroid'+ , getEpisode+ , getFriends+ , getHero+ , getHeroIO+ , getHuman+ , id_+ , homePlanet+ , name+ , secretBackstory+ , typeName+ ) where++import Data.Monoid (mempty)+import Control.Applicative ( Alternative(..)+ , liftA2 + )+import Control.Monad.IO.Class (MonadIO(..))+import Control.Monad.Trans.Except (throwE)+import Data.Maybe (catMaybes)+import Data.Text (Text)+import Language.GraphQL.Trans++-- * Data+-- See https://github.com/graphql/graphql-js/blob/master/src/__tests__/starWarsData.js++-- ** Characters++type ID = Text++data CharCommon = CharCommon+ { _id_ :: ID+ , _name :: Text+ , _friends :: [ID]+ , _appearsIn :: [Int]+ } deriving (Show)+++data Human = Human+ { _humanChar :: CharCommon+ , homePlanet :: Text+ }++data Droid = Droid+ { _droidChar :: CharCommon+ , primaryFunction :: Text+ }++type Character = Either Droid Human++id_ :: Character -> ID+id_ (Left x) = _id_ . _droidChar $ x+id_ (Right x) = _id_ . _humanChar $ x++name :: Character -> Text+name (Left x) = _name . _droidChar $ x+name (Right x) = _name . _humanChar $ x++friends :: Character -> [ID]+friends (Left x) = _friends . _droidChar $ x+friends (Right x) = _friends . _humanChar $ x++appearsIn :: Character -> [Int]+appearsIn (Left x) = _appearsIn . _droidChar $ x+appearsIn (Right x) = _appearsIn . _humanChar $ x++secretBackstory :: MonadIO m => Character -> ActionT m Text+secretBackstory = const $ ActionT $ throwE "secretBackstory is secret."++typeName :: Character -> Text+typeName = either (const "Droid") (const "Human")++luke :: Character+luke = Right luke'++luke' :: Human+luke' = Human+ { _humanChar = CharCommon+ { _id_ = "1000"+ , _name = "Luke Skywalker"+ , _friends = ["1002","1003","2000","2001"]+ , _appearsIn = [4,5,6]+ }+ , homePlanet = "Tatooine"+ }++vader :: Human+vader = Human+ { _humanChar = CharCommon+ { _id_ = "1001"+ , _name = "Darth Vader"+ , _friends = ["1004"]+ , _appearsIn = [4,5,6]+ }+ , homePlanet = "Tatooine"+ }++han :: Human+han = Human+ { _humanChar = CharCommon+ { _id_ = "1002"+ , _name = "Han Solo"+ , _friends = ["1000","1003","2001" ]+ , _appearsIn = [4,5,6]+ }+ , homePlanet = mempty+ }++leia :: Human+leia = Human+ { _humanChar = CharCommon+ { _id_ = "1003"+ , _name = "Leia Organa"+ , _friends = ["1000","1002","2000","2001"]+ , _appearsIn = [4,5,6]+ }+ , homePlanet = "Alderaan"+ }++tarkin :: Human+tarkin = Human+ { _humanChar = CharCommon+ { _id_ = "1004"+ , _name = "Wilhuff Tarkin"+ , _friends = ["1001"]+ , _appearsIn = [4]+ }+ , homePlanet = mempty+ }++threepio :: Droid+threepio = Droid+ { _droidChar = CharCommon+ { _id_ = "2000"+ , _name = "C-3PO"+ , _friends = ["1000","1002","1003","2001" ]+ , _appearsIn = [ 4, 5, 6 ]+ }+ , primaryFunction = "Protocol"+ }++artoo :: Character+artoo = Left artoo'++artoo' :: Droid+artoo' = Droid+ { _droidChar = CharCommon+ { _id_ = "2001"+ , _name = "R2-D2"+ , _friends = ["1000","1002","1003"]+ , _appearsIn = [4,5,6]+ }+ , primaryFunction = "Astrometch"+ }++-- ** Helper functions++getHero :: Int -> Character+getHero 5 = luke+getHero _ = artoo++getHeroIO :: Int -> IO Character+getHeroIO = pure . getHero++getHuman :: Alternative f => ID -> f Character+getHuman = fmap Right . getHuman'++getHuman' :: Alternative f => ID -> f Human+getHuman' "1000" = pure luke'+getHuman' "1001" = pure vader+getHuman' "1002" = pure han+getHuman' "1003" = pure leia+getHuman' "1004" = pure tarkin+getHuman' _ = empty++getDroid :: Alternative f => ID -> f Character+getDroid = fmap Left . getDroid'++getDroid' :: Alternative f => ID -> f Droid+getDroid' "2000" = pure threepio+getDroid' "2001" = pure artoo'+getDroid' _ = empty++getFriends :: Character -> [Character]+getFriends char = catMaybes $ liftA2 (<|>) getDroid getHuman <$> friends char++getEpisode :: Alternative f => Int -> f Text+getEpisode 4 = pure "NEWHOPE"+getEpisode 5 = pure "EMPIRE"+getEpisode 6 = pure "JEDI"+getEpisode _ = empty
+ tests/Test/StarWars/QuerySpec.hs view
@@ -0,0 +1,351 @@+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE QuasiQuotes #-}+module Test.StarWars.QuerySpec+ ( spec+ ) where++import qualified Data.Aeson as Aeson+import Data.Aeson ( object+ , (.=)+ )+import Data.Text (Text)+import Language.GraphQL+import Language.GraphQL.Schema (Subs)+import Text.RawString.QQ (r)+import Test.Hspec.Expectations ( Expectation+ , shouldBe+ )+import Test.Hspec ( Spec+ , describe+ , it+ )+import Test.StarWars.Schema++-- * Test+-- See https://github.com/graphql/graphql-js/blob/master/src/__tests__/starWarsQueryTests.js++spec :: Spec+spec = describe "Star Wars Query Tests" $ do+ describe "Basic Queries" $ do+ it "R2-D2 hero" $ testQuery+ [r| query HeroNameQuery {+ hero {+ id+ }+ }+ |]+ $ object [ "data" .= object ["hero" .= object ["id" .= ("2001" :: Text)]]]+ it "R2-D2 ID and friends" $ testQuery+ [r| query HeroNameAndFriendsQuery {+ hero {+ id+ name+ friends {+ name+ }+ }+ }+ |]+ $ object [ "data" .= object [+ "hero" .= object+ [ "id" .= ("2001" :: Text)+ , r2d2Name+ , "friends" .=+ [ object [lukeName]+ , object [hanName]+ , object [leiaName]+ ]+ ]+ ]]++ describe "Nested Queries" $ do+ it "R2-D2 friends" $ testQuery+ [r| query NestedQuery {+ hero {+ name+ friends {+ name+ appearsIn+ friends {+ name+ }+ }+ }+ }+ |]+ $ object [ "data" .= object [+ "hero" .= object [+ "name" .= ("R2-D2" :: Text)+ , "friends" .= [+ object [+ "name" .= ("Luke Skywalker" :: Text)+ , "appearsIn" .= ["NEWHOPE","EMPIRE","JEDI" :: Text]+ , "friends" .= [+ object [hanName]+ , object [leiaName]+ , object [c3poName]+ , object [r2d2Name]+ ]+ ]+ , object [+ hanName+ , "appearsIn" .= [ "NEWHOPE","EMPIRE","JEDI" :: Text]+ , "friends" .= [+ object [lukeName]+ , object [leiaName]+ , object [r2d2Name]+ ]+ ]+ , object [+ leiaName+ , "appearsIn" .= [ "NEWHOPE","EMPIRE","JEDI" :: Text]+ , "friends" .= [+ object [lukeName]+ , object [hanName]+ , object [c3poName]+ , object [r2d2Name]+ ]+ ]+ ]+ ]+ ]]+ it "Luke ID" $ testQuery+ [r| query FetchLukeQuery {+ human(id: "1000") {+ name+ }+ }+ |]+ $ object [ "data" .= object [+ "human" .= object [lukeName]+ ]]++ it "Luke ID with variable" $ testQueryParams+ (\v -> if v == "someId" then Just "1000" else Nothing)+ [r| query FetchSomeIDQuery($someId: String!) {+ human(id: $someId) {+ name+ }+ }+ |]+ $ object [ "data" .= object [+ "human" .= object [lukeName]+ ]]+ it "Han ID with variable" $ testQueryParams+ (\v -> if v == "someId" then Just "1002" else Nothing)+ [r| query FetchSomeIDQuery($someId: String!) {+ human(id: $someId) {+ name+ }+ }+ |]+ $ object [ "data" .= object [+ "human" .= object [hanName]+ ]]+ it "Invalid ID" $ testQueryParams+ (\v -> if v == "id" then Just "Not a valid ID" else Nothing)+ [r| query humanQuery($id: String!) {+ human(id: $id) {+ name+ }+ }+ |] $ object ["data" .= object ["human" .= Aeson.Null]]+ it "Luke aliased" $ testQuery+ [r| query FetchLukeAliased {+ luke: human(id: "1000") {+ name+ }+ }+ |]+ $ object [ "data" .= object [+ "luke" .= object [lukeName]+ ]]+ it "R2-D2 ID and friends aliased" $ testQuery+ [r| query HeroNameAndFriendsQuery {+ hero {+ id+ name+ friends {+ friendName: name+ }+ }+ }+ |]+ $ object [ "data" .= object [+ "hero" .= object [+ "id" .= ("2001" :: Text)+ , r2d2Name+ , "friends" .= [+ object ["friendName" .= ("Luke Skywalker" :: Text)]+ , object ["friendName" .= ("Han Solo" :: Text)]+ , object ["friendName" .= ("Leia Organa" :: Text)]+ ]+ ]+ ]]+ it "Luke and Leia aliased" $ testQuery+ [r| query FetchLukeAndLeiaAliased {+ luke: human(id: "1000") {+ name+ }+ leia: human(id: "1003") {+ name+ }+ }+ |]+ $ object [ "data" .= object [+ "luke" .= object [lukeName]+ , "leia" .= object [leiaName]+ ]]++ describe "Fragments for complex queries" $ do+ it "Aliases to query for duplicate content" $ testQuery+ [r| query DuplicateFields {+ luke: human(id: "1000") {+ name+ homePlanet+ }+ leia: human(id: "1003") {+ name+ homePlanet+ }+ }+ |]+ $ object [ "data" .= object [+ "luke" .= object [lukeName, tatooine]+ , "leia" .= object [leiaName, alderaan]+ ]]+ it "Fragment for duplicate content" $ testQuery+ [r| query UseFragment {+ luke: human(id: "1000") {+ ...HumanFragment+ }+ leia: human(id: "1003") {+ ...HumanFragment+ }+ }+ fragment HumanFragment on Human {+ name+ homePlanet+ }+ |]+ $ object [ "data" .= object [+ "luke" .= object [lukeName, tatooine]+ , "leia" .= object [leiaName, alderaan]+ ]]++ describe "__typename" $ do+ it "R2D2 is a Droid" $ testQuery+ [r| query CheckTypeOfR2 {+ hero {+ __typename+ name+ }+ }+ |]+ $ object ["data" .= object [+ "hero" .= object ["__typename" .= ("Droid" :: Text), r2d2Name]+ ]]+ it "Luke is a human" $ testQuery+ [r| query CheckTypeOfLuke {+ hero(episode: EMPIRE) {+ __typename+ name+ }+ }+ |]+ $ object ["data" .= object [+ "hero" .= object ["__typename" .= ("Human" :: Text), lukeName]+ ]]++ describe "Errors in resolvers" $ do+ it "error on secretBackstory" $ testQuery+ [r|+ query HeroNameQuery {+ hero {+ name+ secretBackstory+ }+ }+ |]+ $ object+ [ "data" .= object+ [ "hero" .= object+ [ "name" .= ("R2-D2" :: Text)+ , "secretBackstory" .= Aeson.Null+ ]+ ]+ , "errors" .=+ [ object+ ["message" .= ("secretBackstory is secret." :: Text)]+ ]+ ]+ it "Error in a list" $ testQuery+ [r| query HeroNameQuery {+ hero {+ name+ friends {+ name+ secretBackstory+ }+ }+ }+ |]+ $ object ["data" .= object+ [ "hero" .= object+ [ "name" .= ("R2-D2" :: Text)+ , "friends" .=+ [ object+ [ "name" .= ("Luke Skywalker" :: Text)+ , "secretBackstory" .= Aeson.Null+ ]+ , object+ [ "name" .= ("Han Solo" :: Text)+ , "secretBackstory" .= Aeson.Null+ ]+ , object+ [ "name" .= ("Leia Organa" :: Text)+ , "secretBackstory" .= Aeson.Null+ ]+ ]+ ]+ ]+ , "errors" .=+ [ object ["message" .= ("secretBackstory is secret." :: Text)]+ , object ["message" .= ("secretBackstory is secret." :: Text)]+ , object ["message" .= ("secretBackstory is secret." :: Text)]+ ]+ ]+ it "error on secretBackstory with alias" $ testQuery+ [r| query HeroNameQuery {+ mainHero: hero {+ name+ story: secretBackstory+ }+ }+ |]+ $ object+ [ "data" .= object+ [ "mainHero" .= object+ [ "name" .= ("R2-D2" :: Text)+ , "story" .= Aeson.Null+ ]+ ]+ , "errors" .=+ [ object ["message" .= ("secretBackstory is secret." :: Text)]+ ]+ ]++ where+ lukeName = "name" .= ("Luke Skywalker" :: Text)+ leiaName = "name" .= ("Leia Organa" :: Text)+ hanName = "name" .= ("Han Solo" :: Text)+ r2d2Name = "name" .= ("R2-D2" :: Text)+ c3poName = "name" .= ("C-3PO" :: Text)+ tatooine = "homePlanet" .= ("Tatooine" :: Text)+ alderaan = "homePlanet" .= ("Alderaan" :: Text)++testQuery :: Text -> Aeson.Value -> Expectation+testQuery q expected = graphql schema q >>= flip shouldBe expected++testQueryParams :: Subs -> Text -> Aeson.Value -> Expectation+testQueryParams f q expected = graphqlSubs schema f q >>= flip shouldBe expected
+ tests/Test/StarWars/Schema.hs view
@@ -0,0 +1,63 @@+{-# LANGUAGE LambdaCase #-}+{-# LANGUAGE OverloadedStrings #-}+module Test.StarWars.Schema+ ( character+ , droid+ , hero+ , human+ , schema+ ) where++import Control.Monad.Trans.Except (throwE)+import Control.Monad.Trans.Class (lift)+import Control.Monad.IO.Class (MonadIO(..))+import Data.List.NonEmpty (NonEmpty((:|)))+import Language.GraphQL.Schema ( Schema+ , Resolver+ , Argument(..)+ , Value(..)+ )+import qualified Language.GraphQL.Schema as Schema+import Language.GraphQL.Trans+import Language.GraphQL.Type+import Test.StarWars.Data++-- * Schema+-- See https://github.com/graphql/graphql-js/blob/master/src/__tests__/starWarsSchema.js++schema :: MonadIO m => Schema m+schema = hero :| [human, droid]++hero :: MonadIO m => Resolver m+hero = Schema.objectA "hero" $ \case+ [] -> character artoo+ [Argument "episode" (ValueEnum "NEWHOPE")] -> character $ getHero 4+ [Argument "episode" (ValueEnum "EMPIRE" )] -> character $ getHero 5+ [Argument "episode" (ValueEnum "JEDI" )] -> character $ getHero 6+ _ -> ActionT $ throwE "Invalid arguments."++human :: MonadIO m => Resolver m+human = Schema.wrappedObjectA "human" $ \case+ [Argument "id" (ValueString i)] -> do+ humanCharacter <- lift $ return $ getHuman i >>= Just+ case humanCharacter of+ Nothing -> return Null+ Just e -> Named <$> character e+ _ -> ActionT $ throwE "Invalid arguments."++droid :: MonadIO m => Resolver m+droid = Schema.objectA "droid" $ \case+ [Argument "id" (ValueString i)] -> character =<< liftIO (getDroid i)+ _ -> ActionT $ throwE "Invalid arguments."++character :: MonadIO m => Character -> ActionT m [Resolver m]+character char = return+ [ Schema.scalar "id" $ return $ id_ char+ , Schema.scalar "name" $ return $ name char+ , Schema.wrappedObject "friends"+ $ traverse character $ List $ Named <$> getFriends char+ , Schema.enum "appearsIn" $ return $ foldMap getEpisode $ appearsIn char+ , Schema.scalar "secretBackstory" $ secretBackstory char+ , Schema.scalar "homePlanet" $ return $ either mempty homePlanet char+ , Schema.scalar "__typename" $ return $ typeName char+ ]
− tests/tasty.hs
@@ -1,28 +0,0 @@-{-# LANGUAGE CPP #-}-{-# LANGUAGE OverloadedStrings #-}-module Main where--#if !MIN_VERSION_base(4,8,0)-import Control.Applicative ((<$>), (<*>))-#endif--import Data.Attoparsec.Text (parseOnly)-import qualified Data.Text.IO as Text-import Test.Tasty (defaultMain)-import Test.Tasty.HUnit--import qualified Data.GraphQL.Parser as Parser-import qualified Data.GraphQL.Encoder as Encoder--import Paths_graphql (getDataFileName)--main :: IO ()-main = defaultMain =<< testCase "Kitchen Sink"- <$> (assertEqual "Encode" <$> expected <*> actual)- where- expected = Text.readFile- =<< getDataFileName "tests/data/kitchen-sink.min.graphql"-- actual = either (error "Parsing error!") Encoder.document- <$> parseOnly Parser.document- <$> expected