Initial QSfera import
This commit is contained in:
+200
@@ -0,0 +1,200 @@
|
||||
package parser
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
|
||||
"github.com/vektah/gqlparser/v2/ast"
|
||||
"github.com/vektah/gqlparser/v2/gqlerror"
|
||||
"github.com/vektah/gqlparser/v2/lexer"
|
||||
)
|
||||
|
||||
type parser struct {
|
||||
lexer lexer.Lexer
|
||||
err error
|
||||
|
||||
peeked bool
|
||||
peekToken lexer.Token
|
||||
peekError error
|
||||
|
||||
prev lexer.Token
|
||||
|
||||
comment *ast.CommentGroup
|
||||
commentConsuming bool
|
||||
|
||||
tokenCount int
|
||||
maxTokenLimit int
|
||||
}
|
||||
|
||||
func (p *parser) SetMaxTokenLimit(maxToken int) {
|
||||
p.maxTokenLimit = maxToken
|
||||
}
|
||||
|
||||
func (p *parser) consumeComment() (*ast.Comment, bool) {
|
||||
if p.err != nil {
|
||||
return nil, false
|
||||
}
|
||||
tok := p.peek()
|
||||
if tok.Kind != lexer.Comment {
|
||||
return nil, false
|
||||
}
|
||||
p.next()
|
||||
return &ast.Comment{
|
||||
Value: tok.Value,
|
||||
Position: &tok.Pos,
|
||||
}, true
|
||||
}
|
||||
|
||||
func (p *parser) consumeCommentGroup() {
|
||||
if p.err != nil {
|
||||
return
|
||||
}
|
||||
if p.commentConsuming {
|
||||
return
|
||||
}
|
||||
p.commentConsuming = true
|
||||
|
||||
var comments []*ast.Comment
|
||||
for {
|
||||
comment, ok := p.consumeComment()
|
||||
if !ok {
|
||||
break
|
||||
}
|
||||
comments = append(comments, comment)
|
||||
}
|
||||
|
||||
p.comment = &ast.CommentGroup{List: comments}
|
||||
p.commentConsuming = false
|
||||
}
|
||||
|
||||
func (p *parser) peekPos() *ast.Position {
|
||||
if p.err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
peek := p.peek()
|
||||
return &peek.Pos
|
||||
}
|
||||
|
||||
func (p *parser) peek() lexer.Token {
|
||||
if p.err != nil {
|
||||
return p.prev
|
||||
}
|
||||
|
||||
if !p.peeked {
|
||||
p.peekToken, p.peekError = p.lexer.ReadToken()
|
||||
p.peeked = true
|
||||
if p.peekToken.Kind == lexer.Comment {
|
||||
p.consumeCommentGroup()
|
||||
}
|
||||
}
|
||||
|
||||
return p.peekToken
|
||||
}
|
||||
|
||||
func (p *parser) error(tok lexer.Token, format string, args ...any) {
|
||||
if p.err != nil {
|
||||
return
|
||||
}
|
||||
p.err = gqlerror.ErrorLocf(tok.Pos.Src.Name, tok.Pos.Line, tok.Pos.Column, format, args...)
|
||||
}
|
||||
|
||||
func (p *parser) next() lexer.Token {
|
||||
if p.err != nil {
|
||||
return p.prev
|
||||
}
|
||||
// Increment the token count before reading the next token
|
||||
p.tokenCount++
|
||||
if p.maxTokenLimit != 0 && p.tokenCount > p.maxTokenLimit {
|
||||
p.err = gqlerror.Errorf("exceeded token limit of %d", p.maxTokenLimit)
|
||||
return p.prev
|
||||
}
|
||||
if p.peeked {
|
||||
p.peeked = false
|
||||
p.comment = nil
|
||||
p.prev, p.err = p.peekToken, p.peekError
|
||||
} else {
|
||||
p.prev, p.err = p.lexer.ReadToken()
|
||||
if p.prev.Kind == lexer.Comment {
|
||||
p.consumeCommentGroup()
|
||||
}
|
||||
}
|
||||
return p.prev
|
||||
}
|
||||
|
||||
func (p *parser) expectKeyword(value string) (lexer.Token, *ast.CommentGroup) {
|
||||
tok := p.peek()
|
||||
comment := p.comment
|
||||
if tok.Kind == lexer.Name && tok.Value == value {
|
||||
return p.next(), comment
|
||||
}
|
||||
|
||||
p.error(tok, "Expected %s, found %s", strconv.Quote(value), tok.String())
|
||||
return tok, comment
|
||||
}
|
||||
|
||||
func (p *parser) expect(kind lexer.Type) (lexer.Token, *ast.CommentGroup) {
|
||||
tok := p.peek()
|
||||
comment := p.comment
|
||||
if tok.Kind == kind {
|
||||
return p.next(), comment
|
||||
}
|
||||
|
||||
p.error(tok, "Expected %s, found %s", kind, tok.Kind.String())
|
||||
return tok, comment
|
||||
}
|
||||
|
||||
func (p *parser) skip(kind lexer.Type) bool {
|
||||
if p.err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
tok := p.peek()
|
||||
|
||||
if tok.Kind != kind {
|
||||
return false
|
||||
}
|
||||
p.next()
|
||||
return true
|
||||
}
|
||||
|
||||
func (p *parser) unexpectedError() {
|
||||
p.unexpectedToken(p.peek())
|
||||
}
|
||||
|
||||
func (p *parser) unexpectedToken(tok lexer.Token) {
|
||||
p.error(tok, "Unexpected %s", tok.String())
|
||||
}
|
||||
|
||||
func (p *parser) many(start, end lexer.Type, cb func()) {
|
||||
hasDef := p.skip(start)
|
||||
if !hasDef {
|
||||
return
|
||||
}
|
||||
|
||||
for p.peek().Kind != end && p.err == nil {
|
||||
cb()
|
||||
}
|
||||
p.next()
|
||||
}
|
||||
|
||||
func (p *parser) some(start, end lexer.Type, cb func()) *ast.CommentGroup {
|
||||
hasDef := p.skip(start)
|
||||
if !hasDef {
|
||||
return nil
|
||||
}
|
||||
|
||||
called := false
|
||||
for p.peek().Kind != end && p.err == nil {
|
||||
called = true
|
||||
cb()
|
||||
}
|
||||
|
||||
if !called {
|
||||
p.error(p.peek(), "expected at least one definition, found %s", p.peek().Kind.String())
|
||||
return nil
|
||||
}
|
||||
|
||||
comment := p.comment
|
||||
p.next()
|
||||
return comment
|
||||
}
|
||||
+372
@@ -0,0 +1,372 @@
|
||||
package parser
|
||||
|
||||
import (
|
||||
. "github.com/vektah/gqlparser/v2/ast" //nolint:staticcheck // bad, yeah
|
||||
"github.com/vektah/gqlparser/v2/lexer"
|
||||
)
|
||||
|
||||
func ParseQuery(source *Source) (*QueryDocument, error) {
|
||||
p := parser{
|
||||
lexer: lexer.New(source),
|
||||
maxTokenLimit: 0, // 0 means unlimited
|
||||
}
|
||||
return p.parseQueryDocument(), p.err
|
||||
}
|
||||
|
||||
func ParseQueryWithTokenLimit(source *Source, maxTokenLimit int) (*QueryDocument, error) {
|
||||
p := parser{
|
||||
lexer: lexer.New(source),
|
||||
maxTokenLimit: maxTokenLimit,
|
||||
}
|
||||
return p.parseQueryDocument(), p.err
|
||||
}
|
||||
|
||||
func (p *parser) parseQueryDocument() *QueryDocument {
|
||||
var doc QueryDocument
|
||||
for p.peek().Kind != lexer.EOF {
|
||||
if p.err != nil {
|
||||
return &doc
|
||||
}
|
||||
doc.Position = p.peekPos()
|
||||
switch p.peek().Kind {
|
||||
case lexer.Name:
|
||||
switch p.peek().Value {
|
||||
case "query", "mutation", "subscription":
|
||||
doc.Operations = append(doc.Operations, p.parseOperationDefinition())
|
||||
case "fragment":
|
||||
doc.Fragments = append(doc.Fragments, p.parseFragmentDefinition())
|
||||
default:
|
||||
p.unexpectedError()
|
||||
}
|
||||
case lexer.BraceL:
|
||||
doc.Operations = append(doc.Operations, p.parseOperationDefinition())
|
||||
default:
|
||||
p.unexpectedError()
|
||||
}
|
||||
}
|
||||
|
||||
return &doc
|
||||
}
|
||||
|
||||
func (p *parser) parseOperationDefinition() *OperationDefinition {
|
||||
if p.peek().Kind == lexer.BraceL {
|
||||
return &OperationDefinition{
|
||||
Position: p.peekPos(),
|
||||
Comment: p.comment,
|
||||
Operation: Query,
|
||||
SelectionSet: p.parseRequiredSelectionSet(),
|
||||
}
|
||||
}
|
||||
|
||||
var od OperationDefinition
|
||||
od.Position = p.peekPos()
|
||||
od.Comment = p.comment
|
||||
od.Operation = p.parseOperationType()
|
||||
|
||||
if p.peek().Kind == lexer.Name {
|
||||
od.Name = p.next().Value
|
||||
}
|
||||
|
||||
od.VariableDefinitions = p.parseVariableDefinitions()
|
||||
od.Directives = p.parseDirectives(false)
|
||||
od.SelectionSet = p.parseRequiredSelectionSet()
|
||||
|
||||
return &od
|
||||
}
|
||||
|
||||
func (p *parser) parseOperationType() Operation {
|
||||
tok := p.next()
|
||||
switch tok.Value {
|
||||
case "query":
|
||||
return Query
|
||||
case "mutation":
|
||||
return Mutation
|
||||
case "subscription":
|
||||
return Subscription
|
||||
}
|
||||
p.unexpectedToken(tok)
|
||||
return ""
|
||||
}
|
||||
|
||||
func (p *parser) parseVariableDefinitions() VariableDefinitionList {
|
||||
var defs []*VariableDefinition
|
||||
p.some(lexer.ParenL, lexer.ParenR, func() {
|
||||
defs = append(defs, p.parseVariableDefinition())
|
||||
})
|
||||
|
||||
return defs
|
||||
}
|
||||
|
||||
func (p *parser) parseVariableDefinition() *VariableDefinition {
|
||||
var def VariableDefinition
|
||||
def.Position = p.peekPos()
|
||||
def.Comment = p.comment
|
||||
def.Variable = p.parseVariable()
|
||||
|
||||
p.expect(lexer.Colon)
|
||||
|
||||
def.Type = p.parseTypeReference()
|
||||
|
||||
if p.skip(lexer.Equals) {
|
||||
def.DefaultValue = p.parseValueLiteral(true)
|
||||
}
|
||||
|
||||
def.Directives = p.parseDirectives(false)
|
||||
|
||||
return &def
|
||||
}
|
||||
|
||||
func (p *parser) parseVariable() string {
|
||||
p.expect(lexer.Dollar)
|
||||
return p.parseName()
|
||||
}
|
||||
|
||||
func (p *parser) parseOptionalSelectionSet() SelectionSet {
|
||||
var selections []Selection
|
||||
p.some(lexer.BraceL, lexer.BraceR, func() {
|
||||
selections = append(selections, p.parseSelection())
|
||||
})
|
||||
|
||||
return selections
|
||||
}
|
||||
|
||||
func (p *parser) parseRequiredSelectionSet() SelectionSet {
|
||||
if p.peek().Kind != lexer.BraceL {
|
||||
p.error(p.peek(), "Expected %s, found %s", lexer.BraceL, p.peek().Kind.String())
|
||||
return nil
|
||||
}
|
||||
|
||||
var selections []Selection
|
||||
p.some(lexer.BraceL, lexer.BraceR, func() {
|
||||
selections = append(selections, p.parseSelection())
|
||||
})
|
||||
|
||||
return selections
|
||||
}
|
||||
|
||||
func (p *parser) parseSelection() Selection {
|
||||
if p.peek().Kind == lexer.Spread {
|
||||
return p.parseFragment()
|
||||
}
|
||||
return p.parseField()
|
||||
}
|
||||
|
||||
func (p *parser) parseField() *Field {
|
||||
var field Field
|
||||
field.Position = p.peekPos()
|
||||
field.Comment = p.comment
|
||||
field.Alias = p.parseName()
|
||||
|
||||
if p.skip(lexer.Colon) {
|
||||
field.Name = p.parseName()
|
||||
} else {
|
||||
field.Name = field.Alias
|
||||
}
|
||||
|
||||
field.Arguments = p.parseArguments(false)
|
||||
field.Directives = p.parseDirectives(false)
|
||||
if p.peek().Kind == lexer.BraceL {
|
||||
field.SelectionSet = p.parseOptionalSelectionSet()
|
||||
}
|
||||
|
||||
return &field
|
||||
}
|
||||
|
||||
func (p *parser) parseArguments(isConst bool) ArgumentList {
|
||||
var arguments ArgumentList
|
||||
p.some(lexer.ParenL, lexer.ParenR, func() {
|
||||
arguments = append(arguments, p.parseArgument(isConst))
|
||||
})
|
||||
|
||||
return arguments
|
||||
}
|
||||
|
||||
func (p *parser) parseArgument(isConst bool) *Argument {
|
||||
arg := Argument{}
|
||||
arg.Position = p.peekPos()
|
||||
arg.Comment = p.comment
|
||||
arg.Name = p.parseName()
|
||||
p.expect(lexer.Colon)
|
||||
|
||||
arg.Value = p.parseValueLiteral(isConst)
|
||||
return &arg
|
||||
}
|
||||
|
||||
func (p *parser) parseFragment() Selection {
|
||||
_, comment := p.expect(lexer.Spread)
|
||||
|
||||
if peek := p.peek(); peek.Kind == lexer.Name && peek.Value != "on" {
|
||||
return &FragmentSpread{
|
||||
Position: p.peekPos(),
|
||||
Comment: comment,
|
||||
Name: p.parseFragmentName(),
|
||||
Directives: p.parseDirectives(false),
|
||||
}
|
||||
}
|
||||
|
||||
var def InlineFragment
|
||||
def.Position = p.peekPos()
|
||||
def.Comment = comment
|
||||
if p.peek().Value == "on" {
|
||||
p.next() // "on"
|
||||
|
||||
def.TypeCondition = p.parseName()
|
||||
}
|
||||
|
||||
def.Directives = p.parseDirectives(false)
|
||||
def.SelectionSet = p.parseRequiredSelectionSet()
|
||||
return &def
|
||||
}
|
||||
|
||||
func (p *parser) parseFragmentDefinition() *FragmentDefinition {
|
||||
var def FragmentDefinition
|
||||
def.Position = p.peekPos()
|
||||
def.Comment = p.comment
|
||||
p.expectKeyword("fragment")
|
||||
|
||||
def.Name = p.parseFragmentName()
|
||||
def.VariableDefinition = p.parseVariableDefinitions()
|
||||
|
||||
p.expectKeyword("on")
|
||||
|
||||
def.TypeCondition = p.parseName()
|
||||
def.Directives = p.parseDirectives(false)
|
||||
def.SelectionSet = p.parseRequiredSelectionSet()
|
||||
return &def
|
||||
}
|
||||
|
||||
func (p *parser) parseFragmentName() string {
|
||||
if p.peek().Value == "on" {
|
||||
p.unexpectedError()
|
||||
return ""
|
||||
}
|
||||
|
||||
return p.parseName()
|
||||
}
|
||||
|
||||
func (p *parser) parseValueLiteral(isConst bool) *Value {
|
||||
token := p.peek()
|
||||
|
||||
var kind ValueKind
|
||||
switch token.Kind {
|
||||
case lexer.BracketL:
|
||||
return p.parseList(isConst)
|
||||
case lexer.BraceL:
|
||||
return p.parseObject(isConst)
|
||||
case lexer.Dollar:
|
||||
if isConst {
|
||||
p.unexpectedError()
|
||||
return nil
|
||||
}
|
||||
return &Value{
|
||||
Position: &token.Pos,
|
||||
Comment: p.comment,
|
||||
Raw: p.parseVariable(),
|
||||
Kind: Variable,
|
||||
}
|
||||
case lexer.Int:
|
||||
kind = IntValue
|
||||
case lexer.Float:
|
||||
kind = FloatValue
|
||||
case lexer.String:
|
||||
kind = StringValue
|
||||
case lexer.BlockString:
|
||||
kind = BlockValue
|
||||
case lexer.Name:
|
||||
switch token.Value {
|
||||
case "true", "false":
|
||||
kind = BooleanValue
|
||||
case "null":
|
||||
kind = NullValue
|
||||
default:
|
||||
kind = EnumValue
|
||||
}
|
||||
default:
|
||||
p.unexpectedError()
|
||||
return nil
|
||||
}
|
||||
|
||||
p.next()
|
||||
|
||||
return &Value{Position: &token.Pos, Comment: p.comment, Raw: token.Value, Kind: kind}
|
||||
}
|
||||
|
||||
func (p *parser) parseList(isConst bool) *Value {
|
||||
var values ChildValueList
|
||||
pos := p.peekPos()
|
||||
comment := p.comment
|
||||
p.many(lexer.BracketL, lexer.BracketR, func() {
|
||||
values = append(values, &ChildValue{Value: p.parseValueLiteral(isConst)})
|
||||
})
|
||||
|
||||
return &Value{Children: values, Kind: ListValue, Position: pos, Comment: comment}
|
||||
}
|
||||
|
||||
func (p *parser) parseObject(isConst bool) *Value {
|
||||
var fields ChildValueList
|
||||
pos := p.peekPos()
|
||||
comment := p.comment
|
||||
p.many(lexer.BraceL, lexer.BraceR, func() {
|
||||
fields = append(fields, p.parseObjectField(isConst))
|
||||
})
|
||||
|
||||
return &Value{Children: fields, Kind: ObjectValue, Position: pos, Comment: comment}
|
||||
}
|
||||
|
||||
func (p *parser) parseObjectField(isConst bool) *ChildValue {
|
||||
field := ChildValue{}
|
||||
field.Position = p.peekPos()
|
||||
field.Comment = p.comment
|
||||
field.Name = p.parseName()
|
||||
|
||||
p.expect(lexer.Colon)
|
||||
|
||||
field.Value = p.parseValueLiteral(isConst)
|
||||
return &field
|
||||
}
|
||||
|
||||
func (p *parser) parseDirectives(isConst bool) []*Directive {
|
||||
var directives []*Directive
|
||||
|
||||
for p.peek().Kind == lexer.At {
|
||||
if p.err != nil {
|
||||
break
|
||||
}
|
||||
directives = append(directives, p.parseDirective(isConst))
|
||||
}
|
||||
return directives
|
||||
}
|
||||
|
||||
func (p *parser) parseDirective(isConst bool) *Directive {
|
||||
p.expect(lexer.At)
|
||||
|
||||
return &Directive{
|
||||
Position: p.peekPos(),
|
||||
Name: p.parseName(),
|
||||
Arguments: p.parseArguments(isConst),
|
||||
}
|
||||
}
|
||||
|
||||
func (p *parser) parseTypeReference() *Type {
|
||||
var typ Type
|
||||
|
||||
if p.skip(lexer.BracketL) {
|
||||
typ.Position = p.peekPos()
|
||||
typ.Elem = p.parseTypeReference()
|
||||
p.expect(lexer.BracketR)
|
||||
} else {
|
||||
typ.Position = p.peekPos()
|
||||
typ.NamedType = p.parseName()
|
||||
}
|
||||
|
||||
if p.skip(lexer.Bang) {
|
||||
typ.NonNull = true
|
||||
}
|
||||
return &typ
|
||||
}
|
||||
|
||||
func (p *parser) parseName() string {
|
||||
token, _ := p.expect(lexer.Name)
|
||||
|
||||
return token.Value
|
||||
}
|
||||
+545
@@ -0,0 +1,545 @@
|
||||
parser provides useful errors:
|
||||
- name: unclosed paren
|
||||
input: '{'
|
||||
error:
|
||||
message: "Expected Name, found <EOF>"
|
||||
locations: [{line: 1, column: 2}]
|
||||
|
||||
- name: missing on in fragment
|
||||
input: |
|
||||
{ ...MissingOn }
|
||||
fragment MissingOn Type
|
||||
error:
|
||||
message: 'Expected "on", found Name "Type"'
|
||||
locations: [{ line: 2, column: 20 }]
|
||||
|
||||
- name: missing name after alias
|
||||
input: '{ field: {} }'
|
||||
error:
|
||||
message: "Expected Name, found {"
|
||||
locations: [{ line: 1, column: 10 }]
|
||||
|
||||
- name: not an operation
|
||||
input: 'notanoperation Foo { field }'
|
||||
error:
|
||||
message: 'Unexpected Name "notanoperation"'
|
||||
locations: [{ line: 1, column: 1 }]
|
||||
|
||||
- name: a wild splat appears
|
||||
input: '...'
|
||||
error:
|
||||
message: 'Unexpected ...'
|
||||
locations: [{ line: 1, column: 1}]
|
||||
|
||||
variables:
|
||||
- name: are allowed in args
|
||||
input: '{ field(complex: { a: { b: [ $var ] } }) }'
|
||||
|
||||
- name: are not allowed in default args
|
||||
input: 'query Foo($x: Complex = { a: { b: [ $var ] } }) { field }'
|
||||
error:
|
||||
message: 'Unexpected $'
|
||||
locations: [{ line: 1, column: 37 }]
|
||||
|
||||
- name: can have directives
|
||||
input: 'query ($withDirective: String @first @second, $withoutDirective: String) { f }'
|
||||
ast: |
|
||||
<QueryDocument>
|
||||
Operations: [OperationDefinition]
|
||||
- <OperationDefinition>
|
||||
Operation: Operation("query")
|
||||
VariableDefinitions: [VariableDefinition]
|
||||
- <VariableDefinition>
|
||||
Variable: "withDirective"
|
||||
Type: String
|
||||
Directives: [Directive]
|
||||
- <Directive>
|
||||
Name: "first"
|
||||
- <Directive>
|
||||
Name: "second"
|
||||
- <VariableDefinition>
|
||||
Variable: "withoutDirective"
|
||||
Type: String
|
||||
SelectionSet: [Selection]
|
||||
- <Field>
|
||||
Alias: "f"
|
||||
Name: "f"
|
||||
|
||||
fragments:
|
||||
- name: can not be named 'on'
|
||||
input: 'fragment on on on { on }'
|
||||
error:
|
||||
message: 'Unexpected Name "on"'
|
||||
locations: [{ line: 1, column: 10 }]
|
||||
|
||||
- name: can not spread fragments called 'on'
|
||||
input: '{ ...on }'
|
||||
error:
|
||||
message: 'Expected Name, found }'
|
||||
locations: [{ line: 1, column: 9 }]
|
||||
|
||||
encoding:
|
||||
- name: multibyte characters are supported
|
||||
input: |
|
||||
# This comment has a ਊ multi-byte character.
|
||||
{ field(arg: "Has a ਊ multi-byte character.") }
|
||||
ast: |
|
||||
<QueryDocument>
|
||||
Operations: [OperationDefinition]
|
||||
- <OperationDefinition>
|
||||
Operation: Operation("query")
|
||||
SelectionSet: [Selection]
|
||||
- <Field>
|
||||
Alias: "field"
|
||||
Name: "field"
|
||||
Arguments: [Argument]
|
||||
- <Argument>
|
||||
Name: "arg"
|
||||
Value: "Has a ਊ multi-byte character."
|
||||
|
||||
keywords are allowed anywhere a name is:
|
||||
- name: on
|
||||
input: |
|
||||
query on {
|
||||
... a
|
||||
... on on { field }
|
||||
}
|
||||
fragment a on Type {
|
||||
on(on: $on)
|
||||
@on(on: on)
|
||||
}
|
||||
|
||||
- name: subscription
|
||||
input: |
|
||||
query subscription {
|
||||
... subscription
|
||||
... on subscription { field }
|
||||
}
|
||||
fragment subscription on Type {
|
||||
subscription(subscription: $subscription)
|
||||
@subscription(subscription: subscription)
|
||||
}
|
||||
|
||||
- name: true
|
||||
input: |
|
||||
query true {
|
||||
... true
|
||||
... on true { field }
|
||||
}
|
||||
fragment true on Type {
|
||||
true(true: $true)
|
||||
@true(true: true)
|
||||
}
|
||||
|
||||
operations:
|
||||
- name: anonymous mutation
|
||||
input: 'mutation { mutationField }'
|
||||
|
||||
- name: named mutation
|
||||
input: 'mutation Foo { mutationField }'
|
||||
|
||||
- name: anonymous subscription
|
||||
input: 'subscription { subscriptionField }'
|
||||
|
||||
- name: named subscription
|
||||
input: 'subscription Foo { subscriptionField }'
|
||||
|
||||
|
||||
ast:
|
||||
- name: simple query
|
||||
input: |
|
||||
{
|
||||
node(id: 4) {
|
||||
id,
|
||||
name
|
||||
}
|
||||
}
|
||||
ast: |
|
||||
<QueryDocument>
|
||||
Operations: [OperationDefinition]
|
||||
- <OperationDefinition>
|
||||
Operation: Operation("query")
|
||||
SelectionSet: [Selection]
|
||||
- <Field>
|
||||
Alias: "node"
|
||||
Name: "node"
|
||||
Arguments: [Argument]
|
||||
- <Argument>
|
||||
Name: "id"
|
||||
Value: 4
|
||||
SelectionSet: [Selection]
|
||||
- <Field>
|
||||
Alias: "id"
|
||||
Name: "id"
|
||||
- <Field>
|
||||
Alias: "name"
|
||||
Name: "name"
|
||||
|
||||
- name: nameless query with no variables
|
||||
input: |
|
||||
query {
|
||||
node {
|
||||
id
|
||||
}
|
||||
}
|
||||
ast: |
|
||||
<QueryDocument>
|
||||
Operations: [OperationDefinition]
|
||||
- <OperationDefinition>
|
||||
Operation: Operation("query")
|
||||
SelectionSet: [Selection]
|
||||
- <Field>
|
||||
Alias: "node"
|
||||
Name: "node"
|
||||
SelectionSet: [Selection]
|
||||
- <Field>
|
||||
Alias: "id"
|
||||
Name: "id"
|
||||
|
||||
- name: fragment defined variables
|
||||
input: 'fragment a($v: Boolean = false) on t { f(v: $v) }'
|
||||
ast: |
|
||||
<QueryDocument>
|
||||
Fragments: [FragmentDefinition]
|
||||
- <FragmentDefinition>
|
||||
Name: "a"
|
||||
VariableDefinition: [VariableDefinition]
|
||||
- <VariableDefinition>
|
||||
Variable: "v"
|
||||
Type: Boolean
|
||||
DefaultValue: false
|
||||
TypeCondition: "t"
|
||||
SelectionSet: [Selection]
|
||||
- <Field>
|
||||
Alias: "f"
|
||||
Name: "f"
|
||||
Arguments: [Argument]
|
||||
- <Argument>
|
||||
Name: "v"
|
||||
Value: $v
|
||||
|
||||
|
||||
values:
|
||||
- name: null
|
||||
input: '{ f(id: null) }'
|
||||
ast: |
|
||||
<QueryDocument>
|
||||
Operations: [OperationDefinition]
|
||||
- <OperationDefinition>
|
||||
Operation: Operation("query")
|
||||
SelectionSet: [Selection]
|
||||
- <Field>
|
||||
Alias: "f"
|
||||
Name: "f"
|
||||
Arguments: [Argument]
|
||||
- <Argument>
|
||||
Name: "id"
|
||||
Value: null
|
||||
|
||||
- name: strings
|
||||
input: '{ f(long: """long""", short: "short") } '
|
||||
ast: |
|
||||
<QueryDocument>
|
||||
Operations: [OperationDefinition]
|
||||
- <OperationDefinition>
|
||||
Operation: Operation("query")
|
||||
SelectionSet: [Selection]
|
||||
- <Field>
|
||||
Alias: "f"
|
||||
Name: "f"
|
||||
Arguments: [Argument]
|
||||
- <Argument>
|
||||
Name: "long"
|
||||
Value: "long"
|
||||
- <Argument>
|
||||
Name: "short"
|
||||
Value: "short"
|
||||
|
||||
- name: list
|
||||
input: '{ f(id: [1,2]) }'
|
||||
ast: |
|
||||
<QueryDocument>
|
||||
Operations: [OperationDefinition]
|
||||
- <OperationDefinition>
|
||||
Operation: Operation("query")
|
||||
SelectionSet: [Selection]
|
||||
- <Field>
|
||||
Alias: "f"
|
||||
Name: "f"
|
||||
Arguments: [Argument]
|
||||
- <Argument>
|
||||
Name: "id"
|
||||
Value: [1,2]
|
||||
|
||||
types:
|
||||
- name: common types
|
||||
input: 'query ($string: String, $int: Int, $arr: [Arr], $notnull: [Arr!]!) { f }'
|
||||
ast: |
|
||||
<QueryDocument>
|
||||
Operations: [OperationDefinition]
|
||||
- <OperationDefinition>
|
||||
Operation: Operation("query")
|
||||
VariableDefinitions: [VariableDefinition]
|
||||
- <VariableDefinition>
|
||||
Variable: "string"
|
||||
Type: String
|
||||
- <VariableDefinition>
|
||||
Variable: "int"
|
||||
Type: Int
|
||||
- <VariableDefinition>
|
||||
Variable: "arr"
|
||||
Type: [Arr]
|
||||
- <VariableDefinition>
|
||||
Variable: "notnull"
|
||||
Type: [Arr!]!
|
||||
SelectionSet: [Selection]
|
||||
- <Field>
|
||||
Alias: "f"
|
||||
Name: "f"
|
||||
|
||||
large queries:
|
||||
- name: kitchen sink
|
||||
input: |
|
||||
# Copyright (c) 2015-present, Facebook, Inc.
|
||||
#
|
||||
# This source code is licensed under the MIT license found in the
|
||||
# LICENSE file in the root directory of this source tree.
|
||||
|
||||
query queryName($foo: ComplexType, $site: Site = MOBILE) {
|
||||
whoever123is: node(id: [123, 456]) {
|
||||
id ,
|
||||
... on User @defer {
|
||||
field2 {
|
||||
id ,
|
||||
alias: field1(first:10, after:$foo,) @include(if: $foo) {
|
||||
id,
|
||||
...frag
|
||||
}
|
||||
}
|
||||
}
|
||||
... @skip(unless: $foo) {
|
||||
id
|
||||
}
|
||||
... {
|
||||
id
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
mutation likeStory {
|
||||
like(story: 123) @defer {
|
||||
story {
|
||||
id
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
subscription StoryLikeSubscription($input: StoryLikeSubscribeInput) {
|
||||
storyLikeSubscribe(input: $input) {
|
||||
story {
|
||||
likers {
|
||||
count
|
||||
}
|
||||
likeSentence {
|
||||
text
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fragment frag on Friend {
|
||||
foo(size: $size, bar: $b, obj: {key: "value", block: """
|
||||
block string uses \"""
|
||||
"""})
|
||||
}
|
||||
|
||||
{
|
||||
unnamed(truthy: true, falsey: false, nullish: null),
|
||||
query
|
||||
}
|
||||
ast: |
|
||||
<QueryDocument>
|
||||
Operations: [OperationDefinition]
|
||||
- <OperationDefinition>
|
||||
Operation: Operation("query")
|
||||
Name: "queryName"
|
||||
VariableDefinitions: [VariableDefinition]
|
||||
- <VariableDefinition>
|
||||
Variable: "foo"
|
||||
Type: ComplexType
|
||||
- <VariableDefinition>
|
||||
Variable: "site"
|
||||
Type: Site
|
||||
DefaultValue: MOBILE
|
||||
SelectionSet: [Selection]
|
||||
- <Field>
|
||||
Alias: "whoever123is"
|
||||
Name: "node"
|
||||
Arguments: [Argument]
|
||||
- <Argument>
|
||||
Name: "id"
|
||||
Value: [123,456]
|
||||
SelectionSet: [Selection]
|
||||
- <Field>
|
||||
Alias: "id"
|
||||
Name: "id"
|
||||
- <InlineFragment>
|
||||
TypeCondition: "User"
|
||||
Directives: [Directive]
|
||||
- <Directive>
|
||||
Name: "defer"
|
||||
SelectionSet: [Selection]
|
||||
- <Field>
|
||||
Alias: "field2"
|
||||
Name: "field2"
|
||||
SelectionSet: [Selection]
|
||||
- <Field>
|
||||
Alias: "id"
|
||||
Name: "id"
|
||||
- <Field>
|
||||
Alias: "alias"
|
||||
Name: "field1"
|
||||
Arguments: [Argument]
|
||||
- <Argument>
|
||||
Name: "first"
|
||||
Value: 10
|
||||
- <Argument>
|
||||
Name: "after"
|
||||
Value: $foo
|
||||
Directives: [Directive]
|
||||
- <Directive>
|
||||
Name: "include"
|
||||
Arguments: [Argument]
|
||||
- <Argument>
|
||||
Name: "if"
|
||||
Value: $foo
|
||||
SelectionSet: [Selection]
|
||||
- <Field>
|
||||
Alias: "id"
|
||||
Name: "id"
|
||||
- <FragmentSpread>
|
||||
Name: "frag"
|
||||
- <InlineFragment>
|
||||
Directives: [Directive]
|
||||
- <Directive>
|
||||
Name: "skip"
|
||||
Arguments: [Argument]
|
||||
- <Argument>
|
||||
Name: "unless"
|
||||
Value: $foo
|
||||
SelectionSet: [Selection]
|
||||
- <Field>
|
||||
Alias: "id"
|
||||
Name: "id"
|
||||
- <InlineFragment>
|
||||
SelectionSet: [Selection]
|
||||
- <Field>
|
||||
Alias: "id"
|
||||
Name: "id"
|
||||
Comment: "# Copyright (c) 2015-present, Facebook, Inc.\n#\n# This source code is licensed under the MIT license found in the\n# LICENSE file in the root directory of this source tree.\n"
|
||||
- <OperationDefinition>
|
||||
Operation: Operation("mutation")
|
||||
Name: "likeStory"
|
||||
SelectionSet: [Selection]
|
||||
- <Field>
|
||||
Alias: "like"
|
||||
Name: "like"
|
||||
Arguments: [Argument]
|
||||
- <Argument>
|
||||
Name: "story"
|
||||
Value: 123
|
||||
Directives: [Directive]
|
||||
- <Directive>
|
||||
Name: "defer"
|
||||
SelectionSet: [Selection]
|
||||
- <Field>
|
||||
Alias: "story"
|
||||
Name: "story"
|
||||
SelectionSet: [Selection]
|
||||
- <Field>
|
||||
Alias: "id"
|
||||
Name: "id"
|
||||
- <OperationDefinition>
|
||||
Operation: Operation("subscription")
|
||||
Name: "StoryLikeSubscription"
|
||||
VariableDefinitions: [VariableDefinition]
|
||||
- <VariableDefinition>
|
||||
Variable: "input"
|
||||
Type: StoryLikeSubscribeInput
|
||||
SelectionSet: [Selection]
|
||||
- <Field>
|
||||
Alias: "storyLikeSubscribe"
|
||||
Name: "storyLikeSubscribe"
|
||||
Arguments: [Argument]
|
||||
- <Argument>
|
||||
Name: "input"
|
||||
Value: $input
|
||||
SelectionSet: [Selection]
|
||||
- <Field>
|
||||
Alias: "story"
|
||||
Name: "story"
|
||||
SelectionSet: [Selection]
|
||||
- <Field>
|
||||
Alias: "likers"
|
||||
Name: "likers"
|
||||
SelectionSet: [Selection]
|
||||
- <Field>
|
||||
Alias: "count"
|
||||
Name: "count"
|
||||
- <Field>
|
||||
Alias: "likeSentence"
|
||||
Name: "likeSentence"
|
||||
SelectionSet: [Selection]
|
||||
- <Field>
|
||||
Alias: "text"
|
||||
Name: "text"
|
||||
- <OperationDefinition>
|
||||
Operation: Operation("query")
|
||||
SelectionSet: [Selection]
|
||||
- <Field>
|
||||
Alias: "unnamed"
|
||||
Name: "unnamed"
|
||||
Arguments: [Argument]
|
||||
- <Argument>
|
||||
Name: "truthy"
|
||||
Value: true
|
||||
- <Argument>
|
||||
Name: "falsey"
|
||||
Value: false
|
||||
- <Argument>
|
||||
Name: "nullish"
|
||||
Value: null
|
||||
- <Field>
|
||||
Alias: "query"
|
||||
Name: "query"
|
||||
Fragments: [FragmentDefinition]
|
||||
- <FragmentDefinition>
|
||||
Name: "frag"
|
||||
TypeCondition: "Friend"
|
||||
SelectionSet: [Selection]
|
||||
- <Field>
|
||||
Alias: "foo"
|
||||
Name: "foo"
|
||||
Arguments: [Argument]
|
||||
- <Argument>
|
||||
Name: "size"
|
||||
Value: $size
|
||||
- <Argument>
|
||||
Name: "bar"
|
||||
Value: $b
|
||||
- <Argument>
|
||||
Name: "obj"
|
||||
Value: {key:"value",block:"block string uses \"\"\""}
|
||||
|
||||
fuzzer:
|
||||
- name: 01
|
||||
input: '{__typename{...}}'
|
||||
error:
|
||||
message: 'Expected {, found }'
|
||||
locations: [{ line: 1, column: 16 }]
|
||||
|
||||
- name: 02
|
||||
input: '{...{__typename{...{}}}}'
|
||||
error:
|
||||
message: 'expected at least one definition, found }'
|
||||
locations: [{ line: 1, column: 21 }]
|
||||
+635
@@ -0,0 +1,635 @@
|
||||
package parser
|
||||
|
||||
import (
|
||||
. "github.com/vektah/gqlparser/v2/ast" //nolint:staticcheck // bad, yeah
|
||||
"github.com/vektah/gqlparser/v2/lexer"
|
||||
)
|
||||
|
||||
func ParseSchemas(inputs ...*Source) (*SchemaDocument, error) {
|
||||
sd := &SchemaDocument{}
|
||||
for _, input := range inputs {
|
||||
inputAst, err := ParseSchema(input)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
sd.Merge(inputAst)
|
||||
}
|
||||
return sd, nil
|
||||
}
|
||||
|
||||
func ParseSchema(source *Source) (*SchemaDocument, error) {
|
||||
p := parser{
|
||||
lexer: lexer.New(source),
|
||||
maxTokenLimit: 0, // default value is unlimited
|
||||
}
|
||||
sd, err := p.parseSchemaDocument(), p.err
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, def := range sd.Definitions {
|
||||
def.BuiltIn = source.BuiltIn
|
||||
}
|
||||
for _, def := range sd.Extensions {
|
||||
def.BuiltIn = source.BuiltIn
|
||||
}
|
||||
|
||||
return sd, nil
|
||||
}
|
||||
|
||||
func ParseSchemasWithLimit(maxTokenLimit int, inputs ...*Source) (*SchemaDocument, error) {
|
||||
sd := &SchemaDocument{}
|
||||
for _, input := range inputs {
|
||||
inputAst, err := ParseSchemaWithLimit(input, maxTokenLimit)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
sd.Merge(inputAst)
|
||||
}
|
||||
return sd, nil
|
||||
}
|
||||
|
||||
func ParseSchemaWithLimit(source *Source, maxTokenLimit int) (*SchemaDocument, error) {
|
||||
p := parser{
|
||||
lexer: lexer.New(source),
|
||||
maxTokenLimit: maxTokenLimit, // 0 is unlimited
|
||||
}
|
||||
sd, err := p.parseSchemaDocument(), p.err
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, def := range sd.Definitions {
|
||||
def.BuiltIn = source.BuiltIn
|
||||
}
|
||||
for _, def := range sd.Extensions {
|
||||
def.BuiltIn = source.BuiltIn
|
||||
}
|
||||
|
||||
return sd, nil
|
||||
}
|
||||
|
||||
func (p *parser) parseSchemaDocument() *SchemaDocument {
|
||||
var doc SchemaDocument
|
||||
doc.Position = p.peekPos()
|
||||
for p.peek().Kind != lexer.EOF {
|
||||
if p.err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
var description descriptionWithComment
|
||||
if p.peek().Kind == lexer.BlockString || p.peek().Kind == lexer.String {
|
||||
description = p.parseDescription()
|
||||
}
|
||||
|
||||
if p.peek().Kind != lexer.Name {
|
||||
p.unexpectedError()
|
||||
break
|
||||
}
|
||||
|
||||
switch p.peek().Value {
|
||||
case "scalar", "type", "interface", "union", "enum", "input":
|
||||
doc.Definitions = append(doc.Definitions, p.parseTypeSystemDefinition(description))
|
||||
case "schema":
|
||||
doc.Schema = append(doc.Schema, p.parseSchemaDefinition(description))
|
||||
case "directive":
|
||||
doc.Directives = append(doc.Directives, p.parseDirectiveDefinition(description))
|
||||
case "extend":
|
||||
if description.text != "" {
|
||||
p.unexpectedToken(p.prev)
|
||||
}
|
||||
p.parseTypeSystemExtension(&doc)
|
||||
default:
|
||||
p.unexpectedError()
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// treat end of file comments
|
||||
doc.Comment = p.comment
|
||||
|
||||
return &doc
|
||||
}
|
||||
|
||||
func (p *parser) parseDescription() descriptionWithComment {
|
||||
token := p.peek()
|
||||
|
||||
var desc descriptionWithComment
|
||||
if token.Kind != lexer.BlockString && token.Kind != lexer.String {
|
||||
return desc
|
||||
}
|
||||
|
||||
desc.comment = p.comment
|
||||
desc.text = p.next().Value
|
||||
return desc
|
||||
}
|
||||
|
||||
func (p *parser) parseTypeSystemDefinition(description descriptionWithComment) *Definition {
|
||||
tok := p.peek()
|
||||
if tok.Kind != lexer.Name {
|
||||
p.unexpectedError()
|
||||
return nil
|
||||
}
|
||||
|
||||
switch tok.Value {
|
||||
case "scalar":
|
||||
return p.parseScalarTypeDefinition(description)
|
||||
case "type":
|
||||
return p.parseObjectTypeDefinition(description)
|
||||
case "interface":
|
||||
return p.parseInterfaceTypeDefinition(description)
|
||||
case "union":
|
||||
return p.parseUnionTypeDefinition(description)
|
||||
case "enum":
|
||||
return p.parseEnumTypeDefinition(description)
|
||||
case "input":
|
||||
return p.parseInputObjectTypeDefinition(description)
|
||||
default:
|
||||
p.unexpectedError()
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func (p *parser) parseSchemaDefinition(description descriptionWithComment) *SchemaDefinition {
|
||||
_, comment := p.expectKeyword("schema")
|
||||
|
||||
def := SchemaDefinition{}
|
||||
def.Position = p.peekPos()
|
||||
def.BeforeDescriptionComment = description.comment
|
||||
def.Description = description.text
|
||||
def.AfterDescriptionComment = comment
|
||||
def.Directives = p.parseDirectives(true)
|
||||
|
||||
def.EndOfDefinitionComment = p.some(lexer.BraceL, lexer.BraceR, func() {
|
||||
def.OperationTypes = append(def.OperationTypes, p.parseOperationTypeDefinition())
|
||||
})
|
||||
return &def
|
||||
}
|
||||
|
||||
func (p *parser) parseOperationTypeDefinition() *OperationTypeDefinition {
|
||||
var op OperationTypeDefinition
|
||||
op.Position = p.peekPos()
|
||||
op.Comment = p.comment
|
||||
op.Operation = p.parseOperationType()
|
||||
p.expect(lexer.Colon)
|
||||
op.Type = p.parseName()
|
||||
return &op
|
||||
}
|
||||
|
||||
func (p *parser) parseScalarTypeDefinition(description descriptionWithComment) *Definition {
|
||||
_, comment := p.expectKeyword("scalar")
|
||||
|
||||
var def Definition
|
||||
def.Position = p.peekPos()
|
||||
def.BeforeDescriptionComment = description.comment
|
||||
def.Description = description.text
|
||||
def.AfterDescriptionComment = comment
|
||||
def.Kind = Scalar
|
||||
def.Name = p.parseName()
|
||||
def.Directives = p.parseDirectives(true)
|
||||
return &def
|
||||
}
|
||||
|
||||
func (p *parser) parseObjectTypeDefinition(description descriptionWithComment) *Definition {
|
||||
_, comment := p.expectKeyword("type")
|
||||
|
||||
var def Definition
|
||||
def.Position = p.peekPos()
|
||||
def.Kind = Object
|
||||
def.BeforeDescriptionComment = description.comment
|
||||
def.Description = description.text
|
||||
def.AfterDescriptionComment = comment
|
||||
def.Name = p.parseName()
|
||||
def.Interfaces = p.parseImplementsInterfaces()
|
||||
def.Directives = p.parseDirectives(true)
|
||||
def.Fields, def.EndOfDefinitionComment = p.parseFieldsDefinition()
|
||||
return &def
|
||||
}
|
||||
|
||||
func (p *parser) parseImplementsInterfaces() []string {
|
||||
var types []string
|
||||
if p.peek().Value == "implements" {
|
||||
p.next()
|
||||
// optional leading ampersand
|
||||
p.skip(lexer.Amp)
|
||||
|
||||
types = append(types, p.parseName())
|
||||
for p.skip(lexer.Amp) && p.err == nil {
|
||||
types = append(types, p.parseName())
|
||||
}
|
||||
}
|
||||
return types
|
||||
}
|
||||
|
||||
func (p *parser) parseFieldsDefinition() (FieldList, *CommentGroup) {
|
||||
var defs FieldList
|
||||
comment := p.some(lexer.BraceL, lexer.BraceR, func() {
|
||||
defs = append(defs, p.parseFieldDefinition())
|
||||
})
|
||||
return defs, comment
|
||||
}
|
||||
|
||||
func (p *parser) parseFieldDefinition() *FieldDefinition {
|
||||
var def FieldDefinition
|
||||
def.Position = p.peekPos()
|
||||
|
||||
desc := p.parseDescription()
|
||||
if desc.text != "" {
|
||||
def.BeforeDescriptionComment = desc.comment
|
||||
def.Description = desc.text
|
||||
}
|
||||
|
||||
p.peek() // peek to set p.comment
|
||||
def.AfterDescriptionComment = p.comment
|
||||
def.Name = p.parseName()
|
||||
def.Arguments = p.parseArgumentDefs()
|
||||
p.expect(lexer.Colon)
|
||||
def.Type = p.parseTypeReference()
|
||||
def.Directives = p.parseDirectives(true)
|
||||
|
||||
return &def
|
||||
}
|
||||
|
||||
func (p *parser) parseArgumentDefs() ArgumentDefinitionList {
|
||||
var args ArgumentDefinitionList
|
||||
p.some(lexer.ParenL, lexer.ParenR, func() {
|
||||
args = append(args, p.parseArgumentDef())
|
||||
})
|
||||
return args
|
||||
}
|
||||
|
||||
func (p *parser) parseArgumentDef() *ArgumentDefinition {
|
||||
var def ArgumentDefinition
|
||||
def.Position = p.peekPos()
|
||||
|
||||
desc := p.parseDescription()
|
||||
if desc.text != "" {
|
||||
def.BeforeDescriptionComment = desc.comment
|
||||
def.Description = desc.text
|
||||
}
|
||||
|
||||
p.peek() // peek to set p.comment
|
||||
def.AfterDescriptionComment = p.comment
|
||||
def.Name = p.parseName()
|
||||
p.expect(lexer.Colon)
|
||||
def.Type = p.parseTypeReference()
|
||||
if p.skip(lexer.Equals) {
|
||||
def.DefaultValue = p.parseValueLiteral(true)
|
||||
}
|
||||
def.Directives = p.parseDirectives(true)
|
||||
return &def
|
||||
}
|
||||
|
||||
func (p *parser) parseInputValueDef() *FieldDefinition {
|
||||
var def FieldDefinition
|
||||
def.Position = p.peekPos()
|
||||
|
||||
desc := p.parseDescription()
|
||||
if desc.text != "" {
|
||||
def.BeforeDescriptionComment = desc.comment
|
||||
def.Description = desc.text
|
||||
}
|
||||
|
||||
p.peek() // peek to set p.comment
|
||||
def.AfterDescriptionComment = p.comment
|
||||
def.Name = p.parseName()
|
||||
p.expect(lexer.Colon)
|
||||
def.Type = p.parseTypeReference()
|
||||
if p.skip(lexer.Equals) {
|
||||
def.DefaultValue = p.parseValueLiteral(true)
|
||||
}
|
||||
def.Directives = p.parseDirectives(true)
|
||||
return &def
|
||||
}
|
||||
|
||||
func (p *parser) parseInterfaceTypeDefinition(description descriptionWithComment) *Definition {
|
||||
_, comment := p.expectKeyword("interface")
|
||||
|
||||
var def Definition
|
||||
def.Position = p.peekPos()
|
||||
def.Kind = Interface
|
||||
def.BeforeDescriptionComment = description.comment
|
||||
def.Description = description.text
|
||||
def.AfterDescriptionComment = comment
|
||||
def.Name = p.parseName()
|
||||
def.Interfaces = p.parseImplementsInterfaces()
|
||||
def.Directives = p.parseDirectives(true)
|
||||
def.Fields, def.EndOfDefinitionComment = p.parseFieldsDefinition()
|
||||
return &def
|
||||
}
|
||||
|
||||
func (p *parser) parseUnionTypeDefinition(description descriptionWithComment) *Definition {
|
||||
_, comment := p.expectKeyword("union")
|
||||
|
||||
var def Definition
|
||||
def.Position = p.peekPos()
|
||||
def.Kind = Union
|
||||
def.BeforeDescriptionComment = description.comment
|
||||
def.Description = description.text
|
||||
def.AfterDescriptionComment = comment
|
||||
def.Name = p.parseName()
|
||||
def.Directives = p.parseDirectives(true)
|
||||
def.Types = p.parseUnionMemberTypes()
|
||||
return &def
|
||||
}
|
||||
|
||||
func (p *parser) parseUnionMemberTypes() []string {
|
||||
var types []string
|
||||
if p.skip(lexer.Equals) {
|
||||
// optional leading pipe
|
||||
p.skip(lexer.Pipe)
|
||||
|
||||
types = append(types, p.parseName())
|
||||
for p.skip(lexer.Pipe) && p.err == nil {
|
||||
types = append(types, p.parseName())
|
||||
}
|
||||
}
|
||||
return types
|
||||
}
|
||||
|
||||
func (p *parser) parseEnumTypeDefinition(description descriptionWithComment) *Definition {
|
||||
_, comment := p.expectKeyword("enum")
|
||||
|
||||
var def Definition
|
||||
def.Position = p.peekPos()
|
||||
def.Kind = Enum
|
||||
def.BeforeDescriptionComment = description.comment
|
||||
def.Description = description.text
|
||||
def.AfterDescriptionComment = comment
|
||||
def.Name = p.parseName()
|
||||
def.Directives = p.parseDirectives(true)
|
||||
def.EnumValues, def.EndOfDefinitionComment = p.parseEnumValuesDefinition()
|
||||
return &def
|
||||
}
|
||||
|
||||
func (p *parser) parseEnumValuesDefinition() (EnumValueList, *CommentGroup) {
|
||||
var values EnumValueList
|
||||
comment := p.some(lexer.BraceL, lexer.BraceR, func() {
|
||||
values = append(values, p.parseEnumValueDefinition())
|
||||
})
|
||||
return values, comment
|
||||
}
|
||||
|
||||
func (p *parser) parseEnumValueDefinition() *EnumValueDefinition {
|
||||
var def EnumValueDefinition
|
||||
def.Position = p.peekPos()
|
||||
desc := p.parseDescription()
|
||||
if desc.text != "" {
|
||||
def.BeforeDescriptionComment = desc.comment
|
||||
def.Description = desc.text
|
||||
}
|
||||
|
||||
p.peek() // peek to set p.comment
|
||||
def.AfterDescriptionComment = p.comment
|
||||
|
||||
def.Name = p.parseName()
|
||||
def.Directives = p.parseDirectives(true)
|
||||
|
||||
return &def
|
||||
}
|
||||
|
||||
func (p *parser) parseInputObjectTypeDefinition(description descriptionWithComment) *Definition {
|
||||
_, comment := p.expectKeyword("input")
|
||||
|
||||
var def Definition
|
||||
def.Position = p.peekPos()
|
||||
def.Kind = InputObject
|
||||
def.BeforeDescriptionComment = description.comment
|
||||
def.Description = description.text
|
||||
def.AfterDescriptionComment = comment
|
||||
def.Name = p.parseName()
|
||||
def.Directives = p.parseDirectives(true)
|
||||
def.Fields, def.EndOfDefinitionComment = p.parseInputFieldsDefinition()
|
||||
return &def
|
||||
}
|
||||
|
||||
func (p *parser) parseInputFieldsDefinition() (FieldList, *CommentGroup) {
|
||||
var values FieldList
|
||||
comment := p.some(lexer.BraceL, lexer.BraceR, func() {
|
||||
values = append(values, p.parseInputValueDef())
|
||||
})
|
||||
return values, comment
|
||||
}
|
||||
|
||||
func (p *parser) parseTypeSystemExtension(doc *SchemaDocument) {
|
||||
_, comment := p.expectKeyword("extend")
|
||||
|
||||
switch p.peek().Value {
|
||||
case "schema":
|
||||
doc.SchemaExtension = append(doc.SchemaExtension, p.parseSchemaExtension(comment))
|
||||
case "scalar":
|
||||
doc.Extensions = append(doc.Extensions, p.parseScalarTypeExtension(comment))
|
||||
case "type":
|
||||
doc.Extensions = append(doc.Extensions, p.parseObjectTypeExtension(comment))
|
||||
case "interface":
|
||||
doc.Extensions = append(doc.Extensions, p.parseInterfaceTypeExtension(comment))
|
||||
case "union":
|
||||
doc.Extensions = append(doc.Extensions, p.parseUnionTypeExtension(comment))
|
||||
case "enum":
|
||||
doc.Extensions = append(doc.Extensions, p.parseEnumTypeExtension(comment))
|
||||
case "input":
|
||||
doc.Extensions = append(doc.Extensions, p.parseInputObjectTypeExtension(comment))
|
||||
default:
|
||||
p.unexpectedError()
|
||||
}
|
||||
}
|
||||
|
||||
func (p *parser) parseSchemaExtension(comment *CommentGroup) *SchemaDefinition {
|
||||
p.expectKeyword("schema")
|
||||
|
||||
var def SchemaDefinition
|
||||
def.Position = p.peekPos()
|
||||
def.AfterDescriptionComment = comment
|
||||
def.Directives = p.parseDirectives(true)
|
||||
def.EndOfDefinitionComment = p.some(lexer.BraceL, lexer.BraceR, func() {
|
||||
def.OperationTypes = append(def.OperationTypes, p.parseOperationTypeDefinition())
|
||||
})
|
||||
if len(def.Directives) == 0 && len(def.OperationTypes) == 0 {
|
||||
p.unexpectedError()
|
||||
}
|
||||
return &def
|
||||
}
|
||||
|
||||
func (p *parser) parseScalarTypeExtension(comment *CommentGroup) *Definition {
|
||||
p.expectKeyword("scalar")
|
||||
|
||||
var def Definition
|
||||
def.Position = p.peekPos()
|
||||
def.AfterDescriptionComment = comment
|
||||
def.Kind = Scalar
|
||||
def.Name = p.parseName()
|
||||
def.Directives = p.parseDirectives(true)
|
||||
if len(def.Directives) == 0 {
|
||||
p.unexpectedError()
|
||||
}
|
||||
return &def
|
||||
}
|
||||
|
||||
func (p *parser) parseObjectTypeExtension(comment *CommentGroup) *Definition {
|
||||
p.expectKeyword("type")
|
||||
|
||||
var def Definition
|
||||
def.Position = p.peekPos()
|
||||
def.AfterDescriptionComment = comment
|
||||
def.Kind = Object
|
||||
def.Name = p.parseName()
|
||||
def.Interfaces = p.parseImplementsInterfaces()
|
||||
def.Directives = p.parseDirectives(true)
|
||||
def.Fields, def.EndOfDefinitionComment = p.parseFieldsDefinition()
|
||||
if len(def.Interfaces) == 0 && len(def.Directives) == 0 && len(def.Fields) == 0 {
|
||||
p.unexpectedError()
|
||||
}
|
||||
return &def
|
||||
}
|
||||
|
||||
func (p *parser) parseInterfaceTypeExtension(comment *CommentGroup) *Definition {
|
||||
p.expectKeyword("interface")
|
||||
|
||||
var def Definition
|
||||
def.Position = p.peekPos()
|
||||
def.AfterDescriptionComment = comment
|
||||
def.Kind = Interface
|
||||
def.Name = p.parseName()
|
||||
def.Directives = p.parseDirectives(true)
|
||||
def.Fields, def.EndOfDefinitionComment = p.parseFieldsDefinition()
|
||||
if len(def.Directives) == 0 && len(def.Fields) == 0 {
|
||||
p.unexpectedError()
|
||||
}
|
||||
return &def
|
||||
}
|
||||
|
||||
func (p *parser) parseUnionTypeExtension(comment *CommentGroup) *Definition {
|
||||
p.expectKeyword("union")
|
||||
|
||||
var def Definition
|
||||
def.Position = p.peekPos()
|
||||
def.AfterDescriptionComment = comment
|
||||
def.Kind = Union
|
||||
def.Name = p.parseName()
|
||||
def.Directives = p.parseDirectives(true)
|
||||
def.Types = p.parseUnionMemberTypes()
|
||||
|
||||
if len(def.Directives) == 0 && len(def.Types) == 0 {
|
||||
p.unexpectedError()
|
||||
}
|
||||
return &def
|
||||
}
|
||||
|
||||
func (p *parser) parseEnumTypeExtension(comment *CommentGroup) *Definition {
|
||||
p.expectKeyword("enum")
|
||||
|
||||
var def Definition
|
||||
def.Position = p.peekPos()
|
||||
def.AfterDescriptionComment = comment
|
||||
def.Kind = Enum
|
||||
def.Name = p.parseName()
|
||||
def.Directives = p.parseDirectives(true)
|
||||
def.EnumValues, def.EndOfDefinitionComment = p.parseEnumValuesDefinition()
|
||||
if len(def.Directives) == 0 && len(def.EnumValues) == 0 {
|
||||
p.unexpectedError()
|
||||
}
|
||||
return &def
|
||||
}
|
||||
|
||||
func (p *parser) parseInputObjectTypeExtension(comment *CommentGroup) *Definition {
|
||||
p.expectKeyword("input")
|
||||
|
||||
var def Definition
|
||||
def.Position = p.peekPos()
|
||||
def.AfterDescriptionComment = comment
|
||||
def.Kind = InputObject
|
||||
def.Name = p.parseName()
|
||||
def.Directives = p.parseDirectives(false)
|
||||
def.Fields, def.EndOfDefinitionComment = p.parseInputFieldsDefinition()
|
||||
if len(def.Directives) == 0 && len(def.Fields) == 0 {
|
||||
p.unexpectedError()
|
||||
}
|
||||
return &def
|
||||
}
|
||||
|
||||
func (p *parser) parseDirectiveDefinition(description descriptionWithComment) *DirectiveDefinition {
|
||||
_, comment := p.expectKeyword("directive")
|
||||
p.expect(lexer.At)
|
||||
|
||||
var def DirectiveDefinition
|
||||
def.Position = p.peekPos()
|
||||
def.BeforeDescriptionComment = description.comment
|
||||
def.Description = description.text
|
||||
def.AfterDescriptionComment = comment
|
||||
def.Name = p.parseName()
|
||||
def.Arguments = p.parseArgumentDefs()
|
||||
|
||||
if peek := p.peek(); peek.Kind == lexer.Name && peek.Value == "repeatable" {
|
||||
def.IsRepeatable = true
|
||||
p.skip(lexer.Name)
|
||||
}
|
||||
|
||||
p.expectKeyword("on")
|
||||
def.Locations = p.parseDirectiveLocations()
|
||||
return &def
|
||||
}
|
||||
|
||||
func (p *parser) parseDirectiveLocations() []DirectiveLocation {
|
||||
p.skip(lexer.Pipe)
|
||||
|
||||
locations := []DirectiveLocation{p.parseDirectiveLocation()}
|
||||
|
||||
for p.skip(lexer.Pipe) && p.err == nil {
|
||||
locations = append(locations, p.parseDirectiveLocation())
|
||||
}
|
||||
|
||||
return locations
|
||||
}
|
||||
|
||||
func (p *parser) parseDirectiveLocation() DirectiveLocation {
|
||||
name, _ := p.expect(lexer.Name)
|
||||
|
||||
switch name.Value {
|
||||
case `QUERY`:
|
||||
return LocationQuery
|
||||
case `MUTATION`:
|
||||
return LocationMutation
|
||||
case `SUBSCRIPTION`:
|
||||
return LocationSubscription
|
||||
case `FIELD`:
|
||||
return LocationField
|
||||
case `FRAGMENT_DEFINITION`:
|
||||
return LocationFragmentDefinition
|
||||
case `FRAGMENT_SPREAD`:
|
||||
return LocationFragmentSpread
|
||||
case `INLINE_FRAGMENT`:
|
||||
return LocationInlineFragment
|
||||
case `VARIABLE_DEFINITION`:
|
||||
return LocationVariableDefinition
|
||||
case `SCHEMA`:
|
||||
return LocationSchema
|
||||
case `SCALAR`:
|
||||
return LocationScalar
|
||||
case `OBJECT`:
|
||||
return LocationObject
|
||||
case `FIELD_DEFINITION`:
|
||||
return LocationFieldDefinition
|
||||
case `ARGUMENT_DEFINITION`:
|
||||
return LocationArgumentDefinition
|
||||
case `INTERFACE`:
|
||||
return LocationInterface
|
||||
case `UNION`:
|
||||
return LocationUnion
|
||||
case `ENUM`:
|
||||
return LocationEnum
|
||||
case `ENUM_VALUE`:
|
||||
return LocationEnumValue
|
||||
case `INPUT_OBJECT`:
|
||||
return LocationInputObject
|
||||
case `INPUT_FIELD_DEFINITION`:
|
||||
return LocationInputFieldDefinition
|
||||
}
|
||||
|
||||
p.unexpectedToken(name)
|
||||
return ""
|
||||
}
|
||||
|
||||
type descriptionWithComment struct {
|
||||
text string
|
||||
comment *CommentGroup
|
||||
}
|
||||
+760
@@ -0,0 +1,760 @@
|
||||
object types:
|
||||
- name: simple
|
||||
input: |
|
||||
type Hello {
|
||||
world: String
|
||||
}
|
||||
ast: |
|
||||
<SchemaDocument>
|
||||
Definitions: [Definition]
|
||||
- <Definition>
|
||||
Kind: DefinitionKind("OBJECT")
|
||||
Name: "Hello"
|
||||
Fields: [FieldDefinition]
|
||||
- <FieldDefinition>
|
||||
Name: "world"
|
||||
Type: String
|
||||
|
||||
- name: with comments
|
||||
input: |
|
||||
# Hello
|
||||
# Hello another
|
||||
type Hello {
|
||||
# World
|
||||
# World another
|
||||
world: String
|
||||
# end of type comments
|
||||
}
|
||||
# end of file comments
|
||||
ast: |
|
||||
<SchemaDocument>
|
||||
Definitions: [Definition]
|
||||
- <Definition>
|
||||
Kind: DefinitionKind("OBJECT")
|
||||
Name: "Hello"
|
||||
Fields: [FieldDefinition]
|
||||
- <FieldDefinition>
|
||||
Name: "world"
|
||||
Type: String
|
||||
AfterDescriptionComment: "# World\n# World another\n"
|
||||
AfterDescriptionComment: "# Hello\n# Hello another\n"
|
||||
EndOfDefinitionComment: "# end of type comments\n"
|
||||
Comment: "# end of file comments\n"
|
||||
|
||||
- name: with comments and description
|
||||
input: |
|
||||
# Hello
|
||||
# Hello another
|
||||
"type description"
|
||||
# Hello after description
|
||||
# Hello after description another
|
||||
type Hello {
|
||||
# World
|
||||
# World another
|
||||
"field description"
|
||||
# World after description
|
||||
# World after description another
|
||||
world: String
|
||||
# end of definition coments
|
||||
# end of definition comments another
|
||||
}
|
||||
ast: |
|
||||
<SchemaDocument>
|
||||
Definitions: [Definition]
|
||||
- <Definition>
|
||||
Kind: DefinitionKind("OBJECT")
|
||||
Description: "type description"
|
||||
Name: "Hello"
|
||||
Fields: [FieldDefinition]
|
||||
- <FieldDefinition>
|
||||
Description: "field description"
|
||||
Name: "world"
|
||||
Type: String
|
||||
BeforeDescriptionComment: "# World\n# World another\n"
|
||||
AfterDescriptionComment: "# World after description\n# World after description another\n"
|
||||
BeforeDescriptionComment: "# Hello\n# Hello another\n"
|
||||
AfterDescriptionComment: "# Hello after description\n# Hello after description another\n"
|
||||
EndOfDefinitionComment: "# end of definition coments\n# end of definition comments another\n"
|
||||
|
||||
- name: with description
|
||||
input: |
|
||||
"Description"
|
||||
type Hello {
|
||||
world: String
|
||||
}
|
||||
ast: |
|
||||
<SchemaDocument>
|
||||
Definitions: [Definition]
|
||||
- <Definition>
|
||||
Kind: DefinitionKind("OBJECT")
|
||||
Description: "Description"
|
||||
Name: "Hello"
|
||||
Fields: [FieldDefinition]
|
||||
- <FieldDefinition>
|
||||
Name: "world"
|
||||
Type: String
|
||||
|
||||
- name: with block description
|
||||
input: |
|
||||
# Before description comment
|
||||
"""
|
||||
Description
|
||||
"""
|
||||
# Even with comments between them
|
||||
type Hello {
|
||||
world: String
|
||||
}
|
||||
ast: |
|
||||
<SchemaDocument>
|
||||
Definitions: [Definition]
|
||||
- <Definition>
|
||||
Kind: DefinitionKind("OBJECT")
|
||||
Description: "Description"
|
||||
Name: "Hello"
|
||||
Fields: [FieldDefinition]
|
||||
- <FieldDefinition>
|
||||
Name: "world"
|
||||
Type: String
|
||||
BeforeDescriptionComment: "# Before description comment\n"
|
||||
AfterDescriptionComment: "# Even with comments between them\n"
|
||||
- name: with field arg
|
||||
input: |
|
||||
type Hello {
|
||||
world(flag: Boolean): String
|
||||
}
|
||||
ast: |
|
||||
<SchemaDocument>
|
||||
Definitions: [Definition]
|
||||
- <Definition>
|
||||
Kind: DefinitionKind("OBJECT")
|
||||
Name: "Hello"
|
||||
Fields: [FieldDefinition]
|
||||
- <FieldDefinition>
|
||||
Name: "world"
|
||||
Arguments: [ArgumentDefinition]
|
||||
- <ArgumentDefinition>
|
||||
Name: "flag"
|
||||
Type: Boolean
|
||||
Type: String
|
||||
|
||||
- name: with field arg and default value
|
||||
input: |
|
||||
type Hello {
|
||||
world(flag: Boolean = true): String
|
||||
}
|
||||
ast: |
|
||||
<SchemaDocument>
|
||||
Definitions: [Definition]
|
||||
- <Definition>
|
||||
Kind: DefinitionKind("OBJECT")
|
||||
Name: "Hello"
|
||||
Fields: [FieldDefinition]
|
||||
- <FieldDefinition>
|
||||
Name: "world"
|
||||
Arguments: [ArgumentDefinition]
|
||||
- <ArgumentDefinition>
|
||||
Name: "flag"
|
||||
DefaultValue: true
|
||||
Type: Boolean
|
||||
Type: String
|
||||
|
||||
- name: with field list arg
|
||||
input: |
|
||||
type Hello {
|
||||
world(things: [String]): String
|
||||
}
|
||||
ast: |
|
||||
<SchemaDocument>
|
||||
Definitions: [Definition]
|
||||
- <Definition>
|
||||
Kind: DefinitionKind("OBJECT")
|
||||
Name: "Hello"
|
||||
Fields: [FieldDefinition]
|
||||
- <FieldDefinition>
|
||||
Name: "world"
|
||||
Arguments: [ArgumentDefinition]
|
||||
- <ArgumentDefinition>
|
||||
Name: "things"
|
||||
Type: [String]
|
||||
Type: String
|
||||
|
||||
- name: with two args
|
||||
input: |
|
||||
type Hello {
|
||||
world(argOne: Boolean, argTwo: Int): String
|
||||
}
|
||||
ast: |
|
||||
<SchemaDocument>
|
||||
Definitions: [Definition]
|
||||
- <Definition>
|
||||
Kind: DefinitionKind("OBJECT")
|
||||
Name: "Hello"
|
||||
Fields: [FieldDefinition]
|
||||
- <FieldDefinition>
|
||||
Name: "world"
|
||||
Arguments: [ArgumentDefinition]
|
||||
- <ArgumentDefinition>
|
||||
Name: "argOne"
|
||||
Type: Boolean
|
||||
- <ArgumentDefinition>
|
||||
Name: "argTwo"
|
||||
Type: Int
|
||||
Type: String
|
||||
- name: must define one or more fields
|
||||
input: |
|
||||
type Hello {}
|
||||
error:
|
||||
message: "expected at least one definition, found }"
|
||||
locations: [{ line: 1, column: 13 }]
|
||||
|
||||
type extensions:
|
||||
- name: Object extension
|
||||
input: |
|
||||
# comment
|
||||
extend type Hello {
|
||||
# comment world
|
||||
world: String
|
||||
# end of definition comment
|
||||
}
|
||||
ast: |
|
||||
<SchemaDocument>
|
||||
Extensions: [Definition]
|
||||
- <Definition>
|
||||
Kind: DefinitionKind("OBJECT")
|
||||
Name: "Hello"
|
||||
Fields: [FieldDefinition]
|
||||
- <FieldDefinition>
|
||||
Name: "world"
|
||||
Type: String
|
||||
AfterDescriptionComment: "# comment world\n"
|
||||
AfterDescriptionComment: "# comment\n"
|
||||
EndOfDefinitionComment: "# end of definition comment\n"
|
||||
|
||||
- name: without any fields
|
||||
input: "extend type Hello implements Greeting"
|
||||
ast: |
|
||||
<SchemaDocument>
|
||||
Extensions: [Definition]
|
||||
- <Definition>
|
||||
Kind: DefinitionKind("OBJECT")
|
||||
Name: "Hello"
|
||||
Interfaces: [string]
|
||||
- "Greeting"
|
||||
|
||||
- name: without fields twice
|
||||
input: |
|
||||
extend type Hello implements Greeting
|
||||
extend type Hello implements SecondGreeting
|
||||
ast: |
|
||||
<SchemaDocument>
|
||||
Extensions: [Definition]
|
||||
- <Definition>
|
||||
Kind: DefinitionKind("OBJECT")
|
||||
Name: "Hello"
|
||||
Interfaces: [string]
|
||||
- "Greeting"
|
||||
- <Definition>
|
||||
Kind: DefinitionKind("OBJECT")
|
||||
Name: "Hello"
|
||||
Interfaces: [string]
|
||||
- "SecondGreeting"
|
||||
|
||||
- name: without anything errors
|
||||
input: "extend type Hello"
|
||||
error:
|
||||
message: "Unexpected <EOF>"
|
||||
locations: [{ line: 1, column: 18 }]
|
||||
|
||||
- name: can have descriptions # hmm, this might not be spec compliant...
|
||||
input: |
|
||||
"Description"
|
||||
extend type Hello {
|
||||
world: String
|
||||
}
|
||||
error:
|
||||
message: 'Unexpected String "Description"'
|
||||
locations: [{ line: 1, column: 2 }]
|
||||
|
||||
- name: can not have descriptions on types
|
||||
input: |
|
||||
extend "Description" type Hello {
|
||||
world: String
|
||||
}
|
||||
error:
|
||||
message: Unexpected String "Description"
|
||||
locations: [{ line: 1, column: 9 }]
|
||||
|
||||
- name: all can have directives
|
||||
input: |
|
||||
extend scalar Foo @deprecated
|
||||
extend type Foo @deprecated
|
||||
extend interface Foo @deprecated
|
||||
extend union Foo @deprecated
|
||||
extend enum Foo @deprecated
|
||||
extend input Foo @deprecated
|
||||
ast: |
|
||||
<SchemaDocument>
|
||||
Extensions: [Definition]
|
||||
- <Definition>
|
||||
Kind: DefinitionKind("SCALAR")
|
||||
Name: "Foo"
|
||||
Directives: [Directive]
|
||||
- <Directive>
|
||||
Name: "deprecated"
|
||||
- <Definition>
|
||||
Kind: DefinitionKind("OBJECT")
|
||||
Name: "Foo"
|
||||
Directives: [Directive]
|
||||
- <Directive>
|
||||
Name: "deprecated"
|
||||
- <Definition>
|
||||
Kind: DefinitionKind("INTERFACE")
|
||||
Name: "Foo"
|
||||
Directives: [Directive]
|
||||
- <Directive>
|
||||
Name: "deprecated"
|
||||
- <Definition>
|
||||
Kind: DefinitionKind("UNION")
|
||||
Name: "Foo"
|
||||
Directives: [Directive]
|
||||
- <Directive>
|
||||
Name: "deprecated"
|
||||
- <Definition>
|
||||
Kind: DefinitionKind("ENUM")
|
||||
Name: "Foo"
|
||||
Directives: [Directive]
|
||||
- <Directive>
|
||||
Name: "deprecated"
|
||||
- <Definition>
|
||||
Kind: DefinitionKind("INPUT_OBJECT")
|
||||
Name: "Foo"
|
||||
Directives: [Directive]
|
||||
- <Directive>
|
||||
Name: "deprecated"
|
||||
|
||||
schema definition:
|
||||
- name: simple
|
||||
input: |
|
||||
schema {
|
||||
query: Query
|
||||
}
|
||||
ast: |
|
||||
<SchemaDocument>
|
||||
Schema: [SchemaDefinition]
|
||||
- <SchemaDefinition>
|
||||
OperationTypes: [OperationTypeDefinition]
|
||||
- <OperationTypeDefinition>
|
||||
Operation: Operation("query")
|
||||
Type: "Query"
|
||||
|
||||
- name: with comments and description
|
||||
input: |
|
||||
# before description comment
|
||||
"description"
|
||||
# after description comment
|
||||
schema {
|
||||
# before field comment
|
||||
query: Query
|
||||
# after field comment
|
||||
}
|
||||
ast: |
|
||||
<SchemaDocument>
|
||||
Schema: [SchemaDefinition]
|
||||
- <SchemaDefinition>
|
||||
Description: "description"
|
||||
OperationTypes: [OperationTypeDefinition]
|
||||
- <OperationTypeDefinition>
|
||||
Operation: Operation("query")
|
||||
Type: "Query"
|
||||
Comment: "# before field comment\n"
|
||||
BeforeDescriptionComment: "# before description comment\n"
|
||||
AfterDescriptionComment: "# after description comment\n"
|
||||
EndOfDefinitionComment: "# after field comment\n"
|
||||
|
||||
schema extensions:
|
||||
- name: simple
|
||||
input: |
|
||||
extend schema {
|
||||
mutation: Mutation
|
||||
}
|
||||
ast: |
|
||||
<SchemaDocument>
|
||||
SchemaExtension: [SchemaDefinition]
|
||||
- <SchemaDefinition>
|
||||
OperationTypes: [OperationTypeDefinition]
|
||||
- <OperationTypeDefinition>
|
||||
Operation: Operation("mutation")
|
||||
Type: "Mutation"
|
||||
|
||||
- name: with comment and description
|
||||
input: |
|
||||
# before extend comment
|
||||
extend schema {
|
||||
# before field comment
|
||||
mutation: Mutation
|
||||
# after field comment
|
||||
}
|
||||
ast: |
|
||||
<SchemaDocument>
|
||||
SchemaExtension: [SchemaDefinition]
|
||||
- <SchemaDefinition>
|
||||
OperationTypes: [OperationTypeDefinition]
|
||||
- <OperationTypeDefinition>
|
||||
Operation: Operation("mutation")
|
||||
Type: "Mutation"
|
||||
Comment: "# before field comment\n"
|
||||
AfterDescriptionComment: "# before extend comment\n"
|
||||
EndOfDefinitionComment: "# after field comment\n"
|
||||
|
||||
- name: directive only
|
||||
input: "extend schema @directive"
|
||||
ast: |
|
||||
<SchemaDocument>
|
||||
SchemaExtension: [SchemaDefinition]
|
||||
- <SchemaDefinition>
|
||||
Directives: [Directive]
|
||||
- <Directive>
|
||||
Name: "directive"
|
||||
|
||||
- name: without anything errors
|
||||
input: "extend schema"
|
||||
error:
|
||||
message: "Unexpected <EOF>"
|
||||
locations: [{ line: 1, column: 14}]
|
||||
|
||||
inheritance:
|
||||
- name: single
|
||||
input: "type Hello implements World { field: String }"
|
||||
ast: |
|
||||
<SchemaDocument>
|
||||
Definitions: [Definition]
|
||||
- <Definition>
|
||||
Kind: DefinitionKind("OBJECT")
|
||||
Name: "Hello"
|
||||
Interfaces: [string]
|
||||
- "World"
|
||||
Fields: [FieldDefinition]
|
||||
- <FieldDefinition>
|
||||
Name: "field"
|
||||
Type: String
|
||||
|
||||
- name: multi
|
||||
input: "type Hello implements Wo & rld { field: String }"
|
||||
ast: |
|
||||
<SchemaDocument>
|
||||
Definitions: [Definition]
|
||||
- <Definition>
|
||||
Kind: DefinitionKind("OBJECT")
|
||||
Name: "Hello"
|
||||
Interfaces: [string]
|
||||
- "Wo"
|
||||
- "rld"
|
||||
Fields: [FieldDefinition]
|
||||
- <FieldDefinition>
|
||||
Name: "field"
|
||||
Type: String
|
||||
|
||||
- name: multi with leading amp
|
||||
input: "type Hello implements & Wo & rld { field: String }"
|
||||
ast: |
|
||||
<SchemaDocument>
|
||||
Definitions: [Definition]
|
||||
- <Definition>
|
||||
Kind: DefinitionKind("OBJECT")
|
||||
Name: "Hello"
|
||||
Interfaces: [string]
|
||||
- "Wo"
|
||||
- "rld"
|
||||
Fields: [FieldDefinition]
|
||||
- <FieldDefinition>
|
||||
Name: "field"
|
||||
Type: String
|
||||
|
||||
enums:
|
||||
- name: single value
|
||||
input: "enum Hello { WORLD }"
|
||||
ast: |
|
||||
<SchemaDocument>
|
||||
Definitions: [Definition]
|
||||
- <Definition>
|
||||
Kind: DefinitionKind("ENUM")
|
||||
Name: "Hello"
|
||||
EnumValues: [EnumValueDefinition]
|
||||
- <EnumValueDefinition>
|
||||
Name: "WORLD"
|
||||
|
||||
- name: double value
|
||||
input: "enum Hello { WO, RLD }"
|
||||
ast: |
|
||||
<SchemaDocument>
|
||||
Definitions: [Definition]
|
||||
- <Definition>
|
||||
Kind: DefinitionKind("ENUM")
|
||||
Name: "Hello"
|
||||
EnumValues: [EnumValueDefinition]
|
||||
- <EnumValueDefinition>
|
||||
Name: "WO"
|
||||
- <EnumValueDefinition>
|
||||
Name: "RLD"
|
||||
- name: must define one or more unique enum values
|
||||
input: |
|
||||
enum Hello {}
|
||||
error:
|
||||
message: "expected at least one definition, found }"
|
||||
locations: [{ line: 1, column: 13 }]
|
||||
|
||||
interface:
|
||||
- name: simple
|
||||
input: |
|
||||
interface Hello {
|
||||
world: String
|
||||
}
|
||||
ast: |
|
||||
<SchemaDocument>
|
||||
Definitions: [Definition]
|
||||
- <Definition>
|
||||
Kind: DefinitionKind("INTERFACE")
|
||||
Name: "Hello"
|
||||
Fields: [FieldDefinition]
|
||||
- <FieldDefinition>
|
||||
Name: "world"
|
||||
Type: String
|
||||
- name: must define one or more fields
|
||||
input: |
|
||||
interface Hello {}
|
||||
error:
|
||||
message: "expected at least one definition, found }"
|
||||
locations: [{ line: 1, column: 18 }]
|
||||
|
||||
- name: may define intermediate interfaces
|
||||
input: |
|
||||
interface IA {
|
||||
id: ID!
|
||||
}
|
||||
|
||||
interface IIA implements IA {
|
||||
id: ID!
|
||||
}
|
||||
|
||||
type A implements IIA {
|
||||
id: ID!
|
||||
}
|
||||
ast: |
|
||||
<SchemaDocument>
|
||||
Definitions: [Definition]
|
||||
- <Definition>
|
||||
Kind: DefinitionKind("INTERFACE")
|
||||
Name: "IA"
|
||||
Fields: [FieldDefinition]
|
||||
- <FieldDefinition>
|
||||
Name: "id"
|
||||
Type: ID!
|
||||
- <Definition>
|
||||
Kind: DefinitionKind("INTERFACE")
|
||||
Name: "IIA"
|
||||
Interfaces: [string]
|
||||
- "IA"
|
||||
Fields: [FieldDefinition]
|
||||
- <FieldDefinition>
|
||||
Name: "id"
|
||||
Type: ID!
|
||||
- <Definition>
|
||||
Kind: DefinitionKind("OBJECT")
|
||||
Name: "A"
|
||||
Interfaces: [string]
|
||||
- "IIA"
|
||||
Fields: [FieldDefinition]
|
||||
- <FieldDefinition>
|
||||
Name: "id"
|
||||
Type: ID!
|
||||
|
||||
unions:
|
||||
- name: simple
|
||||
input: "union Hello = World"
|
||||
ast: |
|
||||
<SchemaDocument>
|
||||
Definitions: [Definition]
|
||||
- <Definition>
|
||||
Kind: DefinitionKind("UNION")
|
||||
Name: "Hello"
|
||||
Types: [string]
|
||||
- "World"
|
||||
|
||||
- name: with two types
|
||||
input: "union Hello = Wo | Rld"
|
||||
ast: |
|
||||
<SchemaDocument>
|
||||
Definitions: [Definition]
|
||||
- <Definition>
|
||||
Kind: DefinitionKind("UNION")
|
||||
Name: "Hello"
|
||||
Types: [string]
|
||||
- "Wo"
|
||||
- "Rld"
|
||||
|
||||
- name: with leading pipe
|
||||
input: "union Hello = | Wo | Rld"
|
||||
ast: |
|
||||
<SchemaDocument>
|
||||
Definitions: [Definition]
|
||||
- <Definition>
|
||||
Kind: DefinitionKind("UNION")
|
||||
Name: "Hello"
|
||||
Types: [string]
|
||||
- "Wo"
|
||||
- "Rld"
|
||||
|
||||
- name: cant be empty
|
||||
input: "union Hello = || Wo | Rld"
|
||||
error:
|
||||
message: "Expected Name, found |"
|
||||
locations: [{ line: 1, column: 16 }]
|
||||
|
||||
- name: cant double pipe
|
||||
input: "union Hello = Wo || Rld"
|
||||
error:
|
||||
message: "Expected Name, found |"
|
||||
locations: [{ line: 1, column: 19 }]
|
||||
|
||||
- name: cant have trailing pipe
|
||||
input: "union Hello = | Wo | Rld |"
|
||||
error:
|
||||
message: "Expected Name, found <EOF>"
|
||||
locations: [{ line: 1, column: 27 }]
|
||||
|
||||
scalar:
|
||||
- name: simple
|
||||
input: "scalar Hello"
|
||||
ast: |
|
||||
<SchemaDocument>
|
||||
Definitions: [Definition]
|
||||
- <Definition>
|
||||
Kind: DefinitionKind("SCALAR")
|
||||
Name: "Hello"
|
||||
|
||||
input object:
|
||||
- name: simple
|
||||
input: |
|
||||
input Hello {
|
||||
world: String
|
||||
}
|
||||
ast: |
|
||||
<SchemaDocument>
|
||||
Definitions: [Definition]
|
||||
- <Definition>
|
||||
Kind: DefinitionKind("INPUT_OBJECT")
|
||||
Name: "Hello"
|
||||
Fields: [FieldDefinition]
|
||||
- <FieldDefinition>
|
||||
Name: "world"
|
||||
Type: String
|
||||
|
||||
- name: can not have args
|
||||
input: |
|
||||
input Hello {
|
||||
world(foo: Int): String
|
||||
}
|
||||
error:
|
||||
message: "Expected :, found ("
|
||||
locations: [{ line: 2, column: 8 }]
|
||||
- name: must define one or more input fields
|
||||
input: |
|
||||
input Hello {}
|
||||
error:
|
||||
message: "expected at least one definition, found }"
|
||||
locations: [{ line: 1, column: 14 }]
|
||||
|
||||
directives:
|
||||
- name: simple
|
||||
input: directive @foo on FIELD
|
||||
ast: |
|
||||
<SchemaDocument>
|
||||
Directives: [DirectiveDefinition]
|
||||
- <DirectiveDefinition>
|
||||
Name: "foo"
|
||||
Locations: [DirectiveLocation]
|
||||
- DirectiveLocation("FIELD")
|
||||
IsRepeatable: false
|
||||
|
||||
- name: executable
|
||||
input: |
|
||||
directive @onQuery on QUERY
|
||||
directive @onMutation on MUTATION
|
||||
directive @onSubscription on SUBSCRIPTION
|
||||
directive @onField on FIELD
|
||||
directive @onFragmentDefinition on FRAGMENT_DEFINITION
|
||||
directive @onFragmentSpread on FRAGMENT_SPREAD
|
||||
directive @onInlineFragment on INLINE_FRAGMENT
|
||||
directive @onVariableDefinition on VARIABLE_DEFINITION
|
||||
ast: |
|
||||
<SchemaDocument>
|
||||
Directives: [DirectiveDefinition]
|
||||
- <DirectiveDefinition>
|
||||
Name: "onQuery"
|
||||
Locations: [DirectiveLocation]
|
||||
- DirectiveLocation("QUERY")
|
||||
IsRepeatable: false
|
||||
- <DirectiveDefinition>
|
||||
Name: "onMutation"
|
||||
Locations: [DirectiveLocation]
|
||||
- DirectiveLocation("MUTATION")
|
||||
IsRepeatable: false
|
||||
- <DirectiveDefinition>
|
||||
Name: "onSubscription"
|
||||
Locations: [DirectiveLocation]
|
||||
- DirectiveLocation("SUBSCRIPTION")
|
||||
IsRepeatable: false
|
||||
- <DirectiveDefinition>
|
||||
Name: "onField"
|
||||
Locations: [DirectiveLocation]
|
||||
- DirectiveLocation("FIELD")
|
||||
IsRepeatable: false
|
||||
- <DirectiveDefinition>
|
||||
Name: "onFragmentDefinition"
|
||||
Locations: [DirectiveLocation]
|
||||
- DirectiveLocation("FRAGMENT_DEFINITION")
|
||||
IsRepeatable: false
|
||||
- <DirectiveDefinition>
|
||||
Name: "onFragmentSpread"
|
||||
Locations: [DirectiveLocation]
|
||||
- DirectiveLocation("FRAGMENT_SPREAD")
|
||||
IsRepeatable: false
|
||||
- <DirectiveDefinition>
|
||||
Name: "onInlineFragment"
|
||||
Locations: [DirectiveLocation]
|
||||
- DirectiveLocation("INLINE_FRAGMENT")
|
||||
IsRepeatable: false
|
||||
- <DirectiveDefinition>
|
||||
Name: "onVariableDefinition"
|
||||
Locations: [DirectiveLocation]
|
||||
- DirectiveLocation("VARIABLE_DEFINITION")
|
||||
IsRepeatable: false
|
||||
|
||||
- name: repeatable
|
||||
input: directive @foo repeatable on FIELD
|
||||
ast: |
|
||||
<SchemaDocument>
|
||||
Directives: [DirectiveDefinition]
|
||||
- <DirectiveDefinition>
|
||||
Name: "foo"
|
||||
Locations: [DirectiveLocation]
|
||||
- DirectiveLocation("FIELD")
|
||||
IsRepeatable: true
|
||||
|
||||
- name: invalid location
|
||||
input: "directive @foo on FIELD | INCORRECT_LOCATION"
|
||||
error:
|
||||
message: 'Unexpected Name "INCORRECT_LOCATION"'
|
||||
locations: [{ line: 1, column: 27 }]
|
||||
|
||||
fuzzer:
|
||||
- name: 1
|
||||
input: "type o{d(g:["
|
||||
error:
|
||||
message: 'Expected Name, found <EOF>'
|
||||
locations: [{ line: 1, column: 13 }]
|
||||
- name: 2
|
||||
input: "\"\"\"\r"
|
||||
error:
|
||||
message: 'Unexpected <Invalid>'
|
||||
locations: [{ line: 2, column: 1 }]
|
||||
Reference in New Issue
Block a user