graphql 0.10.9 → 0.11.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.rb +8 -5
  3. data/lib/graphql/definition_helpers/defined_by_config.rb +2 -1
  4. data/lib/graphql/field.rb +34 -7
  5. data/lib/graphql/input_object_type.rb +4 -3
  6. data/lib/graphql/invalid_null_error.rb +22 -0
  7. data/lib/graphql/language/nodes.rb +165 -58
  8. data/lib/graphql/language/transform.rb +5 -6
  9. data/lib/graphql/non_null_type.rb +0 -4
  10. data/lib/graphql/query.rb +1 -2
  11. data/lib/graphql/query/arguments.rb +39 -7
  12. data/lib/graphql/query/literal_input.rb +1 -1
  13. data/lib/graphql/query/serial_execution.rb +30 -1
  14. data/lib/graphql/query/serial_execution/execution_context.rb +30 -0
  15. data/lib/graphql/query/serial_execution/field_resolution.rb +27 -21
  16. data/lib/graphql/query/serial_execution/operation_resolution.rb +9 -6
  17. data/lib/graphql/query/serial_execution/selection_resolution.rb +49 -56
  18. data/lib/graphql/query/{base_execution → serial_execution}/value_resolution.rb +35 -24
  19. data/lib/graphql/static_validation/complexity_validator.rb +27 -0
  20. data/lib/graphql/static_validation/literal_validator.rb +4 -4
  21. data/lib/graphql/static_validation/rules/argument_literals_are_compatible.rb +1 -0
  22. data/lib/graphql/static_validation/rules/variable_default_values_are_correctly_typed.rb +1 -1
  23. data/lib/graphql/static_validation/rules/variables_are_input_types.rb +1 -1
  24. data/lib/graphql/static_validation/validator.rb +1 -1
  25. data/lib/graphql/version.rb +1 -1
  26. data/readme.md +8 -4
  27. data/spec/graphql/execution_error_spec.rb +7 -1
  28. data/spec/graphql/field_spec.rb +53 -0
  29. data/spec/graphql/input_object_type_spec.rb +8 -1
  30. data/spec/graphql/introspection/schema_type_spec.rb +2 -1
  31. data/spec/graphql/language/transform_spec.rb +14 -14
  32. data/spec/graphql/object_type_spec.rb +7 -0
  33. data/spec/graphql/query/arguments_spec.rb +5 -5
  34. data/spec/graphql/query/executor_spec.rb +31 -0
  35. data/spec/graphql/query/serial_execution/execution_context_spec.rb +55 -0
  36. data/spec/graphql/query/{base_execution → serial_execution}/value_resolution_spec.rb +1 -1
  37. data/spec/graphql/query/variables_spec.rb +1 -1
  38. data/spec/graphql/static_validation/complexity_validator.rb +15 -0
  39. data/spec/graphql/static_validation/rules/argument_literals_are_compatible_spec.rb +2 -1
  40. data/spec/graphql/static_validation/validator_spec.rb +1 -1
  41. data/spec/support/dairy_app.rb +15 -0
  42. data/spec/support/minimum_input_object.rb +13 -0
  43. metadata +14 -6
  44. data/lib/graphql/query/base_execution.rb +0 -32
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: f073abd68886cc4042f66cff4457c9adcae153f7
4
- data.tar.gz: 895ea36770f92665af805b9181c8641fb5614567
3
+ metadata.gz: 1cd9ee8f70346b2d0419202f3997fd562149ab8f
4
+ data.tar.gz: a0faf5a06c605e3d44cd23b65e0e6b8cd8fc04a6
5
5
  SHA512:
6
- metadata.gz: ce609e9c9523281b908378697e48344ffdc4e8c0597b69b30ddcea15f51468fa971805e4346f6aad5c36536f760bfefccd6449abcaf1825e80f925af149682c1
7
- data.tar.gz: 78611caafff37f1b1a142aa74af6eadac62b5cfdd35c0099f5afda063a95142ed1d036004e6eb7d2904762459b2c38cb97e36944af2b636f33fe7ef8ef15e69d
6
+ metadata.gz: ac440bd4b73f1f5c3c9de705928bfb90f4498ad0d614c856c3d9ad74fcead1b1881b663176dd368b48cbe7b651dddc1e32258799d4c334ea9347b8c550ce69f9
7
+ data.tar.gz: 3e573c20b9ab0ba44fdbd1a2e73a373b4d5c6e822ee5c4ebfc21c08849ea4ac5c37ac5933ca9e5c8128f0415a908b2c30868d4b7830d4e67f8008ff70c4434d9
@@ -16,12 +16,14 @@ module GraphQL
16
16
  end
17
17
 
18
18
  # Turn a query string into an AST
19
- # @param string [String] a GraphQL query string
20
- # @param as [Symbol] If you want to use this to parse some _piece_ of a document, pass the rule name (from {GraphQL::Parser})
19
+ # @param [String] a GraphQL query string
21
20
  # @return [GraphQL::Language::Nodes::Document]
22
- def self.parse(string, as: nil)
23
- parser = as ? GraphQL::PARSER.send(as) : GraphQL::PARSER
24
- tree = parser.parse(string)
21
+ def self.parse(query_string)
22
+ parse_with_parslet(query_string)
23
+ end
24
+
25
+ def self.parse_with_parslet(string)
26
+ tree = GraphQL::PARSER.parse(string)
25
27
  document = GraphQL::TRANSFORM.apply(tree)
26
28
  if !document.is_a?(GraphQL::Language::Nodes::Document)
27
29
  raise("Parse failed! Sorry, somehow we failed to turn this string into a document. Please report this bug!")
@@ -66,6 +68,7 @@ require 'graphql/schema/printer'
66
68
  # Order does not matter for these:
67
69
 
68
70
  require 'graphql/execution_error'
71
+ require 'graphql/invalid_null_error'
69
72
  require 'graphql/query'
70
73
  require 'graphql/repl'
71
74
  require 'graphql/static_validation'
@@ -26,6 +26,7 @@ module GraphQL::DefinitionHelpers::DefinedByConfig
26
26
  :interfaces, # object
27
27
  :deprecation_reason, # field
28
28
  :type, # field / argument
29
+ :property, # field
29
30
  :resolve, # field / directive
30
31
  :resolve_type, # interface / union
31
32
  :possible_types, # interface / union
@@ -59,7 +60,7 @@ module GraphQL::DefinitionHelpers::DefinedByConfig
59
60
  end
60
61
  type && field.type = type
61
62
  desc && field.description = desc
62
- property && field.resolve = -> (t,a,c) { t.public_send(property)}
63
+ property && field.property = property
63
64
  field.name ||= name.to_s
64
65
  fields[name.to_s] = field
65
66
  end
@@ -8,6 +8,16 @@
8
8
  # field :name, types.String, "The name of this thing "
9
9
  # end
10
10
  #
11
+ # @example handling a circular reference
12
+ # # If the field's type isn't defined yet, you have two options:
13
+ #
14
+ # GraphQL::ObjectType.define do
15
+ # # If you pass a Proc, it will be evaluated at schema build-time
16
+ # field :city, -> { CityType }
17
+ # # If you pass a String, it will be looked up in the global namespace at schema build-time
18
+ # field :country, "CountryType"
19
+ # end
20
+ #
11
21
  # @example creating a field that accesses a different property on the object
12
22
  # GraphQL::ObjectType.define do
13
23
  # # use the `property` option:
@@ -28,15 +38,14 @@
28
38
  # end
29
39
  #
30
40
  class GraphQL::Field
31
- DEFAULT_RESOLVE = -> (o, a, c) { GraphQL::Query::DEFAULT_RESOLVE }
32
41
  include GraphQL::DefinitionHelpers::DefinedByConfig
33
- attr_accessor :arguments, :deprecation_reason, :name, :description, :type
42
+ attr_accessor :arguments, :deprecation_reason, :name, :description, :type, :property
34
43
  attr_reader :resolve_proc
35
- defined_by_config :arguments, :deprecation_reason, :name, :description, :type, :resolve
44
+ defined_by_config :arguments, :deprecation_reason, :name, :description, :type, :resolve, :property
36
45
 
37
46
  def initialize
38
47
  @arguments = {}
39
- @resolve_proc = DEFAULT_RESOLVE
48
+ @resolve_proc = build_default_resolver
40
49
  end
41
50
 
42
51
  def arguments(new_arguments=nil)
@@ -63,19 +72,37 @@ class GraphQL::Field
63
72
  end
64
73
 
65
74
  def resolve=(resolve_proc)
66
- @resolve_proc = resolve_proc || DEFAULT_RESOLVE
75
+ @resolve_proc = resolve_proc || build_default_resolver
67
76
  end
68
77
 
69
78
  # Get the return type for this field.
70
79
  def type
71
- if @type.is_a?(Proc)
80
+ case @type
81
+ when Proc
72
82
  # lazy-eval it
73
83
  @type = @type.call
84
+ when String
85
+ # Get a constant by this name
86
+ @type = Object.const_get(@type)
87
+ else
88
+ @type
74
89
  end
75
- @type
76
90
  end
77
91
 
78
92
  def to_s
79
93
  "<Field: #{name || "not-named"}>"
80
94
  end
95
+
96
+ private
97
+
98
+ def build_default_resolver
99
+ # Note: lambda accesses the current Field via self
100
+ -> (t, a, c) do
101
+ if property = self.property
102
+ t.public_send(property)
103
+ else
104
+ GraphQL::Query::DEFAULT_RESOLVE
105
+ end
106
+ end
107
+ end
81
108
  end
@@ -20,10 +20,11 @@ class GraphQL::InputObjectType < GraphQL::BaseType
20
20
  GraphQL::TypeKinds::INPUT_OBJECT
21
21
  end
22
22
 
23
+ # assert that all present fields are defined
24
+ # _and_ all defined fields have valid values
23
25
  def valid_non_null_input?(input)
24
- return false unless input.is_a?(Hash) || input.is_a?(GraphQL::Query::Arguments)
25
- return false unless input.all? { |name, value| input_fields[name] }
26
- input_fields.all? { |name, field| field.type.valid_input?(input[name]) }
26
+ input.all? { |name, value| input_fields[name] } &&
27
+ input_fields.all? { |name, field| field.type.valid_input?(input[name]) }
27
28
  end
28
29
 
29
30
  def coerce_non_null_input(value)
@@ -0,0 +1,22 @@
1
+ module GraphQL
2
+ # Raised automatically when a field's resolve function returns `nil`
3
+ # for a non-null field.
4
+ class InvalidNullError < RuntimeError
5
+ def initialize(field_name, value)
6
+ @field_name = field_name
7
+ @value = value
8
+ end
9
+
10
+ # @return [Hash] An entry for the response's "errors" key
11
+ def to_h
12
+ {
13
+ "message" => "Cannot return null for non-nullable field #{@field_name}"
14
+ }
15
+ end
16
+
17
+ # @return [Boolean] Whether the null in question was caused by another error
18
+ def parent_error?
19
+ @value.is_a?(GraphQL::ExecutionError)
20
+ end
21
+ end
22
+ end
@@ -8,86 +8,193 @@ module GraphQL
8
8
  attr_accessor :line, :col
9
9
 
10
10
  # @param options [Hash] Must contain all attributes defined by {required_attrs}, may also include `position_source`
11
- def initialize(options)
12
- required_keys = self.class.required_attrs
13
- allowed_keys = required_keys + [:line, :col]
14
- position_source = options.delete(:position_source)
15
- if !position_source.nil?
16
- options[:line], options[:col] = position_source.line_and_column
11
+ def initialize(options={})
12
+ if options.key?(:position_source)
13
+ position_source = options.delete(:position_source)
14
+ @line, @col = position_source.line_and_column
15
+ elsif options.key?(:line)
16
+ @line = options.delete(:line)
17
+ @col = options.delete(:col)
17
18
  end
18
19
 
19
- present_keys = options.keys
20
- extra_keys = present_keys - allowed_keys
21
- if extra_keys.any?
22
- raise ArgumentError, "#{self.class.name} Extra arguments: #{extra_keys}"
23
- end
24
-
25
- missing_keys = required_keys - present_keys
26
- if missing_keys.any?
27
- raise ArgumentError, "#{self.class.name} Missing arguments: #{missing_keys}"
28
- end
20
+ initialize_node(options)
21
+ end
29
22
 
30
- allowed_keys.each do |key|
31
- if options.has_key?(key)
32
- value = options[key]
33
- self.send("#{key}=", value)
34
- end
35
- end
23
+ # This is called with node-specific options
24
+ def initialize_node(options={})
25
+ raise NotImplementedError
36
26
  end
37
27
 
38
- # Test all attributes, checking for any other nodes below this one
28
+ # @return [GraphQL::Language::Nodes::AbstractNode] all nodes in the tree below this one
39
29
  def children
40
- self.class.required_attrs
41
- .map { |attr| send(attr) }
30
+ self.class.child_attributes
31
+ .map { |attr_name| public_send(attr_name) }
42
32
  .flatten
43
- .select { |val| val.is_a?(GraphQL::Language::Nodes::AbstractNode) }
44
33
  end
45
34
 
46
35
  class << self
47
- attr_reader :required_attrs
48
- # Defines attributes which are required at initialization.
49
- def attr_required(*attr_names)
50
- @required_attrs ||= []
51
- @required_attrs += attr_names
52
- attr_accessor(*attr_names)
36
+ def child_attributes(*attr_names)
37
+ @child_attributes ||= []
38
+ @child_attributes += attr_names
53
39
  end
40
+ end
54
41
 
55
- # Create a new AbstractNode child which
56
- # requires and exposes {attr_names}.
57
- # @param attr_names [Array<Symbol>] Attributes this node class will have
58
- # @param block [Block] Block passed to `Class.new`
59
- # @return [Class] A new node class
60
- def create(*attr_names, &block)
61
- cls = Class.new(self, &block)
62
- cls.attr_required(*attr_names)
63
- cls
64
- end
42
+ def position
43
+ [line, col]
44
+ end
45
+ end
46
+
47
+ class WrapperType < AbstractNode
48
+ attr_accessor :of_type
49
+ def initialize_node(of_type: nil)
50
+ @of_type = of_type
51
+ end
52
+
53
+ def children
54
+ [].freeze
55
+ end
56
+ end
57
+
58
+ class NameOnlyNode < AbstractNode
59
+ attr_accessor :name
60
+ def initialize_node(name: nil)
61
+ @name = name
62
+ end
63
+
64
+ def children
65
+ [].freeze
65
66
  end
66
67
  end
67
68
 
68
- Argument = AbstractNode.create(:name, :value)
69
- Directive = AbstractNode.create(:name, :arguments)
70
- Document = AbstractNode.create(:parts)
71
- Enum = AbstractNode.create(:name)
72
- Field = AbstractNode.create(:name, :alias, :arguments, :directives, :selections)
73
- FragmentDefinition = AbstractNode.create(:name, :type, :directives, :selections)
74
- FragmentSpread = AbstractNode.create(:name, :directives)
75
- InlineFragment = AbstractNode.create(:type, :directives, :selections)
76
- InputObject = AbstractNode.create(:pairs) do
69
+
70
+ class Argument < AbstractNode
71
+ attr_accessor :name, :value
72
+
73
+ def initialize_node(name: nil, value: nil)
74
+ @name = name
75
+ @value = value
76
+ end
77
+
78
+ def children
79
+ [value].flatten.select { |v| v.is_a?(AbstractNode) }
80
+ end
81
+ end
82
+
83
+ class Directive < AbstractNode
84
+ attr_accessor :name, :arguments
85
+ child_attributes :arguments
86
+
87
+ def initialize_node(name: nil, arguments: [])
88
+ @name = name
89
+ @arguments = arguments
90
+ end
91
+ end
92
+
93
+ class Document < AbstractNode
94
+ attr_accessor :definitions
95
+ child_attributes :definitions
96
+
97
+ def initialize_node(definitions: [])
98
+ @definitions = definitions
99
+ end
100
+ end
101
+
102
+ class Enum < NameOnlyNode; end
103
+
104
+ class Field < AbstractNode
105
+ attr_accessor :name, :alias, :arguments, :directives, :selections
106
+ child_attributes :arguments, :directives, :selections
107
+
108
+ def initialize_node(name: nil, arguments: [], directives: [], selections: [], **kwargs)
109
+ @name = name
110
+ # oops, alias is a keyword:
111
+ @alias = kwargs.fetch(:alias, nil)
112
+ @arguments = arguments
113
+ @directives = directives
114
+ @selections = selections
115
+ end
116
+ end
117
+
118
+ class FragmentDefinition < AbstractNode
119
+ attr_accessor :name, :type, :directives, :selections
120
+ child_attributes :directives, :selections
121
+
122
+ def initialize_node(name: nil, type: nil, directives: [], selections: [])
123
+ @name = name
124
+ @type = type
125
+ @directives = directives
126
+ @selections = selections
127
+ end
128
+ end
129
+
130
+ class FragmentSpread < AbstractNode
131
+ attr_accessor :name, :directives
132
+ child_attributes :directives
133
+
134
+ def initialize_node(name: nil, directives: [])
135
+ @name = name
136
+ @directives = directives
137
+ end
138
+ end
139
+
140
+ class InlineFragment < AbstractNode
141
+ attr_accessor :type, :directives, :selections
142
+ child_attributes :directives, :selections
143
+
144
+ def initialize_node(type: nil, directives: [], selections: [])
145
+ @type = type
146
+ @directives = directives
147
+ @selections = selections
148
+ end
149
+ end
150
+
151
+ class InputObject < AbstractNode
152
+ attr_accessor :arguments
153
+ child_attributes :arguments
154
+
155
+ def initialize_node(arguments: [])
156
+ @arguments = arguments
157
+ end
158
+
77
159
  def to_h(options={})
78
- pairs.inject({}) do |memo, pair|
160
+ arguments.inject({}) do |memo, pair|
79
161
  v = pair.value
80
162
  memo[pair.name] = v.is_a?(InputObject) ? v.to_h : v
81
163
  memo
82
164
  end
83
165
  end
84
166
  end
85
- ListType = AbstractNode.create(:of_type)
86
- NonNullType = AbstractNode.create(:of_type)
87
- OperationDefinition = AbstractNode.create(:operation_type, :name, :variables, :directives, :selections)
88
- TypeName = AbstractNode.create(:name)
89
- Variable = AbstractNode.create(:name, :type, :default_value)
90
- VariableIdentifier = AbstractNode.create(:name)
167
+
168
+
169
+
170
+ class ListType < WrapperType; end
171
+ class NonNullType < WrapperType; end
172
+
173
+ class OperationDefinition < AbstractNode
174
+ attr_accessor :operation_type, :name, :variables, :directives, :selections
175
+ child_attributes :variables, :directives, :selections
176
+
177
+ def initialize_node(operation_type: nil, name: nil, variables: [], directives: [], selections: [])
178
+ @operation_type = operation_type
179
+ @name = name
180
+ @variables = variables
181
+ @directives = directives
182
+ @selections = selections
183
+ end
184
+ end
185
+
186
+ class TypeName < NameOnlyNode; end
187
+
188
+ class VariableDefinition < AbstractNode
189
+ attr_accessor :name, :type, :default_value
190
+ def initialize_node(name: nil, type: nil, default_value: nil)
191
+ @name = name
192
+ @type = type
193
+ @default_value = default_value
194
+ end
195
+ end
196
+
197
+ class VariableIdentifier < NameOnlyNode; end
91
198
  end
92
199
  end
93
200
  end
@@ -1,6 +1,5 @@
1
1
  module GraphQL
2
2
  module Language
3
-
4
3
  # {Transform} is a [parslet](http://kschiess.github.io/parslet/) transform for for turning the AST into objects in {GraphQL::Language::Nodes} objects.
5
4
  class Transform < Parslet::Transform
6
5
 
@@ -18,8 +17,8 @@ module GraphQL
18
17
  end
19
18
 
20
19
  # Document
21
- rule(document_parts: sequence(:p)) { CREATE_NODE[:Document, parts: p, line: (p.first ? p.first.line : 1), col: (p.first ? p.first.col : 1)]}
22
- rule(document_parts: simple(:p)) { CREATE_NODE[:Document, parts: [], line: 1, col: 1]}
20
+ rule(document_parts: sequence(:p)) { CREATE_NODE[:Document, definitions: p, line: (p.first ? p.first.line : 1), col: (p.first ? p.first.col : 1)]}
21
+ rule(document_parts: simple(:p)) { CREATE_NODE[:Document, definitions: [], line: 1, col: 1]}
23
22
 
24
23
  # Fragment Definition
25
24
  rule(
@@ -52,8 +51,8 @@ module GraphQL
52
51
  selections: sequence(:s),
53
52
  ) { CREATE_NODE[:OperationDefinition, operation_type: ot.to_s, name: n.to_s, variables: v, directives: d, selections: s, position_source: ot] }
54
53
  optional_sequence(:optional_variables)
55
- 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)}
56
- 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)}
54
+ rule(variable_name: simple(:n), variable_type: simple(:t), variable_optional_default_value: simple(:v)) { CREATE_NODE.(:VariableDefinition, name: n.name, type: t, default_value: v, line: n.line, col: n.col)}
55
+ rule(variable_name: simple(:n), variable_type: simple(:t), variable_optional_default_value: sequence(:v)) { CREATE_NODE.(:VariableDefinition, name: n.name, type: t, default_value: v, line: n.line, col: n.col)}
57
56
  rule(variable_default_value: simple(:v) ) { v }
58
57
  rule(variable_default_value: sequence(:v) ) { v }
59
58
  # Query short-hand
@@ -89,7 +88,7 @@ module GraphQL
89
88
  rule(array: subtree(:v)) { v }
90
89
  rule(array: simple(:v)) { [] } # just `nil`
91
90
  rule(boolean: simple(:v)) { v == "true" ? true : false }
92
- rule(input_object: sequence(:v)) { CREATE_NODE[:InputObject, pairs: v, line: (v.first ? v.first.line : 1), col: (v.first ? v.first.col : 1)] }
91
+ rule(input_object: sequence(:v)) { CREATE_NODE[:InputObject, arguments: v, line: (v.first ? v.first.line : 1), col: (v.first ? v.first.col : 1)] }
93
92
  rule(input_object_name: simple(:n), input_object_value: simple(:v)) { CREATE_NODE[:Argument, name: n.to_s, value: v, position_source: n]}
94
93
  rule(input_object_name: simple(:n), input_object_value: sequence(:v)) { CREATE_NODE[:Argument, name: n.to_s, value: v, position_source: n]}
95
94
  rule(int: simple(:v)) { v.to_i }