graphql 2.3.8 → 2.4.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/generators/graphql/install_generator.rb +46 -0
- data/lib/generators/graphql/orm_mutations_base.rb +1 -1
- data/lib/generators/graphql/templates/base_resolver.erb +2 -0
- data/lib/generators/graphql/type_generator.rb +1 -1
- data/lib/graphql/analysis.rb +1 -1
- data/lib/graphql/current.rb +52 -0
- data/lib/graphql/dataloader/async_dataloader.rb +3 -2
- data/lib/graphql/dataloader/source.rb +6 -3
- data/lib/graphql/dataloader.rb +35 -11
- data/lib/graphql/execution/interpreter/arguments_cache.rb +5 -10
- data/lib/graphql/execution/interpreter/resolve.rb +10 -6
- data/lib/graphql/execution/interpreter/runtime.rb +23 -19
- data/lib/graphql/execution/interpreter.rb +2 -0
- data/lib/graphql/introspection/entry_points.rb +2 -2
- data/lib/graphql/introspection/schema_type.rb +4 -19
- data/lib/graphql/invalid_null_error.rb +1 -1
- data/lib/graphql/language/comment.rb +18 -0
- data/lib/graphql/language/document_from_schema_definition.rb +38 -4
- data/lib/graphql/language/lexer.rb +15 -12
- data/lib/graphql/language/nodes.rb +24 -16
- data/lib/graphql/language/parser.rb +14 -1
- data/lib/graphql/language/printer.rb +23 -7
- data/lib/graphql/language.rb +6 -5
- data/lib/graphql/query/context.rb +4 -2
- data/lib/graphql/query/null_context.rb +1 -5
- data/lib/graphql/query.rb +49 -20
- data/lib/graphql/rubocop/graphql/field_type_in_block.rb +144 -0
- data/lib/graphql/rubocop/graphql/root_types_in_block.rb +38 -0
- data/lib/graphql/rubocop.rb +2 -0
- data/lib/graphql/schema/addition.rb +1 -0
- data/lib/graphql/schema/always_visible.rb +6 -3
- data/lib/graphql/schema/argument.rb +14 -1
- data/lib/graphql/schema/build_from_definition.rb +9 -1
- data/lib/graphql/schema/directive/flagged.rb +1 -1
- data/lib/graphql/schema/enum.rb +49 -15
- data/lib/graphql/schema/enum_value.rb +9 -1
- data/lib/graphql/schema/field/connection_extension.rb +1 -1
- data/lib/graphql/schema/field.rb +91 -36
- data/lib/graphql/schema/input_object.rb +20 -7
- data/lib/graphql/schema/interface.rb +21 -4
- data/lib/graphql/schema/introspection_system.rb +3 -2
- data/lib/graphql/schema/member/base_dsl_methods.rb +15 -0
- data/lib/graphql/schema/member/has_arguments.rb +9 -5
- data/lib/graphql/schema/member/has_fields.rb +5 -5
- data/lib/graphql/schema/member/has_unresolved_type_error.rb +5 -1
- data/lib/graphql/schema/printer.rb +1 -0
- data/lib/graphql/schema/resolver.rb +3 -4
- data/lib/graphql/schema/validator/all_validator.rb +2 -0
- data/lib/graphql/schema/validator/required_validator.rb +28 -4
- data/lib/graphql/schema/visibility/migration.rb +186 -0
- data/lib/graphql/schema/visibility/profile.rb +523 -0
- data/lib/graphql/schema/visibility.rb +75 -0
- data/lib/graphql/schema/warden.rb +80 -21
- data/lib/graphql/schema.rb +254 -85
- data/lib/graphql/static_validation/rules/arguments_are_defined.rb +2 -1
- data/lib/graphql/static_validation/rules/directives_are_defined.rb +2 -1
- data/lib/graphql/static_validation/rules/directives_are_in_valid_locations.rb +2 -0
- data/lib/graphql/static_validation/rules/fields_are_defined_on_type.rb +2 -1
- data/lib/graphql/static_validation/rules/fields_will_merge.rb +1 -0
- data/lib/graphql/static_validation/rules/fragment_types_exist.rb +11 -1
- data/lib/graphql/static_validation/rules/variable_default_values_are_correctly_typed.rb +18 -27
- data/lib/graphql/static_validation/rules/variables_are_input_types.rb +10 -1
- data/lib/graphql/static_validation/validation_context.rb +15 -0
- data/lib/graphql/subscriptions/action_cable_subscriptions.rb +2 -1
- data/lib/graphql/subscriptions.rb +3 -1
- data/lib/graphql/testing/helpers.rb +7 -3
- data/lib/graphql/tracing/notifications_trace.rb +2 -2
- data/lib/graphql/unauthorized_enum_value_error.rb +13 -0
- data/lib/graphql/version.rb +1 -1
- data/lib/graphql.rb +3 -0
- metadata +28 -7
- data/lib/graphql/schema/subset.rb +0 -397
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
require_relative "./base_cop"
|
|
3
|
+
|
|
4
|
+
module GraphQL
|
|
5
|
+
module Rubocop
|
|
6
|
+
module GraphQL
|
|
7
|
+
# Identify (and auto-correct) any root types in your schema file.
|
|
8
|
+
#
|
|
9
|
+
# @example
|
|
10
|
+
# # bad, immediately causes Rails to load `app/graphql/types/query.rb`
|
|
11
|
+
# query Types::Query
|
|
12
|
+
#
|
|
13
|
+
# # good, defers loading until the file is needed
|
|
14
|
+
# query { Types::Query }
|
|
15
|
+
#
|
|
16
|
+
class RootTypesInBlock < BaseCop
|
|
17
|
+
MSG = "type configuration can be moved to a block to defer loading the type's file"
|
|
18
|
+
|
|
19
|
+
def_node_matcher :root_type_config_without_block, <<-Pattern
|
|
20
|
+
(
|
|
21
|
+
send nil? {:query :mutation :subscription} const
|
|
22
|
+
)
|
|
23
|
+
Pattern
|
|
24
|
+
|
|
25
|
+
def on_send(node)
|
|
26
|
+
root_type_config_without_block(node) do
|
|
27
|
+
add_offense(node) do |corrector|
|
|
28
|
+
new_node_source = node.source_range.source
|
|
29
|
+
new_node_source.sub!(/(query|mutation|subscription)/, '\1 {')
|
|
30
|
+
new_node_source << " }"
|
|
31
|
+
corrector.replace(node, new_node_source)
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
data/lib/graphql/rubocop.rb
CHANGED
|
@@ -189,6 +189,7 @@ module GraphQL
|
|
|
189
189
|
add_directives_from(type)
|
|
190
190
|
if type.kind.fields?
|
|
191
191
|
type.all_field_definitions.each do |field|
|
|
192
|
+
field.ensure_loaded
|
|
192
193
|
name = field.graphql_name
|
|
193
194
|
field_type = field.type.unwrap
|
|
194
195
|
if !field_type.is_a?(GraphQL::Schema::LateBoundType)
|
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
module GraphQL
|
|
3
3
|
class Schema
|
|
4
|
-
|
|
4
|
+
module AlwaysVisible
|
|
5
5
|
def self.use(schema, **opts)
|
|
6
|
-
schema.
|
|
7
|
-
|
|
6
|
+
schema.extend(self)
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
def visible?(_member, _context)
|
|
10
|
+
true
|
|
8
11
|
end
|
|
9
12
|
end
|
|
10
13
|
end
|
|
@@ -50,11 +50,12 @@ module GraphQL
|
|
|
50
50
|
# @param deprecation_reason [String]
|
|
51
51
|
# @param validates [Hash, nil] Options for building validators, if any should be applied
|
|
52
52
|
# @param replace_null_with_default [Boolean] if `true`, incoming values of `null` will be replaced with the configured `default_value`
|
|
53
|
-
def initialize(arg_name = nil, type_expr = nil, desc = nil, required: true, type: nil, name: nil, loads: nil, description: nil, ast_node: nil, default_value: NOT_CONFIGURED, as: nil, from_resolver: false, camelize: true, prepare: nil, owner:, validates: nil, directives: nil, deprecation_reason: nil, replace_null_with_default: false, &definition_block)
|
|
53
|
+
def initialize(arg_name = nil, type_expr = nil, desc = nil, required: true, type: nil, name: nil, loads: nil, description: nil, comment: nil, ast_node: nil, default_value: NOT_CONFIGURED, as: nil, from_resolver: false, camelize: true, prepare: nil, owner:, validates: nil, directives: nil, deprecation_reason: nil, replace_null_with_default: false, &definition_block)
|
|
54
54
|
arg_name ||= name
|
|
55
55
|
@name = -(camelize ? Member::BuildType.camelize(arg_name.to_s) : arg_name.to_s)
|
|
56
56
|
@type_expr = type_expr || type
|
|
57
57
|
@description = desc || description
|
|
58
|
+
@comment = comment
|
|
58
59
|
@null = required != true
|
|
59
60
|
@default_value = default_value
|
|
60
61
|
if replace_null_with_default
|
|
@@ -129,6 +130,17 @@ module GraphQL
|
|
|
129
130
|
end
|
|
130
131
|
end
|
|
131
132
|
|
|
133
|
+
attr_writer :comment
|
|
134
|
+
|
|
135
|
+
# @return [String] Comment for this argument
|
|
136
|
+
def comment(text = nil)
|
|
137
|
+
if text
|
|
138
|
+
@comment = text
|
|
139
|
+
else
|
|
140
|
+
@comment
|
|
141
|
+
end
|
|
142
|
+
end
|
|
143
|
+
|
|
132
144
|
# @return [String] Deprecation reason for this argument
|
|
133
145
|
def deprecation_reason(text = nil)
|
|
134
146
|
if text
|
|
@@ -352,6 +364,7 @@ module GraphQL
|
|
|
352
364
|
|
|
353
365
|
# @api private
|
|
354
366
|
def validate_default_value
|
|
367
|
+
return unless default_value?
|
|
355
368
|
coerced_default_value = begin
|
|
356
369
|
# This is weird, but we should accept single-item default values for list-type arguments.
|
|
357
370
|
# If we used `coerce_isolated_input` below, it would do this for us, but it's not really
|
|
@@ -127,11 +127,12 @@ module GraphQL
|
|
|
127
127
|
builder = self
|
|
128
128
|
|
|
129
129
|
found_types = types.values
|
|
130
|
+
object_types = found_types.select { |t| t.respond_to?(:kind) && t.kind.object? }
|
|
130
131
|
schema_class = Class.new(schema_superclass) do
|
|
131
132
|
begin
|
|
132
133
|
# Add these first so that there's some chance of resolving late-bound types
|
|
133
134
|
add_type_and_traverse(found_types, root: false)
|
|
134
|
-
orphan_types(
|
|
135
|
+
orphan_types(object_types)
|
|
135
136
|
query query_root_type
|
|
136
137
|
mutation mutation_root_type
|
|
137
138
|
subscription subscription_root_type
|
|
@@ -141,6 +142,12 @@ module GraphQL
|
|
|
141
142
|
raise InvalidDocumentError, "Type \"#{type_name}\" not found in document.", err_backtrace
|
|
142
143
|
end
|
|
143
144
|
|
|
145
|
+
object_types.each do |t|
|
|
146
|
+
t.interfaces.each do |int_t|
|
|
147
|
+
int_t.orphan_types(t)
|
|
148
|
+
end
|
|
149
|
+
end
|
|
150
|
+
|
|
144
151
|
if default_resolve.respond_to?(:resolve_type)
|
|
145
152
|
def self.resolve_type(*args)
|
|
146
153
|
self.definition_default_resolve.resolve_type(*args)
|
|
@@ -181,6 +188,7 @@ module GraphQL
|
|
|
181
188
|
|
|
182
189
|
def self.inherited(child_class)
|
|
183
190
|
child_class.definition_default_resolve = self.definition_default_resolve
|
|
191
|
+
super
|
|
184
192
|
end
|
|
185
193
|
end
|
|
186
194
|
|
|
@@ -7,7 +7,7 @@ module GraphQL
|
|
|
7
7
|
# In this case, the server hides types and fields _entirely_, unless the current context has certain `:flags` present.
|
|
8
8
|
class Flagged < GraphQL::Schema::Directive
|
|
9
9
|
def initialize(target, **options)
|
|
10
|
-
if target.is_a?(Module)
|
|
10
|
+
if target.is_a?(Module)
|
|
11
11
|
# This is type class of some kind, `include` will put this module
|
|
12
12
|
# in between the type class itself and its super class, so `super` will work fine
|
|
13
13
|
target.include(VisibleByFlag)
|
data/lib/graphql/schema/enum.rb
CHANGED
|
@@ -22,9 +22,21 @@ module GraphQL
|
|
|
22
22
|
class Enum < GraphQL::Schema::Member
|
|
23
23
|
extend GraphQL::Schema::Member::ValidatesInput
|
|
24
24
|
|
|
25
|
+
# This is raised when either:
|
|
26
|
+
#
|
|
27
|
+
# - A resolver returns a value which doesn't match any of the enum's configured values;
|
|
28
|
+
# - Or, the resolver returns a value which matches a value, but that value's `authorized?` check returns false.
|
|
29
|
+
#
|
|
30
|
+
# In either case, the field should be modified so that the invalid value isn't returned.
|
|
31
|
+
#
|
|
32
|
+
# {GraphQL::Schema::Enum} subclasses get their own subclass of this error, so that bug trackers can better show where they came from.
|
|
25
33
|
class UnresolvedValueError < GraphQL::Error
|
|
26
|
-
def initialize(value:, enum:, context:)
|
|
27
|
-
fix_message =
|
|
34
|
+
def initialize(value:, enum:, context:, authorized:)
|
|
35
|
+
fix_message = if authorized == false
|
|
36
|
+
", but this value was unauthorized. Update the field or resolver to return a different value in this case (or return `nil`)."
|
|
37
|
+
else
|
|
38
|
+
", but this isn't a valid value for `#{enum.graphql_name}`. Update the field or resolver to return one of `#{enum.graphql_name}`'s values instead."
|
|
39
|
+
end
|
|
28
40
|
message = if (cp = context[:current_path]) && (cf = context[:current_field])
|
|
29
41
|
"`#{cf.path}` returned `#{value.inspect}` at `#{cp.join(".")}`#{fix_message}"
|
|
30
42
|
else
|
|
@@ -34,6 +46,8 @@ module GraphQL
|
|
|
34
46
|
end
|
|
35
47
|
end
|
|
36
48
|
|
|
49
|
+
# Raised when a {GraphQL::Schema::Enum} is defined to have no values.
|
|
50
|
+
# This can also happen when all values return false for `.visible?`.
|
|
37
51
|
class MissingValuesError < GraphQL::Error
|
|
38
52
|
def initialize(enum_type)
|
|
39
53
|
@enum_type = enum_type
|
|
@@ -43,10 +57,11 @@ module GraphQL
|
|
|
43
57
|
|
|
44
58
|
class << self
|
|
45
59
|
# Define a value for this enum
|
|
46
|
-
# @
|
|
47
|
-
# @
|
|
48
|
-
# @
|
|
49
|
-
# @
|
|
60
|
+
# @option kwargs [String, Symbol] :graphql_name the GraphQL value for this, usually `SCREAMING_CASE`
|
|
61
|
+
# @option kwargs [String] :description, the GraphQL description for this value, present in documentation
|
|
62
|
+
# @option kwargs [String] :comment, the GraphQL comment for this value, present in documentation
|
|
63
|
+
# @option kwargs [::Object] :value the translated Ruby value for this object (defaults to `graphql_name`)
|
|
64
|
+
# @option kwargs [String] :deprecation_reason if this object is deprecated, include a message here
|
|
50
65
|
# @return [void]
|
|
51
66
|
# @see {Schema::EnumValue} which handles these inputs by default
|
|
52
67
|
def value(*args, **kwargs, &block)
|
|
@@ -138,35 +153,54 @@ module GraphQL
|
|
|
138
153
|
else
|
|
139
154
|
nil
|
|
140
155
|
end
|
|
156
|
+
# rescue MissingValuesError
|
|
157
|
+
# nil
|
|
141
158
|
end
|
|
142
159
|
|
|
160
|
+
# Called by the runtime when a field returns a value to give back to the client.
|
|
161
|
+
# This method checks that the incoming {value} matches one of the enum's defined values.
|
|
162
|
+
# @param value [Object] Any value matching the values for this enum.
|
|
163
|
+
# @param ctx [GraphQL::Query::Context]
|
|
164
|
+
# @raise [GraphQL::Schema::Enum::UnresolvedValueError] if {value} doesn't match a configured value or if the matching value isn't authorized.
|
|
165
|
+
# @return [String] The GraphQL-ready string for {value}
|
|
143
166
|
def coerce_result(value, ctx)
|
|
144
167
|
types = ctx.types
|
|
145
168
|
all_values = types ? types.enum_values(self) : values.each_value
|
|
146
169
|
enum_value = all_values.find { |val| val.value == value }
|
|
147
|
-
if enum_value
|
|
170
|
+
if enum_value && (was_authed = enum_value.authorized?(ctx))
|
|
148
171
|
enum_value.graphql_name
|
|
149
172
|
else
|
|
150
|
-
raise self::UnresolvedValueError.new(enum: self, value: value, context: ctx)
|
|
173
|
+
raise self::UnresolvedValueError.new(enum: self, value: value, context: ctx, authorized: was_authed)
|
|
151
174
|
end
|
|
152
175
|
end
|
|
153
176
|
|
|
177
|
+
# Called by the runtime with incoming string representations from a query.
|
|
178
|
+
# It will match the string to a configured by name or by Ruby value.
|
|
179
|
+
# @param value_name [String, Object] A string from a GraphQL query, or a Ruby value matching a `value(..., value: ...)` configuration
|
|
180
|
+
# @param ctx [GraphQL::Query::Context]
|
|
181
|
+
# @raise [GraphQL::UnauthorizedEnumValueError] if an {EnumValue} matches but returns false for `.authorized?`. Goes to {Schema.unauthorized_object}.
|
|
182
|
+
# @return [Object] The Ruby value for the matched {GraphQL::Schema::EnumValue}
|
|
154
183
|
def coerce_input(value_name, ctx)
|
|
155
184
|
all_values = ctx.types ? ctx.types.enum_values(self) : values.each_value
|
|
156
185
|
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
186
|
+
# This tries matching by incoming GraphQL string, then checks Ruby-defined values
|
|
187
|
+
if v = (all_values.find { |val| val.graphql_name == value_name } || all_values.find { |val| val.value == value_name })
|
|
188
|
+
if v.authorized?(ctx)
|
|
189
|
+
v.value
|
|
190
|
+
else
|
|
191
|
+
raise GraphQL::UnauthorizedEnumValueError.new(type: self, enum_value: v, context: ctx)
|
|
192
|
+
end
|
|
163
193
|
else
|
|
164
194
|
nil
|
|
165
195
|
end
|
|
166
196
|
end
|
|
167
197
|
|
|
168
198
|
def inherited(child_class)
|
|
169
|
-
child_class.
|
|
199
|
+
if child_class.name
|
|
200
|
+
# Don't assign a custom error class to anonymous classes
|
|
201
|
+
# because they would end up with names like `#<Class0x1234>::UnresolvedValueError` which messes up bug trackers
|
|
202
|
+
child_class.const_set(:UnresolvedValueError, Class.new(Schema::Enum::UnresolvedValueError))
|
|
203
|
+
end
|
|
170
204
|
super
|
|
171
205
|
end
|
|
172
206
|
|
|
@@ -30,10 +30,11 @@ module GraphQL
|
|
|
30
30
|
# @return [Class] The enum type that owns this value
|
|
31
31
|
attr_reader :owner
|
|
32
32
|
|
|
33
|
-
def initialize(graphql_name, desc = nil, owner:, ast_node: nil, directives: nil, description: nil, value: NOT_CONFIGURED, deprecation_reason: nil, &block)
|
|
33
|
+
def initialize(graphql_name, desc = nil, owner:, ast_node: nil, directives: nil, description: nil, comment: nil, value: NOT_CONFIGURED, deprecation_reason: nil, &block)
|
|
34
34
|
@graphql_name = graphql_name.to_s
|
|
35
35
|
GraphQL::NameValidator.validate!(@graphql_name)
|
|
36
36
|
@description = desc || description
|
|
37
|
+
@comment = comment
|
|
37
38
|
@value = value == NOT_CONFIGURED ? @graphql_name : value
|
|
38
39
|
if deprecation_reason
|
|
39
40
|
self.deprecation_reason = deprecation_reason
|
|
@@ -58,6 +59,13 @@ module GraphQL
|
|
|
58
59
|
@description
|
|
59
60
|
end
|
|
60
61
|
|
|
62
|
+
def comment(new_comment = nil)
|
|
63
|
+
if new_comment
|
|
64
|
+
@comment = new_comment
|
|
65
|
+
end
|
|
66
|
+
@comment
|
|
67
|
+
end
|
|
68
|
+
|
|
61
69
|
def value(new_val = nil)
|
|
62
70
|
unless new_val.nil?
|
|
63
71
|
@value = new_val
|
|
@@ -50,7 +50,7 @@ module GraphQL
|
|
|
50
50
|
if field.has_default_page_size? && !value.has_default_page_size_override?
|
|
51
51
|
value.default_page_size = field.default_page_size
|
|
52
52
|
end
|
|
53
|
-
if
|
|
53
|
+
if (custom_t = context.schema.connections.edge_class_for_field(@field))
|
|
54
54
|
value.edge_class = custom_t
|
|
55
55
|
end
|
|
56
56
|
value
|
data/lib/graphql/schema/field.rb
CHANGED
|
@@ -106,7 +106,7 @@ module GraphQL
|
|
|
106
106
|
# @param subscription [Class] A {GraphQL::Schema::Subscription} class to use for field configuration
|
|
107
107
|
# @return [GraphQL::Schema:Field] an instance of `self`
|
|
108
108
|
# @see {.initialize} for other options
|
|
109
|
-
def self.from_options(name = nil, type = nil, desc = nil, resolver: nil, mutation: nil, subscription: nil,**kwargs, &block)
|
|
109
|
+
def self.from_options(name = nil, type = nil, desc = nil, comment: nil, resolver: nil, mutation: nil, subscription: nil,**kwargs, &block)
|
|
110
110
|
if (resolver_class = resolver || mutation || subscription)
|
|
111
111
|
# Add a reference to that parent class
|
|
112
112
|
kwargs[:resolver_class] = resolver_class
|
|
@@ -116,6 +116,10 @@ module GraphQL
|
|
|
116
116
|
kwargs[:name] = name
|
|
117
117
|
end
|
|
118
118
|
|
|
119
|
+
if comment
|
|
120
|
+
kwargs[:comment] = comment
|
|
121
|
+
end
|
|
122
|
+
|
|
119
123
|
if !type.nil?
|
|
120
124
|
if desc
|
|
121
125
|
if kwargs[:description]
|
|
@@ -146,11 +150,16 @@ module GraphQL
|
|
|
146
150
|
Member::BuildType.to_type_name(@return_type_expr)
|
|
147
151
|
elsif @resolver_class && @resolver_class.type
|
|
148
152
|
Member::BuildType.to_type_name(@resolver_class.type)
|
|
149
|
-
|
|
153
|
+
elsif type
|
|
150
154
|
# As a last ditch, try to force loading the return type:
|
|
151
155
|
type.unwrap.name
|
|
152
156
|
end
|
|
153
|
-
|
|
157
|
+
if return_type_name
|
|
158
|
+
@connection = return_type_name.end_with?("Connection") && return_type_name != "Connection"
|
|
159
|
+
else
|
|
160
|
+
# TODO set this when type is set by method
|
|
161
|
+
false # not loaded yet?
|
|
162
|
+
end
|
|
154
163
|
else
|
|
155
164
|
@connection
|
|
156
165
|
end
|
|
@@ -207,6 +216,7 @@ module GraphQL
|
|
|
207
216
|
# @param owner [Class] The type that this field belongs to
|
|
208
217
|
# @param null [Boolean] (defaults to `true`) `true` if this field may return `null`, `false` if it is never `null`
|
|
209
218
|
# @param description [String] Field description
|
|
219
|
+
# @param comment [String] Field comment
|
|
210
220
|
# @param deprecation_reason [String] If present, the field is marked "deprecated" with this message
|
|
211
221
|
# @param method [Symbol] The method to call on the underlying object to resolve this field (defaults to `name`)
|
|
212
222
|
# @param hash_key [String, Symbol] The hash key to lookup on the underlying object (if its a Hash) to resolve this field (defaults to `name` or `name.to_s`)
|
|
@@ -231,13 +241,13 @@ module GraphQL
|
|
|
231
241
|
# @param method_conflict_warning [Boolean] If false, skip the warning if this field's method conflicts with a built-in method
|
|
232
242
|
# @param validates [Array<Hash>] Configurations for validating this field
|
|
233
243
|
# @param fallback_value [Object] A fallback value if the method is not defined
|
|
234
|
-
def initialize(type: nil, name: nil, owner: nil, null: nil, description: NOT_CONFIGURED, deprecation_reason: nil, method: nil, hash_key: nil, dig: nil, resolver_method: nil, connection: nil, max_page_size: NOT_CONFIGURED, default_page_size: NOT_CONFIGURED, scope: nil, introspection: false, camelize: true, trace: nil, complexity: nil, ast_node: nil, extras: EMPTY_ARRAY, extensions: EMPTY_ARRAY, connection_extension: self.class.connection_extension, resolver_class: nil, subscription_scope: nil, relay_node_field: false, relay_nodes_field: false, method_conflict_warning: true, broadcastable: NOT_CONFIGURED, arguments: EMPTY_HASH, directives: EMPTY_HASH, validates: EMPTY_ARRAY, fallback_value: NOT_CONFIGURED, dynamic_introspection: false, &definition_block)
|
|
244
|
+
def initialize(type: nil, name: nil, owner: nil, null: nil, description: NOT_CONFIGURED, comment: NOT_CONFIGURED, deprecation_reason: nil, method: nil, hash_key: nil, dig: nil, resolver_method: nil, connection: nil, max_page_size: NOT_CONFIGURED, default_page_size: NOT_CONFIGURED, scope: nil, introspection: false, camelize: true, trace: nil, complexity: nil, ast_node: nil, extras: EMPTY_ARRAY, extensions: EMPTY_ARRAY, connection_extension: self.class.connection_extension, resolver_class: nil, subscription_scope: nil, relay_node_field: false, relay_nodes_field: false, method_conflict_warning: true, broadcastable: NOT_CONFIGURED, arguments: EMPTY_HASH, directives: EMPTY_HASH, validates: EMPTY_ARRAY, fallback_value: NOT_CONFIGURED, dynamic_introspection: false, &definition_block)
|
|
235
245
|
if name.nil?
|
|
236
246
|
raise ArgumentError, "missing first `name` argument or keyword `name:`"
|
|
237
247
|
end
|
|
238
248
|
if !(resolver_class)
|
|
239
|
-
if type.nil?
|
|
240
|
-
raise ArgumentError, "missing second `type` argument or
|
|
249
|
+
if type.nil? && !block_given?
|
|
250
|
+
raise ArgumentError, "missing second `type` argument, keyword `type:`, or a block containing `type(...)`"
|
|
241
251
|
end
|
|
242
252
|
end
|
|
243
253
|
@original_name = name
|
|
@@ -247,6 +257,7 @@ module GraphQL
|
|
|
247
257
|
@name = -(camelize ? Member::BuildType.camelize(name_s) : name_s)
|
|
248
258
|
|
|
249
259
|
@description = description
|
|
260
|
+
@comment = comment
|
|
250
261
|
@type = @owner_type = @own_validators = @own_directives = @own_arguments = @arguments_statically_coercible = nil # these will be prepared later if necessary
|
|
251
262
|
|
|
252
263
|
self.deprecation_reason = deprecation_reason
|
|
@@ -302,6 +313,7 @@ module GraphQL
|
|
|
302
313
|
@ast_node = ast_node
|
|
303
314
|
@method_conflict_warning = method_conflict_warning
|
|
304
315
|
@fallback_value = fallback_value
|
|
316
|
+
@definition_block = definition_block
|
|
305
317
|
|
|
306
318
|
arguments.each do |name, arg|
|
|
307
319
|
case arg
|
|
@@ -321,18 +333,7 @@ module GraphQL
|
|
|
321
333
|
|
|
322
334
|
@extensions = EMPTY_ARRAY
|
|
323
335
|
@call_after_define = false
|
|
324
|
-
|
|
325
|
-
# but should it run after the definition block?
|
|
326
|
-
if scoped?
|
|
327
|
-
self.extension(ScopeExtension)
|
|
328
|
-
end
|
|
329
|
-
|
|
330
|
-
# The problem with putting this after the definition_block
|
|
331
|
-
# is that it would override arguments
|
|
332
|
-
if connection? && connection_extension
|
|
333
|
-
self.extension(connection_extension)
|
|
334
|
-
end
|
|
335
|
-
|
|
336
|
+
set_pagination_extensions(connection_extension: connection_extension)
|
|
336
337
|
# Do this last so we have as much context as possible when initializing them:
|
|
337
338
|
if extensions.any?
|
|
338
339
|
self.extensions(extensions)
|
|
@@ -352,16 +353,29 @@ module GraphQL
|
|
|
352
353
|
self.validates(validates)
|
|
353
354
|
end
|
|
354
355
|
|
|
355
|
-
if
|
|
356
|
-
|
|
357
|
-
|
|
356
|
+
if @definition_block.nil?
|
|
357
|
+
self.extensions.each(&:after_define_apply)
|
|
358
|
+
@call_after_define = true
|
|
359
|
+
end
|
|
360
|
+
end
|
|
361
|
+
|
|
362
|
+
# Calls the definition block, if one was given.
|
|
363
|
+
# This is deferred so that references to the return type
|
|
364
|
+
# can be lazily evaluated, reducing Rails boot time.
|
|
365
|
+
# @return [self]
|
|
366
|
+
# @api private
|
|
367
|
+
def ensure_loaded
|
|
368
|
+
if @definition_block
|
|
369
|
+
if @definition_block.arity == 1
|
|
370
|
+
@definition_block.call(self)
|
|
358
371
|
else
|
|
359
|
-
instance_eval(
|
|
372
|
+
instance_eval(&@definition_block)
|
|
360
373
|
end
|
|
374
|
+
self.extensions.each(&:after_define_apply)
|
|
375
|
+
@call_after_define = true
|
|
376
|
+
@definition_block = nil
|
|
361
377
|
end
|
|
362
|
-
|
|
363
|
-
self.extensions.each(&:after_define_apply)
|
|
364
|
-
@call_after_define = true
|
|
378
|
+
self
|
|
365
379
|
end
|
|
366
380
|
|
|
367
381
|
attr_accessor :dynamic_introspection
|
|
@@ -393,6 +407,20 @@ module GraphQL
|
|
|
393
407
|
end
|
|
394
408
|
end
|
|
395
409
|
|
|
410
|
+
# @param text [String]
|
|
411
|
+
# @return [String, nil]
|
|
412
|
+
def comment(text = nil)
|
|
413
|
+
if text
|
|
414
|
+
@comment = text
|
|
415
|
+
elsif !NOT_CONFIGURED.equal?(@comment)
|
|
416
|
+
@comment
|
|
417
|
+
elsif @resolver_class
|
|
418
|
+
@resolver_class.comment
|
|
419
|
+
else
|
|
420
|
+
nil
|
|
421
|
+
end
|
|
422
|
+
end
|
|
423
|
+
|
|
396
424
|
# Read extension instances from this field,
|
|
397
425
|
# or add new classes/options to be initialized on this field.
|
|
398
426
|
# Extensions are executed in the order they are added.
|
|
@@ -413,7 +441,7 @@ module GraphQL
|
|
|
413
441
|
new_extensions.each do |extension_config|
|
|
414
442
|
if extension_config.is_a?(Hash)
|
|
415
443
|
extension_class, options = *extension_config.to_a[0]
|
|
416
|
-
self.extension(extension_class, options)
|
|
444
|
+
self.extension(extension_class, **options)
|
|
417
445
|
else
|
|
418
446
|
self.extension(extension_config)
|
|
419
447
|
end
|
|
@@ -433,7 +461,7 @@ module GraphQL
|
|
|
433
461
|
# @param extension_class [Class] subclass of {Schema::FieldExtension}
|
|
434
462
|
# @param options [Hash] if provided, given as `options:` when initializing `extension`.
|
|
435
463
|
# @return [void]
|
|
436
|
-
def extension(extension_class, options
|
|
464
|
+
def extension(extension_class, **options)
|
|
437
465
|
extension_inst = extension_class.new(field: self, options: options)
|
|
438
466
|
if @extensions.frozen?
|
|
439
467
|
@extensions = @extensions.dup
|
|
@@ -483,7 +511,7 @@ module GraphQL
|
|
|
483
511
|
if arguments[:last] && (max_possible_page_size.nil? || arguments[:last] > max_possible_page_size)
|
|
484
512
|
max_possible_page_size = arguments[:last]
|
|
485
513
|
end
|
|
486
|
-
elsif arguments.is_a?(GraphQL::UnauthorizedError)
|
|
514
|
+
elsif arguments.is_a?(GraphQL::ExecutionError) || arguments.is_a?(GraphQL::UnauthorizedError)
|
|
487
515
|
raise arguments
|
|
488
516
|
end
|
|
489
517
|
|
|
@@ -577,16 +605,29 @@ module GraphQL
|
|
|
577
605
|
class MissingReturnTypeError < GraphQL::Error; end
|
|
578
606
|
attr_writer :type
|
|
579
607
|
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
608
|
+
# Get or set the return type of this field.
|
|
609
|
+
#
|
|
610
|
+
# It may return nil if no type was configured or if the given definition block wasn't called yet.
|
|
611
|
+
# @param new_type [Module, GraphQL::Schema::NonNull, GraphQL::Schema::List] A GraphQL return type
|
|
612
|
+
# @return [Module, GraphQL::Schema::NonNull, GraphQL::Schema::List, nil] the configured type for this field
|
|
613
|
+
def type(new_type = NOT_CONFIGURED)
|
|
614
|
+
if NOT_CONFIGURED.equal?(new_type)
|
|
615
|
+
if @resolver_class
|
|
616
|
+
return_type = @return_type_expr || @resolver_class.type_expr
|
|
617
|
+
if return_type.nil?
|
|
618
|
+
raise MissingReturnTypeError, "Can't determine the return type for #{self.path} (it has `resolver: #{@resolver_class}`, perhaps that class is missing a `type ...` declaration, or perhaps its type causes a cyclical loading issue)"
|
|
619
|
+
end
|
|
620
|
+
nullable = @return_type_null.nil? ? @resolver_class.null : @return_type_null
|
|
621
|
+
Member::BuildType.parse_type(return_type, null: nullable)
|
|
622
|
+
elsif !@return_type_expr.nil?
|
|
623
|
+
@type ||= Member::BuildType.parse_type(@return_type_expr, null: @return_type_null)
|
|
585
624
|
end
|
|
586
|
-
nullable = @return_type_null.nil? ? @resolver_class.null : @return_type_null
|
|
587
|
-
Member::BuildType.parse_type(return_type, null: nullable)
|
|
588
625
|
else
|
|
589
|
-
@
|
|
626
|
+
@return_type_expr = new_type
|
|
627
|
+
# If `type` is set in the definition block, then the `connection_extension: ...` given as a keyword won't be used, hmm...
|
|
628
|
+
# Also, arguments added by `connection_extension` will clobber anything previously defined,
|
|
629
|
+
# so `type(...)` should go first.
|
|
630
|
+
set_pagination_extensions(connection_extension: self.class.connection_extension)
|
|
590
631
|
end
|
|
591
632
|
rescue GraphQL::Schema::InvalidDocumentError, MissingReturnTypeError => err
|
|
592
633
|
# Let this propagate up
|
|
@@ -897,6 +938,20 @@ ERR
|
|
|
897
938
|
raise ArgumentError, "Invalid complexity for #{self.path}: #{own_complexity.inspect}"
|
|
898
939
|
end
|
|
899
940
|
end
|
|
941
|
+
|
|
942
|
+
def set_pagination_extensions(connection_extension:)
|
|
943
|
+
# This should run before connection extension,
|
|
944
|
+
# but should it run after the definition block?
|
|
945
|
+
if scoped?
|
|
946
|
+
self.extension(ScopeExtension, call_after_define: false)
|
|
947
|
+
end
|
|
948
|
+
|
|
949
|
+
# The problem with putting this after the definition_block
|
|
950
|
+
# is that it would override arguments
|
|
951
|
+
if connection? && connection_extension
|
|
952
|
+
self.extension(connection_extension, call_after_define: false)
|
|
953
|
+
end
|
|
954
|
+
end
|
|
900
955
|
end
|
|
901
956
|
end
|
|
902
957
|
end
|
|
@@ -133,12 +133,14 @@ module GraphQL
|
|
|
133
133
|
end
|
|
134
134
|
# Add a method access
|
|
135
135
|
method_name = argument_defn.keyword
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
136
|
+
suppress_redefinition_warning do
|
|
137
|
+
class_eval <<-RUBY, __FILE__, __LINE__
|
|
138
|
+
def #{method_name}
|
|
139
|
+
self[#{method_name.inspect}]
|
|
140
|
+
end
|
|
141
|
+
alias_method :#{method_name}, :#{method_name}
|
|
142
|
+
RUBY
|
|
143
|
+
end
|
|
142
144
|
argument_defn
|
|
143
145
|
end
|
|
144
146
|
|
|
@@ -163,7 +165,7 @@ module GraphQL
|
|
|
163
165
|
|
|
164
166
|
# Inject missing required arguments
|
|
165
167
|
missing_required_inputs = ctx.types.arguments(self).reduce({}) do |m, (argument)|
|
|
166
|
-
if !input.key?(argument.graphql_name) && argument.type.non_null? && types.argument(self, argument.graphql_name)
|
|
168
|
+
if !input.key?(argument.graphql_name) && argument.type.non_null? && !argument.default_value? && types.argument(self, argument.graphql_name)
|
|
167
169
|
m[argument.graphql_name] = nil
|
|
168
170
|
end
|
|
169
171
|
|
|
@@ -243,6 +245,17 @@ module GraphQL
|
|
|
243
245
|
|
|
244
246
|
result
|
|
245
247
|
end
|
|
248
|
+
|
|
249
|
+
private
|
|
250
|
+
|
|
251
|
+
# Suppress redefinition warning for objectId arguments
|
|
252
|
+
def suppress_redefinition_warning
|
|
253
|
+
verbose = $VERBOSE
|
|
254
|
+
$VERBOSE = nil
|
|
255
|
+
yield
|
|
256
|
+
ensure
|
|
257
|
+
$VERBOSE = verbose
|
|
258
|
+
end
|
|
246
259
|
end
|
|
247
260
|
|
|
248
261
|
private
|
|
@@ -63,6 +63,7 @@ module GraphQL
|
|
|
63
63
|
|
|
64
64
|
child_class.introspection(introspection)
|
|
65
65
|
child_class.description(description)
|
|
66
|
+
child_class.comment(nil)
|
|
66
67
|
# If interfaces are mixed into each other, only define this class once
|
|
67
68
|
if !child_class.const_defined?(:UnresolvedTypeError, false)
|
|
68
69
|
add_unresolved_type_error(child_class)
|
|
@@ -82,13 +83,29 @@ module GraphQL
|
|
|
82
83
|
super
|
|
83
84
|
end
|
|
84
85
|
|
|
86
|
+
# Register other Interface or Object types as implementers of this Interface.
|
|
87
|
+
#
|
|
88
|
+
# When those Interfaces or Objects aren't used as the return values of fields,
|
|
89
|
+
# they may have to be registered using this method so that GraphQL-Ruby can find them.
|
|
90
|
+
# @param types [Class, Module]
|
|
91
|
+
# @return [Array<Module, Class>] Implementers of this interface, if they're registered
|
|
85
92
|
def orphan_types(*types)
|
|
86
93
|
if types.any?
|
|
87
|
-
@orphan_types
|
|
94
|
+
@orphan_types ||= []
|
|
95
|
+
@orphan_types.concat(types)
|
|
88
96
|
else
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
97
|
+
if defined?(@orphan_types)
|
|
98
|
+
all_orphan_types = @orphan_types.dup
|
|
99
|
+
if defined?(super)
|
|
100
|
+
all_orphan_types += super
|
|
101
|
+
all_orphan_types.uniq!
|
|
102
|
+
end
|
|
103
|
+
all_orphan_types
|
|
104
|
+
elsif defined?(super)
|
|
105
|
+
super
|
|
106
|
+
else
|
|
107
|
+
EmptyObjects::EMPTY_ARRAY
|
|
108
|
+
end
|
|
92
109
|
end
|
|
93
110
|
end
|
|
94
111
|
|
|
@@ -25,7 +25,7 @@ module GraphQL
|
|
|
25
25
|
load_constant(:DirectiveLocationEnum)
|
|
26
26
|
]
|
|
27
27
|
@types = {}
|
|
28
|
-
@possible_types = {}.
|
|
28
|
+
@possible_types = {}.compare_by_identity
|
|
29
29
|
type_defns.each do |t|
|
|
30
30
|
@types[t.graphql_name] = t
|
|
31
31
|
@possible_types[t] = [t]
|
|
@@ -90,7 +90,8 @@ module GraphQL
|
|
|
90
90
|
def resolve_late_binding(late_bound_type)
|
|
91
91
|
case late_bound_type
|
|
92
92
|
when GraphQL::Schema::LateBoundType
|
|
93
|
-
|
|
93
|
+
type_name = late_bound_type.name
|
|
94
|
+
@types[type_name] || @schema.get_type(type_name)
|
|
94
95
|
when GraphQL::Schema::List
|
|
95
96
|
resolve_late_binding(late_bound_type.of_type).to_list_type
|
|
96
97
|
when GraphQL::Schema::NonNull
|