graphql 1.12.16 → 1.13.2
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/core.rb +3 -1
- data/lib/generators/graphql/install_generator.rb +9 -2
- data/lib/generators/graphql/mutation_generator.rb +1 -1
- data/lib/generators/graphql/object_generator.rb +2 -1
- data/lib/generators/graphql/relay.rb +19 -11
- data/lib/generators/graphql/templates/schema.erb +14 -2
- data/lib/generators/graphql/type_generator.rb +0 -1
- data/lib/graphql/analysis/ast/field_usage.rb +3 -3
- data/lib/graphql/analysis/ast/query_complexity.rb +10 -14
- data/lib/graphql/analysis/ast/visitor.rb +4 -4
- data/lib/graphql/backtrace/table.rb +1 -1
- data/lib/graphql/base_type.rb +4 -2
- data/lib/graphql/boolean_type.rb +1 -1
- data/lib/graphql/dataloader/source.rb +50 -2
- data/lib/graphql/dataloader.rb +93 -37
- data/lib/graphql/define/instance_definable.rb +1 -1
- data/lib/graphql/deprecated_dsl.rb +11 -3
- data/lib/graphql/deprecation.rb +1 -5
- data/lib/graphql/directive/deprecated_directive.rb +1 -1
- data/lib/graphql/directive/include_directive.rb +1 -1
- data/lib/graphql/directive/skip_directive.rb +1 -1
- data/lib/graphql/directive.rb +0 -4
- data/lib/graphql/enum_type.rb +5 -1
- data/lib/graphql/execution/errors.rb +1 -0
- data/lib/graphql/execution/interpreter/arguments.rb +1 -1
- data/lib/graphql/execution/interpreter/arguments_cache.rb +2 -2
- data/lib/graphql/execution/interpreter/runtime.rb +39 -23
- data/lib/graphql/execution/lookahead.rb +2 -2
- data/lib/graphql/execution/multiplex.rb +4 -1
- data/lib/graphql/float_type.rb +1 -1
- data/lib/graphql/id_type.rb +1 -1
- data/lib/graphql/int_type.rb +1 -1
- data/lib/graphql/integer_encoding_error.rb +18 -2
- data/lib/graphql/introspection/directive_type.rb +1 -1
- data/lib/graphql/introspection/entry_points.rb +2 -2
- data/lib/graphql/introspection/enum_value_type.rb +2 -2
- data/lib/graphql/introspection/field_type.rb +2 -2
- data/lib/graphql/introspection/input_value_type.rb +10 -4
- data/lib/graphql/introspection/schema_type.rb +2 -2
- data/lib/graphql/introspection/type_type.rb +10 -10
- data/lib/graphql/language/block_string.rb +2 -6
- data/lib/graphql/language/document_from_schema_definition.rb +4 -2
- data/lib/graphql/language/lexer.rb +0 -3
- data/lib/graphql/language/lexer.rl +0 -4
- data/lib/graphql/language/nodes.rb +12 -2
- data/lib/graphql/language/parser.rb +442 -434
- data/lib/graphql/language/parser.y +5 -4
- data/lib/graphql/language/printer.rb +6 -1
- data/lib/graphql/language/sanitized_printer.rb +5 -5
- data/lib/graphql/language/token.rb +0 -4
- data/lib/graphql/name_validator.rb +0 -4
- data/lib/graphql/pagination/connections.rb +35 -16
- data/lib/graphql/query/arguments.rb +1 -1
- data/lib/graphql/query/arguments_cache.rb +1 -1
- data/lib/graphql/query/context.rb +15 -2
- data/lib/graphql/query/literal_input.rb +1 -1
- data/lib/graphql/query/null_context.rb +12 -7
- data/lib/graphql/query/serial_execution/field_resolution.rb +1 -1
- data/lib/graphql/query/validation_pipeline.rb +1 -1
- data/lib/graphql/query/variables.rb +5 -1
- data/lib/graphql/query.rb +4 -0
- data/lib/graphql/relay/edges_instrumentation.rb +0 -1
- data/lib/graphql/relay/global_id_resolve.rb +1 -1
- data/lib/graphql/relay/page_info.rb +1 -1
- data/lib/graphql/rubocop/graphql/base_cop.rb +36 -0
- data/lib/graphql/rubocop/graphql/default_null_true.rb +43 -0
- data/lib/graphql/rubocop/graphql/default_required_true.rb +43 -0
- data/lib/graphql/rubocop.rb +4 -0
- data/lib/graphql/schema/addition.rb +37 -28
- data/lib/graphql/schema/argument.rb +79 -34
- data/lib/graphql/schema/build_from_definition.rb +5 -5
- data/lib/graphql/schema/directive/feature.rb +1 -1
- data/lib/graphql/schema/directive/flagged.rb +2 -2
- data/lib/graphql/schema/directive/include.rb +1 -1
- data/lib/graphql/schema/directive/skip.rb +1 -1
- data/lib/graphql/schema/directive/transform.rb +1 -1
- data/lib/graphql/schema/directive.rb +7 -3
- data/lib/graphql/schema/enum.rb +60 -10
- data/lib/graphql/schema/enum_value.rb +6 -0
- data/lib/graphql/schema/field/connection_extension.rb +1 -1
- data/lib/graphql/schema/field.rb +140 -42
- data/lib/graphql/schema/field_extension.rb +52 -2
- data/lib/graphql/schema/find_inherited_value.rb +1 -0
- data/lib/graphql/schema/finder.rb +5 -5
- data/lib/graphql/schema/input_object.rb +13 -14
- data/lib/graphql/schema/interface.rb +11 -20
- data/lib/graphql/schema/introspection_system.rb +1 -1
- data/lib/graphql/schema/list.rb +3 -1
- data/lib/graphql/schema/member/accepts_definition.rb +15 -3
- data/lib/graphql/schema/member/build_type.rb +0 -4
- data/lib/graphql/schema/member/cached_graphql_definition.rb +29 -2
- data/lib/graphql/schema/member/has_arguments.rb +145 -57
- data/lib/graphql/schema/member/has_deprecation_reason.rb +1 -1
- data/lib/graphql/schema/member/has_fields.rb +76 -18
- data/lib/graphql/schema/member/has_interfaces.rb +90 -0
- data/lib/graphql/schema/member.rb +1 -0
- data/lib/graphql/schema/non_null.rb +3 -1
- data/lib/graphql/schema/object.rb +10 -75
- data/lib/graphql/schema/printer.rb +1 -1
- data/lib/graphql/schema/relay_classic_mutation.rb +37 -3
- data/lib/graphql/schema/resolver/has_payload_type.rb +27 -2
- data/lib/graphql/schema/resolver.rb +49 -64
- data/lib/graphql/schema/scalar.rb +2 -0
- data/lib/graphql/schema/subscription.rb +17 -9
- data/lib/graphql/schema/traversal.rb +1 -1
- data/lib/graphql/schema/type_expression.rb +1 -1
- data/lib/graphql/schema/type_membership.rb +18 -4
- data/lib/graphql/schema/union.rb +8 -1
- data/lib/graphql/schema/validator/allow_blank_validator.rb +29 -0
- data/lib/graphql/schema/validator/allow_null_validator.rb +26 -0
- data/lib/graphql/schema/validator/exclusion_validator.rb +3 -1
- data/lib/graphql/schema/validator/format_validator.rb +4 -5
- data/lib/graphql/schema/validator/inclusion_validator.rb +3 -1
- data/lib/graphql/schema/validator/length_validator.rb +5 -3
- data/lib/graphql/schema/validator/numericality_validator.rb +13 -2
- data/lib/graphql/schema/validator.rb +33 -25
- data/lib/graphql/schema/warden.rb +116 -52
- data/lib/graphql/schema.rb +124 -27
- data/lib/graphql/static_validation/base_visitor.rb +8 -5
- data/lib/graphql/static_validation/definition_dependencies.rb +0 -1
- data/lib/graphql/static_validation/error.rb +3 -1
- data/lib/graphql/static_validation/literal_validator.rb +1 -1
- data/lib/graphql/static_validation/rules/argument_literals_are_compatible.rb +1 -1
- data/lib/graphql/static_validation/rules/fields_will_merge.rb +52 -26
- data/lib/graphql/static_validation/rules/fields_will_merge_error.rb +25 -4
- data/lib/graphql/static_validation/rules/fragments_are_finite.rb +2 -2
- data/lib/graphql/static_validation/rules/required_arguments_are_present.rb +3 -1
- data/lib/graphql/static_validation/rules/required_input_object_attributes_are_present.rb +4 -4
- data/lib/graphql/static_validation/rules/variable_usages_are_allowed.rb +7 -7
- data/lib/graphql/static_validation/validation_context.rb +8 -2
- data/lib/graphql/static_validation/validator.rb +15 -12
- data/lib/graphql/string_encoding_error.rb +13 -3
- data/lib/graphql/string_type.rb +1 -1
- data/lib/graphql/subscriptions/action_cable_subscriptions.rb +15 -5
- data/lib/graphql/subscriptions/event.rb +66 -13
- data/lib/graphql/subscriptions/serialize.rb +1 -1
- data/lib/graphql/subscriptions.rb +17 -19
- data/lib/graphql/tracing/appsignal_tracing.rb +15 -0
- data/lib/graphql/types/int.rb +1 -1
- data/lib/graphql/types/relay/connection_behaviors.rb +26 -9
- data/lib/graphql/types/relay/default_relay.rb +5 -1
- data/lib/graphql/types/relay/edge_behaviors.rb +13 -2
- data/lib/graphql/types/relay/has_node_field.rb +1 -1
- data/lib/graphql/types/relay/has_nodes_field.rb +1 -1
- data/lib/graphql/types/string.rb +1 -1
- data/lib/graphql/unauthorized_error.rb +1 -1
- data/lib/graphql/version.rb +1 -1
- data/lib/graphql.rb +10 -32
- data/readme.md +1 -1
- metadata +13 -6
@@ -7,6 +7,7 @@ module GraphQL
|
|
7
7
|
class Object < GraphQL::Schema::Member
|
8
8
|
extend GraphQL::Schema::Member::AcceptsDefinition
|
9
9
|
extend GraphQL::Schema::Member::HasFields
|
10
|
+
extend GraphQL::Schema::Member::HasInterfaces
|
10
11
|
|
11
12
|
# @return [Object] the application object this type is wrapping
|
12
13
|
attr_reader :object
|
@@ -103,84 +104,16 @@ module GraphQL
|
|
103
104
|
super
|
104
105
|
end
|
105
106
|
|
106
|
-
def implements(*new_interfaces, **options)
|
107
|
-
new_memberships = []
|
108
|
-
new_interfaces.each do |int|
|
109
|
-
if int.is_a?(Module)
|
110
|
-
unless int.include?(GraphQL::Schema::Interface)
|
111
|
-
raise "#{int} cannot be implemented since it's not a GraphQL Interface. Use `include` for plain Ruby modules."
|
112
|
-
end
|
113
|
-
|
114
|
-
new_memberships << int.type_membership_class.new(int, self, **options)
|
115
|
-
|
116
|
-
# Include the methods here,
|
117
|
-
# `.fields` will use the inheritance chain
|
118
|
-
# to find inherited fields
|
119
|
-
include(int)
|
120
|
-
elsif int.is_a?(GraphQL::InterfaceType)
|
121
|
-
new_memberships << int.type_membership_class.new(int, self, **options)
|
122
|
-
elsif int.is_a?(String) || int.is_a?(GraphQL::Schema::LateBoundType)
|
123
|
-
if options.any?
|
124
|
-
raise ArgumentError, "`implements(...)` doesn't support options with late-loaded types yet. Remove #{options} and open an issue to request this feature."
|
125
|
-
end
|
126
|
-
new_memberships << int
|
127
|
-
else
|
128
|
-
raise ArgumentError, "Unexpected interface definition (expected module): #{int} (#{int.class})"
|
129
|
-
end
|
130
|
-
end
|
131
|
-
|
132
|
-
# Remove any interfaces which are being replaced (late-bound types are updated in place this way)
|
133
|
-
own_interface_type_memberships.reject! { |old_i_m|
|
134
|
-
old_int_type = old_i_m.respond_to?(:abstract_type) ? old_i_m.abstract_type : old_i_m
|
135
|
-
old_name = Schema::Member::BuildType.to_type_name(old_int_type)
|
136
|
-
|
137
|
-
new_memberships.any? { |new_i_m|
|
138
|
-
new_int_type = new_i_m.respond_to?(:abstract_type) ? new_i_m.abstract_type : new_i_m
|
139
|
-
new_name = Schema::Member::BuildType.to_type_name(new_int_type)
|
140
|
-
|
141
|
-
new_name == old_name
|
142
|
-
}
|
143
|
-
}
|
144
|
-
own_interface_type_memberships.concat(new_memberships)
|
145
|
-
end
|
146
|
-
|
147
|
-
def own_interface_type_memberships
|
148
|
-
@own_interface_type_memberships ||= []
|
149
|
-
end
|
150
|
-
|
151
|
-
def interface_type_memberships
|
152
|
-
own_interface_type_memberships + (superclass.respond_to?(:interface_type_memberships) ? superclass.interface_type_memberships : [])
|
153
|
-
end
|
154
|
-
|
155
|
-
# param context [Query::Context] If omitted, skip filtering.
|
156
|
-
def interfaces(context = GraphQL::Query::NullContext)
|
157
|
-
visible_interfaces = []
|
158
|
-
unfiltered = context == GraphQL::Query::NullContext
|
159
|
-
own_interface_type_memberships.each do |type_membership|
|
160
|
-
# During initialization, `type_memberships` can hold late-bound types
|
161
|
-
case type_membership
|
162
|
-
when String, Schema::LateBoundType
|
163
|
-
visible_interfaces << type_membership
|
164
|
-
when Schema::TypeMembership
|
165
|
-
if unfiltered || type_membership.visible?(context)
|
166
|
-
visible_interfaces << type_membership.abstract_type
|
167
|
-
end
|
168
|
-
else
|
169
|
-
raise "Invariant: Unexpected type_membership #{type_membership.class}: #{type_membership.inspect}"
|
170
|
-
end
|
171
|
-
end
|
172
|
-
visible_interfaces + (superclass <= GraphQL::Schema::Object ? superclass.interfaces(context) : [])
|
173
|
-
end
|
174
|
-
|
175
107
|
# @return [Hash<String => GraphQL::Schema::Field>] All of this object's fields, indexed by name
|
176
108
|
# @see get_field A faster way to find one field by name ({#fields} merges hashes of inherited fields; {#get_field} just looks up one field.)
|
177
|
-
def fields
|
109
|
+
def fields(context = GraphQL::Query::NullContext)
|
178
110
|
all_fields = super
|
111
|
+
# This adds fields from legacy-style interfaces only.
|
112
|
+
# Multi-fields are not supported here.
|
179
113
|
interfaces.each do |int|
|
180
|
-
# Include legacy-style interfaces, too
|
181
114
|
if int.is_a?(GraphQL::InterfaceType)
|
182
115
|
int_f = {}
|
183
|
-
int.fields.each do |name, legacy_field|
|
116
|
+
int.fields.each do |name, legacy_field| # rubocop:disable Development/ContextIsPassedCop -- legacy-related
|
184
117
|
int_f[name] = field_class.from_options(name, field: legacy_field)
|
185
118
|
end
|
186
119
|
all_fields = int_f.merge(all_fields)
|
@@ -189,6 +122,8 @@ module GraphQL
|
|
189
122
|
all_fields
|
190
123
|
end
|
191
124
|
|
125
|
+
prepend Schema::Member::CachedGraphQLDefinition::DeprecatedToGraphQL
|
126
|
+
|
192
127
|
# @return [GraphQL::ObjectType]
|
193
128
|
def to_graphql
|
194
129
|
obj_type = GraphQL::ObjectType.new
|
@@ -198,9 +133,9 @@ module GraphQL
|
|
198
133
|
obj_type.introspection = introspection
|
199
134
|
obj_type.mutation = mutation
|
200
135
|
obj_type.ast_node = ast_node
|
201
|
-
fields.each do |field_name, field_inst|
|
202
|
-
field_defn = field_inst.to_graphql
|
203
|
-
obj_type.fields[field_defn.name] = field_defn
|
136
|
+
fields.each do |field_name, field_inst| # rubocop:disable Development/ContextIsPassedCop -- legacy-related
|
137
|
+
field_defn = field_inst.to_graphql(silence_deprecation_warning: true)
|
138
|
+
obj_type.fields[field_defn.name] = field_defn # rubocop:disable Development/ContextIsPassedCop -- legacy-related
|
204
139
|
end
|
205
140
|
|
206
141
|
obj_type.metadata[:type_class] = self
|
@@ -56,7 +56,7 @@ module GraphQL
|
|
56
56
|
def self.print_introspection_schema
|
57
57
|
query_root = Class.new(GraphQL::Schema::Object) do
|
58
58
|
graphql_name "Root"
|
59
|
-
field :throwaway_field, String
|
59
|
+
field :throwaway_field, String
|
60
60
|
end
|
61
61
|
schema = Class.new(GraphQL::Schema) { query(query_root) }
|
62
62
|
|
@@ -22,7 +22,7 @@ module GraphQL
|
|
22
22
|
#
|
23
23
|
class RelayClassicMutation < GraphQL::Schema::Mutation
|
24
24
|
# The payload should always include this field
|
25
|
-
field(:client_mutation_id, String, "A unique identifier for the client performing the mutation."
|
25
|
+
field(:client_mutation_id, String, "A unique identifier for the client performing the mutation.")
|
26
26
|
# Relay classic default:
|
27
27
|
null(true)
|
28
28
|
|
@@ -81,6 +81,31 @@ module GraphQL
|
|
81
81
|
end
|
82
82
|
|
83
83
|
class << self
|
84
|
+
|
85
|
+
# Also apply this argument to the input type:
|
86
|
+
def argument(*args, **kwargs, &block)
|
87
|
+
it = input_type # make sure any inherited arguments are already added to it
|
88
|
+
arg = super
|
89
|
+
|
90
|
+
# This definition might be overriding something inherited;
|
91
|
+
# if it is, remove the inherited definition so it's not confused at runtime as having multiple definitions
|
92
|
+
prev_args = it.own_arguments[arg.graphql_name]
|
93
|
+
case prev_args
|
94
|
+
when GraphQL::Schema::Argument
|
95
|
+
if prev_args.owner != self
|
96
|
+
it.own_arguments.delete(arg.graphql_name)
|
97
|
+
end
|
98
|
+
when Array
|
99
|
+
prev_args.reject! { |a| a.owner != self }
|
100
|
+
if prev_args.empty?
|
101
|
+
it.own_arguments.delete(arg.graphql_name)
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
it.add_argument(arg)
|
106
|
+
arg
|
107
|
+
end
|
108
|
+
|
84
109
|
# The base class for generated input object types
|
85
110
|
# @param new_class [Class] The base class to use for generating input object definitions
|
86
111
|
# @return [Class] The base class for this mutation's generated input object (default is {GraphQL::Schema::InputObject})
|
@@ -115,20 +140,29 @@ module GraphQL
|
|
115
140
|
# To customize how input objects are generated, override this method
|
116
141
|
# @return [Class] a subclass of {.input_object_class}
|
117
142
|
def generate_input_type
|
118
|
-
mutation_args =
|
143
|
+
mutation_args = all_argument_definitions
|
119
144
|
mutation_name = graphql_name
|
120
145
|
mutation_class = self
|
121
146
|
Class.new(input_object_class) do
|
122
147
|
graphql_name("#{mutation_name}Input")
|
123
148
|
description("Autogenerated input type of #{mutation_name}")
|
124
149
|
mutation(mutation_class)
|
125
|
-
|
150
|
+
# these might be inherited:
|
151
|
+
mutation_args.each do |arg|
|
126
152
|
add_argument(arg)
|
127
153
|
end
|
128
154
|
argument :client_mutation_id, String, "A unique identifier for the client performing the mutation.", required: false
|
129
155
|
end
|
130
156
|
end
|
131
157
|
end
|
158
|
+
|
159
|
+
private
|
160
|
+
|
161
|
+
def authorize_arguments(args, values)
|
162
|
+
# remove the `input` wrapper to match values
|
163
|
+
input_args = args["input"].type.unwrap.arguments(context)
|
164
|
+
super(input_args, values)
|
165
|
+
end
|
132
166
|
end
|
133
167
|
end
|
134
168
|
end
|
@@ -38,6 +38,9 @@ module GraphQL
|
|
38
38
|
# @return [Class]
|
39
39
|
def object_class(new_class = nil)
|
40
40
|
if new_class
|
41
|
+
if defined?(@payload_type)
|
42
|
+
raise "Can't configure `object_class(...)` after the payload type has already been initialized. Move this configuration higher up the class definition."
|
43
|
+
end
|
41
44
|
@object_class = new_class
|
42
45
|
else
|
43
46
|
@object_class || find_inherited_value(:object_class, GraphQL::Schema::Object)
|
@@ -46,6 +49,28 @@ module GraphQL
|
|
46
49
|
|
47
50
|
NO_INTERFACES = [].freeze
|
48
51
|
|
52
|
+
def field(*args, **kwargs, &block)
|
53
|
+
pt = payload_type # make sure it's initialized with any inherited fields
|
54
|
+
field_defn = super
|
55
|
+
|
56
|
+
# Remove any inherited fields to avoid false conflicts at runtime
|
57
|
+
prev_fields = pt.own_fields[field_defn.graphql_name]
|
58
|
+
case prev_fields
|
59
|
+
when GraphQL::Schema::Field
|
60
|
+
if prev_fields.owner != self
|
61
|
+
pt.own_fields.delete(field_defn.graphql_name)
|
62
|
+
end
|
63
|
+
when Array
|
64
|
+
prev_fields.reject! { |f| f.owner != self }
|
65
|
+
if prev_fields.empty?
|
66
|
+
pt.own_fields.delete(field_defn.graphql_name)
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
pt.add_field(field_defn, method_conflict_warning: false)
|
71
|
+
field_defn
|
72
|
+
end
|
73
|
+
|
49
74
|
private
|
50
75
|
|
51
76
|
# Build a subclass of {.object_class} based on `self`.
|
@@ -53,11 +78,11 @@ module GraphQL
|
|
53
78
|
# Override this hook to customize return type generation.
|
54
79
|
def generate_payload_type
|
55
80
|
resolver_name = graphql_name
|
56
|
-
resolver_fields =
|
81
|
+
resolver_fields = all_field_definitions
|
57
82
|
Class.new(object_class) do
|
58
83
|
graphql_name("#{resolver_name}Payload")
|
59
84
|
description("Autogenerated return type of #{resolver_name}")
|
60
|
-
resolver_fields.each do |
|
85
|
+
resolver_fields.each do |f|
|
61
86
|
# Reattach the already-defined field here
|
62
87
|
# (The field's `.owner` will still point to the mutation, not the object type, I think)
|
63
88
|
# Don't re-warn about a method conflict. Since this type is generated, it should be fixed in the resolver instead.
|
@@ -28,7 +28,7 @@ module GraphQL
|
|
28
28
|
include Schema::Member::HasPath
|
29
29
|
extend Schema::Member::HasPath
|
30
30
|
|
31
|
-
# @param object [Object]
|
31
|
+
# @param object [Object] The application object that this field is being resolved on
|
32
32
|
# @param context [GraphQL::Query::Context]
|
33
33
|
# @param field [GraphQL::Schema::Field]
|
34
34
|
def initialize(object:, context:, field:)
|
@@ -37,10 +37,9 @@ module GraphQL
|
|
37
37
|
@field = field
|
38
38
|
# Since this hash is constantly rebuilt, cache it for this call
|
39
39
|
@arguments_by_keyword = {}
|
40
|
-
self.class.arguments.each do |name, arg|
|
40
|
+
self.class.arguments(context).each do |name, arg|
|
41
41
|
@arguments_by_keyword[arg.keyword] = arg
|
42
42
|
end
|
43
|
-
@arguments_loads_as_type = self.class.arguments_loads_as_type
|
44
43
|
@prepared_arguments = nil
|
45
44
|
end
|
46
45
|
|
@@ -110,7 +109,7 @@ module GraphQL
|
|
110
109
|
public_send(self.class.resolve_method)
|
111
110
|
end
|
112
111
|
else
|
113
|
-
|
112
|
+
raise GraphQL::UnauthorizedFieldError.new(context: context, object: object, type: field.owner, field: field)
|
114
113
|
end
|
115
114
|
end
|
116
115
|
end
|
@@ -146,7 +145,25 @@ module GraphQL
|
|
146
145
|
# @raise [GraphQL::UnauthorizedError] To signal an authorization failure
|
147
146
|
# @return [Boolean, early_return_data] If `false`, execution will stop (and `early_return_data` will be returned instead, if present.)
|
148
147
|
def authorized?(**inputs)
|
149
|
-
self.class
|
148
|
+
arg_owner = @field # || self.class
|
149
|
+
args = arg_owner.arguments(context)
|
150
|
+
authorize_arguments(args, inputs)
|
151
|
+
end
|
152
|
+
|
153
|
+
# Called when an object loaded by `loads:` fails the `.authorized?` check for its resolved GraphQL object type.
|
154
|
+
#
|
155
|
+
# By default, the error is re-raised and passed along to {{Schema.unauthorized_object}}.
|
156
|
+
#
|
157
|
+
# Any value returned here will be used _instead of_ of the loaded object.
|
158
|
+
# @param err [GraphQL::UnauthorizedError]
|
159
|
+
def unauthorized_object(err)
|
160
|
+
raise err
|
161
|
+
end
|
162
|
+
|
163
|
+
private
|
164
|
+
|
165
|
+
def authorize_arguments(args, inputs)
|
166
|
+
args.each_value do |argument|
|
150
167
|
arg_keyword = argument.keyword
|
151
168
|
if inputs.key?(arg_keyword) && !(arg_value = inputs[arg_keyword]).nil? && (arg_value != argument.default_value)
|
152
169
|
arg_auth, err = argument.authorized?(self, arg_value, context)
|
@@ -161,8 +178,6 @@ module GraphQL
|
|
161
178
|
end
|
162
179
|
end
|
163
180
|
|
164
|
-
private
|
165
|
-
|
166
181
|
def load_arguments(args)
|
167
182
|
prepared_args = {}
|
168
183
|
prepare_lazies = []
|
@@ -170,18 +185,14 @@ module GraphQL
|
|
170
185
|
args.each do |key, value|
|
171
186
|
arg_defn = @arguments_by_keyword[key]
|
172
187
|
if arg_defn
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
if context.schema.lazy?(prepped_value)
|
178
|
-
prepare_lazies << context.schema.after_lazy(prepped_value) do |finished_prepped_value|
|
179
|
-
prepared_args[key] = finished_prepped_value
|
180
|
-
end
|
188
|
+
prepped_value = prepared_args[key] = arg_defn.load_and_authorize_value(self, value, context)
|
189
|
+
if context.schema.lazy?(prepped_value)
|
190
|
+
prepare_lazies << context.schema.after_lazy(prepped_value) do |finished_prepped_value|
|
191
|
+
prepared_args[key] = finished_prepped_value
|
181
192
|
end
|
182
193
|
end
|
183
194
|
else
|
184
|
-
#
|
195
|
+
# these are `extras:`
|
185
196
|
prepared_args[key] = value
|
186
197
|
end
|
187
198
|
end
|
@@ -194,8 +205,8 @@ module GraphQL
|
|
194
205
|
end
|
195
206
|
end
|
196
207
|
|
197
|
-
def
|
198
|
-
|
208
|
+
def get_argument(name, context = GraphQL::Query::NullContext)
|
209
|
+
self.class.get_argument(name, context)
|
199
210
|
end
|
200
211
|
|
201
212
|
class << self
|
@@ -218,8 +229,10 @@ module GraphQL
|
|
218
229
|
own_extras + (superclass.respond_to?(:extras) ? superclass.extras : [])
|
219
230
|
end
|
220
231
|
|
221
|
-
#
|
222
|
-
#
|
232
|
+
# If `true` (default), then the return type for this resolver will be nullable.
|
233
|
+
# If `false`, then the return type is non-null.
|
234
|
+
#
|
235
|
+
# @see #type which sets the return type of this field and accepts a `null:` option
|
223
236
|
# @param allow_null [Boolean] Whether or not the response can be null
|
224
237
|
def null(allow_null = nil)
|
225
238
|
if !allow_null.nil?
|
@@ -298,13 +311,27 @@ module GraphQL
|
|
298
311
|
end
|
299
312
|
|
300
313
|
def field_options
|
314
|
+
|
315
|
+
all_args = {}
|
316
|
+
all_argument_definitions.each do |arg|
|
317
|
+
if (prev_entry = all_args[arg.graphql_name])
|
318
|
+
if prev_entry.is_a?(Array)
|
319
|
+
prev_entry << arg
|
320
|
+
else
|
321
|
+
all_args[arg.graphql_name] = [prev_entry, arg]
|
322
|
+
end
|
323
|
+
else
|
324
|
+
all_args[arg.graphql_name] = arg
|
325
|
+
end
|
326
|
+
end
|
327
|
+
|
301
328
|
field_opts = {
|
302
329
|
type: type_expr,
|
303
330
|
description: description,
|
304
331
|
extras: extras,
|
305
332
|
resolver_method: :resolve_with_support,
|
306
333
|
resolver_class: self,
|
307
|
-
arguments:
|
334
|
+
arguments: all_args,
|
308
335
|
null: null,
|
309
336
|
complexity: complexity,
|
310
337
|
broadcastable: broadcastable?,
|
@@ -332,47 +359,9 @@ module GraphQL
|
|
332
359
|
# also add some preparation hook methods which will be used for this argument
|
333
360
|
# @see {GraphQL::Schema::Argument#initialize} for the signature
|
334
361
|
def argument(*args, **kwargs, &block)
|
335
|
-
loads = kwargs[:loads]
|
336
362
|
# Use `from_resolver: true` to short-circuit the InputObject's own `loads:` implementation
|
337
363
|
# so that we can support `#load_{x}` methods below.
|
338
|
-
|
339
|
-
own_arguments_loads_as_type[arg_defn.keyword] = loads if loads
|
340
|
-
|
341
|
-
if !method_defined?(:"load_#{arg_defn.keyword}")
|
342
|
-
if loads && arg_defn.type.list?
|
343
|
-
class_eval <<-RUBY, __FILE__, __LINE__ + 1
|
344
|
-
def load_#{arg_defn.keyword}(values)
|
345
|
-
argument = @arguments_by_keyword[:#{arg_defn.keyword}]
|
346
|
-
lookup_as_type = @arguments_loads_as_type[:#{arg_defn.keyword}]
|
347
|
-
context.schema.after_lazy(values) do |values2|
|
348
|
-
GraphQL::Execution::Lazy.all(values2.map { |value| load_application_object(argument, lookup_as_type, value, context) })
|
349
|
-
end
|
350
|
-
end
|
351
|
-
RUBY
|
352
|
-
elsif loads
|
353
|
-
class_eval <<-RUBY, __FILE__, __LINE__ + 1
|
354
|
-
def load_#{arg_defn.keyword}(value)
|
355
|
-
argument = @arguments_by_keyword[:#{arg_defn.keyword}]
|
356
|
-
lookup_as_type = @arguments_loads_as_type[:#{arg_defn.keyword}]
|
357
|
-
load_application_object(argument, lookup_as_type, value, context)
|
358
|
-
end
|
359
|
-
RUBY
|
360
|
-
else
|
361
|
-
class_eval <<-RUBY, __FILE__, __LINE__ + 1
|
362
|
-
def load_#{arg_defn.keyword}(value)
|
363
|
-
value
|
364
|
-
end
|
365
|
-
RUBY
|
366
|
-
end
|
367
|
-
end
|
368
|
-
|
369
|
-
arg_defn
|
370
|
-
end
|
371
|
-
|
372
|
-
# @api private
|
373
|
-
def arguments_loads_as_type
|
374
|
-
inherited_lookups = superclass.respond_to?(:arguments_loads_as_type) ? superclass.arguments_loads_as_type : {}
|
375
|
-
inherited_lookups.merge(own_arguments_loads_as_type)
|
364
|
+
super(*args, from_resolver: true, **kwargs)
|
376
365
|
end
|
377
366
|
|
378
367
|
# Registers new extension
|
@@ -408,10 +397,6 @@ module GraphQL
|
|
408
397
|
def own_extensions
|
409
398
|
@own_extensions
|
410
399
|
end
|
411
|
-
|
412
|
-
def own_arguments_loads_as_type
|
413
|
-
@own_arguments_loads_as_type ||= {}
|
414
|
-
end
|
415
400
|
end
|
416
401
|
end
|
417
402
|
end
|
@@ -14,7 +14,7 @@ module GraphQL
|
|
14
14
|
class Subscription < GraphQL::Schema::Resolver
|
15
15
|
extend GraphQL::Schema::Resolver::HasPayloadType
|
16
16
|
extend GraphQL::Schema::Member::HasFields
|
17
|
-
|
17
|
+
NO_UPDATE = :no_update
|
18
18
|
# The generated payload type is required; If there's no payload,
|
19
19
|
# propagate null.
|
20
20
|
null false
|
@@ -58,11 +58,9 @@ module GraphQL
|
|
58
58
|
end
|
59
59
|
end
|
60
60
|
|
61
|
-
#
|
61
|
+
# The default implementation returns nothing on subscribe.
|
62
62
|
# Override it to return an object or
|
63
|
-
# `:no_response` to return nothing.
|
64
|
-
#
|
65
|
-
# The default is `:no_response`.
|
63
|
+
# `:no_response` to (explicitly) return nothing.
|
66
64
|
def subscribe(args = {})
|
67
65
|
:no_response
|
68
66
|
end
|
@@ -70,7 +68,7 @@ module GraphQL
|
|
70
68
|
# Wrap the user-provided `#update` hook
|
71
69
|
def resolve_update(**args)
|
72
70
|
ret_val = args.any? ? update(**args) : update
|
73
|
-
if ret_val ==
|
71
|
+
if ret_val == NO_UPDATE
|
74
72
|
context.namespace(:subscriptions)[:no_update] = true
|
75
73
|
context.skip
|
76
74
|
else
|
@@ -79,7 +77,7 @@ module GraphQL
|
|
79
77
|
end
|
80
78
|
|
81
79
|
# The default implementation returns the root object.
|
82
|
-
# Override it to return
|
80
|
+
# Override it to return {NO_UPDATE} if you want to
|
83
81
|
# skip updates sometimes. Or override it to return a different object.
|
84
82
|
def update(args = {})
|
85
83
|
object
|
@@ -105,10 +103,12 @@ module GraphQL
|
|
105
103
|
# Call this method to provide a new subscription_scope; OR
|
106
104
|
# call it without an argument to get the subscription_scope
|
107
105
|
# @param new_scope [Symbol]
|
106
|
+
# @param optional [Boolean] If true, then don't require `scope:` to be provided to updates to this subscription.
|
108
107
|
# @return [Symbol]
|
109
|
-
def self.subscription_scope(new_scope = READING_SCOPE)
|
108
|
+
def self.subscription_scope(new_scope = READING_SCOPE, optional: false)
|
110
109
|
if new_scope != READING_SCOPE
|
111
110
|
@subscription_scope = new_scope
|
111
|
+
@subscription_scope_optional = optional
|
112
112
|
elsif defined?(@subscription_scope)
|
113
113
|
@subscription_scope
|
114
114
|
else
|
@@ -116,6 +116,14 @@ module GraphQL
|
|
116
116
|
end
|
117
117
|
end
|
118
118
|
|
119
|
+
def self.subscription_scope_optional?
|
120
|
+
if defined?(@subscription_scope_optional)
|
121
|
+
@subscription_scope_optional
|
122
|
+
else
|
123
|
+
find_inherited_value(:subscription_scope_optional, false)
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
119
127
|
# This is called during initial subscription to get a "name" for this subscription.
|
120
128
|
# Later, when `.trigger` is called, this will be called again to build another "name".
|
121
129
|
# Any subscribers with matching topic will begin the update flow.
|
@@ -124,7 +132,7 @@ module GraphQL
|
|
124
132
|
# In that implementation, only `.trigger` calls with _exact matches_ result in updates to subscribers.
|
125
133
|
#
|
126
134
|
# To implement a filtered stream-type subscription flow, override this method to return a string with field name and subscription scope.
|
127
|
-
# Then, implement {#update} to compare its arguments to the current `object` and return
|
135
|
+
# Then, implement {#update} to compare its arguments to the current `object` and return {NO_UPDATE} when an
|
128
136
|
# update should be filtered out.
|
129
137
|
#
|
130
138
|
# @see {#update} for how to skip updates when an event comes with a matching topic.
|
@@ -173,7 +173,7 @@ Some late-bound types couldn't be resolved:
|
|
173
173
|
end
|
174
174
|
when Class
|
175
175
|
if member.respond_to?(:graphql_definition)
|
176
|
-
graphql_member = member.graphql_definition
|
176
|
+
graphql_member = member.graphql_definition(silence_deprecation_warning: true)
|
177
177
|
visit(schema, graphql_member, context_description)
|
178
178
|
else
|
179
179
|
raise GraphQL::Schema::InvalidTypeError.new("Unexpected traversal member: #{member} (#{member.class.name})")
|
@@ -11,7 +11,7 @@ module GraphQL
|
|
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.get_type(ast_node.name)
|
14
|
+
type_owner.get_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)
|
@@ -4,8 +4,6 @@ module GraphQL
|
|
4
4
|
class Schema
|
5
5
|
# This class joins an object type to an abstract type (interface or union) of which
|
6
6
|
# it is a member.
|
7
|
-
#
|
8
|
-
# TODO: Not yet implemented for interfaces.
|
9
7
|
class TypeMembership
|
10
8
|
# @return [Class<GraphQL::Schema::Object>]
|
11
9
|
attr_accessor :object_type
|
@@ -26,9 +24,25 @@ module GraphQL
|
|
26
24
|
end
|
27
25
|
|
28
26
|
# @return [Boolean] if false, {#object_type} will be treated as _not_ a member of {#abstract_type}
|
29
|
-
def visible?(
|
30
|
-
|
27
|
+
def visible?(ctx)
|
28
|
+
warden = Warden.from_context(ctx)
|
29
|
+
(@object_type.respond_to?(:visible?) ? warden.visible_type?(@object_type, ctx) : true) &&
|
30
|
+
(@abstract_type.respond_to?(:visible?) ? warden.visible_type?(@abstract_type, ctx) : true)
|
31
31
|
end
|
32
|
+
|
33
|
+
def graphql_name
|
34
|
+
"#{@object_type.graphql_name}.#{@abstract_type.kind.interface? ? "implements" : "belongsTo" }.#{@abstract_type.graphql_name}"
|
35
|
+
end
|
36
|
+
|
37
|
+
def path
|
38
|
+
graphql_name
|
39
|
+
end
|
40
|
+
|
41
|
+
def inspect
|
42
|
+
"#<#{self.class} #{@object_type.inspect} => #{@abstract_type.inspect}>"
|
43
|
+
end
|
44
|
+
|
45
|
+
alias :type_class :itself
|
32
46
|
end
|
33
47
|
end
|
34
48
|
end
|
data/lib/graphql/schema/union.rb
CHANGED
@@ -19,8 +19,9 @@ module GraphQL
|
|
19
19
|
end
|
20
20
|
else
|
21
21
|
visible_types = []
|
22
|
+
warden = Warden.from_context(context)
|
22
23
|
type_memberships.each do |type_membership|
|
23
|
-
if
|
24
|
+
if warden.visible_type_membership?(type_membership, context)
|
24
25
|
visible_types << type_membership.object_type
|
25
26
|
end
|
26
27
|
end
|
@@ -28,6 +29,12 @@ module GraphQL
|
|
28
29
|
end
|
29
30
|
end
|
30
31
|
|
32
|
+
def all_possible_types
|
33
|
+
type_memberships.map(&:object_type)
|
34
|
+
end
|
35
|
+
|
36
|
+
prepend GraphQL::Schema::Member::CachedGraphQLDefinition::DeprecatedToGraphQL
|
37
|
+
|
31
38
|
def to_graphql
|
32
39
|
type_defn = GraphQL::UnionType.new
|
33
40
|
type_defn.name = graphql_name
|
@@ -0,0 +1,29 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module GraphQL
|
4
|
+
class Schema
|
5
|
+
class Validator
|
6
|
+
# Use this to specifically reject values that respond to `.blank?` and respond truthy for that method.
|
7
|
+
#
|
8
|
+
# @example Require a non-empty string for an argument
|
9
|
+
# argument :name, String, required: true, validate: { allow_blank: false }
|
10
|
+
class AllowBlankValidator < Validator
|
11
|
+
def initialize(allow_blank_positional, allow_blank: nil, message: "%{validated} can't be blank", **default_options)
|
12
|
+
@message = message
|
13
|
+
super(**default_options)
|
14
|
+
@allow_blank = allow_blank.nil? ? allow_blank_positional : allow_blank
|
15
|
+
end
|
16
|
+
|
17
|
+
def validate(_object, _context, value)
|
18
|
+
if value.respond_to?(:blank?) && value.blank?
|
19
|
+
if (value.nil? && @allow_null) || @allow_blank
|
20
|
+
# pass
|
21
|
+
else
|
22
|
+
@message
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module GraphQL
|
4
|
+
class Schema
|
5
|
+
class Validator
|
6
|
+
# Use this to specifically reject or permit `nil` values (given as `null` from GraphQL).
|
7
|
+
#
|
8
|
+
# @example require a non-null value for an argument if it is provided
|
9
|
+
# argument :name, String, required: false, validates: { allow_null: false }
|
10
|
+
class AllowNullValidator < Validator
|
11
|
+
MESSAGE = "%{validated} can't be null"
|
12
|
+
def initialize(allow_null_positional, allow_null: nil, message: MESSAGE, **default_options)
|
13
|
+
@message = message
|
14
|
+
super(**default_options)
|
15
|
+
@allow_null = allow_null.nil? ? allow_null_positional : allow_null
|
16
|
+
end
|
17
|
+
|
18
|
+
def validate(_object, _context, value)
|
19
|
+
if value.nil? && !@allow_null
|
20
|
+
@message
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|