graphql 2.3.7 → 2.4.8
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.
Potentially problematic release.
This version of graphql might be problematic. Click here for more details.
- 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 +9 -5
- 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 +52 -6
- 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/default_subscription_resolve_extension.rb +12 -10
- data/lib/graphql/subscriptions/event.rb +13 -2
- 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/active_support_notifications_trace.rb +1 -1
- data/lib/graphql/tracing/active_support_notifications_tracing.rb +1 -1
- data/lib/graphql/tracing/appoptics_trace.rb +2 -0
- data/lib/graphql/tracing/appoptics_tracing.rb +2 -0
- data/lib/graphql/tracing/appsignal_trace.rb +2 -0
- data/lib/graphql/tracing/appsignal_tracing.rb +2 -0
- data/lib/graphql/tracing/call_legacy_tracers.rb +66 -0
- data/lib/graphql/tracing/data_dog_trace.rb +2 -0
- data/lib/graphql/tracing/data_dog_tracing.rb +2 -0
- data/lib/graphql/tracing/legacy_hooks_trace.rb +1 -0
- data/lib/graphql/tracing/legacy_trace.rb +4 -61
- data/lib/graphql/tracing/new_relic_trace.rb +2 -0
- data/lib/graphql/tracing/new_relic_tracing.rb +2 -0
- data/lib/graphql/tracing/notifications_trace.rb +2 -2
- data/lib/graphql/tracing/notifications_tracing.rb +2 -0
- data/lib/graphql/tracing/null_trace.rb +9 -0
- data/lib/graphql/tracing/prometheus_trace/graphql_collector.rb +2 -0
- data/lib/graphql/tracing/prometheus_trace.rb +5 -0
- data/lib/graphql/tracing/prometheus_tracing.rb +2 -0
- data/lib/graphql/tracing/scout_trace.rb +2 -0
- data/lib/graphql/tracing/scout_tracing.rb +2 -0
- data/lib/graphql/tracing/sentry_trace.rb +2 -0
- data/lib/graphql/tracing/statsd_trace.rb +2 -0
- data/lib/graphql/tracing/statsd_tracing.rb +2 -0
- data/lib/graphql/tracing/trace.rb +3 -0
- data/lib/graphql/tracing.rb +28 -30
- 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 +33 -8
- data/lib/graphql/language/token.rb +0 -34
- data/lib/graphql/schema/invalid_type_error.rb +0 -7
@@ -8,6 +8,7 @@ module GraphQL
|
|
8
8
|
# - Arguments, via `.argument(...)` helper, which will be applied to the field.
|
9
9
|
# - Return type, via `.type(..., null: ...)`, which will be applied to the field.
|
10
10
|
# - Description, via `.description(...)`, which will be applied to the field
|
11
|
+
# - Comment, via `.comment(...)`, which will be applied to the field
|
11
12
|
# - Resolution, via `#resolve(**args)` method, which will be called to resolve the field.
|
12
13
|
# - `#object` and `#context` accessors for use during `#resolve`.
|
13
14
|
#
|
@@ -19,7 +20,7 @@ module GraphQL
|
|
19
20
|
# @see {GraphQL::Function} `Resolver` is a replacement for `GraphQL::Function`
|
20
21
|
class Resolver
|
21
22
|
include Schema::Member::GraphQLTypeNames
|
22
|
-
# Really we only need description from here, but:
|
23
|
+
# Really we only need description & comment from here, but:
|
23
24
|
extend Schema::Member::BaseDSLMethods
|
24
25
|
extend GraphQL::Schema::Member::HasArguments
|
25
26
|
extend GraphQL::Schema::Member::HasValidators
|
@@ -36,7 +37,7 @@ module GraphQL
|
|
36
37
|
@field = field
|
37
38
|
# Since this hash is constantly rebuilt, cache it for this call
|
38
39
|
@arguments_by_keyword = {}
|
39
|
-
|
40
|
+
context.types.arguments(self.class).each do |arg|
|
40
41
|
@arguments_by_keyword[arg.keyword] = arg
|
41
42
|
end
|
42
43
|
@prepared_arguments = nil
|
@@ -66,7 +67,7 @@ module GraphQL
|
|
66
67
|
# @api private
|
67
68
|
def resolve_with_support(**args)
|
68
69
|
# First call the ready? hook which may raise
|
69
|
-
raw_ready_val = if args.
|
70
|
+
raw_ready_val = if !args.empty?
|
70
71
|
ready?(**args)
|
71
72
|
else
|
72
73
|
ready?
|
@@ -87,7 +88,7 @@ module GraphQL
|
|
87
88
|
@prepared_arguments = loaded_args
|
88
89
|
Schema::Validator.validate!(self.class.validators, object, context, loaded_args, as: @field)
|
89
90
|
# Then call `authorized?`, which may raise or may return a lazy object
|
90
|
-
raw_authorized_val = if loaded_args.
|
91
|
+
raw_authorized_val = if !loaded_args.empty?
|
91
92
|
authorized?(**loaded_args)
|
92
93
|
else
|
93
94
|
authorized?
|
@@ -116,7 +117,7 @@ module GraphQL
|
|
116
117
|
|
117
118
|
# @api private {GraphQL::Schema::Mutation} uses this to clear the dataloader cache
|
118
119
|
def call_resolve(args_hash)
|
119
|
-
if args_hash.
|
120
|
+
if !args_hash.empty?
|
120
121
|
public_send(self.class.resolve_method, **args_hash)
|
121
122
|
else
|
122
123
|
public_send(self.class.resolve_method)
|
@@ -152,7 +153,7 @@ module GraphQL
|
|
152
153
|
# @return [Boolean, early_return_data] If `false`, execution will stop (and `early_return_data` will be returned instead, if present.)
|
153
154
|
def authorized?(**inputs)
|
154
155
|
arg_owner = @field # || self.class
|
155
|
-
args =
|
156
|
+
args = context.types.arguments(arg_owner)
|
156
157
|
authorize_arguments(args, inputs)
|
157
158
|
end
|
158
159
|
|
@@ -169,7 +170,7 @@ module GraphQL
|
|
169
170
|
private
|
170
171
|
|
171
172
|
def authorize_arguments(args, inputs)
|
172
|
-
args.
|
173
|
+
args.each do |argument|
|
173
174
|
arg_keyword = argument.keyword
|
174
175
|
if inputs.key?(arg_keyword) && !(arg_value = inputs[arg_keyword]).nil? && (arg_value != argument.default_value)
|
175
176
|
auth_result = argument.authorized?(self, arg_value, context)
|
@@ -182,10 +183,9 @@ module GraphQL
|
|
182
183
|
elsif auth_result == false
|
183
184
|
return auth_result
|
184
185
|
end
|
185
|
-
else
|
186
|
-
true
|
187
186
|
end
|
188
187
|
end
|
188
|
+
true
|
189
189
|
end
|
190
190
|
|
191
191
|
def load_arguments(args)
|
@@ -208,7 +208,7 @@ module GraphQL
|
|
208
208
|
end
|
209
209
|
|
210
210
|
# Avoid returning a lazy if none are needed
|
211
|
-
if prepare_lazies.
|
211
|
+
if !prepare_lazies.empty?
|
212
212
|
GraphQL::Execution::Lazy.all(prepare_lazies).then { prepared_args }
|
213
213
|
else
|
214
214
|
prepared_args
|
@@ -394,7 +394,7 @@ module GraphQL
|
|
394
394
|
if superclass.respond_to?(:extensions)
|
395
395
|
s_exts = superclass.extensions
|
396
396
|
if own_exts
|
397
|
-
if s_exts.
|
397
|
+
if !s_exts.empty?
|
398
398
|
own_exts + s_exts
|
399
399
|
else
|
400
400
|
own_exts
|
@@ -409,9 +409,7 @@ module GraphQL
|
|
409
409
|
|
410
410
|
private
|
411
411
|
|
412
|
-
|
413
|
-
@own_extensions
|
414
|
-
end
|
412
|
+
attr_reader :own_extensions
|
415
413
|
end
|
416
414
|
end
|
417
415
|
end
|
@@ -19,13 +19,22 @@ module GraphQL
|
|
19
19
|
# propagate null.
|
20
20
|
null false
|
21
21
|
|
22
|
+
# @api private
|
22
23
|
def initialize(object:, context:, field:)
|
23
24
|
super
|
24
25
|
# Figure out whether this is an update or an initial subscription
|
25
26
|
@mode = context.query.subscription_update? ? :update : :subscribe
|
27
|
+
@subscription_written = false
|
28
|
+
@original_arguments = nil
|
29
|
+
if (subs_ns = context.namespace(:subscriptions)) &&
|
30
|
+
(sub_insts = subs_ns[:subscriptions])
|
31
|
+
sub_insts[context.current_path] = self
|
32
|
+
end
|
26
33
|
end
|
27
34
|
|
35
|
+
# @api private
|
28
36
|
def resolve_with_support(**args)
|
37
|
+
@original_arguments = args # before `loads:` have been run
|
29
38
|
result = nil
|
30
39
|
unsubscribed = true
|
31
40
|
unsubscribed_result = catch :graphql_subscription_unsubscribed do
|
@@ -46,7 +55,9 @@ module GraphQL
|
|
46
55
|
end
|
47
56
|
end
|
48
57
|
|
49
|
-
# Implement the {Resolve} API
|
58
|
+
# Implement the {Resolve} API.
|
59
|
+
# You can implement this if you want code to run for _both_ the initial subscription
|
60
|
+
# and for later updates. Or, implement {#subscribe} and {#update}
|
50
61
|
def resolve(**args)
|
51
62
|
# Dispatch based on `@mode`, which will raise a `NoMethodError` if we ever
|
52
63
|
# have an unexpected `@mode`
|
@@ -54,8 +65,9 @@ module GraphQL
|
|
54
65
|
end
|
55
66
|
|
56
67
|
# Wrap the user-defined `#subscribe` hook
|
68
|
+
# @api private
|
57
69
|
def resolve_subscribe(**args)
|
58
|
-
ret_val = args.
|
70
|
+
ret_val = !args.empty? ? subscribe(**args) : subscribe
|
59
71
|
if ret_val == :no_response
|
60
72
|
context.skip
|
61
73
|
else
|
@@ -71,8 +83,9 @@ module GraphQL
|
|
71
83
|
end
|
72
84
|
|
73
85
|
# Wrap the user-provided `#update` hook
|
86
|
+
# @api private
|
74
87
|
def resolve_update(**args)
|
75
|
-
ret_val = args.
|
88
|
+
ret_val = !args.empty? ? update(**args) : update
|
76
89
|
if ret_val == NO_UPDATE
|
77
90
|
context.namespace(:subscriptions)[:no_update] = true
|
78
91
|
context.skip
|
@@ -106,14 +119,13 @@ module GraphQL
|
|
106
119
|
throw :graphql_subscription_unsubscribed, update_value
|
107
120
|
end
|
108
121
|
|
109
|
-
READING_SCOPE = ::Object.new
|
110
122
|
# Call this method to provide a new subscription_scope; OR
|
111
123
|
# call it without an argument to get the subscription_scope
|
112
124
|
# @param new_scope [Symbol]
|
113
125
|
# @param optional [Boolean] If true, then don't require `scope:` to be provided to updates to this subscription.
|
114
126
|
# @return [Symbol]
|
115
|
-
def self.subscription_scope(new_scope =
|
116
|
-
if new_scope !=
|
127
|
+
def self.subscription_scope(new_scope = NOT_CONFIGURED, optional: false)
|
128
|
+
if new_scope != NOT_CONFIGURED
|
117
129
|
@subscription_scope = new_scope
|
118
130
|
@subscription_scope_optional = optional
|
119
131
|
elsif defined?(@subscription_scope)
|
@@ -150,6 +162,40 @@ module GraphQL
|
|
150
162
|
def self.topic_for(arguments:, field:, scope:)
|
151
163
|
Subscriptions::Serialize.dump_recursive([scope, field.graphql_name, arguments])
|
152
164
|
end
|
165
|
+
|
166
|
+
# Calls through to `schema.subscriptions` to register this subscription with the backend.
|
167
|
+
# This is automatically called by GraphQL-Ruby after a query finishes successfully,
|
168
|
+
# but if you need to commit the subscription during `#subscribe`, you can call it there.
|
169
|
+
# (This method also sets a flag showing that this subscription was already written.)
|
170
|
+
#
|
171
|
+
# If you call this method yourself, you may also need to {#unsubscribe}
|
172
|
+
# or call `subscriptions.delete_subscription` to clean up the database if the query crashes with an error
|
173
|
+
# later in execution.
|
174
|
+
# @return [void]
|
175
|
+
def write_subscription
|
176
|
+
if subscription_written?
|
177
|
+
raise GraphQL::Error, "`write_subscription` was called but `#{self.class}#subscription_written?` is already true. Remove a call to `write subscription`."
|
178
|
+
else
|
179
|
+
@subscription_written = true
|
180
|
+
context.schema.subscriptions.write_subscription(context.query, [event])
|
181
|
+
end
|
182
|
+
nil
|
183
|
+
end
|
184
|
+
|
185
|
+
# @return [Boolean] `true` if {#write_subscription} was called already
|
186
|
+
def subscription_written?
|
187
|
+
@subscription_written
|
188
|
+
end
|
189
|
+
|
190
|
+
# @return [Subscriptions::Event] This object is used as a representation of this subscription for the backend
|
191
|
+
def event
|
192
|
+
@event ||= Subscriptions::Event.new(
|
193
|
+
name: field.name,
|
194
|
+
arguments: @original_arguments,
|
195
|
+
context: context,
|
196
|
+
field: field,
|
197
|
+
)
|
198
|
+
end
|
153
199
|
end
|
154
200
|
end
|
155
201
|
end
|
@@ -5,13 +5,13 @@ module GraphQL
|
|
5
5
|
module TypeExpression
|
6
6
|
# Fetch a type from a type map by its AST specification.
|
7
7
|
# Return `nil` if not found.
|
8
|
-
# @param type_owner [#
|
8
|
+
# @param type_owner [#type] A thing for looking up types by name
|
9
9
|
# @param ast_node [GraphQL::Language::Nodes::AbstractNode]
|
10
10
|
# @return [Class, GraphQL::Schema::NonNull, GraphQL::Schema:List]
|
11
11
|
def self.build_type(type_owner, ast_node)
|
12
12
|
case ast_node
|
13
13
|
when GraphQL::Language::Nodes::TypeName
|
14
|
-
type_owner.
|
14
|
+
type_owner.type(ast_node.name) # rubocop:disable Development/ContextIsPassedCop -- this is a `context` or `warden`, it's already query-aware
|
15
15
|
when GraphQL::Language::Nodes::NonNullType
|
16
16
|
ast_inner_type = ast_node.of_type
|
17
17
|
inner_type = build_type(type_owner, ast_inner_type)
|
data/lib/graphql/schema/union.rb
CHANGED
@@ -11,7 +11,7 @@ module GraphQL
|
|
11
11
|
end
|
12
12
|
|
13
13
|
def possible_types(*types, context: GraphQL::Query::NullContext.instance, **options)
|
14
|
-
if types.
|
14
|
+
if !types.empty?
|
15
15
|
types.each do |t|
|
16
16
|
assert_valid_union_member(t)
|
17
17
|
type_memberships << type_membership_class.new(self, t, **options)
|
@@ -0,0 +1,62 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module GraphQL
|
4
|
+
class Schema
|
5
|
+
class Validator
|
6
|
+
# Use this to validate each member of an array value.
|
7
|
+
#
|
8
|
+
# @example validate format of all strings in an array
|
9
|
+
#
|
10
|
+
# argument :handles, [String],
|
11
|
+
# validates: { all: { format: { with: /\A[a-z0-9_]+\Z/ } } }
|
12
|
+
#
|
13
|
+
# @example multiple validators can be combined
|
14
|
+
#
|
15
|
+
# argument :handles, [String],
|
16
|
+
# validates: { all: { format: { with: /\A[a-z0-9_]+\Z/ }, length: { maximum: 32 } } }
|
17
|
+
#
|
18
|
+
# @example any type can be used
|
19
|
+
#
|
20
|
+
# argument :choices, [Integer],
|
21
|
+
# validates: { all: { inclusion: { in: 1..12 } } }
|
22
|
+
#
|
23
|
+
class AllValidator < Validator
|
24
|
+
def initialize(validated:, allow_blank: false, allow_null: false, **validators)
|
25
|
+
super(validated: validated, allow_blank: allow_blank, allow_null: allow_null)
|
26
|
+
|
27
|
+
@validators = Validator.from_config(validated, validators)
|
28
|
+
end
|
29
|
+
|
30
|
+
def validate(object, context, value)
|
31
|
+
return EMPTY_ARRAY if permitted_empty_value?(value)
|
32
|
+
|
33
|
+
all_errors = EMPTY_ARRAY
|
34
|
+
|
35
|
+
value.each do |subvalue|
|
36
|
+
@validators.each do |validator|
|
37
|
+
errors = validator.validate(object, context, subvalue)
|
38
|
+
if errors &&
|
39
|
+
(errors.is_a?(Array) && errors != EMPTY_ARRAY) ||
|
40
|
+
(errors.is_a?(String))
|
41
|
+
if all_errors.frozen? # It's empty
|
42
|
+
all_errors = []
|
43
|
+
end
|
44
|
+
if errors.is_a?(String)
|
45
|
+
all_errors << errors
|
46
|
+
else
|
47
|
+
all_errors.concat(errors)
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
unless all_errors.frozen?
|
54
|
+
all_errors.uniq!
|
55
|
+
end
|
56
|
+
|
57
|
+
all_errors
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
@@ -35,9 +35,10 @@ module GraphQL
|
|
35
35
|
# end
|
36
36
|
#
|
37
37
|
class RequiredValidator < Validator
|
38
|
-
# @param one_of [
|
38
|
+
# @param one_of [Array<Symbol>] A list of arguments, exactly one of which is required for this field
|
39
|
+
# @param argument [Symbol] An argument that is required for this field
|
39
40
|
# @param message [String]
|
40
|
-
def initialize(one_of: nil, argument: nil, message:
|
41
|
+
def initialize(one_of: nil, argument: nil, message: nil, **default_options)
|
41
42
|
@one_of = if one_of
|
42
43
|
one_of
|
43
44
|
elsif argument
|
@@ -49,7 +50,7 @@ module GraphQL
|
|
49
50
|
super(**default_options)
|
50
51
|
end
|
51
52
|
|
52
|
-
def validate(_object,
|
53
|
+
def validate(_object, context, value)
|
53
54
|
matched_conditions = 0
|
54
55
|
|
55
56
|
if !value.nil?
|
@@ -73,9 +74,32 @@ module GraphQL
|
|
73
74
|
if matched_conditions == 1
|
74
75
|
nil # OK
|
75
76
|
else
|
76
|
-
@message
|
77
|
+
@message || build_message(context)
|
77
78
|
end
|
78
79
|
end
|
80
|
+
|
81
|
+
def build_message(context)
|
82
|
+
argument_definitions = @validated.arguments(context).values
|
83
|
+
required_names = @one_of.map do |arg_keyword|
|
84
|
+
if arg_keyword.is_a?(Array)
|
85
|
+
names = arg_keyword.map { |arg| arg_keyword_to_grapqhl_name(argument_definitions, arg) }
|
86
|
+
"(" + names.join(" and ") + ")"
|
87
|
+
else
|
88
|
+
arg_keyword_to_grapqhl_name(argument_definitions, arg_keyword)
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
if required_names.size == 1
|
93
|
+
"%{validated} must include the following argument: #{required_names.first}."
|
94
|
+
else
|
95
|
+
"%{validated} must include exactly one of the following arguments: #{required_names.join(", ")}."
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
def arg_keyword_to_grapqhl_name(argument_definitions, arg_keyword)
|
100
|
+
argument_definition = argument_definitions.find { |defn| defn.keyword == arg_keyword }
|
101
|
+
argument_definition.graphql_name
|
102
|
+
end
|
79
103
|
end
|
80
104
|
end
|
81
105
|
end
|
@@ -143,7 +143,7 @@ module GraphQL
|
|
143
143
|
end
|
144
144
|
end
|
145
145
|
|
146
|
-
if all_errors.
|
146
|
+
if !all_errors.empty?
|
147
147
|
raise ValidationFailedError.new(errors: all_errors)
|
148
148
|
end
|
149
149
|
nil
|
@@ -169,3 +169,5 @@ require "graphql/schema/validator/allow_null_validator"
|
|
169
169
|
GraphQL::Schema::Validator.install(:allow_null, GraphQL::Schema::Validator::AllowNullValidator)
|
170
170
|
require "graphql/schema/validator/allow_blank_validator"
|
171
171
|
GraphQL::Schema::Validator.install(:allow_blank, GraphQL::Schema::Validator::AllowBlankValidator)
|
172
|
+
require "graphql/schema/validator/all_validator"
|
173
|
+
GraphQL::Schema::Validator.install(:all, GraphQL::Schema::Validator::AllValidator)
|
@@ -0,0 +1,188 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
module GraphQL
|
3
|
+
class Schema
|
4
|
+
class Visibility
|
5
|
+
# You can use this to see how {GraphQL::Schema::Warden} and {GraphQL::Schema::Visibility::Profile}
|
6
|
+
# handle `.visible?` differently in your schema.
|
7
|
+
#
|
8
|
+
# It runs the same method on both implementations and raises an error when the results diverge.
|
9
|
+
#
|
10
|
+
# To fix the error, modify your schema so that both implementations return the same thing.
|
11
|
+
# Or, open an issue on GitHub to discuss the difference.
|
12
|
+
#
|
13
|
+
# This plugin adds overhead to runtime and may cause unexpected crashes -- **don't** use it in production!
|
14
|
+
#
|
15
|
+
# This plugin adds two keys to `context` when running:
|
16
|
+
#
|
17
|
+
# - `visibility_migration_running: true`
|
18
|
+
# - For the {Schema::Warden} which it instantiates, it adds `visibility_migration_warden_running: true`.
|
19
|
+
#
|
20
|
+
# Use those keys to modify your `visible?` behavior as needed.
|
21
|
+
#
|
22
|
+
# Also, in a pinch, you can set `skip_visibility_migration_error: true` in context to turn off this behavior per-query.
|
23
|
+
# (In that case, it uses {Profile} directly.)
|
24
|
+
#
|
25
|
+
# @example Adding this plugin
|
26
|
+
#
|
27
|
+
# use GraphQL::Schema::Visibility, migration_errors: true
|
28
|
+
#
|
29
|
+
class Migration < GraphQL::Schema::Visibility::Profile
|
30
|
+
class RuntimeTypesMismatchError < GraphQL::Error
|
31
|
+
def initialize(method_called, warden_result, profile_result, method_args)
|
32
|
+
super(<<~ERR)
|
33
|
+
Mismatch in types for `##{method_called}(#{method_args.map(&:inspect).join(", ")})`:
|
34
|
+
|
35
|
+
#{compare_results(warden_result, profile_result)}
|
36
|
+
|
37
|
+
Update your `.visible?` implementation to make these implementations return the same value.
|
38
|
+
|
39
|
+
See: https://graphql-ruby.org/authorization/visibility_migration.html
|
40
|
+
ERR
|
41
|
+
end
|
42
|
+
|
43
|
+
private
|
44
|
+
def compare_results(warden_result, profile_result)
|
45
|
+
if warden_result.is_a?(Array) && profile_result.is_a?(Array)
|
46
|
+
all_results = warden_result | profile_result
|
47
|
+
all_results.sort_by!(&:graphql_name)
|
48
|
+
|
49
|
+
entries_text = all_results.map { |entry| "#{entry.graphql_name} (#{entry})"}
|
50
|
+
width = entries_text.map(&:size).max
|
51
|
+
yes = " ✔ "
|
52
|
+
no = " "
|
53
|
+
res = "".dup
|
54
|
+
res << "#{"Result".center(width)} Warden Profile \n"
|
55
|
+
all_results.each_with_index do |entry, idx|
|
56
|
+
res << "#{entries_text[idx].ljust(width)}#{warden_result.include?(entry) ? yes : no}#{profile_result.include?(entry) ? yes : no}\n"
|
57
|
+
end
|
58
|
+
res << "\n"
|
59
|
+
else
|
60
|
+
"- Warden returned: #{humanize(warden_result)}\n\n- Visibility::Profile returned: #{humanize(profile_result)}"
|
61
|
+
end
|
62
|
+
end
|
63
|
+
def humanize(val)
|
64
|
+
case val
|
65
|
+
when Array
|
66
|
+
"#{val.size}: #{val.map { |v| humanize(v) }.sort.inspect}"
|
67
|
+
when Module
|
68
|
+
if val.respond_to?(:graphql_name)
|
69
|
+
"#{val.graphql_name} (#{val.inspect})"
|
70
|
+
else
|
71
|
+
val.inspect
|
72
|
+
end
|
73
|
+
else
|
74
|
+
val.inspect
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
def initialize(context:, schema:, name: nil)
|
80
|
+
@name = name
|
81
|
+
@skip_error = context[:skip_visibility_migration_error] || context.is_a?(Query::NullContext) || context.is_a?(Hash)
|
82
|
+
@profile_types = GraphQL::Schema::Visibility::Profile.new(context: context, schema: schema)
|
83
|
+
if !@skip_error
|
84
|
+
context[:visibility_migration_running] = true
|
85
|
+
warden_ctx_vals = context.to_h.dup
|
86
|
+
warden_ctx_vals[:visibility_migration_warden_running] = true
|
87
|
+
if schema.const_defined?(:WardenCompatSchema, false) # don't use a defn from a superclass
|
88
|
+
warden_schema = schema.const_get(:WardenCompatSchema, false)
|
89
|
+
else
|
90
|
+
warden_schema = Class.new(schema)
|
91
|
+
warden_schema.use_visibility_profile = false
|
92
|
+
# TODO public API
|
93
|
+
warden_schema.send(:add_type_and_traverse, [warden_schema.query, warden_schema.mutation, warden_schema.subscription].compact, root: true)
|
94
|
+
warden_schema.send(:add_type_and_traverse, warden_schema.directives.values + warden_schema.orphan_types, root: false)
|
95
|
+
schema.const_set(:WardenCompatSchema, warden_schema)
|
96
|
+
end
|
97
|
+
warden_ctx = GraphQL::Query::Context.new(query: context.query, values: warden_ctx_vals)
|
98
|
+
warden_ctx.warden = GraphQL::Schema::Warden.new(schema: warden_schema, context: warden_ctx)
|
99
|
+
warden_ctx.warden.skip_warning = true
|
100
|
+
warden_ctx.types = @warden_types = warden_ctx.warden.visibility_profile
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
def loaded_types
|
105
|
+
@profile_types.loaded_types
|
106
|
+
end
|
107
|
+
|
108
|
+
PUBLIC_PROFILE_METHODS = [
|
109
|
+
:enum_values,
|
110
|
+
:interfaces,
|
111
|
+
:all_types,
|
112
|
+
:all_types_h,
|
113
|
+
:fields,
|
114
|
+
:loadable?,
|
115
|
+
:loadable_possible_types,
|
116
|
+
:type,
|
117
|
+
:arguments,
|
118
|
+
:argument,
|
119
|
+
:directive_exists?,
|
120
|
+
:directives,
|
121
|
+
:field,
|
122
|
+
:query_root,
|
123
|
+
:mutation_root,
|
124
|
+
:possible_types,
|
125
|
+
:subscription_root,
|
126
|
+
:reachable_type?,
|
127
|
+
:visible_enum_value?,
|
128
|
+
]
|
129
|
+
|
130
|
+
PUBLIC_PROFILE_METHODS.each do |profile_method|
|
131
|
+
define_method(profile_method) do |*args|
|
132
|
+
call_method_and_compare(profile_method, args)
|
133
|
+
end
|
134
|
+
end
|
135
|
+
|
136
|
+
def call_method_and_compare(method, args)
|
137
|
+
res_1 = @profile_types.public_send(method, *args)
|
138
|
+
if @skip_error
|
139
|
+
return res_1
|
140
|
+
end
|
141
|
+
|
142
|
+
res_2 = @warden_types.public_send(method, *args)
|
143
|
+
normalized_res_1 = res_1.is_a?(Array) ? Set.new(res_1) : res_1
|
144
|
+
normalized_res_2 = res_2.is_a?(Array) ? Set.new(res_2) : res_2
|
145
|
+
if !equivalent_schema_members?(normalized_res_1, normalized_res_2)
|
146
|
+
# Raise the errors with the orignally returned values:
|
147
|
+
err = RuntimeTypesMismatchError.new(method, res_2, res_1, args)
|
148
|
+
raise err
|
149
|
+
else
|
150
|
+
res_1
|
151
|
+
end
|
152
|
+
end
|
153
|
+
|
154
|
+
def equivalent_schema_members?(member1, member2)
|
155
|
+
if member1.class != member2.class
|
156
|
+
return false
|
157
|
+
end
|
158
|
+
|
159
|
+
case member1
|
160
|
+
when Set
|
161
|
+
member1_array = member1.to_a.sort_by(&:graphql_name)
|
162
|
+
member2_array = member2.to_a.sort_by(&:graphql_name)
|
163
|
+
member1_array.each_with_index do |inner_member1, idx|
|
164
|
+
inner_member2 = member2_array[idx]
|
165
|
+
equivalent_schema_members?(inner_member1, inner_member2)
|
166
|
+
end
|
167
|
+
when GraphQL::Schema::Field
|
168
|
+
member1.ensure_loaded
|
169
|
+
member2.ensure_loaded
|
170
|
+
if member1.introspection? && member2.introspection?
|
171
|
+
member1.inspect == member2.inspect
|
172
|
+
else
|
173
|
+
member1 == member2
|
174
|
+
end
|
175
|
+
when Module
|
176
|
+
if member1.introspection? && member2.introspection?
|
177
|
+
member1.graphql_name == member2.graphql_name
|
178
|
+
else
|
179
|
+
member1 == member2
|
180
|
+
end
|
181
|
+
else
|
182
|
+
member1 == member2
|
183
|
+
end
|
184
|
+
end
|
185
|
+
end
|
186
|
+
end
|
187
|
+
end
|
188
|
+
end
|