From fdf5914626ad172a8a459696f0114ef990c0d5cb Mon Sep 17 00:00:00 2001 From: Eugen Wissner Date: Sat, 28 Dec 2019 07:07:58 +0100 Subject: [PATCH] Move AST to AST.Document --- CHANGELOG.md | 8 +- src/Language/GraphQL/AST.hs | 155 +-------------- src/Language/GraphQL/AST/Document.hs | 223 ++++++++++++++++++++-- src/Language/GraphQL/AST/Encoder.hs | 2 +- src/Language/GraphQL/AST/Parser.hs | 17 +- src/Language/GraphQL/Execute/Transform.hs | 12 +- tests/Language/GraphQL/AST/EncoderSpec.hs | 3 +- 7 files changed, 232 insertions(+), 188 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index aab1a10..518b53e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,9 +11,11 @@ and this project adheres to - AST for the GraphQL schema. ### Changed -- Rename `AST.Definition` into `AST.ExecutableDefinition`. - `AST.TypeSystemDefinition` and `AST.TypeSystemExtension` can also be - definitions. +- Rename `AST.Definition` into `AST.Document.ExecutableDefinition`. + `AST.Document.TypeSystemDefinition` and `AST.Document.TypeSystemExtension` + can also be definitions. +- Move all AST data to `AST.Document` and reexport them. +- Rename `AST.OperationSelectionSet` to `AST.Document.SelectionSet`. ### Removed - `AST.Field`, `AST.InlineFragment` and `AST.FragmentSpread`. diff --git a/src/Language/GraphQL/AST.hs b/src/Language/GraphQL/AST.hs index ea7912a..aba6dfd 100644 --- a/src/Language/GraphQL/AST.hs +++ b/src/Language/GraphQL/AST.hs @@ -1,157 +1,6 @@ -- | Target AST for Parser. module Language.GraphQL.AST - ( Alias - , Argument(..) - , ExecutableDefinition(..) - , Directive(..) - , FragmentDefinition(..) - , Name - , NonNullType(..) - , ObjectField(..) - , OperationDefinition(..) - , OperationType(..) - , Selection(..) - , SelectionSet - , SelectionSetOpt - , Type(..) - , TypeCondition - , Value(..) - , VariableDefinition(..) + ( module Language.GraphQL.AST.Document ) where -import Data.Int (Int32) -import Data.List.NonEmpty (NonEmpty) -import Data.Text (Text) - --- | Name -type Name = Text - --- | Directive. -data Directive = Directive Name [Argument] deriving (Eq, Show) - --- * Operations - --- | Top-level definition of a document, either an operation or a fragment. -data ExecutableDefinition - = DefinitionOperation OperationDefinition - | DefinitionFragment FragmentDefinition - deriving (Eq, Show) - --- | Operation definition. -data OperationDefinition - = OperationSelectionSet SelectionSet - | OperationDefinition OperationType - (Maybe Name) - [VariableDefinition] - [Directive] - SelectionSet - deriving (Eq, Show) - --- | GraphQL has 3 operation types: queries, mutations and subscribtions. --- --- Currently only queries and mutations are supported. -data OperationType = Query | Mutation deriving (Eq, Show) - --- * Selections - --- | "Top-level" selection, selection on an operation or fragment. -type SelectionSet = NonEmpty Selection - --- | Field selection. -type SelectionSetOpt = [Selection] - --- | Single GraphQL field. --- --- The only required property of a field is its name. Optionally it can also --- have an alias, arguments or a list of subfields. --- --- Given the following query: --- --- @ --- { --- zuck: user(id: 4) { --- id --- name --- } --- } --- @ --- --- * "user", "id" and "name" are field names. --- * "user" has two subfields, "id" and "name". --- * "zuck" is an alias for "user". "id" and "name" have no aliases. --- * "id: 4" is an argument for "user". "id" and "name" don't have any --- arguments. -data Selection - = Field (Maybe Alias) Name [Argument] [Directive] SelectionSetOpt - | FragmentSpread Name [Directive] - | InlineFragment (Maybe TypeCondition) [Directive] SelectionSet - deriving (Eq, Show) - --- | Alternative field name. --- --- @ --- { --- smallPic: profilePic(size: 64) --- bigPic: profilePic(size: 1024) --- } --- @ --- --- Here "smallPic" and "bigPic" are aliases for the same field, "profilePic", --- used to distinquish between profile pictures with different arguments --- (sizes). -type Alias = Name - --- | Single argument. --- --- @ --- { --- user(id: 4) { --- name --- } --- } --- @ --- --- Here "id" is an argument for the field "user" and its value is 4. -data Argument = Argument Name Value deriving (Eq,Show) - --- | Fragment definition. -data FragmentDefinition - = FragmentDefinition Name TypeCondition [Directive] SelectionSet - deriving (Eq, Show) - --- * Inputs - --- | Input value. -data Value = Variable Name - | Int Int32 - | Float Double - | String Text - | Boolean Bool - | Null - | Enum Name - | List [Value] - | Object [ObjectField] - deriving (Eq, Show) - --- | Key-value pair. --- --- A list of 'ObjectField's represents a GraphQL object type. -data ObjectField = ObjectField Name Value deriving (Eq, Show) - --- | Variable definition. -data VariableDefinition = VariableDefinition Name Type (Maybe Value) - deriving (Eq, Show) - --- | Type condition. -type TypeCondition = Name - --- | Type representation. -data Type = TypeNamed Name - | TypeList Type - | TypeNonNull NonNullType - deriving (Eq, Show) - --- | Helper type to represent Non-Null types and lists of such types. -data NonNullType = NonNullTypeNamed Name - | NonNullTypeList Type - deriving (Eq, Show) +import Language.GraphQL.AST.Document diff --git a/src/Language/GraphQL/AST/Document.hs b/src/Language/GraphQL/AST/Document.hs index ebe8918..b94b629 100644 --- a/src/Language/GraphQL/AST/Document.hs +++ b/src/Language/GraphQL/AST/Document.hs @@ -1,34 +1,46 @@ -- | This module defines an abstract syntax tree for the @GraphQL@ language. It --- follows closely the structure given in the specification. Please refer to --- . --- for more information. +-- follows closely the structure given in the specification. Please refer to +-- . +-- for more information. module Language.GraphQL.AST.Document - ( Definition(..) + ( Alias + , Argument(..) + , Definition(ExecutableDefinition) + , Directive(..) , Document , ExecutableDefinition(..) + , FragmentDefinition(..) + , Name + , NonNullType(..) + , ObjectField(..) + , OperationDefinition(..) + , OperationType(..) + , Selection(..) + , SelectionSet + , SelectionSetOpt + , Type(..) + , TypeCondition + , Value(..) + , VariableDefinition(..) ) where +import Data.Int (Int32) import Data.List.NonEmpty (NonEmpty) import Data.Text (Text) -import Language.GraphQL.AST - ( ExecutableDefinition(..) - , Directive - , Name - , OperationType - , Type - , Value - ) import Language.GraphQL.AST.DirectiveLocation -- * Language +-- ** Source Text + +-- | Name. +type Name = Text + -- ** Document -- | GraphQL document. type Document = NonEmpty Definition -type NamedType = Name - -- | All kinds of definitions that can occur in a GraphQL document. data Definition = ExecutableDefinition ExecutableDefinition @@ -36,12 +48,190 @@ data Definition | TypeSystemExtension TypeSystemExtension deriving (Eq, Show) +-- | Top-level definition of a document, either an operation or a fragment. +data ExecutableDefinition + = DefinitionOperation OperationDefinition + | DefinitionFragment FragmentDefinition + deriving (Eq, Show) + +-- ** Operations + +-- | Operation definition. +data OperationDefinition + = SelectionSet SelectionSet + | OperationDefinition + OperationType + (Maybe Name) + [VariableDefinition] + [Directive] + SelectionSet + deriving (Eq, Show) + +-- | GraphQL has 3 operation types: +-- +-- * query - a read-only fetch. +-- * mutation - a write operation followed by a fetch. +-- * subscription - a long-lived request that fetches data in response to +-- source events. +-- +-- Currently only queries and mutations are supported. +data OperationType = Query | Mutation deriving (Eq, Show) + +-- ** Selection Sets + +-- | "Top-level" selection, selection on an operation or fragment. +type SelectionSet = NonEmpty Selection + +-- | Field selection. +type SelectionSetOpt = [Selection] + +-- | Selection is a single entry in a selection set. It can be a single field, +-- fragment spread or inline fragment. +-- +-- The only required property of a field is its name. Optionally it can also +-- have an alias, arguments, directives and a list of subfields. +-- +-- In the following query "user" is a field with two subfields, "id" and "name": +-- +-- @ +-- { +-- user { +-- id +-- name +-- } +-- } +-- @ +-- +-- A fragment spread refers to a fragment defined outside the operation and is +-- expanded at the execution time. +-- +-- @ +-- { +-- user { +-- ...userFragment +-- } +-- } +-- +-- fragment userFragment on UserType { +-- id +-- name +-- } +-- @ +-- +-- Inline fragments are similar but they don't have any name and the type +-- condition ("on UserType") is optional. +-- +-- @ +-- { +-- user { +-- ... on UserType { +-- id +-- name +-- } +-- } +-- @ +data Selection + = Field (Maybe Alias) Name [Argument] [Directive] SelectionSetOpt + | FragmentSpread Name [Directive] + | InlineFragment (Maybe TypeCondition) [Directive] SelectionSet + deriving (Eq, Show) + +-- ** Arguments + +-- | Single argument. +-- +-- @ +-- { +-- user(id: 4) { +-- name +-- } +-- } +-- @ +-- +-- Here "id" is an argument for the field "user" and its value is 4. +data Argument = Argument Name Value deriving (Eq,Show) + +-- ** Field Alias + +-- | Alternative field name. +-- +-- @ +-- { +-- smallPic: profilePic(size: 64) +-- bigPic: profilePic(size: 1024) +-- } +-- @ +-- +-- Here "smallPic" and "bigPic" are aliases for the same field, "profilePic", +-- used to distinquish between profile pictures with different arguments +-- (sizes). +type Alias = Name + +-- ** Fragments + +-- | Fragment definition. +data FragmentDefinition + = FragmentDefinition Name TypeCondition [Directive] SelectionSet + deriving (Eq, Show) + +-- | Type condition. +type TypeCondition = Name + +-- ** Input Values + +-- | Input value. +data Value + = Variable Name + | Int Int32 + | Float Double + | String Text + | Boolean Bool + | Null + | Enum Name + | List [Value] + | Object [ObjectField] + deriving (Eq, Show) + +-- | Key-value pair. +-- +-- A list of 'ObjectField's represents a GraphQL object type. +data ObjectField = ObjectField Name Value deriving (Eq, Show) + +-- ** Variables + +-- | Variable definition. +data VariableDefinition = VariableDefinition Name Type (Maybe Value) + deriving (Eq, Show) + +-- ** Type References + +-- | Type representation. +data Type + = TypeNamed Name + | TypeList Type + | TypeNonNull NonNullType + deriving (Eq, Show) + +type NamedType = Name + +-- | Helper type to represent Non-Null types and lists of such types. +data NonNullType + = NonNullTypeNamed Name + | NonNullTypeList Type + deriving (Eq, Show) + +-- ** Directives + +-- | Directive. +data Directive = Directive Name [Argument] deriving (Eq, Show) + -- * Type System data TypeSystemDefinition = SchemaDefinition [Directive] RootOperationTypeDefinitions | TypeDefinition TypeDefinition - | DirectiveDefinition Description Name ArgumentsDefinition DirectiveLocation + | DirectiveDefinition + Description Name ArgumentsDefinition DirectiveLocation deriving (Eq, Show) -- ** Type System Extensions @@ -109,7 +299,8 @@ newtype ImplementsInterfaces = ImplementsInterfaces (NonEmpty NamedType) newtype ImplementsInterfacesOpt = ImplementsInterfacesOpt [NamedType] deriving (Eq, Show) -data FieldDefinition = FieldDefinition Description Name ArgumentsDefinition Type +data FieldDefinition + = FieldDefinition Description Name ArgumentsDefinition Type deriving (Eq, Show) newtype ArgumentsDefinition = ArgumentsDefinition [InputValueDefinition] diff --git a/src/Language/GraphQL/AST/Encoder.hs b/src/Language/GraphQL/AST/Encoder.hs index e33068d..eb865c9 100644 --- a/src/Language/GraphQL/AST/Encoder.hs +++ b/src/Language/GraphQL/AST/Encoder.hs @@ -66,7 +66,7 @@ definition formatter x = fragmentDefinition formatter fragment operationDefinition :: Formatter -> Full.OperationDefinition -> Lazy.Text -operationDefinition formatter (Full.OperationSelectionSet sels) +operationDefinition formatter (Full.SelectionSet sels) = selectionSet formatter sels operationDefinition formatter (Full.OperationDefinition Full.Query name vars dirs sels) = "query " <> node formatter name vars dirs sels diff --git a/src/Language/GraphQL/AST/Parser.hs b/src/Language/GraphQL/AST/Parser.hs index ad2b96d..113b0a4 100644 --- a/src/Language/GraphQL/AST/Parser.hs +++ b/src/Language/GraphQL/AST/Parser.hs @@ -25,13 +25,16 @@ definition = DefinitionOperation <$> operationDefinition "definition error!" operationDefinition :: Parser OperationDefinition -operationDefinition = OperationSelectionSet <$> selectionSet - <|> OperationDefinition <$> operationType - <*> optional name - <*> opt variableDefinitions - <*> opt directives - <*> selectionSet - "operationDefinition error" +operationDefinition = SelectionSet <$> selectionSet + <|> operationDefinition' + "operationDefinition error" + where + operationDefinition' + = OperationDefinition <$> operationType + <*> optional name + <*> opt variableDefinitions + <*> opt directives + <*> selectionSet operationType :: Parser OperationType operationType = Query <$ symbol "query" diff --git a/src/Language/GraphQL/Execute/Transform.hs b/src/Language/GraphQL/Execute/Transform.hs index 28b9481..7c2e100 100644 --- a/src/Language/GraphQL/Execute/Transform.hs +++ b/src/Language/GraphQL/Execute/Transform.hs @@ -58,13 +58,13 @@ operations operations' = do lift . lift $ NonEmpty.nonEmpty coreOperations operation :: Full.OperationDefinition -> TransformT Core.Operation -operation (Full.OperationSelectionSet sels) = - operation $ Full.OperationDefinition Full.Query mempty mempty mempty sels +operation (Full.SelectionSet sels) + = operation $ Full.OperationDefinition Full.Query mempty mempty mempty sels -- TODO: Validate Variable definitions with substituter -operation (Full.OperationDefinition Full.Query name _vars _dirs sels) = - Core.Query name <$> appendSelection sels -operation (Full.OperationDefinition Full.Mutation name _vars _dirs sels) = - Core.Mutation name <$> appendSelection sels +operation (Full.OperationDefinition Full.Query name _vars _dirs sels) + = Core.Query name <$> appendSelection sels +operation (Full.OperationDefinition Full.Mutation name _vars _dirs sels) + = Core.Mutation name <$> appendSelection sels -- * Selection diff --git a/tests/Language/GraphQL/AST/EncoderSpec.hs b/tests/Language/GraphQL/AST/EncoderSpec.hs index 619d7e3..00a875c 100644 --- a/tests/Language/GraphQL/AST/EncoderSpec.hs +++ b/tests/Language/GraphQL/AST/EncoderSpec.hs @@ -35,8 +35,7 @@ spec = do it "indents block strings in arguments" $ let arguments = [Argument "message" (String "line1\nline2")] field = Field Nothing "field" arguments [] [] - set = OperationSelectionSet $ pure field - operation = DefinitionOperation set + operation = DefinitionOperation $ SelectionSet $ pure field in definition pretty operation `shouldBe` [r|{ field(message: """ line1