graphql 2.5.11 → 2.6.10
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/detailed_trace_generator.rb +77 -0
- data/lib/generators/graphql/field_extractor.rb +17 -1
- data/lib/generators/graphql/templates/create_graphql_detailed_traces.erb +10 -0
- data/lib/generators/graphql/templates/schema.erb +2 -1
- data/lib/graphql/analysis/query_complexity.rb +31 -15
- data/lib/graphql/analysis.rb +20 -13
- data/lib/graphql/backtrace/table.rb +10 -1
- data/lib/graphql/current.rb +7 -1
- data/lib/graphql/dashboard/application_controller.rb +45 -0
- data/lib/graphql/dashboard/landings_controller.rb +9 -0
- data/lib/graphql/dashboard/statics_controller.rb +31 -0
- data/lib/graphql/dashboard/subscriptions.rb +2 -1
- data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/clients/_form.html.erb +1 -0
- data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/clients/edit.html.erb +2 -2
- data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/clients/index.html.erb +1 -1
- data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/clients/new.html.erb +1 -1
- data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/index_entries/index.html.erb +1 -1
- data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/operations/show.html.erb +1 -1
- data/lib/graphql/dashboard/views/graphql/dashboard/subscriptions/topics/show.html.erb +1 -1
- data/lib/graphql/dashboard/views/layouts/graphql/dashboard/application.html.erb +7 -7
- data/lib/graphql/dashboard.rb +14 -73
- data/lib/graphql/dataloader/async_dataloader.rb +336 -60
- data/lib/graphql/dataloader/null_dataloader.rb +48 -10
- data/lib/graphql/dataloader/source.rb +32 -19
- data/lib/graphql/dataloader.rb +107 -39
- data/lib/graphql/date_encoding_error.rb +1 -1
- data/lib/graphql/execution/directive_checks.rb +2 -0
- data/lib/graphql/execution/field_resolve_step.rb +804 -0
- data/lib/graphql/execution/finalize.rb +229 -0
- data/lib/graphql/execution/input_values.rb +333 -0
- data/lib/graphql/execution/interpreter/arguments_cache.rb +3 -0
- data/lib/graphql/execution/interpreter/handles_raw_value.rb +6 -0
- data/lib/graphql/execution/interpreter/resolve.rb +7 -13
- data/lib/graphql/execution/interpreter/runtime/graphql_result.rb +13 -0
- data/lib/graphql/execution/interpreter/runtime.rb +60 -40
- data/lib/graphql/execution/interpreter.rb +17 -23
- data/lib/graphql/execution/lazy.rb +1 -1
- data/lib/graphql/execution/load_argument_step.rb +102 -0
- data/lib/graphql/execution/multiplex.rb +1 -1
- data/lib/graphql/execution/next.rb +98 -0
- data/lib/graphql/execution/prepare_object_step.rb +151 -0
- data/lib/graphql/execution/resolve_type_step.rb +27 -0
- data/lib/graphql/execution/runner.rb +457 -0
- data/lib/graphql/execution/selections_step.rb +95 -0
- data/lib/graphql/execution.rb +15 -4
- data/lib/graphql/execution_error.rb +17 -12
- data/lib/graphql/float_decoding_error.rb +13 -0
- data/lib/graphql/float_encoding_error.rb +28 -0
- data/lib/graphql/introspection/directive_type.rb +7 -3
- data/lib/graphql/introspection/dynamic_fields.rb +5 -1
- data/lib/graphql/introspection/entry_points.rb +11 -3
- data/lib/graphql/introspection/enum_value_type.rb +5 -5
- data/lib/graphql/introspection/field_type.rb +13 -5
- data/lib/graphql/introspection/input_value_type.rb +21 -13
- data/lib/graphql/introspection/schema_type.rb +6 -2
- data/lib/graphql/introspection/type_type.rb +64 -28
- data/lib/graphql/invalid_null_error.rb +11 -5
- data/lib/graphql/language/block_string.rb +6 -11
- data/lib/graphql/language/cache.rb +84 -14
- data/lib/graphql/language/document_from_schema_definition.rb +2 -1
- data/lib/graphql/language/lexer.rb +27 -17
- data/lib/graphql/language/nodes.rb +2 -1
- data/lib/graphql/language/parser.rb +1 -1
- data/lib/graphql/language.rb +32 -17
- data/lib/graphql/pagination/connection.rb +2 -0
- data/lib/graphql/pagination/connections.rb +30 -0
- data/lib/graphql/pagination/relation_connection.rb +10 -1
- data/lib/graphql/query/context.rb +10 -3
- data/lib/graphql/query/null_context.rb +9 -3
- data/lib/graphql/query/partial.rb +18 -3
- data/lib/graphql/query/variable_validation_error.rb +16 -1
- data/lib/graphql/query/variables.rb +1 -1
- data/lib/graphql/query.rb +12 -3
- data/lib/graphql/railtie.rb +2 -1
- data/lib/graphql/runtime_error.rb +6 -0
- data/lib/graphql/schema/argument.rb +13 -1
- data/lib/graphql/schema/build_from_definition.rb +28 -1
- data/lib/graphql/schema/directive/feature.rb +4 -0
- data/lib/graphql/schema/directive/transform.rb +20 -0
- data/lib/graphql/schema/directive.rb +48 -13
- data/lib/graphql/schema/field/connection_extension.rb +4 -37
- data/lib/graphql/schema/field/scope_extension.rb +18 -13
- data/lib/graphql/schema/field.rb +79 -48
- data/lib/graphql/schema/field_extension.rb +11 -8
- data/lib/graphql/schema/has_single_input_argument.rb +24 -13
- data/lib/graphql/schema/input_object.rb +6 -2
- data/lib/graphql/schema/interface.rb +26 -0
- data/lib/graphql/schema/introspection_system.rb +6 -21
- data/lib/graphql/schema/list.rb +5 -1
- data/lib/graphql/schema/member/base_dsl_methods.rb +1 -11
- data/lib/graphql/schema/member/has_arguments.rb +43 -14
- data/lib/graphql/schema/member/has_authorization.rb +35 -0
- data/lib/graphql/schema/member/has_dataloader.rb +37 -0
- data/lib/graphql/schema/member/has_fields.rb +86 -5
- data/lib/graphql/schema/member.rb +5 -0
- data/lib/graphql/schema/non_null.rb +1 -1
- data/lib/graphql/schema/object.rb +1 -0
- data/lib/graphql/schema/printer.rb +1 -1
- data/lib/graphql/schema/ractor_shareable.rb +1 -0
- data/lib/graphql/schema/relay_classic_mutation.rb +16 -2
- data/lib/graphql/schema/resolver.rb +76 -1
- data/lib/graphql/schema/subscription.rb +53 -10
- data/lib/graphql/schema/timeout.rb +2 -2
- data/lib/graphql/schema/validator/allow_blank_validator.rb +3 -3
- data/lib/graphql/schema/validator/allow_null_validator.rb +3 -3
- data/lib/graphql/schema/validator/exclusion_validator.rb +2 -2
- data/lib/graphql/schema/validator/format_validator.rb +3 -3
- data/lib/graphql/schema/validator/inclusion_validator.rb +2 -2
- data/lib/graphql/schema/validator/length_validator.rb +6 -6
- data/lib/graphql/schema/validator/numericality_validator.rb +19 -19
- data/lib/graphql/schema/validator/required_validator.rb +38 -5
- data/lib/graphql/schema/validator.rb +9 -0
- data/lib/graphql/schema/visibility/profile.rb +74 -53
- data/lib/graphql/schema/visibility/visit.rb +1 -1
- data/lib/graphql/schema/visibility.rb +32 -24
- data/lib/graphql/schema/wrapper.rb +11 -1
- data/lib/graphql/schema.rb +93 -27
- data/lib/graphql/static_validation/base_visitor.rb +90 -66
- data/lib/graphql/static_validation/rules/argument_literals_are_compatible.rb +1 -1
- data/lib/graphql/static_validation/rules/argument_names_are_unique.rb +18 -6
- data/lib/graphql/static_validation/rules/arguments_are_defined.rb +5 -2
- data/lib/graphql/static_validation/rules/directives_are_defined.rb +5 -2
- data/lib/graphql/static_validation/rules/fields_are_defined_on_type.rb +4 -3
- data/lib/graphql/static_validation/rules/fields_have_appropriate_selections.rb +14 -4
- data/lib/graphql/static_validation/rules/fields_will_merge.rb +351 -248
- data/lib/graphql/static_validation/rules/fields_will_merge_error.rb +4 -4
- data/lib/graphql/static_validation/rules/fragment_spreads_are_possible.rb +3 -3
- data/lib/graphql/static_validation/rules/fragment_types_exist.rb +10 -7
- data/lib/graphql/static_validation/rules/required_arguments_are_present.rb +27 -7
- data/lib/graphql/static_validation/rules/variables_are_input_types.rb +12 -9
- data/lib/graphql/static_validation/validation_context.rb +1 -1
- data/lib/graphql/subscriptions/action_cable_subscriptions.rb +1 -0
- data/lib/graphql/subscriptions/default_subscription_resolve_extension.rb +40 -10
- data/lib/graphql/subscriptions/serialize.rb +10 -4
- data/lib/graphql/subscriptions.rb +36 -1
- data/lib/graphql/testing/helpers.rb +12 -9
- data/lib/graphql/testing/mock_action_cable.rb +111 -0
- data/lib/graphql/testing.rb +1 -0
- data/lib/graphql/tracing/detailed_trace/active_record_backend.rb +74 -0
- data/lib/graphql/tracing/detailed_trace.rb +70 -7
- data/lib/graphql/tracing/perfetto_trace.rb +213 -81
- data/lib/graphql/tracing/sentry_trace.rb +3 -1
- data/lib/graphql/tracing/trace.rb +6 -0
- data/lib/graphql/types/float.rb +18 -4
- data/lib/graphql/types/relay/connection_behaviors.rb +8 -6
- data/lib/graphql/types/relay/edge_behaviors.rb +4 -3
- data/lib/graphql/types/relay/has_node_field.rb +13 -8
- data/lib/graphql/types/relay/has_nodes_field.rb +13 -8
- data/lib/graphql/types/relay/node_behaviors.rb +13 -2
- data/lib/graphql/unauthorized_error.rb +9 -1
- data/lib/graphql/version.rb +1 -1
- data/lib/graphql.rb +8 -2
- metadata +23 -59
|
@@ -23,6 +23,7 @@ module GraphQL
|
|
|
23
23
|
# Really we only need description & comment from here, but:
|
|
24
24
|
extend Schema::Member::BaseDSLMethods
|
|
25
25
|
extend GraphQL::Schema::Member::HasArguments
|
|
26
|
+
extend GraphQL::Schema::Member::HasAuthorization
|
|
26
27
|
extend GraphQL::Schema::Member::HasValidators
|
|
27
28
|
include Schema::Member::HasPath
|
|
28
29
|
extend Schema::Member::HasPath
|
|
@@ -45,8 +46,10 @@ module GraphQL
|
|
|
45
46
|
@prepared_arguments = nil
|
|
46
47
|
end
|
|
47
48
|
|
|
49
|
+
attr_accessor :exec_result, :exec_index, :field_resolve_step, :raw_arguments
|
|
50
|
+
|
|
48
51
|
# @return [Object] The application object this field is being resolved on
|
|
49
|
-
|
|
52
|
+
attr_accessor :object
|
|
50
53
|
|
|
51
54
|
# @return [GraphQL::Query::Context]
|
|
52
55
|
attr_reader :context
|
|
@@ -54,6 +57,74 @@ module GraphQL
|
|
|
54
57
|
# @return [GraphQL::Schema::Field]
|
|
55
58
|
attr_reader :field
|
|
56
59
|
|
|
60
|
+
attr_writer :prepared_arguments
|
|
61
|
+
|
|
62
|
+
def call
|
|
63
|
+
q = context.query
|
|
64
|
+
trace_objs = [object]
|
|
65
|
+
q.current_trace.begin_execute_field(field, @prepared_arguments, trace_objs, q)
|
|
66
|
+
is_ready = ready?(**@prepared_arguments)
|
|
67
|
+
runner = @field_resolve_step.runner
|
|
68
|
+
if runner.resolves_lazies && runner.schema.lazy?(is_ready)
|
|
69
|
+
is_ready, new_return_value = runner.schema.sync_lazy(is_ready)
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
if is_ready.is_a?(Array)
|
|
73
|
+
is_ready, new_return_value = is_ready
|
|
74
|
+
if is_ready != false
|
|
75
|
+
raise "Unexpected result from #ready? (expected `true`, `false` or `[false, {...}]`): [#{is_ready.inspect}, #{new_return_value.inspect}]"
|
|
76
|
+
else
|
|
77
|
+
new_return_value
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
if is_ready
|
|
82
|
+
begin
|
|
83
|
+
is_authed, new_return_value = authorized?(**@prepared_arguments)
|
|
84
|
+
rescue GraphQL::UnauthorizedError => err
|
|
85
|
+
new_return_value = q.schema.unauthorized_object(err)
|
|
86
|
+
is_authed = false
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
if runner.resolves_lazies && runner.schema.lazy?(is_authed)
|
|
91
|
+
is_authed, new_return_value = runner.schema.sync_lazy(is_authed)
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
result = if is_authed
|
|
95
|
+
Schema::Validator.validate!(self.class.validators, object, context, @prepared_arguments, as: @field)
|
|
96
|
+
call_resolve(@prepared_arguments)
|
|
97
|
+
elsif new_return_value.nil?
|
|
98
|
+
err = UnauthorizedFieldError.new(object: object, type: @field_resolve_step.parent_type, context: context, field: @field)
|
|
99
|
+
context.schema.unauthorized_field(err)
|
|
100
|
+
else
|
|
101
|
+
new_return_value
|
|
102
|
+
end
|
|
103
|
+
q = context.query
|
|
104
|
+
q.current_trace.end_execute_field(field, @prepared_arguments, trace_objs, q, [result])
|
|
105
|
+
exec_result[exec_index] = result
|
|
106
|
+
rescue GraphQL::UnauthorizedError => auth_err
|
|
107
|
+
exec_result[exec_index] = begin
|
|
108
|
+
context.schema.unauthorized_object(auth_err)
|
|
109
|
+
rescue GraphQL::ExecutionError => exec_err
|
|
110
|
+
exec_err
|
|
111
|
+
end
|
|
112
|
+
rescue GraphQL::RuntimeError => err
|
|
113
|
+
exec_result[exec_index] = err
|
|
114
|
+
rescue StandardError => stderr
|
|
115
|
+
exec_result[exec_index] = begin
|
|
116
|
+
context.query.handle_or_reraise(stderr)
|
|
117
|
+
rescue GraphQL::ExecutionError => ex_err
|
|
118
|
+
ex_err
|
|
119
|
+
end
|
|
120
|
+
ensure
|
|
121
|
+
field_pending_steps = field_resolve_step.pending_steps
|
|
122
|
+
field_pending_steps.delete(self)
|
|
123
|
+
if field_pending_steps.size == 0 && field_resolve_step.field_results
|
|
124
|
+
field_resolve_step.runner.add_step(field_resolve_step)
|
|
125
|
+
end
|
|
126
|
+
end
|
|
127
|
+
|
|
57
128
|
def arguments
|
|
58
129
|
@prepared_arguments || raise("Arguments have not been prepared yet, still waiting for #load_arguments to resolve. (Call `.arguments` later in the code.)")
|
|
59
130
|
end
|
|
@@ -154,6 +225,10 @@ module GraphQL
|
|
|
154
225
|
authorize_arguments(args, inputs)
|
|
155
226
|
end
|
|
156
227
|
|
|
228
|
+
def self.authorizes?(context)
|
|
229
|
+
self.instance_method(:authorized?).owner != GraphQL::Schema::Resolver
|
|
230
|
+
end
|
|
231
|
+
|
|
157
232
|
# Called when an object loaded by `loads:` fails the `.authorized?` check for its resolved GraphQL object type.
|
|
158
233
|
#
|
|
159
234
|
# By default, the error is re-raised and passed along to {{Schema.unauthorized_object}}.
|
|
@@ -15,8 +15,6 @@ module GraphQL
|
|
|
15
15
|
extend GraphQL::Schema::Resolver::HasPayloadType
|
|
16
16
|
extend GraphQL::Schema::Member::HasFields
|
|
17
17
|
NO_UPDATE = :no_update
|
|
18
|
-
# The generated payload type is required; If there's no payload,
|
|
19
|
-
# propagate null.
|
|
20
18
|
null false
|
|
21
19
|
|
|
22
20
|
# @api private
|
|
@@ -32,14 +30,47 @@ module GraphQL
|
|
|
32
30
|
end
|
|
33
31
|
end
|
|
34
32
|
|
|
33
|
+
# @api private
|
|
34
|
+
def call_resolve(args_hash)
|
|
35
|
+
if @field_resolve_step.nil?
|
|
36
|
+
super
|
|
37
|
+
else
|
|
38
|
+
context.namespace(:subscriptions)[:update_event] = event
|
|
39
|
+
result = nil
|
|
40
|
+
unsubscribed = true
|
|
41
|
+
unsubscribed_result = nil
|
|
42
|
+
begin
|
|
43
|
+
result = super
|
|
44
|
+
unsubscribed = false
|
|
45
|
+
rescue EarlyUnsubscribe => err
|
|
46
|
+
unsubscribed_result = err.unsubscribed_result
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
if unsubscribed
|
|
51
|
+
if unsubscribed_result
|
|
52
|
+
context.namespace(:subscriptions)[:final_update] = true
|
|
53
|
+
unsubscribed_result
|
|
54
|
+
else
|
|
55
|
+
context.skip
|
|
56
|
+
end
|
|
57
|
+
else
|
|
58
|
+
result
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
|
|
35
63
|
# @api private
|
|
36
64
|
def resolve_with_support(**args)
|
|
37
65
|
@original_arguments = args # before `loads:` have been run
|
|
38
66
|
result = nil
|
|
39
67
|
unsubscribed = true
|
|
40
|
-
unsubscribed_result =
|
|
68
|
+
unsubscribed_result = nil
|
|
69
|
+
begin
|
|
41
70
|
result = super
|
|
42
71
|
unsubscribed = false
|
|
72
|
+
rescue EarlyUnsubscribe => err
|
|
73
|
+
unsubscribed_result = err.unsubscribed_result
|
|
43
74
|
end
|
|
44
75
|
|
|
45
76
|
|
|
@@ -116,7 +147,13 @@ module GraphQL
|
|
|
116
147
|
# @return [void]
|
|
117
148
|
def unsubscribe(update_value = nil)
|
|
118
149
|
context.namespace(:subscriptions)[:unsubscribed] = true
|
|
119
|
-
|
|
150
|
+
err = EarlyUnsubscribe.new
|
|
151
|
+
err.unsubscribed_result = update_value
|
|
152
|
+
raise err
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
class EarlyUnsubscribe < GraphQL::RuntimeError
|
|
156
|
+
attr_accessor :unsubscribed_result
|
|
120
157
|
end
|
|
121
158
|
|
|
122
159
|
# Call this method to provide a new subscription_scope; OR
|
|
@@ -189,12 +226,18 @@ module GraphQL
|
|
|
189
226
|
|
|
190
227
|
# @return [Subscriptions::Event] This object is used as a representation of this subscription for the backend
|
|
191
228
|
def event
|
|
192
|
-
@event ||=
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
229
|
+
@event ||= begin
|
|
230
|
+
if @original_arguments.nil? && @field_resolve_step
|
|
231
|
+
@original_arguments, _errors = @field_resolve_step.arguments_without_loads
|
|
232
|
+
end
|
|
233
|
+
|
|
234
|
+
Subscriptions::Event.new(
|
|
235
|
+
name: field.name,
|
|
236
|
+
arguments: @original_arguments,
|
|
237
|
+
context: context,
|
|
238
|
+
field: field,
|
|
239
|
+
)
|
|
240
|
+
end
|
|
198
241
|
end
|
|
199
242
|
end
|
|
200
243
|
end
|
|
@@ -68,7 +68,7 @@ module GraphQL
|
|
|
68
68
|
super
|
|
69
69
|
end
|
|
70
70
|
|
|
71
|
-
def
|
|
71
|
+
def begin_execute_field(field, _arguments, _objects, query)
|
|
72
72
|
timeout_state = query.context.namespace(@timeout).fetch(:state)
|
|
73
73
|
# If the `:state` is `false`, then `max_seconds(query)` opted out of timeout for this query.
|
|
74
74
|
if timeout_state == false
|
|
@@ -84,7 +84,7 @@ module GraphQL
|
|
|
84
84
|
|
|
85
85
|
# `handle_timeout` may have set this to be `false`
|
|
86
86
|
if timeout_state != false
|
|
87
|
-
error
|
|
87
|
+
raise error
|
|
88
88
|
else
|
|
89
89
|
super
|
|
90
90
|
end
|
|
@@ -8,7 +8,7 @@ module GraphQL
|
|
|
8
8
|
# @example Require a non-empty string for an argument
|
|
9
9
|
# argument :name, String, required: true, validate: { allow_blank: false }
|
|
10
10
|
class AllowBlankValidator < Validator
|
|
11
|
-
def initialize(allow_blank_positional, allow_blank: nil, message: "%{validated} can't be blank", **default_options)
|
|
11
|
+
def initialize(allow_blank_positional = nil, allow_blank: nil, message: "%{validated} can't be blank", **default_options)
|
|
12
12
|
@message = message
|
|
13
13
|
super(**default_options)
|
|
14
14
|
@allow_blank = allow_blank.nil? ? allow_blank_positional : allow_blank
|
|
@@ -16,10 +16,10 @@ module GraphQL
|
|
|
16
16
|
|
|
17
17
|
def validate(_object, _context, value)
|
|
18
18
|
if value.respond_to?(:blank?) && value.blank?
|
|
19
|
-
if (value.nil? && @allow_null) || @allow_blank
|
|
19
|
+
if (value.nil? && validation_parameter(@allow_null)) || validation_parameter(@allow_blank)
|
|
20
20
|
# pass
|
|
21
21
|
else
|
|
22
|
-
@message
|
|
22
|
+
validation_parameter(@message)
|
|
23
23
|
end
|
|
24
24
|
end
|
|
25
25
|
end
|
|
@@ -9,15 +9,15 @@ module GraphQL
|
|
|
9
9
|
# argument :name, String, required: false, validates: { allow_null: false }
|
|
10
10
|
class AllowNullValidator < Validator
|
|
11
11
|
MESSAGE = "%{validated} can't be null"
|
|
12
|
-
def initialize(allow_null_positional, allow_null: nil, message: MESSAGE, **default_options)
|
|
12
|
+
def initialize(allow_null_positional = nil, allow_null: nil, message: MESSAGE, **default_options)
|
|
13
13
|
@message = message
|
|
14
14
|
super(**default_options)
|
|
15
15
|
@allow_null = allow_null.nil? ? allow_null_positional : allow_null
|
|
16
16
|
end
|
|
17
17
|
|
|
18
18
|
def validate(_object, _context, value)
|
|
19
|
-
if value.nil? &&
|
|
20
|
-
@message
|
|
19
|
+
if value.nil? && !validation_parameter(@allow_null)
|
|
20
|
+
validation_parameter(@message)
|
|
21
21
|
end
|
|
22
22
|
end
|
|
23
23
|
end
|
|
@@ -23,8 +23,8 @@ module GraphQL
|
|
|
23
23
|
def validate(_object, _context, value)
|
|
24
24
|
if permitted_empty_value?(value)
|
|
25
25
|
# pass
|
|
26
|
-
elsif @in_list.include?(value)
|
|
27
|
-
@message
|
|
26
|
+
elsif validation_parameter(@in_list).include?(value)
|
|
27
|
+
validation_parameter(@message)
|
|
28
28
|
end
|
|
29
29
|
end
|
|
30
30
|
end
|
|
@@ -37,9 +37,9 @@ module GraphQL
|
|
|
37
37
|
if permitted_empty_value?(value)
|
|
38
38
|
# Do nothing
|
|
39
39
|
elsif value.nil? ||
|
|
40
|
-
(@with_pattern && !value.match?(@with_pattern)) ||
|
|
41
|
-
(@without_pattern && value.match?(@without_pattern))
|
|
42
|
-
@message
|
|
40
|
+
(@with_pattern && !value.match?(validation_parameter(@with_pattern))) ||
|
|
41
|
+
(@without_pattern && value.match?(validation_parameter(@without_pattern)))
|
|
42
|
+
validation_parameter(@message)
|
|
43
43
|
end
|
|
44
44
|
end
|
|
45
45
|
end
|
|
@@ -25,8 +25,8 @@ module GraphQL
|
|
|
25
25
|
def validate(_object, _context, value)
|
|
26
26
|
if permitted_empty_value?(value)
|
|
27
27
|
# pass
|
|
28
|
-
elsif
|
|
29
|
-
@message
|
|
28
|
+
elsif !validation_parameter(@in_list).include?(value)
|
|
29
|
+
validation_parameter(@message)
|
|
30
30
|
end
|
|
31
31
|
end
|
|
32
32
|
end
|
|
@@ -45,12 +45,12 @@ module GraphQL
|
|
|
45
45
|
def validate(_object, _context, value)
|
|
46
46
|
return if permitted_empty_value?(value) # pass in this case
|
|
47
47
|
length = value.nil? ? 0 : value.length
|
|
48
|
-
if @maximum && length >
|
|
49
|
-
partial_format(@too_long, { count:
|
|
50
|
-
elsif @minimum && length <
|
|
51
|
-
partial_format(@too_short, { count:
|
|
52
|
-
elsif @is && length !=
|
|
53
|
-
partial_format(@wrong_length, { count:
|
|
48
|
+
if (current_max = validation_parameter(@maximum)) && length > current_max
|
|
49
|
+
partial_format(validation_parameter(@too_long), { count: current_max })
|
|
50
|
+
elsif (current_min = validation_parameter(@minimum)) && length < current_min
|
|
51
|
+
partial_format(validation_parameter(@too_short), { count: current_min })
|
|
52
|
+
elsif (current_is = validation_parameter(@is)) && length != current_is
|
|
53
|
+
partial_format(validation_parameter(@wrong_length), { count: current_is })
|
|
54
54
|
end
|
|
55
55
|
end
|
|
56
56
|
end
|
|
@@ -55,25 +55,25 @@ module GraphQL
|
|
|
55
55
|
if permitted_empty_value?(value)
|
|
56
56
|
# pass in this case
|
|
57
57
|
elsif value.nil? # @allow_null is handled in the parent class
|
|
58
|
-
@null_message
|
|
59
|
-
elsif @greater_than && value <=
|
|
60
|
-
partial_format(@message, { comparison: "greater than", target:
|
|
61
|
-
elsif @greater_than_or_equal_to && value <
|
|
62
|
-
partial_format(@message, { comparison: "greater than or equal to", target:
|
|
63
|
-
elsif @less_than && value >=
|
|
64
|
-
partial_format(@message, { comparison: "less than", target:
|
|
65
|
-
elsif @less_than_or_equal_to && value >
|
|
66
|
-
partial_format(@message, { comparison: "less than or equal to", target:
|
|
67
|
-
elsif @equal_to && value !=
|
|
68
|
-
partial_format(@message, { comparison: "equal to", target:
|
|
69
|
-
elsif @other_than && value ==
|
|
70
|
-
partial_format(@message, { comparison: "something other than", target:
|
|
71
|
-
elsif @even && !value.even?
|
|
72
|
-
(partial_format(@message, { comparison: "even", target: "" })).strip
|
|
73
|
-
elsif @odd && !value.odd?
|
|
74
|
-
(partial_format(@message, { comparison: "odd", target: "" })).strip
|
|
75
|
-
elsif @within &&
|
|
76
|
-
partial_format(@message, { comparison: "within", target:
|
|
58
|
+
validation_parameter(@null_message)
|
|
59
|
+
elsif (current_greater_than = validation_parameter(@greater_than)) && value <= current_greater_than
|
|
60
|
+
partial_format(validation_parameter(@message), { comparison: "greater than", target: current_greater_than })
|
|
61
|
+
elsif (current_greater_than_or_equal_to = validation_parameter(@greater_than_or_equal_to)) && value < current_greater_than_or_equal_to
|
|
62
|
+
partial_format(validation_parameter(@message), { comparison: "greater than or equal to", target: current_greater_than_or_equal_to })
|
|
63
|
+
elsif (current_less_than = validation_parameter(@less_than)) && value >= current_less_than
|
|
64
|
+
partial_format(validation_parameter(@message), { comparison: "less than", target: current_less_than })
|
|
65
|
+
elsif (current_less_than_or_equal_to = validation_parameter(@less_than_or_equal_to)) && value > current_less_than_or_equal_to
|
|
66
|
+
partial_format(validation_parameter(@message), { comparison: "less than or equal to", target: current_less_than_or_equal_to })
|
|
67
|
+
elsif (current_equal_to = validation_parameter(@equal_to)) && value != current_equal_to
|
|
68
|
+
partial_format(validation_parameter(@message), { comparison: "equal to", target: current_equal_to })
|
|
69
|
+
elsif (current_other_than = validation_parameter(@other_than)) && value == current_other_than
|
|
70
|
+
partial_format(validation_parameter(@message), { comparison: "something other than", target: current_other_than })
|
|
71
|
+
elsif validation_parameter(@even) && !value.even?
|
|
72
|
+
(partial_format(validation_parameter(@message), { comparison: "even", target: "" })).strip
|
|
73
|
+
elsif validation_parameter(@odd) && !value.odd?
|
|
74
|
+
(partial_format(validation_parameter(@message), { comparison: "odd", target: "" })).strip
|
|
75
|
+
elsif (current_within = validation_parameter(@within)) && !current_within.include?(value)
|
|
76
|
+
partial_format(validation_parameter(@message), { comparison: "within", target: current_within })
|
|
77
77
|
end
|
|
78
78
|
end
|
|
79
79
|
end
|
|
@@ -8,6 +8,13 @@ module GraphQL
|
|
|
8
8
|
#
|
|
9
9
|
# (This is for specifying mutually exclusive sets of arguments.)
|
|
10
10
|
#
|
|
11
|
+
# If you use {GraphQL::Schema::Visibility} to hide all the arguments in a `one_of: [..]` set,
|
|
12
|
+
# then a developer-facing {GraphQL::Error} will be raised during execution. Pass `allow_all_hidden: true` to
|
|
13
|
+
# skip validation in this case instead.
|
|
14
|
+
#
|
|
15
|
+
# This validator also implements `argument ... required: :nullable`. If an argument has `required: :nullable`
|
|
16
|
+
# but it's hidden with {GraphQL::Schema::Visibility}, then this validator doesn't run.
|
|
17
|
+
#
|
|
11
18
|
# @example Require exactly one of these arguments
|
|
12
19
|
#
|
|
13
20
|
# field :update_amount, IngredientAmount, null: false do
|
|
@@ -37,15 +44,17 @@ module GraphQL
|
|
|
37
44
|
class RequiredValidator < Validator
|
|
38
45
|
# @param one_of [Array<Symbol>] A list of arguments, exactly one of which is required for this field
|
|
39
46
|
# @param argument [Symbol] An argument that is required for this field
|
|
47
|
+
# @param allow_all_hidden [Boolean] If `true`, then this validator won't run if all the `one_of: ...` arguments have been hidden
|
|
40
48
|
# @param message [String]
|
|
41
|
-
def initialize(one_of: nil, argument: nil, message: nil, **default_options)
|
|
49
|
+
def initialize(one_of: nil, argument: nil, allow_all_hidden: nil, message: nil, **default_options)
|
|
42
50
|
@one_of = if one_of
|
|
43
51
|
one_of
|
|
44
52
|
elsif argument
|
|
45
|
-
[argument]
|
|
53
|
+
[ argument ]
|
|
46
54
|
else
|
|
47
55
|
raise ArgumentError, "`one_of:` or `argument:` must be given in `validates required: {...}`"
|
|
48
56
|
end
|
|
57
|
+
@allow_all_hidden = allow_all_hidden.nil? ? !!argument : allow_all_hidden
|
|
49
58
|
@message = message
|
|
50
59
|
super(**default_options)
|
|
51
60
|
end
|
|
@@ -54,10 +63,18 @@ module GraphQL
|
|
|
54
63
|
fully_matched_conditions = 0
|
|
55
64
|
partially_matched_conditions = 0
|
|
56
65
|
|
|
66
|
+
visible_keywords = context.types.arguments(@validated).map(&:keyword)
|
|
67
|
+
no_visible_conditions = true
|
|
68
|
+
|
|
57
69
|
if !value.nil?
|
|
58
|
-
@one_of.each do |one_of_condition|
|
|
70
|
+
validation_parameter(@one_of).each do |one_of_condition|
|
|
71
|
+
one_of_condition = validation_parameter(one_of_condition)
|
|
59
72
|
case one_of_condition
|
|
60
73
|
when Symbol
|
|
74
|
+
if no_visible_conditions && visible_keywords.include?(one_of_condition)
|
|
75
|
+
no_visible_conditions = false
|
|
76
|
+
end
|
|
77
|
+
|
|
61
78
|
if value.key?(one_of_condition)
|
|
62
79
|
fully_matched_conditions += 1
|
|
63
80
|
end
|
|
@@ -66,6 +83,9 @@ module GraphQL
|
|
|
66
83
|
full_match = true
|
|
67
84
|
|
|
68
85
|
one_of_condition.each do |k|
|
|
86
|
+
if no_visible_conditions && visible_keywords.include?(k)
|
|
87
|
+
no_visible_conditions = false
|
|
88
|
+
end
|
|
69
89
|
if value.key?(k)
|
|
70
90
|
any_match = true
|
|
71
91
|
else
|
|
@@ -88,10 +108,22 @@ module GraphQL
|
|
|
88
108
|
end
|
|
89
109
|
end
|
|
90
110
|
|
|
111
|
+
if no_visible_conditions
|
|
112
|
+
if validation_parameter(@allow_all_hidden)
|
|
113
|
+
return nil
|
|
114
|
+
else
|
|
115
|
+
raise GraphQL::Error, <<~ERR
|
|
116
|
+
#{@validated.path} validates `required: ...` but all required arguments were hidden.
|
|
117
|
+
|
|
118
|
+
Update your schema definition to allow the client to see some fields or skip validation by adding `required: { ..., allow_all_hidden: true }`
|
|
119
|
+
ERR
|
|
120
|
+
end
|
|
121
|
+
end
|
|
122
|
+
|
|
91
123
|
if fully_matched_conditions == 1 && partially_matched_conditions == 0
|
|
92
124
|
nil # OK
|
|
93
125
|
else
|
|
94
|
-
@message || build_message(context)
|
|
126
|
+
validation_parameter(@message) || build_message(context)
|
|
95
127
|
end
|
|
96
128
|
end
|
|
97
129
|
|
|
@@ -99,8 +131,9 @@ module GraphQL
|
|
|
99
131
|
argument_definitions = context.types.arguments(@validated)
|
|
100
132
|
|
|
101
133
|
required_names = @one_of.map do |arg_keyword|
|
|
134
|
+
arg_keyword = validation_parameter(arg_keyword)
|
|
102
135
|
if arg_keyword.is_a?(Array)
|
|
103
|
-
names = arg_keyword.map { |arg| arg_keyword_to_graphql_name(argument_definitions, arg) }
|
|
136
|
+
names = arg_keyword.map { |arg| arg_keyword_to_graphql_name(argument_definitions, validation_parameter(arg)) }
|
|
104
137
|
names.compact! # hidden arguments are `nil`
|
|
105
138
|
"(" + names.join(" and ") + ")"
|
|
106
139
|
else
|
|
@@ -34,6 +34,15 @@ module GraphQL
|
|
|
34
34
|
string
|
|
35
35
|
end
|
|
36
36
|
|
|
37
|
+
# @return [Object] The current value to use for validation, based on `config_value` from configuration time. If a Proc is given, this calls it and returns it.
|
|
38
|
+
def validation_parameter(config_value)
|
|
39
|
+
if config_value.is_a?(Proc)
|
|
40
|
+
config_value.call
|
|
41
|
+
else
|
|
42
|
+
config_value
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
|
|
37
46
|
# @return [Boolean] `true` if `value` is `nil` and this validator has `allow_null: true` or if value is `.blank?` and this validator has `allow_blank: true`
|
|
38
47
|
def permitted_empty_value?(value)
|
|
39
48
|
(value.nil? && @allow_null) ||
|
|
@@ -54,6 +54,9 @@ module GraphQL
|
|
|
54
54
|
@cached_fields.default_proc = nil
|
|
55
55
|
@cached_arguments.default_proc = nil
|
|
56
56
|
@loadable_possible_types.default_proc = nil
|
|
57
|
+
@cached_field_result.default_proc = nil
|
|
58
|
+
@cached_field_result.each { |_, h| h.default_proc = nil }
|
|
59
|
+
@cached_type_result.default_proc = nil
|
|
57
60
|
super
|
|
58
61
|
end
|
|
59
62
|
|
|
@@ -122,6 +125,14 @@ module GraphQL
|
|
|
122
125
|
end.compare_by_identity
|
|
123
126
|
|
|
124
127
|
@loadable_possible_types = Hash.new { |h, union_type| h[union_type] = union_type.possible_types }.compare_by_identity
|
|
128
|
+
|
|
129
|
+
# Combined cache for field(owner, field_name) — avoids repeated kind check + parent lookup + visibility check
|
|
130
|
+
@cached_field_result = Hash.new { |h, owner|
|
|
131
|
+
h[owner] = Hash.new { |h2, field_name| h2[field_name] = compute_field(owner, field_name) }
|
|
132
|
+
}.compare_by_identity
|
|
133
|
+
|
|
134
|
+
# Cache for type(type_name) — avoids repeated get_type + visibility + referenced? checks
|
|
135
|
+
@cached_type_result = Hash.new { |h, type_name| h[type_name] = compute_type(type_name) }
|
|
125
136
|
end
|
|
126
137
|
|
|
127
138
|
def field_on_visible_interface?(field, owner)
|
|
@@ -149,58 +160,11 @@ module GraphQL
|
|
|
149
160
|
end
|
|
150
161
|
|
|
151
162
|
def type(type_name)
|
|
152
|
-
|
|
153
|
-
if t
|
|
154
|
-
if t.is_a?(Array)
|
|
155
|
-
vis_t = nil
|
|
156
|
-
t.each do |t_defn|
|
|
157
|
-
if @cached_visible[t_defn] && referenced?(t_defn)
|
|
158
|
-
if vis_t.nil?
|
|
159
|
-
vis_t = t_defn
|
|
160
|
-
else
|
|
161
|
-
raise_duplicate_definition(vis_t, t_defn)
|
|
162
|
-
end
|
|
163
|
-
end
|
|
164
|
-
end
|
|
165
|
-
vis_t
|
|
166
|
-
else
|
|
167
|
-
if t && @cached_visible[t] && referenced?(t)
|
|
168
|
-
t
|
|
169
|
-
else
|
|
170
|
-
nil
|
|
171
|
-
end
|
|
172
|
-
end
|
|
173
|
-
end
|
|
163
|
+
@cached_type_result[type_name]
|
|
174
164
|
end
|
|
175
165
|
|
|
176
166
|
def field(owner, field_name)
|
|
177
|
-
|
|
178
|
-
field
|
|
179
|
-
elsif owner == query_root && (entry_point_field = @schema.introspection_system.entry_point(name: field_name))
|
|
180
|
-
entry_point_field
|
|
181
|
-
elsif (dynamic_field = @schema.introspection_system.dynamic_field(name: field_name))
|
|
182
|
-
dynamic_field
|
|
183
|
-
else
|
|
184
|
-
nil
|
|
185
|
-
end
|
|
186
|
-
if f.is_a?(Array)
|
|
187
|
-
visible_f = nil
|
|
188
|
-
f.each do |f_defn|
|
|
189
|
-
if @cached_visible_fields[owner][f_defn]
|
|
190
|
-
|
|
191
|
-
if visible_f.nil?
|
|
192
|
-
visible_f = f_defn
|
|
193
|
-
else
|
|
194
|
-
raise_duplicate_definition(visible_f, f_defn)
|
|
195
|
-
end
|
|
196
|
-
end
|
|
197
|
-
end
|
|
198
|
-
visible_f&.ensure_loaded
|
|
199
|
-
elsif f && @cached_visible_fields[owner][f.ensure_loaded]
|
|
200
|
-
f
|
|
201
|
-
else
|
|
202
|
-
nil
|
|
203
|
-
end
|
|
167
|
+
@cached_field_result[owner][field_name]
|
|
204
168
|
end
|
|
205
169
|
|
|
206
170
|
def fields(owner)
|
|
@@ -306,6 +270,7 @@ module GraphQL
|
|
|
306
270
|
def preload
|
|
307
271
|
load_all_types
|
|
308
272
|
@all_types.each do |type_name, type_defn|
|
|
273
|
+
type(type_name)
|
|
309
274
|
if type_defn.kind.fields?
|
|
310
275
|
fields(type_defn).each do |f|
|
|
311
276
|
field(type_defn, f.graphql_name)
|
|
@@ -325,11 +290,13 @@ module GraphQL
|
|
|
325
290
|
end
|
|
326
291
|
# Lots more to do here
|
|
327
292
|
end
|
|
328
|
-
@schema.
|
|
329
|
-
|
|
330
|
-
|
|
293
|
+
if @schema.query
|
|
294
|
+
@schema.introspection_system.entry_points.each do |f|
|
|
295
|
+
arguments(f).each do |arg|
|
|
296
|
+
argument(f, arg.graphql_name)
|
|
297
|
+
end
|
|
298
|
+
field(@schema.query, f.graphql_name)
|
|
331
299
|
end
|
|
332
|
-
field(@schema.query, f.graphql_name)
|
|
333
300
|
end
|
|
334
301
|
@schema.introspection_system.dynamic_fields.each do |f|
|
|
335
302
|
arguments(f).each do |arg|
|
|
@@ -341,6 +308,60 @@ module GraphQL
|
|
|
341
308
|
|
|
342
309
|
private
|
|
343
310
|
|
|
311
|
+
def compute_type(type_name)
|
|
312
|
+
t = @visibility.get_type(type_name) # rubocop:disable Development/ContextIsPassedCop
|
|
313
|
+
if t
|
|
314
|
+
if t.is_a?(Array)
|
|
315
|
+
vis_t = nil
|
|
316
|
+
t.each do |t_defn|
|
|
317
|
+
if @cached_visible[t_defn] && referenced?(t_defn)
|
|
318
|
+
if vis_t.nil?
|
|
319
|
+
vis_t = t_defn
|
|
320
|
+
else
|
|
321
|
+
raise_duplicate_definition(vis_t, t_defn)
|
|
322
|
+
end
|
|
323
|
+
end
|
|
324
|
+
end
|
|
325
|
+
vis_t
|
|
326
|
+
else
|
|
327
|
+
if t && @cached_visible[t] && referenced?(t)
|
|
328
|
+
t
|
|
329
|
+
else
|
|
330
|
+
nil
|
|
331
|
+
end
|
|
332
|
+
end
|
|
333
|
+
end
|
|
334
|
+
end
|
|
335
|
+
|
|
336
|
+
def compute_field(owner, field_name)
|
|
337
|
+
f = if owner.kind.fields? && (field = @cached_parent_fields[owner][field_name])
|
|
338
|
+
field
|
|
339
|
+
elsif owner == query_root && (entry_point_field = @schema.introspection_system.entry_point(name: field_name))
|
|
340
|
+
entry_point_field
|
|
341
|
+
elsif (dynamic_field = @schema.introspection_system.dynamic_field(name: field_name))
|
|
342
|
+
dynamic_field
|
|
343
|
+
else
|
|
344
|
+
nil
|
|
345
|
+
end
|
|
346
|
+
if f.is_a?(Array)
|
|
347
|
+
visible_f = nil
|
|
348
|
+
f.each do |f_defn|
|
|
349
|
+
if @cached_visible_fields[owner][f_defn]
|
|
350
|
+
if visible_f.nil?
|
|
351
|
+
visible_f = f_defn
|
|
352
|
+
else
|
|
353
|
+
raise_duplicate_definition(visible_f, f_defn)
|
|
354
|
+
end
|
|
355
|
+
end
|
|
356
|
+
end
|
|
357
|
+
visible_f&.ensure_loaded
|
|
358
|
+
elsif f && @cached_visible_fields[owner][f.ensure_loaded]
|
|
359
|
+
f
|
|
360
|
+
else
|
|
361
|
+
nil
|
|
362
|
+
end
|
|
363
|
+
end
|
|
364
|
+
|
|
344
365
|
def non_duplicate_items(definitions, visibility_cache)
|
|
345
366
|
non_dups = []
|
|
346
367
|
names = Set.new
|
|
@@ -102,7 +102,7 @@ module GraphQL
|
|
|
102
102
|
|
|
103
103
|
missed_late_types_streak = 0
|
|
104
104
|
while (owner, late_type = @late_bound_types.shift)
|
|
105
|
-
if (late_type.is_a?(String) && (type = Member::BuildType.constantize(
|
|
105
|
+
if (late_type.is_a?(String) && (type = Member::BuildType.constantize(late_type))) ||
|
|
106
106
|
(late_type.is_a?(LateBoundType) && (type = @visited_types.find { |t| t.graphql_name == late_type.graphql_name }))
|
|
107
107
|
missed_late_types_streak = 0 # might succeed next round
|
|
108
108
|
update_type_owner(owner, type)
|