graphql 0.10.9 → 0.11.0

Sign up to get free protection for your applications and to get access to all the features.
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 }