graphql 2.3.14 → 2.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/lib/generators/graphql/orm_mutations_base.rb +1 -1
- data/lib/generators/graphql/templates/base_resolver.erb +2 -0
- data/lib/generators/graphql/type_generator.rb +1 -1
- data/lib/graphql/analysis.rb +1 -1
- data/lib/graphql/dataloader/async_dataloader.rb +3 -2
- data/lib/graphql/dataloader/source.rb +1 -1
- data/lib/graphql/dataloader.rb +31 -10
- data/lib/graphql/execution/interpreter/resolve.rb +10 -6
- data/lib/graphql/invalid_null_error.rb +1 -1
- data/lib/graphql/language/comment.rb +18 -0
- data/lib/graphql/language/document_from_schema_definition.rb +38 -4
- data/lib/graphql/language/lexer.rb +15 -12
- data/lib/graphql/language/nodes.rb +22 -14
- data/lib/graphql/language/parser.rb +5 -0
- data/lib/graphql/language/printer.rb +23 -7
- data/lib/graphql/language.rb +6 -5
- data/lib/graphql/query/null_context.rb +1 -1
- data/lib/graphql/query.rb +49 -16
- data/lib/graphql/rubocop/graphql/field_type_in_block.rb +23 -8
- data/lib/graphql/schema/always_visible.rb +6 -3
- data/lib/graphql/schema/argument.rb +14 -1
- data/lib/graphql/schema/build_from_definition.rb +1 -0
- data/lib/graphql/schema/enum.rb +3 -0
- data/lib/graphql/schema/enum_value.rb +9 -1
- data/lib/graphql/schema/field.rb +35 -14
- data/lib/graphql/schema/input_object.rb +20 -7
- data/lib/graphql/schema/interface.rb +1 -0
- data/lib/graphql/schema/member/base_dsl_methods.rb +15 -0
- data/lib/graphql/schema/member/has_arguments.rb +2 -2
- data/lib/graphql/schema/member/has_fields.rb +2 -2
- data/lib/graphql/schema/printer.rb +1 -0
- data/lib/graphql/schema/resolver.rb +3 -4
- data/lib/graphql/schema/validator/required_validator.rb +28 -4
- data/lib/graphql/schema/visibility/migration.rb +186 -0
- data/lib/graphql/schema/visibility/profile.rb +523 -0
- data/lib/graphql/schema/visibility.rb +75 -0
- data/lib/graphql/schema/warden.rb +77 -15
- data/lib/graphql/schema.rb +203 -61
- data/lib/graphql/static_validation/rules/arguments_are_defined.rb +2 -1
- data/lib/graphql/static_validation/rules/directives_are_defined.rb +2 -1
- data/lib/graphql/static_validation/rules/directives_are_in_valid_locations.rb +2 -0
- data/lib/graphql/static_validation/rules/fields_are_defined_on_type.rb +2 -1
- data/lib/graphql/static_validation/rules/fields_will_merge.rb +1 -0
- data/lib/graphql/static_validation/rules/fragment_types_exist.rb +11 -1
- data/lib/graphql/static_validation/rules/variables_are_input_types.rb +10 -1
- data/lib/graphql/static_validation/validation_context.rb +15 -0
- data/lib/graphql/subscriptions/action_cable_subscriptions.rb +2 -1
- data/lib/graphql/subscriptions.rb +3 -1
- data/lib/graphql/testing/helpers.rb +2 -1
- data/lib/graphql/tracing/notifications_trace.rb +2 -2
- data/lib/graphql/version.rb +1 -1
- metadata +11 -9
- data/lib/graphql/schema/subset.rb +0 -509
- data/lib/graphql/schema/types_migration.rb +0 -187
@@ -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
|
@@ -0,0 +1,186 @@
|
|
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
|
+
:type,
|
116
|
+
:arguments,
|
117
|
+
:argument,
|
118
|
+
:directive_exists?,
|
119
|
+
:directives,
|
120
|
+
:field,
|
121
|
+
:query_root,
|
122
|
+
:mutation_root,
|
123
|
+
:possible_types,
|
124
|
+
:subscription_root,
|
125
|
+
:reachable_type?
|
126
|
+
]
|
127
|
+
|
128
|
+
PUBLIC_PROFILE_METHODS.each do |profile_method|
|
129
|
+
define_method(profile_method) do |*args|
|
130
|
+
call_method_and_compare(profile_method, args)
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
134
|
+
def call_method_and_compare(method, args)
|
135
|
+
res_1 = @profile_types.public_send(method, *args)
|
136
|
+
if @skip_error
|
137
|
+
return res_1
|
138
|
+
end
|
139
|
+
|
140
|
+
res_2 = @warden_types.public_send(method, *args)
|
141
|
+
normalized_res_1 = res_1.is_a?(Array) ? Set.new(res_1) : res_1
|
142
|
+
normalized_res_2 = res_2.is_a?(Array) ? Set.new(res_2) : res_2
|
143
|
+
if !equivalent_schema_members?(normalized_res_1, normalized_res_2)
|
144
|
+
# Raise the errors with the orignally returned values:
|
145
|
+
err = RuntimeTypesMismatchError.new(method, res_2, res_1, args)
|
146
|
+
raise err
|
147
|
+
else
|
148
|
+
res_1
|
149
|
+
end
|
150
|
+
end
|
151
|
+
|
152
|
+
def equivalent_schema_members?(member1, member2)
|
153
|
+
if member1.class != member2.class
|
154
|
+
return false
|
155
|
+
end
|
156
|
+
|
157
|
+
case member1
|
158
|
+
when Set
|
159
|
+
member1_array = member1.to_a.sort_by(&:graphql_name)
|
160
|
+
member2_array = member2.to_a.sort_by(&:graphql_name)
|
161
|
+
member1_array.each_with_index do |inner_member1, idx|
|
162
|
+
inner_member2 = member2_array[idx]
|
163
|
+
equivalent_schema_members?(inner_member1, inner_member2)
|
164
|
+
end
|
165
|
+
when GraphQL::Schema::Field
|
166
|
+
member1.ensure_loaded
|
167
|
+
member2.ensure_loaded
|
168
|
+
if member1.introspection? && member2.introspection?
|
169
|
+
member1.inspect == member2.inspect
|
170
|
+
else
|
171
|
+
member1 == member2
|
172
|
+
end
|
173
|
+
when Module
|
174
|
+
if member1.introspection? && member2.introspection?
|
175
|
+
member1.graphql_name == member2.graphql_name
|
176
|
+
else
|
177
|
+
member1 == member2
|
178
|
+
end
|
179
|
+
else
|
180
|
+
member1 == member2
|
181
|
+
end
|
182
|
+
end
|
183
|
+
end
|
184
|
+
end
|
185
|
+
end
|
186
|
+
end
|