graphql 2.3.7 → 2.4.7
Sign up to get free protection for your applications and to get access to all the features.
- 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/field_usage.rb +1 -1
- data/lib/graphql/analysis/query_complexity.rb +3 -3
- data/lib/graphql/analysis/visitor.rb +8 -7
- data/lib/graphql/analysis.rb +4 -4
- data/lib/graphql/autoload.rb +38 -0
- data/lib/graphql/current.rb +52 -0
- data/lib/graphql/dataloader/async_dataloader.rb +7 -6
- data/lib/graphql/dataloader/source.rb +7 -4
- data/lib/graphql/dataloader.rb +40 -19
- data/lib/graphql/execution/interpreter/arguments_cache.rb +5 -10
- data/lib/graphql/execution/interpreter/resolve.rb +13 -9
- data/lib/graphql/execution/interpreter/runtime.rb +35 -31
- data/lib/graphql/execution/interpreter.rb +6 -4
- data/lib/graphql/execution/lookahead.rb +18 -11
- data/lib/graphql/introspection/directive_type.rb +1 -1
- data/lib/graphql/introspection/entry_points.rb +2 -2
- data/lib/graphql/introspection/field_type.rb +1 -1
- data/lib/graphql/introspection/schema_type.rb +6 -11
- data/lib/graphql/introspection/type_type.rb +5 -5
- data/lib/graphql/invalid_null_error.rb +1 -1
- data/lib/graphql/language/cache.rb +13 -0
- data/lib/graphql/language/comment.rb +18 -0
- data/lib/graphql/language/document_from_schema_definition.rb +62 -34
- data/lib/graphql/language/lexer.rb +18 -15
- data/lib/graphql/language/nodes.rb +24 -16
- data/lib/graphql/language/parser.rb +14 -1
- data/lib/graphql/language/printer.rb +31 -15
- data/lib/graphql/language/sanitized_printer.rb +1 -1
- data/lib/graphql/language.rb +6 -6
- data/lib/graphql/pagination/connection.rb +1 -1
- data/lib/graphql/query/context/scoped_context.rb +1 -1
- data/lib/graphql/query/context.rb +13 -6
- data/lib/graphql/query/null_context.rb +3 -5
- data/lib/graphql/query/variable_validation_error.rb +1 -1
- data/lib/graphql/query.rb +72 -18
- data/lib/graphql/railtie.rb +7 -0
- 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 +2 -1
- data/lib/graphql/schema/always_visible.rb +6 -2
- 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 +2 -2
- data/lib/graphql/schema/directive.rb +1 -1
- data/lib/graphql/schema/enum.rb +71 -23
- data/lib/graphql/schema/enum_value.rb +10 -2
- data/lib/graphql/schema/field/connection_extension.rb +1 -1
- data/lib/graphql/schema/field/scope_extension.rb +1 -1
- data/lib/graphql/schema/field.rb +102 -47
- data/lib/graphql/schema/field_extension.rb +1 -1
- data/lib/graphql/schema/has_single_input_argument.rb +5 -2
- data/lib/graphql/schema/input_object.rb +90 -39
- data/lib/graphql/schema/interface.rb +22 -5
- data/lib/graphql/schema/introspection_system.rb +5 -16
- data/lib/graphql/schema/loader.rb +1 -1
- data/lib/graphql/schema/member/base_dsl_methods.rb +15 -0
- data/lib/graphql/schema/member/has_arguments.rb +36 -23
- data/lib/graphql/schema/member/has_directives.rb +3 -3
- data/lib/graphql/schema/member/has_fields.rb +26 -6
- data/lib/graphql/schema/member/has_interfaces.rb +4 -4
- data/lib/graphql/schema/member/has_unresolved_type_error.rb +5 -1
- data/lib/graphql/schema/member/has_validators.rb +1 -1
- data/lib/graphql/schema/object.rb +8 -0
- data/lib/graphql/schema/printer.rb +1 -0
- data/lib/graphql/schema/relay_classic_mutation.rb +0 -1
- data/lib/graphql/schema/resolver.rb +12 -14
- data/lib/graphql/schema/subscription.rb +2 -2
- data/lib/graphql/schema/type_expression.rb +2 -2
- data/lib/graphql/schema/union.rb +1 -1
- data/lib/graphql/schema/validator/all_validator.rb +62 -0
- data/lib/graphql/schema/validator/required_validator.rb +28 -4
- data/lib/graphql/schema/validator.rb +3 -1
- data/lib/graphql/schema/visibility/migration.rb +188 -0
- data/lib/graphql/schema/visibility/profile.rb +359 -0
- data/lib/graphql/schema/visibility/visit.rb +190 -0
- data/lib/graphql/schema/visibility.rb +294 -0
- data/lib/graphql/schema/warden.rb +179 -16
- data/lib/graphql/schema.rb +348 -94
- data/lib/graphql/static_validation/base_visitor.rb +6 -5
- data/lib/graphql/static_validation/literal_validator.rb +4 -4
- data/lib/graphql/static_validation/rules/argument_literals_are_compatible.rb +1 -1
- data/lib/graphql/static_validation/rules/argument_names_are_unique.rb +1 -1
- data/lib/graphql/static_validation/rules/arguments_are_defined.rb +3 -2
- data/lib/graphql/static_validation/rules/directives_are_defined.rb +3 -3
- 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 +12 -2
- data/lib/graphql/static_validation/rules/fields_have_appropriate_selections.rb +2 -2
- data/lib/graphql/static_validation/rules/fields_will_merge.rb +8 -7
- data/lib/graphql/static_validation/rules/fragment_spreads_are_possible.rb +3 -3
- data/lib/graphql/static_validation/rules/fragment_types_exist.rb +12 -2
- data/lib/graphql/static_validation/rules/fragments_are_on_composite_types.rb +1 -1
- data/lib/graphql/static_validation/rules/mutation_root_exists.rb +1 -1
- data/lib/graphql/static_validation/rules/no_definitions_are_present.rb +1 -1
- data/lib/graphql/static_validation/rules/query_root_exists.rb +1 -1
- data/lib/graphql/static_validation/rules/required_arguments_are_present.rb +4 -4
- data/lib/graphql/static_validation/rules/required_input_object_attributes_are_present.rb +3 -3
- data/lib/graphql/static_validation/rules/subscription_root_exists.rb +1 -1
- data/lib/graphql/static_validation/rules/unique_directives_per_location.rb +1 -1
- data/lib/graphql/static_validation/rules/variable_default_values_are_correctly_typed.rb +18 -27
- data/lib/graphql/static_validation/rules/variable_names_are_unique.rb +1 -1
- data/lib/graphql/static_validation/rules/variable_usages_are_allowed.rb +2 -2
- data/lib/graphql/static_validation/rules/variables_are_input_types.rb +11 -2
- data/lib/graphql/static_validation/validation_context.rb +18 -2
- data/lib/graphql/subscriptions/action_cable_subscriptions.rb +3 -2
- data/lib/graphql/subscriptions/broadcast_analyzer.rb +10 -4
- data/lib/graphql/subscriptions/event.rb +1 -1
- data/lib/graphql/subscriptions/serialize.rb +2 -0
- data/lib/graphql/subscriptions.rb +6 -4
- data/lib/graphql/testing/helpers.rb +10 -6
- data/lib/graphql/tracing/notifications_trace.rb +2 -2
- data/lib/graphql/types/relay/connection_behaviors.rb +12 -2
- data/lib/graphql/types/relay/edge_behaviors.rb +11 -1
- data/lib/graphql/types/relay/page_info_behaviors.rb +4 -0
- data/lib/graphql/types.rb +18 -11
- data/lib/graphql/unauthorized_enum_value_error.rb +13 -0
- data/lib/graphql/version.rb +1 -1
- data/lib/graphql.rb +53 -45
- metadata +31 -8
- data/lib/graphql/language/token.rb +0 -34
- data/lib/graphql/schema/invalid_type_error.rb +0 -7
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)
|
@@ -71,10 +86,24 @@ module GraphQL
|
|
71
86
|
def enum_values(context = GraphQL::Query::NullContext.instance)
|
72
87
|
inherited_values = superclass.respond_to?(:enum_values) ? superclass.enum_values(context) : nil
|
73
88
|
visible_values = []
|
74
|
-
|
89
|
+
types = Warden.types_from_context(context)
|
75
90
|
own_values.each do |key, values_entry|
|
76
|
-
|
77
|
-
|
91
|
+
visible_value = nil
|
92
|
+
if values_entry.is_a?(Array)
|
93
|
+
values_entry.each do |v|
|
94
|
+
if types.visible_enum_value?(v, context)
|
95
|
+
if visible_value.nil?
|
96
|
+
visible_value = v
|
97
|
+
visible_values << v
|
98
|
+
else
|
99
|
+
raise DuplicateNamesError.new(
|
100
|
+
duplicated_name: v.path, duplicated_definition_1: visible_value.inspect, duplicated_definition_2: v.inspect
|
101
|
+
)
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
105
|
+
elsif types.visible_enum_value?(values_entry, context)
|
106
|
+
visible_values << values_entry
|
78
107
|
end
|
79
108
|
end
|
80
109
|
|
@@ -130,7 +159,7 @@ module GraphQL
|
|
130
159
|
end
|
131
160
|
|
132
161
|
def validate_non_null_input(value_name, ctx, max_errors: nil)
|
133
|
-
allowed_values = ctx.
|
162
|
+
allowed_values = ctx.types.enum_values(self)
|
134
163
|
matching_value = allowed_values.find { |v| v.graphql_name == value_name }
|
135
164
|
|
136
165
|
if matching_value.nil?
|
@@ -138,35 +167,54 @@ module GraphQL
|
|
138
167
|
else
|
139
168
|
nil
|
140
169
|
end
|
170
|
+
# rescue MissingValuesError
|
171
|
+
# nil
|
141
172
|
end
|
142
173
|
|
174
|
+
# Called by the runtime when a field returns a value to give back to the client.
|
175
|
+
# This method checks that the incoming {value} matches one of the enum's defined values.
|
176
|
+
# @param value [Object] Any value matching the values for this enum.
|
177
|
+
# @param ctx [GraphQL::Query::Context]
|
178
|
+
# @raise [GraphQL::Schema::Enum::UnresolvedValueError] if {value} doesn't match a configured value or if the matching value isn't authorized.
|
179
|
+
# @return [String] The GraphQL-ready string for {value}
|
143
180
|
def coerce_result(value, ctx)
|
144
|
-
|
145
|
-
all_values =
|
181
|
+
types = ctx.types
|
182
|
+
all_values = types ? types.enum_values(self) : values.each_value
|
146
183
|
enum_value = all_values.find { |val| val.value == value }
|
147
|
-
if enum_value
|
184
|
+
if enum_value && (was_authed = enum_value.authorized?(ctx))
|
148
185
|
enum_value.graphql_name
|
149
186
|
else
|
150
|
-
raise self::UnresolvedValueError.new(enum: self, value: value, context: ctx)
|
187
|
+
raise self::UnresolvedValueError.new(enum: self, value: value, context: ctx, authorized: was_authed)
|
151
188
|
end
|
152
189
|
end
|
153
190
|
|
191
|
+
# Called by the runtime with incoming string representations from a query.
|
192
|
+
# It will match the string to a configured by name or by Ruby value.
|
193
|
+
# @param value_name [String, Object] A string from a GraphQL query, or a Ruby value matching a `value(..., value: ...)` configuration
|
194
|
+
# @param ctx [GraphQL::Query::Context]
|
195
|
+
# @raise [GraphQL::UnauthorizedEnumValueError] if an {EnumValue} matches but returns false for `.authorized?`. Goes to {Schema.unauthorized_object}.
|
196
|
+
# @return [Object] The Ruby value for the matched {GraphQL::Schema::EnumValue}
|
154
197
|
def coerce_input(value_name, ctx)
|
155
|
-
all_values = ctx.
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
198
|
+
all_values = ctx.types ? ctx.types.enum_values(self) : values.each_value
|
199
|
+
|
200
|
+
# This tries matching by incoming GraphQL string, then checks Ruby-defined values
|
201
|
+
if v = (all_values.find { |val| val.graphql_name == value_name } || all_values.find { |val| val.value == value_name })
|
202
|
+
if v.authorized?(ctx)
|
203
|
+
v.value
|
204
|
+
else
|
205
|
+
raise GraphQL::UnauthorizedEnumValueError.new(type: self, enum_value: v, context: ctx)
|
206
|
+
end
|
163
207
|
else
|
164
208
|
nil
|
165
209
|
end
|
166
210
|
end
|
167
211
|
|
168
212
|
def inherited(child_class)
|
169
|
-
child_class.
|
213
|
+
if child_class.name
|
214
|
+
# Don't assign a custom error class to anonymous classes
|
215
|
+
# because they would end up with names like `#<Class0x1234>::UnresolvedValueError` which messes up bug trackers
|
216
|
+
child_class.const_set(:UnresolvedValueError, Class.new(Schema::Enum::UnresolvedValueError))
|
217
|
+
end
|
170
218
|
super
|
171
219
|
end
|
172
220
|
|
@@ -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
|
@@ -66,7 +74,7 @@ module GraphQL
|
|
66
74
|
end
|
67
75
|
|
68
76
|
def inspect
|
69
|
-
"#<#{self.class} #{path} @value=#{@value.inspect}#{description ? " @description=#{description.inspect}" : ""}>"
|
77
|
+
"#<#{self.class} #{path} @value=#{@value.inspect}#{description ? " @description=#{description.inspect}" : ""}#{deprecation_reason ? " @deprecation_reason=#{deprecation_reason.inspect}" : ""}>"
|
70
78
|
end
|
71
79
|
|
72
80
|
def visible?(_ctx); true; end
|
@@ -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
|
@@ -12,7 +12,7 @@ module GraphQL
|
|
12
12
|
if ret_type.respond_to?(:scope_items)
|
13
13
|
scoped_items = ret_type.scope_items(value, context)
|
14
14
|
if !scoped_items.equal?(value) && !ret_type.reauthorize_scoped_objects
|
15
|
-
if (current_runtime_state =
|
15
|
+
if (current_runtime_state = Fiber[:__graphql_runtime_info]) &&
|
16
16
|
(query_runtime_state = current_runtime_state[context.query])
|
17
17
|
query_runtime_state.was_authorized_by_scope_items = true
|
18
18
|
end
|
data/lib/graphql/schema/field.rb
CHANGED
@@ -42,8 +42,8 @@ module GraphQL
|
|
42
42
|
end
|
43
43
|
|
44
44
|
def directives
|
45
|
-
if @resolver_class && (r_dirs = @resolver_class.directives).
|
46
|
-
if (own_dirs = super).
|
45
|
+
if @resolver_class && !(r_dirs = @resolver_class.directives).empty?
|
46
|
+
if !(own_dirs = super).empty?
|
47
47
|
own_dirs + r_dirs
|
48
48
|
else
|
49
49
|
r_dirs
|
@@ -81,7 +81,7 @@ module GraphQL
|
|
81
81
|
end
|
82
82
|
|
83
83
|
def inspect
|
84
|
-
"#<#{self.class} #{path}#{all_argument_definitions.
|
84
|
+
"#<#{self.class} #{path}#{!all_argument_definitions.empty? ? "(...)" : ""}: #{type.to_type_signature}>"
|
85
85
|
end
|
86
86
|
|
87
87
|
alias :mutation :resolver
|
@@ -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,28 +333,17 @@ 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
|
-
if extensions.
|
338
|
+
if !extensions.empty?
|
338
339
|
self.extensions(extensions)
|
339
340
|
end
|
340
341
|
|
341
|
-
if resolver_class && resolver_class.extensions.
|
342
|
+
if resolver_class && !resolver_class.extensions.empty?
|
342
343
|
self.extensions(resolver_class.extensions)
|
343
344
|
end
|
344
345
|
|
345
|
-
if directives.
|
346
|
+
if !directives.empty?
|
346
347
|
directives.each do |(dir_class, options)|
|
347
348
|
self.directive(dir_class, **options)
|
348
349
|
end
|
@@ -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
|
@@ -454,7 +482,7 @@ module GraphQL
|
|
454
482
|
if new_extras.nil?
|
455
483
|
# Read the value
|
456
484
|
field_extras = @extras
|
457
|
-
if @resolver_class &&
|
485
|
+
if @resolver_class && !@resolver_class.extras.empty?
|
458
486
|
field_extras + @resolver_class.extras
|
459
487
|
else
|
460
488
|
field_extras
|
@@ -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
|
@@ -618,7 +659,7 @@ module GraphQL
|
|
618
659
|
using_arg_values = false
|
619
660
|
end
|
620
661
|
|
621
|
-
args = context.
|
662
|
+
args = context.types.arguments(self)
|
622
663
|
args.each do |arg|
|
623
664
|
arg_key = arg.keyword
|
624
665
|
if arg_values.key?(arg_key)
|
@@ -691,7 +732,7 @@ module GraphQL
|
|
691
732
|
method_to_call = resolver_method
|
692
733
|
method_receiver = obj
|
693
734
|
# Call the method with kwargs, if there are any
|
694
|
-
if ruby_kwargs.
|
735
|
+
if !ruby_kwargs.empty?
|
695
736
|
obj.public_send(resolver_method, **ruby_kwargs)
|
696
737
|
else
|
697
738
|
obj.public_send(resolver_method)
|
@@ -711,7 +752,7 @@ module GraphQL
|
|
711
752
|
elsif inner_object.respond_to?(@method_sym)
|
712
753
|
method_to_call = @method_sym
|
713
754
|
method_receiver = obj.object
|
714
|
-
if ruby_kwargs.
|
755
|
+
if !ruby_kwargs.empty?
|
715
756
|
inner_object.public_send(@method_sym, **ruby_kwargs)
|
716
757
|
else
|
717
758
|
inner_object.public_send(@method_sym)
|
@@ -798,7 +839,7 @@ module GraphQL
|
|
798
839
|
unsatisfied_ruby_kwargs.clear
|
799
840
|
end
|
800
841
|
|
801
|
-
if unsatisfied_ruby_kwargs.
|
842
|
+
if !unsatisfied_ruby_kwargs.empty? || !unsatisfied_method_params.empty?
|
802
843
|
raise FieldImplementationFailed.new, <<-ERR
|
803
844
|
Failed to call `#{method_name.inspect}` on #{receiver.inspect} because the Ruby method params were incompatible with the GraphQL arguments:
|
804
845
|
|
@@ -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
|
@@ -32,7 +32,7 @@ module GraphQL
|
|
32
32
|
input_kwargs = {}
|
33
33
|
end
|
34
34
|
|
35
|
-
if input_kwargs.
|
35
|
+
if !input_kwargs.empty?
|
36
36
|
super(**input_kwargs)
|
37
37
|
else
|
38
38
|
super()
|
@@ -136,6 +136,8 @@ module GraphQL
|
|
136
136
|
super || "Autogenerated input type of #{self.mutation.graphql_name}"
|
137
137
|
end
|
138
138
|
end
|
139
|
+
# For compatibility, in case no arguments are defined:
|
140
|
+
has_no_arguments(true)
|
139
141
|
mutation(mutation_class)
|
140
142
|
# these might be inherited:
|
141
143
|
mutation_args.each do |arg|
|
@@ -149,7 +151,8 @@ module GraphQL
|
|
149
151
|
|
150
152
|
def authorize_arguments(args, values)
|
151
153
|
# remove the `input` wrapper to match values
|
152
|
-
|
154
|
+
input_type = args.find { |a| a.graphql_name == "input" }.type.unwrap
|
155
|
+
input_args = context.types.arguments(input_type)
|
153
156
|
super(input_args, values)
|
154
157
|
end
|
155
158
|
end
|