graphql 0.9.5 → 0.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (44) hide show
  1. checksums.yaml +4 -4
  2. data/lib/graphql/base_type.rb +24 -0
  3. data/lib/graphql/definition_helpers/defined_by_config.rb +5 -4
  4. data/lib/graphql/definition_helpers/non_null_with_bang.rb +1 -1
  5. data/lib/graphql/definition_helpers/type_definer.rb +1 -1
  6. data/lib/graphql/enum_type.rb +16 -3
  7. data/lib/graphql/input_object_type.rb +10 -0
  8. data/lib/graphql/language.rb +0 -5
  9. data/lib/graphql/language/nodes.rb +79 -75
  10. data/lib/graphql/language/parser.rb +109 -106
  11. data/lib/graphql/language/transform.rb +100 -91
  12. data/lib/graphql/language/visitor.rb +78 -74
  13. data/lib/graphql/list_type.rb +5 -0
  14. data/lib/graphql/non_null_type.rb +6 -2
  15. data/lib/graphql/query.rb +58 -9
  16. data/lib/graphql/query/arguments.rb +29 -26
  17. data/lib/graphql/query/base_execution/value_resolution.rb +3 -3
  18. data/lib/graphql/query/directive_chain.rb +1 -1
  19. data/lib/graphql/query/executor.rb +6 -27
  20. data/lib/graphql/query/literal_input.rb +89 -0
  21. data/lib/graphql/query/ruby_input.rb +20 -0
  22. data/lib/graphql/query/serial_execution/field_resolution.rb +1 -1
  23. data/lib/graphql/query/variables.rb +39 -0
  24. data/lib/graphql/scalar_type.rb +27 -5
  25. data/lib/graphql/schema.rb +5 -0
  26. data/lib/graphql/schema/type_expression.rb +28 -0
  27. data/lib/graphql/static_validation/literal_validator.rb +5 -2
  28. data/lib/graphql/static_validation/rules/fields_are_defined_on_type.rb +0 -2
  29. data/lib/graphql/static_validation/rules/variable_default_values_are_correctly_typed.rb +1 -1
  30. data/lib/graphql/version.rb +1 -1
  31. data/readme.md +3 -2
  32. data/spec/graphql/enum_type_spec.rb +7 -2
  33. data/spec/graphql/input_object_type_spec.rb +45 -0
  34. data/spec/graphql/language/parser_spec.rb +2 -1
  35. data/spec/graphql/language/transform_spec.rb +5 -0
  36. data/spec/graphql/query/base_execution/value_resolution_spec.rb +46 -0
  37. data/spec/graphql/query/context_spec.rb +37 -0
  38. data/spec/graphql/query/executor_spec.rb +33 -0
  39. data/spec/graphql/query_spec.rb +110 -26
  40. data/spec/graphql/schema/type_expression_spec.rb +38 -0
  41. data/spec/graphql/static_validation/rules/argument_literals_are_compatible_spec.rb +2 -2
  42. data/spec/support/dairy_app.rb +17 -17
  43. data/spec/support/dairy_data.rb +2 -2
  44. metadata +12 -2
@@ -1,108 +1,117 @@
1
- class Parslet::Context
2
- def create_node(name, attributes)
3
- node_class = GraphQL::Language::Nodes.const_get(name)
4
- node_class.new(attributes)
5
- end
6
- end
7
-
8
- module GraphQL::Language
9
- # {Transform} is a [parslet](http://kschiess.github.io/parslet/) transform for for turning the AST into objects in {GraphQL::Language::Nodes} objects.
10
- class Transform < Parslet::Transform
11
- def self.optional_sequence(name)
12
- rule(name => simple(:val)) { [] }
13
- rule(name => sequence(:val)) { val }
1
+ module GraphQL
2
+ module Language
3
+ # Extend `Parslet::Context` with a convenience method
4
+ # for building AST nodes
5
+ module ParsletContextCreateNode
6
+ refine Parslet::Context do
7
+ # @param [Symbol] name of a node constant
8
+ # @param [Hash] attributes to initialize the {Node} with
9
+ # @return [GraphQL::Language::Node] a node of type `name` with attributes `attributes`
10
+ def create_node(name, attributes)
11
+ node_class = GraphQL::Language::Nodes.const_get(name)
12
+ node_class.new(attributes)
13
+ end
14
+ end
14
15
  end
15
16
 
17
+ using ParsletContextCreateNode
16
18
 
19
+ # {Transform} is a [parslet](http://kschiess.github.io/parslet/) transform for for turning the AST into objects in {GraphQL::Language::Nodes} objects.
20
+ class Transform < Parslet::Transform
21
+ def self.optional_sequence(name)
22
+ rule(name => simple(:val)) { [] }
23
+ rule(name => sequence(:val)) { val }
24
+ end
17
25
 
18
- # Document
19
- rule(document_parts: sequence(:p)) { create_node(:Document, parts: p, line: (p.first ? p.first.line : 1), col: (p.first ? p.first.col : 1))}
20
- rule(document_parts: simple(:p)) { create_node(:Document, parts: [], line: 1, col: 1)}
26
+ # Document
27
+ rule(document_parts: sequence(:p)) { create_node(:Document, parts: p, line: (p.first ? p.first.line : 1), col: (p.first ? p.first.col : 1))}
28
+ rule(document_parts: simple(:p)) { create_node(:Document, parts: [], line: 1, col: 1)}
21
29
 
22
- # Fragment Definition
23
- rule(
24
- fragment_keyword: simple(:kw),
25
- fragment_name: simple(:name),
26
- type_condition: simple(:type),
27
- directives: sequence(:directives),
28
- selections: sequence(:selections)
29
- ) { create_node(:FragmentDefinition, name: name.to_s, type: type.to_s, directives: directives, selections: selections, position_source: kw)}
30
+ # Fragment Definition
31
+ rule(
32
+ fragment_keyword: simple(:kw),
33
+ fragment_name: simple(:name),
34
+ type_condition: simple(:type),
35
+ directives: sequence(:directives),
36
+ selections: sequence(:selections)
37
+ ) { create_node(:FragmentDefinition, name: name.to_s, type: type.to_s, directives: directives, selections: selections, position_source: kw)}
30
38
 
31
- rule(
32
- fragment_spread_keyword: simple(:kw),
33
- fragment_spread_name: simple(:n),
34
- directives: sequence(:d)
35
- ) { create_node(:FragmentSpread, name: n.to_s, directives: d, position_source: kw)}
39
+ rule(
40
+ fragment_spread_keyword: simple(:kw),
41
+ fragment_spread_name: simple(:n),
42
+ directives: sequence(:d)
43
+ ) { create_node(:FragmentSpread, name: n.to_s, directives: d, position_source: kw)}
36
44
 
37
- rule(
38
- fragment_spread_keyword: simple(:kw),
39
- inline_fragment_type: simple(:n),
40
- directives: sequence(:d),
41
- selections: sequence(:s),
42
- ) { create_node(:InlineFragment, type: n.to_s, directives: d, selections: s, position_source: kw)}
45
+ rule(
46
+ fragment_spread_keyword: simple(:kw),
47
+ inline_fragment_type: simple(:n),
48
+ directives: sequence(:d),
49
+ selections: sequence(:s),
50
+ ) { create_node(:InlineFragment, type: n.to_s, directives: d, selections: s, position_source: kw)}
43
51
 
44
- # Operation Definition
45
- rule(
46
- operation_type: simple(:ot),
47
- name: simple(:n),
48
- variables: sequence(:v),
49
- directives: sequence(:d),
50
- selections: sequence(:s),
51
- ) { create_node(:OperationDefinition, operation_type: ot.to_s, name: n.to_s, variables: v, directives: d, selections: s, position_source: ot) }
52
- optional_sequence(:optional_variables)
53
- rule(variable_name: simple(:n), variable_type: simple(:t), variable_optional_default_value: simple(:v)) { create_node(:Variable, name: n.name, type: t, default_value: v, line: n.line, col: n.col)}
54
- rule(variable_name: simple(:n), variable_type: simple(:t), variable_optional_default_value: sequence(:v)) { create_node(:Variable, name: n.name, type: t, default_value: v, line: n.line, col: n.col)}
55
- rule(variable_default_value: simple(:v) ) { v }
56
- rule(variable_default_value: sequence(:v) ) { v }
57
- # Query short-hand
58
- rule(unnamed_selections: sequence(:s)) { create_node(:OperationDefinition, selections: s, operation_type: "query", name: nil, variables: [], directives: [], line: s.first.line, col: s.first.col)}
52
+ # Operation Definition
53
+ rule(
54
+ operation_type: simple(:ot),
55
+ name: simple(:n),
56
+ variables: sequence(:v),
57
+ directives: sequence(:d),
58
+ selections: sequence(:s),
59
+ ) { create_node(:OperationDefinition, operation_type: ot.to_s, name: n.to_s, variables: v, directives: d, selections: s, position_source: ot) }
60
+ optional_sequence(:optional_variables)
61
+ rule(variable_name: simple(:n), variable_type: simple(:t), variable_optional_default_value: simple(:v)) { create_node(:Variable, name: n.name, type: t, default_value: v, line: n.line, col: n.col)}
62
+ rule(variable_name: simple(:n), variable_type: simple(:t), variable_optional_default_value: sequence(:v)) { create_node(:Variable, name: n.name, type: t, default_value: v, line: n.line, col: n.col)}
63
+ rule(variable_default_value: simple(:v) ) { v }
64
+ rule(variable_default_value: sequence(:v) ) { v }
65
+ # Query short-hand
66
+ rule(unnamed_selections: sequence(:s)) { create_node(:OperationDefinition, selections: s, operation_type: "query", name: nil, variables: [], directives: [], line: s.first.line, col: s.first.col)}
59
67
 
60
- # Field
61
- rule(
62
- alias: simple(:a),
63
- field_name: simple(:name),
64
- field_arguments: sequence(:args),
65
- directives: sequence(:dir),
66
- selections: sequence(:sel)
67
- ) { create_node(:Field, alias: a && a.to_s, name: name.to_s, arguments: args, directives: dir, selections: sel, position_source: [a, name].find { |part| !part.nil? }) }
68
+ # Field
69
+ rule(
70
+ alias: simple(:a),
71
+ field_name: simple(:name),
72
+ field_arguments: sequence(:args),
73
+ directives: sequence(:dir),
74
+ selections: sequence(:sel)
75
+ ) { create_node(:Field, alias: a && a.to_s, name: name.to_s, arguments: args, directives: dir, selections: sel, position_source: [a, name].find { |part| !part.nil? }) }
68
76
 
69
- rule(alias_name: simple(:a)) { a }
70
- optional_sequence(:optional_field_arguments)
71
- rule(field_argument_name: simple(:n), field_argument_value: simple(:v)) { create_node(:Argument, name: n.to_s, value: v, position_source: n)}
72
- rule(field_argument_name: simple(:n), field_argument_value: sequence(:v)) { create_node(:Argument, name: n.to_s, value: v, position_source: n)}
73
- optional_sequence(:optional_selections)
74
- optional_sequence(:optional_directives)
77
+ rule(alias_name: simple(:a)) { a }
78
+ optional_sequence(:optional_field_arguments)
79
+ rule(field_argument_name: simple(:n), field_argument_value: simple(:v)) { create_node(:Argument, name: n.to_s, value: v, position_source: n)}
80
+ rule(field_argument_name: simple(:n), field_argument_value: sequence(:v)) { create_node(:Argument, name: n.to_s, value: v, position_source: n)}
81
+ optional_sequence(:optional_selections)
82
+ optional_sequence(:optional_directives)
75
83
 
76
- # Directive
77
- rule(directive_name: simple(:name), directive_arguments: sequence(:args)) { create_node(:Directive, name: name.to_s, arguments: args, position_source: name ) }
78
- rule(directive_argument_name: simple(:n), directive_argument_value: simple(:v)) { create_node(:Argument, name: n.to_s, value: v, position_source: n)}
79
- optional_sequence(:optional_directive_arguments)
84
+ # Directive
85
+ rule(directive_name: simple(:name), directive_arguments: sequence(:args)) { create_node(:Directive, name: name.to_s, arguments: args, position_source: name ) }
86
+ rule(directive_argument_name: simple(:n), directive_argument_value: simple(:v)) { create_node(:Argument, name: n.to_s, value: v, position_source: n)}
87
+ optional_sequence(:optional_directive_arguments)
80
88
 
81
- # Type Defs
82
- rule(type_name: simple(:n)) { create_node(:TypeName, name: n.to_s, position_source: n) }
83
- rule(list_type: simple(:t)) { create_node(:ListType, of_type: t, line: t.line, col: t.col)}
84
- rule(non_null_type: simple(:t)) { create_node(:NonNullType, of_type: t, line: t.line, col: t.col)}
89
+ # Type Defs
90
+ rule(type_name: simple(:n)) { create_node(:TypeName, name: n.to_s, position_source: n) }
91
+ rule(list_type: simple(:t)) { create_node(:ListType, of_type: t, line: t.line, col: t.col)}
92
+ rule(non_null_type: simple(:t)) { create_node(:NonNullType, of_type: t, line: t.line, col: t.col)}
85
93
 
86
- # Values
87
- rule(array: sequence(:v)) { v }
88
- rule(boolean: simple(:v)) { v == "true" ? true : false }
89
- rule(input_object: sequence(:v)) { create_node(:InputObject, pairs: v, line: v.first.line, col: v.first.col) }
90
- rule(input_object_name: simple(:n), input_object_value: simple(:v)) { create_node(:Argument, name: n.to_s, value: v, position_source: n)}
91
- rule(input_object_name: simple(:n), input_object_value: sequence(:v)) { create_node(:Argument, name: n.to_s, value: v, position_source: n)}
92
- rule(int: simple(:v)) { v.to_i }
93
- rule(float: simple(:v)) { v.to_f }
94
+ # Values
95
+ rule(array: sequence(:v)) { v }
96
+ rule(boolean: simple(:v)) { v == "true" ? true : false }
97
+ rule(input_object: sequence(:v)) { create_node(:InputObject, pairs: v, line: v.first.line, col: v.first.col) }
98
+ rule(input_object_name: simple(:n), input_object_value: simple(:v)) { create_node(:Argument, name: n.to_s, value: v, position_source: n)}
99
+ rule(input_object_name: simple(:n), input_object_value: sequence(:v)) { create_node(:Argument, name: n.to_s, value: v, position_source: n)}
100
+ rule(int: simple(:v)) { v.to_i }
101
+ rule(float: simple(:v)) { v.to_f }
94
102
 
95
- ESCAPES = /\\(["\\\/bfnrt])/
96
- UTF_8 = /\\u[\da-f]{4}/i
97
- UTF_8_REPLACE = -> (m) { [m[-4..-1].to_i(16)].pack('U') }
103
+ ESCAPES = /\\(["\\\/bfnrt])/
104
+ UTF_8 = /\\u[\da-f]{4}/i
105
+ UTF_8_REPLACE = -> (m) { [m[-4..-1].to_i(16)].pack('U') }
98
106
 
99
- rule(string: simple(:v)) {
100
- string = v.to_s
101
- string.gsub!(ESCAPES, '\1')
102
- string.gsub!(UTF_8, &UTF_8_REPLACE)
103
- string
104
- }
105
- rule(variable: simple(:v)) { create_node(:VariableIdentifier, name: v.to_s, position_source: v) }
106
- rule(enum: simple(:v)) { create_node(:Enum, name: v.to_s, position_source: v)}
107
+ rule(string: simple(:v)) {
108
+ string = v.to_s
109
+ string.gsub!(ESCAPES, '\1')
110
+ string.gsub!(UTF_8, &UTF_8_REPLACE)
111
+ string
112
+ }
113
+ rule(variable: simple(:v)) { create_node(:VariableIdentifier, name: v.to_s, position_source: v) }
114
+ rule(enum: simple(:v)) { create_node(:Enum, name: v.to_s, position_source: v)}
115
+ end
107
116
  end
108
117
  end
@@ -1,88 +1,92 @@
1
- # Depth-first traversal through the tree, calling hooks at each stop.
2
- #
3
- # @example Create a visitor, add hooks, then search a document
4
- # total_field_count = 0
5
- # visitor = GraphQL::Language::Visitor.new
6
- # # Whenever you find a field, increment the field count:
7
- # visitor[GraphQL::Language::Nodes::Field] << -> (node) { total_field_count += 1 }
8
- # # When we finish, print the field count:
9
- # visitor[GraphQL::Language::Nodes::Document].leave << -> (node) { p total_field_count }
10
- # visitor.visit(document)
11
- # # => 6
12
- #
13
- class GraphQL::Language::Visitor
14
- # If any hook returns this value, the {Visitor} stops visiting this
15
- # node right away
16
- SKIP = :_skip
1
+ module GraphQL
2
+ module Language
3
+ # Depth-first traversal through the tree, calling hooks at each stop.
4
+ #
5
+ # @example Create a visitor, add hooks, then search a document
6
+ # total_field_count = 0
7
+ # visitor = GraphQL::Language::Visitor.new
8
+ # # Whenever you find a field, increment the field count:
9
+ # visitor[GraphQL::Language::Nodes::Field] << -> (node) { total_field_count += 1 }
10
+ # # When we finish, print the field count:
11
+ # visitor[GraphQL::Language::Nodes::Document].leave << -> (node) { p total_field_count }
12
+ # visitor.visit(document)
13
+ # # => 6
14
+ #
15
+ class Visitor
16
+ # If any hook returns this value, the {Visitor} stops visiting this
17
+ # node right away
18
+ SKIP = :_skip
17
19
 
18
- # @return [Array<Proc>] Hooks to call when entering _any_ node
19
- attr_reader :enter
20
- # @return [Array<Proc>] Hooks to call when leaving _any_ node
21
- attr_reader :leave
20
+ # @return [Array<Proc>] Hooks to call when entering _any_ node
21
+ attr_reader :enter
22
+ # @return [Array<Proc>] Hooks to call when leaving _any_ node
23
+ attr_reader :leave
22
24
 
23
- def initialize
24
- @visitors = {}
25
- @enter = []
26
- @leave = []
27
- end
25
+ def initialize
26
+ @visitors = {}
27
+ @enter = []
28
+ @leave = []
29
+ end
28
30
 
29
- # Get a {NodeVisitor} for `node_class`
30
- # @param node_class [Class] The node class that you want to listen to
31
- # @return [NodeVisitor]
32
- #
33
- # @example Run a hook whenever you enter a new Field
34
- # visitor[GraphQL::Language::Nodes::Field] << -> (node, parent) { p "Here's a field" }
35
- def [](node_class)
36
- @visitors[node_class] ||= NodeVisitor.new
37
- end
31
+ # Get a {NodeVisitor} for `node_class`
32
+ # @param node_class [Class] The node class that you want to listen to
33
+ # @return [NodeVisitor]
34
+ #
35
+ # @example Run a hook whenever you enter a new Field
36
+ # visitor[GraphQL::Language::Nodes::Field] << -> (node, parent) { p "Here's a field" }
37
+ def [](node_class)
38
+ @visitors[node_class] ||= NodeVisitor.new
39
+ end
38
40
 
39
- # Visit `root` and all children, applying hooks as you go
40
- # @param root [GraphQL::Language::Nodes::AbstractNode] some node to start parsing on
41
- # @return [void]
42
- def visit(root, parent=nil)
43
- begin_visit(root, parent) &&
44
- root.children.reduce(true) { |memo, child| memo && visit(child, root) }
45
- end_visit(root, parent)
46
- end
41
+ # Visit `root` and all children, applying hooks as you go
42
+ # @param root [GraphQL::Language::Nodes::AbstractNode] some node to start parsing on
43
+ # @return [void]
44
+ def visit(root, parent=nil)
45
+ begin_visit(root, parent) &&
46
+ root.children.reduce(true) { |memo, child| memo && visit(child, root) }
47
+ end_visit(root, parent)
48
+ end
47
49
 
48
- private
50
+ private
49
51
 
50
- def begin_visit(node, parent)
51
- self.class.apply_hooks(enter, node, parent)
52
- node_visitor = self[node.class]
53
- self.class.apply_hooks(node_visitor.enter, node, parent)
54
- end
52
+ def begin_visit(node, parent)
53
+ self.class.apply_hooks(enter, node, parent)
54
+ node_visitor = self[node.class]
55
+ self.class.apply_hooks(node_visitor.enter, node, parent)
56
+ end
55
57
 
56
- # Should global `leave` visitors come first or last?
57
- def end_visit(node, parent)
58
- self.class.apply_hooks(leave, node, parent)
59
- node_visitor = self[node.class]
60
- self.class.apply_hooks(node_visitor.leave, node, parent)
61
- end
58
+ # Should global `leave` visitors come first or last?
59
+ def end_visit(node, parent)
60
+ self.class.apply_hooks(leave, node, parent)
61
+ node_visitor = self[node.class]
62
+ self.class.apply_hooks(node_visitor.leave, node, parent)
63
+ end
62
64
 
63
- # If one of the visitors returns SKIP, stop visiting this node
64
- def self.apply_hooks(hooks, node, parent)
65
- hooks.reduce(true) { |memo, proc| memo && (proc.call(node, parent) != SKIP) }
66
- end
65
+ # If one of the visitors returns SKIP, stop visiting this node
66
+ def self.apply_hooks(hooks, node, parent)
67
+ hooks.reduce(true) { |memo, proc| memo && (proc.call(node, parent) != SKIP) }
68
+ end
67
69
 
68
- # Collect `enter` and `leave` hooks for classes in {GraphQL::Language::Nodes}
69
- #
70
- # Access {NodeVisitor}s via {GraphQL::Language::Visitor#[]}
71
- class NodeVisitor
72
- # @return [Array<Proc>] Hooks to call when entering a node of this type
73
- attr_reader :enter
74
- # @return [Array<Proc>] Hooks to call when leaving a node of this type
75
- attr_reader :leave
70
+ # Collect `enter` and `leave` hooks for classes in {GraphQL::Language::Nodes}
71
+ #
72
+ # Access {NodeVisitor}s via {GraphQL::Language::Visitor#[]}
73
+ class NodeVisitor
74
+ # @return [Array<Proc>] Hooks to call when entering a node of this type
75
+ attr_reader :enter
76
+ # @return [Array<Proc>] Hooks to call when leaving a node of this type
77
+ attr_reader :leave
76
78
 
77
- def initialize
78
- @enter = []
79
- @leave = []
80
- end
79
+ def initialize
80
+ @enter = []
81
+ @leave = []
82
+ end
81
83
 
82
- # Shorthand to add a hook to the {#enter} array
83
- # @param hook [Proc] A hook to add
84
- def <<(hook)
85
- enter << hook
84
+ # Shorthand to add a hook to the {#enter} array
85
+ # @param hook [Proc] A hook to add
86
+ def <<(hook)
87
+ enter << hook
88
+ end
89
+ end
86
90
  end
87
91
  end
88
92
  end
@@ -16,4 +16,9 @@ class GraphQL::ListType < GraphQL::BaseType
16
16
  def to_s
17
17
  "[#{of_type.to_s}]"
18
18
  end
19
+
20
+ def coerce_input(value)
21
+ inner_type = of_type
22
+ value.map { |item| inner_type.coerce_input!(item) }
23
+ end
19
24
  end
@@ -13,8 +13,12 @@ class GraphQL::NonNullType < GraphQL::BaseType
13
13
  "Non-Null"
14
14
  end
15
15
 
16
- def coerce(value)
17
- of_type.coerce(value)
16
+ def coerce_input(value)
17
+ of_type.coerce_input(value)
18
+ end
19
+
20
+ def coerce_result(value)
21
+ of_type.coerce_result(value)
18
22
  end
19
23
 
20
24
  def kind
@@ -1,9 +1,23 @@
1
1
  class GraphQL::Query
2
+ class OperationNameMissingError < StandardError
3
+ def initialize(names)
4
+ msg = "You must provide an operation name from: #{names.join(", ")}"
5
+ super(msg)
6
+ end
7
+ end
8
+
9
+ class VariableMissingError < StandardError
10
+ def initialize(name, type)
11
+ msg = "Variable #{name} of type #{type} can't be null"
12
+ super(msg)
13
+ end
14
+ end
15
+
2
16
  # If a resolve function returns `GraphQL::Query::DEFAULT_RESOLVE`,
3
17
  # The executor will send the field's name to the target object
4
18
  # and use the result.
5
19
  DEFAULT_RESOLVE = :__default_resolve
6
- attr_reader :schema, :document, :context, :fragments, :variables, :operations, :debug
20
+ attr_reader :schema, :document, :context, :fragments, :operations, :debug
7
21
 
8
22
  # Prepare query `query_string` on `schema`
9
23
  # @param schema [GraphQL::Schema]
@@ -17,13 +31,11 @@ class GraphQL::Query
17
31
  @schema = schema
18
32
  @debug = debug
19
33
  @context = Context.new(values: context)
20
-
21
- @variables = variables
22
34
  @validate = validate
23
35
  @operation_name = operation_name
24
36
  @fragments = {}
25
37
  @operations = {}
26
-
38
+ @provided_variables = variables
27
39
  @document = GraphQL.parse(query_string)
28
40
  @document.parts.each do |part|
29
41
  if part.is_a?(GraphQL::Language::Nodes::FragmentDefinition)
@@ -40,20 +52,57 @@ class GraphQL::Query
40
52
  return { "errors" => validation_errors }
41
53
  end
42
54
 
43
- @result ||= Executor.new(self, @operation_name).result
55
+ @result ||= Executor.new(self).result
56
+ end
57
+
58
+
59
+ # This is the operation to run for this query.
60
+ # If more than one operation is present, it must be named at runtime.
61
+ # @return [GraphQL::Language::Nodes::OperationDefinition, nil]
62
+ def selected_operation
63
+ @selected_operation ||= find_operation(@operations, @operation_name)
64
+ end
65
+
66
+ # Determine the values for variables of this query, using default values
67
+ # if a value isn't provided at runtime.
68
+ #
69
+ # Raises if a non-null variable isn't provided at runtime.
70
+ # @return [GraphQL::Query::Variables] Variables to apply to this query
71
+ def variables
72
+ @variables ||= GraphQL::Query::Variables.new(
73
+ schema,
74
+ selected_operation.variables,
75
+ @provided_variables
76
+ )
44
77
  end
45
78
 
46
79
  private
47
80
 
48
81
  def validation_errors
49
- @validation_errors ||= @schema.static_validator.validate(@document)
82
+ @validation_errors ||= schema.static_validator.validate(document)
83
+ end
84
+
85
+
86
+ def find_operation(operations, operation_name)
87
+ if operations.length == 1
88
+ operations.values.first
89
+ elsif operations.length == 0
90
+ nil
91
+ elsif !operations.key?(operation_name)
92
+ raise OperationNameMissingError, operations.keys
93
+ else
94
+ operations[operation_name]
95
+ end
50
96
  end
51
97
  end
52
98
 
53
99
  require 'graphql/query/arguments'
54
100
  require 'graphql/query/base_execution'
55
- require 'graphql/query/serial_execution'
56
- require 'graphql/query/type_resolver'
101
+ require 'graphql/query/context'
57
102
  require 'graphql/query/directive_chain'
58
103
  require 'graphql/query/executor'
59
- require 'graphql/query/context'
104
+ require 'graphql/query/literal_input'
105
+ require 'graphql/query/ruby_input'
106
+ require 'graphql/query/serial_execution'
107
+ require 'graphql/query/type_resolver'
108
+ require 'graphql/query/variables'