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.
- checksums.yaml +4 -4
- data/lib/graphql.rb +8 -5
- data/lib/graphql/definition_helpers/defined_by_config.rb +2 -1
- data/lib/graphql/field.rb +34 -7
- data/lib/graphql/input_object_type.rb +4 -3
- data/lib/graphql/invalid_null_error.rb +22 -0
- data/lib/graphql/language/nodes.rb +165 -58
- data/lib/graphql/language/transform.rb +5 -6
- data/lib/graphql/non_null_type.rb +0 -4
- data/lib/graphql/query.rb +1 -2
- data/lib/graphql/query/arguments.rb +39 -7
- data/lib/graphql/query/literal_input.rb +1 -1
- data/lib/graphql/query/serial_execution.rb +30 -1
- data/lib/graphql/query/serial_execution/execution_context.rb +30 -0
- data/lib/graphql/query/serial_execution/field_resolution.rb +27 -21
- data/lib/graphql/query/serial_execution/operation_resolution.rb +9 -6
- data/lib/graphql/query/serial_execution/selection_resolution.rb +49 -56
- data/lib/graphql/query/{base_execution → serial_execution}/value_resolution.rb +35 -24
- data/lib/graphql/static_validation/complexity_validator.rb +27 -0
- data/lib/graphql/static_validation/literal_validator.rb +4 -4
- data/lib/graphql/static_validation/rules/argument_literals_are_compatible.rb +1 -0
- data/lib/graphql/static_validation/rules/variable_default_values_are_correctly_typed.rb +1 -1
- data/lib/graphql/static_validation/rules/variables_are_input_types.rb +1 -1
- data/lib/graphql/static_validation/validator.rb +1 -1
- data/lib/graphql/version.rb +1 -1
- data/readme.md +8 -4
- data/spec/graphql/execution_error_spec.rb +7 -1
- data/spec/graphql/field_spec.rb +53 -0
- data/spec/graphql/input_object_type_spec.rb +8 -1
- data/spec/graphql/introspection/schema_type_spec.rb +2 -1
- data/spec/graphql/language/transform_spec.rb +14 -14
- data/spec/graphql/object_type_spec.rb +7 -0
- data/spec/graphql/query/arguments_spec.rb +5 -5
- data/spec/graphql/query/executor_spec.rb +31 -0
- data/spec/graphql/query/serial_execution/execution_context_spec.rb +55 -0
- data/spec/graphql/query/{base_execution → serial_execution}/value_resolution_spec.rb +1 -1
- data/spec/graphql/query/variables_spec.rb +1 -1
- data/spec/graphql/static_validation/complexity_validator.rb +15 -0
- data/spec/graphql/static_validation/rules/argument_literals_are_compatible_spec.rb +2 -1
- data/spec/graphql/static_validation/validator_spec.rb +1 -1
- data/spec/support/dairy_app.rb +15 -0
- data/spec/support/minimum_input_object.rb +13 -0
- metadata +14 -6
- data/lib/graphql/query/base_execution.rb +0 -32
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 1cd9ee8f70346b2d0419202f3997fd562149ab8f
|
4
|
+
data.tar.gz: a0faf5a06c605e3d44cd23b65e0e6b8cd8fc04a6
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: ac440bd4b73f1f5c3c9de705928bfb90f4498ad0d614c856c3d9ad74fcead1b1881b663176dd368b48cbe7b651dddc1e32258799d4c334ea9347b8c550ce69f9
|
7
|
+
data.tar.gz: 3e573c20b9ab0ba44fdbd1a2e73a373b4d5c6e822ee5c4ebfc21c08849ea4ac5c37ac5933ca9e5c8128f0415a908b2c30868d4b7830d4e67f8008ff70c4434d9
|
data/lib/graphql.rb
CHANGED
@@ -16,12 +16,14 @@ module GraphQL
|
|
16
16
|
end
|
17
17
|
|
18
18
|
# Turn a query string into an AST
|
19
|
-
# @param
|
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(
|
23
|
-
|
24
|
-
|
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.
|
63
|
+
property && field.property = property
|
63
64
|
field.name ||= name.to_s
|
64
65
|
fields[name.to_s] = field
|
65
66
|
end
|
data/lib/graphql/field.rb
CHANGED
@@ -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 =
|
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 ||
|
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
|
-
|
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
|
-
|
25
|
-
|
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
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
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
|
-
|
20
|
-
|
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
|
-
|
31
|
-
|
32
|
-
|
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
|
-
#
|
28
|
+
# @return [GraphQL::Language::Nodes::AbstractNode] all nodes in the tree below this one
|
39
29
|
def children
|
40
|
-
self.class.
|
41
|
-
.map { |
|
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
|
-
|
48
|
-
|
49
|
-
|
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
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
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
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
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
|
-
|
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
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
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,
|
22
|
-
rule(document_parts: simple(:p)) { CREATE_NODE[:Document,
|
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.(:
|
56
|
-
rule(variable_name: simple(:n), variable_type: simple(:t), variable_optional_default_value: sequence(:v)) { CREATE_NODE.(:
|
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,
|
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 }
|