graphql 2.5.11 → 2.5.23
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/templates/create_graphql_detailed_traces.erb +10 -0
- data/lib/graphql/dashboard/application_controller.rb +41 -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 +11 -73
- data/lib/graphql/dataloader/async_dataloader.rb +22 -11
- data/lib/graphql/dataloader/null_dataloader.rb +48 -10
- data/lib/graphql/dataloader.rb +75 -23
- data/lib/graphql/date_encoding_error.rb +1 -1
- 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 +24 -18
- data/lib/graphql/execution/interpreter.rb +8 -22
- data/lib/graphql/execution/lazy.rb +1 -1
- data/lib/graphql/execution/multiplex.rb +1 -1
- data/lib/graphql/execution/next/field_resolve_step.rb +743 -0
- data/lib/graphql/execution/next/load_argument_step.rb +64 -0
- data/lib/graphql/execution/next/prepare_object_step.rb +129 -0
- data/lib/graphql/execution/next/runner.rb +411 -0
- data/lib/graphql/execution/next/selections_step.rb +37 -0
- data/lib/graphql/execution/next.rb +72 -0
- data/lib/graphql/execution.rb +8 -4
- data/lib/graphql/execution_error.rb +17 -10
- 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/type_type.rb +64 -28
- data/lib/graphql/invalid_null_error.rb +11 -5
- data/lib/graphql/language/document_from_schema_definition.rb +2 -1
- data/lib/graphql/language.rb +21 -12
- data/lib/graphql/pagination/connection.rb +2 -0
- data/lib/graphql/pagination/connections.rb +32 -0
- data/lib/graphql/query/context.rb +4 -3
- data/lib/graphql/query/null_context.rb +9 -3
- data/lib/graphql/schema/argument.rb +12 -0
- data/lib/graphql/schema/build_from_definition.rb +10 -1
- data/lib/graphql/schema/directive.rb +22 -4
- data/lib/graphql/schema/field/connection_extension.rb +15 -35
- data/lib/graphql/schema/field/scope_extension.rb +22 -13
- data/lib/graphql/schema/field.rb +79 -48
- data/lib/graphql/schema/field_extension.rb +33 -0
- data/lib/graphql/schema/list.rb +1 -1
- data/lib/graphql/schema/member/base_dsl_methods.rb +1 -1
- 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/resolver.rb +60 -1
- data/lib/graphql/schema/subscription.rb +0 -2
- data/lib/graphql/schema/validator/required_validator.rb +33 -2
- data/lib/graphql/schema/visibility/profile.rb +68 -49
- data/lib/graphql/schema/visibility.rb +3 -3
- data/lib/graphql/schema/wrapper.rb +7 -1
- data/lib/graphql/schema.rb +53 -10
- 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 +322 -256
- 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 +25 -1
- data/lib/graphql/subscriptions/event.rb +1 -0
- data/lib/graphql/subscriptions.rb +21 -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 +209 -79
- data/lib/graphql/tracing/sentry_trace.rb +3 -1
- 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 +17 -3
|
@@ -20,6 +20,23 @@ module GraphQL
|
|
|
20
20
|
dataloader.with(source_class, *source_args).load(load_key)
|
|
21
21
|
end
|
|
22
22
|
|
|
23
|
+
# A shortcut method for loading many keys from a source.
|
|
24
|
+
# Identical to `dataloader.with(source_class, *source_args).load_all(load_keys)`
|
|
25
|
+
#
|
|
26
|
+
# @example
|
|
27
|
+
# field :score, Integer, resolve_batch: true
|
|
28
|
+
#
|
|
29
|
+
# def self.score(posts)
|
|
30
|
+
# dataload_all(PostScoreSource, posts.map(&:id))
|
|
31
|
+
# end
|
|
32
|
+
#
|
|
33
|
+
# @param source_class [Class<GraphQL::Dataloader::Source>]
|
|
34
|
+
# @param source_args [Array<Object>] Any extra parameters defined in `source_class`'s `initialize` method
|
|
35
|
+
# @param load_keys [Array<Object>] The keys to look up using `def fetch`
|
|
36
|
+
def dataload_all(source_class, *source_args, load_keys)
|
|
37
|
+
dataloader.with(source_class, *source_args).load_all(load_keys)
|
|
38
|
+
end
|
|
39
|
+
|
|
23
40
|
# Find an object with ActiveRecord via {Dataloader::ActiveRecordSource}.
|
|
24
41
|
# @param model [Class<ActiveRecord::Base>]
|
|
25
42
|
# @param find_by_value [Object] Usually an `id`, might be another value if `find_by:` is also provided
|
|
@@ -39,6 +56,16 @@ module GraphQL
|
|
|
39
56
|
source.load(find_by_value)
|
|
40
57
|
end
|
|
41
58
|
|
|
59
|
+
# @see dataload_record Like `dataload_record`, but accepts an Array of `find_by_values`
|
|
60
|
+
def dataload_all_records(model, find_by_values, find_by: nil)
|
|
61
|
+
source = if find_by
|
|
62
|
+
dataloader.with(Dataloader::ActiveRecordSource, model, find_by: find_by)
|
|
63
|
+
else
|
|
64
|
+
dataloader.with(Dataloader::ActiveRecordSource, model)
|
|
65
|
+
end
|
|
66
|
+
source.load_all(find_by_values)
|
|
67
|
+
end
|
|
68
|
+
|
|
42
69
|
# Look up an associated record using a Rails association (via {Dataloader::ActiveRecordAssociationSource})
|
|
43
70
|
# @param association_name [Symbol] A `belongs_to` or `has_one` association. (If a `has_many` association is named here, it will be selected without pagination.)
|
|
44
71
|
# @param record [ActiveRecord::Base] The object that the association belongs to.
|
|
@@ -56,6 +83,16 @@ module GraphQL
|
|
|
56
83
|
end
|
|
57
84
|
source.load(record)
|
|
58
85
|
end
|
|
86
|
+
|
|
87
|
+
# @see dataload_association Like `dataload_assocation` but accepts an Array of records (required param)
|
|
88
|
+
def dataload_all_associations(records, association_name, scope: nil)
|
|
89
|
+
source = if scope
|
|
90
|
+
dataloader.with(Dataloader::ActiveRecordAssociationSource, association_name, scope)
|
|
91
|
+
else
|
|
92
|
+
dataloader.with(Dataloader::ActiveRecordAssociationSource, association_name)
|
|
93
|
+
end
|
|
94
|
+
source.load_all(records)
|
|
95
|
+
end
|
|
59
96
|
end
|
|
60
97
|
end
|
|
61
98
|
end
|
|
@@ -5,11 +5,88 @@ module GraphQL
|
|
|
5
5
|
class Member
|
|
6
6
|
# Shared code for Objects, Interfaces, Mutations, Subscriptions
|
|
7
7
|
module HasFields
|
|
8
|
+
include EmptyObjects
|
|
8
9
|
# Add a field to this object or interface with the given definition
|
|
9
|
-
# @
|
|
10
|
+
# @param name_positional [Symbol] The underscore-cased version of this field name (will be camelized for the GraphQL API); `name:` keyword is also accepted
|
|
11
|
+
# @param type_positional [Class, GraphQL::BaseType, Array] The return type of this field; `type:` keyword is also accepted
|
|
12
|
+
# @param desc_positional [String] Field description; `description:` keyword is also accepted
|
|
13
|
+
# @option kwargs [Symbol] :name The underscore-cased version of this field name (will be camelized for the GraphQL API); positional argument also accepted
|
|
14
|
+
# @option kwargs [Class, GraphQL::BaseType, Array] :type The return type of this field; positional argument is also accepted
|
|
15
|
+
# @option kwargs [Boolean] :null (defaults to `true`) `true` if this field may return `null`, `false` if it is never `null`
|
|
16
|
+
# @option kwargs [String] :description Field description; positional argument also accepted
|
|
17
|
+
# @option kwargs [String] :comment Field comment
|
|
18
|
+
# @option kwargs [String] :deprecation_reason If present, the field is marked "deprecated" with this message
|
|
19
|
+
# @option kwargs [Symbol] :method The method to call on the underlying object to resolve this field (defaults to `name`)
|
|
20
|
+
# @option kwargs [String, Symbol] :hash_key The hash key to lookup on the underlying object (if its a Hash) to resolve this field (defaults to `name` or `name.to_s`)
|
|
21
|
+
# @option kwargs [Array<String, Symbol>] :dig The nested hash keys to lookup on the underlying hash to resolve this field using dig
|
|
22
|
+
# @option kwargs [Symbol, true] :resolver_method The method on the type to call to resolve this field (defaults to `name`)
|
|
23
|
+
# @option kwargs [Symbol, true] :resolve_static Used by {Schema.execute_next} to produce a single value, shared by all objects which resolve this field. Called on the owner type class with `context, **arguments`
|
|
24
|
+
# @option kwargs [Symbol, true] :resolve_batch Used by {Schema.execute_next} map `objects` to a same-sized Array of results. Called on the owner type class with `objects, context, **arguments`.
|
|
25
|
+
# @option kwargs [Symbol, true] :resolve_each Used by {Schema.execute_next} to get a value value for each item. Called on the owner type class with `object, context, **arguments`.
|
|
26
|
+
# @option kwargs [Symbol, true] :resolve_legacy_instance_method Used by {Schema.execute_next} to get a value value for each item. Calls an instance method on the object type class.
|
|
27
|
+
# @option kwargs [Boolean] :connection `true` if this field should get automagic connection behavior; default is to infer by `*Connection` in the return type name
|
|
28
|
+
# @option kwargs [Class] :connection_extension The extension to add, to implement connections. If `nil`, no extension is added.
|
|
29
|
+
# @option kwargs [Integer, nil] :max_page_size For connections, the maximum number of items to return from this field, or `nil` to allow unlimited results.
|
|
30
|
+
# @option kwargs [Integer, nil] :default_page_size For connections, the default number of items to return from this field, or `nil` to return unlimited results.
|
|
31
|
+
# @option kwargs [Boolean] :introspection If true, this field will be marked as `#introspection?` and the name may begin with `__`
|
|
32
|
+
# @option kwargs [{String=>GraphQL::Schema::Argument, Hash}] :arguments Arguments for this field (may be added in the block, also)
|
|
33
|
+
# @option kwargs [Boolean] :camelize If true, the field name will be camelized when building the schema
|
|
34
|
+
# @option kwargs [Numeric] :complexity When provided, set the complexity for this field
|
|
35
|
+
# @option kwargs [Boolean] :scope If true, the return type's `.scope_items` method will be called on the return value
|
|
36
|
+
# @option kwargs [Symbol, String] :subscription_scope A key in `context` which will be used to scope subscription payloads
|
|
37
|
+
# @option kwargs [Array<Class, Hash<Class => Object>>] :extensions Named extensions to apply to this field (see also {#extension})
|
|
38
|
+
# @option kwargs [Hash{Class => Hash}] :directives Directives to apply to this field
|
|
39
|
+
# @option kwargs [Boolean] :trace If true, a {GraphQL::Tracing} tracer will measure this scalar field
|
|
40
|
+
# @option kwargs [Boolean] :broadcastable Whether or not this field can be distributed in subscription broadcasts
|
|
41
|
+
# @option kwargs [Language::Nodes::FieldDefinition, nil] :ast_node If this schema was parsed from definition, this AST node defined the field
|
|
42
|
+
# @option kwargs [Boolean] :method_conflict_warning If false, skip the warning if this field's method conflicts with a built-in method
|
|
43
|
+
# @option kwargs [Array<Hash>] :validates Configurations for validating this field
|
|
44
|
+
# @option kwargs [Object] :fallback_value A fallback value if the method is not defined
|
|
45
|
+
# @option kwargs [Class<GraphQL::Schema::Mutation>] :mutation
|
|
46
|
+
# @option kwargs [Class<GraphQL::Schema::Resolver>] :resolver
|
|
47
|
+
# @option kwargs [Class<GraphQL::Schema::Subscription>] :subscription
|
|
48
|
+
# @option kwargs [Boolean] :dynamic_introspection (Private, used by GraphQL-Ruby)
|
|
49
|
+
# @option kwargs [Boolean] :relay_node_field (Private, used by GraphQL-Ruby)
|
|
50
|
+
# @option kwargs [Boolean] :relay_nodes_field (Private, used by GraphQL-Ruby)
|
|
51
|
+
# @option kwargs [Class, Hash] :dataload Shorthand for dataloader lookups
|
|
52
|
+
# @option kwargs [Array<:ast_node, :parent, :lookahead, :owner, :execution_errors, :graphql_name, :argument_details, Symbol>] :extras Extra arguments to be injected into the resolver for this field
|
|
53
|
+
# @param kwargs [Hash] Keywords for defining the field. Any not documented here will be passed to your base field class where they must be handled.
|
|
54
|
+
# @param definition_block [Proc] an additional block for configuring the field. Receive the field as a block param, or, if no block params are defined, then the block is `instance_eval`'d on the new {Field}.
|
|
55
|
+
# @yieldparam field [GraphQL::Schema::Field] The newly-created field instance
|
|
56
|
+
# @yieldreturn [void]
|
|
10
57
|
# @return [GraphQL::Schema::Field]
|
|
11
|
-
def field(
|
|
12
|
-
|
|
58
|
+
def field(name_positional = nil, type_positional = nil, desc_positional = nil, **kwargs, &definition_block)
|
|
59
|
+
resolver = kwargs.delete(:resolver)
|
|
60
|
+
mutation = kwargs.delete(:mutation)
|
|
61
|
+
subscription = kwargs.delete(:subscription)
|
|
62
|
+
if (resolver_class = resolver || mutation || subscription)
|
|
63
|
+
# Add a reference to that parent class
|
|
64
|
+
kwargs[:resolver_class] = resolver_class
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
kwargs[:name] ||= name_positional
|
|
68
|
+
if !type_positional.nil?
|
|
69
|
+
if desc_positional
|
|
70
|
+
if kwargs[:description]
|
|
71
|
+
raise ArgumentError, "Provide description as a positional argument or `description:` keyword, but not both (#{desc_positional.inspect}, #{kwargs[:description].inspect})"
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
kwargs[:description] = desc_positional
|
|
75
|
+
kwargs[:type] = type_positional
|
|
76
|
+
elsif (resolver || mutation) && type_positional.is_a?(String)
|
|
77
|
+
# The return type should be copied from the resolver, and the second positional argument is the description
|
|
78
|
+
kwargs[:description] = type_positional
|
|
79
|
+
else
|
|
80
|
+
kwargs[:type] = type_positional
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
if type_positional.is_a?(Class) && type_positional < GraphQL::Schema::Mutation
|
|
84
|
+
raise ArgumentError, "Use `field #{name_positional.inspect}, mutation: Mutation, ...` to provide a mutation to this field instead"
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
kwargs[:owner] = self
|
|
89
|
+
field_defn = field_class.new(**kwargs, &definition_block)
|
|
13
90
|
add_field(field_defn)
|
|
14
91
|
field_defn
|
|
15
92
|
end
|
|
@@ -73,10 +150,14 @@ module GraphQL
|
|
|
73
150
|
|
|
74
151
|
def global_id_field(field_name, **kwargs)
|
|
75
152
|
type = self
|
|
76
|
-
field field_name, "ID", **kwargs, null: false
|
|
153
|
+
field field_name, "ID", **kwargs, null: false, resolve_each: true
|
|
77
154
|
define_method(field_name) do
|
|
78
155
|
context.schema.id_from_object(object, type, context)
|
|
79
156
|
end
|
|
157
|
+
|
|
158
|
+
define_singleton_method(field_name) do |object, context|
|
|
159
|
+
context.schema.id_from_object(object, type, context)
|
|
160
|
+
end
|
|
80
161
|
end
|
|
81
162
|
|
|
82
163
|
# @param new_has_no_fields [Boolean] Call with `true` to make this Object type ignore the requirement to have any defined fields.
|
|
@@ -232,7 +313,7 @@ module GraphQL
|
|
|
232
313
|
end
|
|
233
314
|
end
|
|
234
315
|
|
|
235
|
-
# @param [GraphQL::Schema::Field]
|
|
316
|
+
# @param field_defn [GraphQL::Schema::Field]
|
|
236
317
|
# @return [String] A warning to give when this field definition might conflict with a built-in method
|
|
237
318
|
def conflict_field_name_warning(field_defn)
|
|
238
319
|
"#{self.graphql_name}'s `field :#{field_defn.original_name}` conflicts with a built-in method, use `resolver_method:` to pick a different resolver method for this field (for example, `resolver_method: :resolve_#{field_defn.resolver_method}` and `def resolve_#{field_defn.resolver_method}`). Or use `method_conflict_warning: false` to suppress this warning."
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
require 'graphql/schema/member/base_dsl_methods'
|
|
3
3
|
require 'graphql/schema/member/graphql_type_names'
|
|
4
4
|
require 'graphql/schema/member/has_ast_node'
|
|
5
|
+
require 'graphql/schema/member/has_authorization'
|
|
5
6
|
require 'graphql/schema/member/has_dataloader'
|
|
6
7
|
require 'graphql/schema/member/has_directives'
|
|
7
8
|
require 'graphql/schema/member/has_deprecation_reason'
|
|
@@ -31,6 +32,10 @@ module GraphQL
|
|
|
31
32
|
extend HasPath
|
|
32
33
|
extend HasAstNode
|
|
33
34
|
extend HasDirectives
|
|
35
|
+
|
|
36
|
+
def self.authorizes?(_ctx)
|
|
37
|
+
false
|
|
38
|
+
end
|
|
34
39
|
end
|
|
35
40
|
end
|
|
36
41
|
end
|
|
@@ -5,6 +5,7 @@ require "graphql/query/null_context"
|
|
|
5
5
|
module GraphQL
|
|
6
6
|
class Schema
|
|
7
7
|
class Object < GraphQL::Schema::Member
|
|
8
|
+
extend GraphQL::Schema::Member::HasAuthorization
|
|
8
9
|
extend GraphQL::Schema::Member::HasFields
|
|
9
10
|
extend GraphQL::Schema::Member::HasInterfaces
|
|
10
11
|
include Member::HasDataloader
|
|
@@ -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,58 @@ 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
|
+
if self.class < Schema::HasSingleInputArgument
|
|
64
|
+
@prepared_arguments = @prepared_arguments[:input]
|
|
65
|
+
end
|
|
66
|
+
q = context.query
|
|
67
|
+
trace_objs = [object]
|
|
68
|
+
q.current_trace.begin_execute_field(field, @prepared_arguments, trace_objs, q)
|
|
69
|
+
begin
|
|
70
|
+
is_authed, new_return_value = authorized?(**@prepared_arguments)
|
|
71
|
+
rescue GraphQL::UnauthorizedError => err
|
|
72
|
+
new_return_value = q.schema.unauthorized_object(err)
|
|
73
|
+
is_authed = true # the error was handled
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
if (runner = @field_resolve_step.runner).resolves_lazies && runner.schema.lazy?(is_authed)
|
|
77
|
+
is_authed, new_return_value = runner.schema.sync_lazy(is_authed)
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
result = if is_authed
|
|
81
|
+
Schema::Validator.validate!(self.class.validators, object, context, @prepared_arguments, as: @field)
|
|
82
|
+
if q.subscription? && @field.owner == context.schema.subscription
|
|
83
|
+
# This needs to use arguments without `loads:`
|
|
84
|
+
@original_arguments = @field_resolve_step.coerce_arguments(@field, @field_resolve_step.ast_node.arguments, false)
|
|
85
|
+
end
|
|
86
|
+
call_resolve(@prepared_arguments)
|
|
87
|
+
elsif new_return_value.nil?
|
|
88
|
+
err = UnauthorizedFieldError.new(object: object, type: @field_resolve_step.parent_type, context: context, field: @field)
|
|
89
|
+
context.schema.unauthorized_field(err)
|
|
90
|
+
else
|
|
91
|
+
new_return_value
|
|
92
|
+
end
|
|
93
|
+
q = context.query
|
|
94
|
+
q.current_trace.end_execute_field(field, @prepared_arguments, trace_objs, q, [result])
|
|
95
|
+
exec_result[exec_index] = result
|
|
96
|
+
rescue RuntimeError => err
|
|
97
|
+
exec_result[exec_index] = err
|
|
98
|
+
rescue StandardError => stderr
|
|
99
|
+
exec_result[exec_index] = begin
|
|
100
|
+
context.query.handle_or_reraise(stderr)
|
|
101
|
+
rescue GraphQL::ExecutionError => ex_err
|
|
102
|
+
ex_err
|
|
103
|
+
end
|
|
104
|
+
ensure
|
|
105
|
+
field_pending_steps = field_resolve_step.pending_steps
|
|
106
|
+
field_pending_steps.delete(self)
|
|
107
|
+
if field_pending_steps.size == 0 && field_resolve_step.field_results
|
|
108
|
+
field_resolve_step.runner.add_step(field_resolve_step)
|
|
109
|
+
end
|
|
110
|
+
end
|
|
111
|
+
|
|
57
112
|
def arguments
|
|
58
113
|
@prepared_arguments || raise("Arguments have not been prepared yet, still waiting for #load_arguments to resolve. (Call `.arguments` later in the code.)")
|
|
59
114
|
end
|
|
@@ -154,6 +209,10 @@ module GraphQL
|
|
|
154
209
|
authorize_arguments(args, inputs)
|
|
155
210
|
end
|
|
156
211
|
|
|
212
|
+
def self.authorizes?(context)
|
|
213
|
+
self.instance_method(:authorized?).owner != GraphQL::Schema::Resolver
|
|
214
|
+
end
|
|
215
|
+
|
|
157
216
|
# Called when an object loaded by `loads:` fails the `.authorized?` check for its resolved GraphQL object type.
|
|
158
217
|
#
|
|
159
218
|
# 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
|
|
@@ -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,17 @@ 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
70
|
@one_of.each do |one_of_condition|
|
|
59
71
|
case one_of_condition
|
|
60
72
|
when Symbol
|
|
73
|
+
if no_visible_conditions && visible_keywords.include?(one_of_condition)
|
|
74
|
+
no_visible_conditions = false
|
|
75
|
+
end
|
|
76
|
+
|
|
61
77
|
if value.key?(one_of_condition)
|
|
62
78
|
fully_matched_conditions += 1
|
|
63
79
|
end
|
|
@@ -66,6 +82,9 @@ module GraphQL
|
|
|
66
82
|
full_match = true
|
|
67
83
|
|
|
68
84
|
one_of_condition.each do |k|
|
|
85
|
+
if no_visible_conditions && visible_keywords.include?(k)
|
|
86
|
+
no_visible_conditions = false
|
|
87
|
+
end
|
|
69
88
|
if value.key?(k)
|
|
70
89
|
any_match = true
|
|
71
90
|
else
|
|
@@ -88,6 +107,18 @@ module GraphQL
|
|
|
88
107
|
end
|
|
89
108
|
end
|
|
90
109
|
|
|
110
|
+
if no_visible_conditions
|
|
111
|
+
if @allow_all_hidden
|
|
112
|
+
return nil
|
|
113
|
+
else
|
|
114
|
+
raise GraphQL::Error, <<~ERR
|
|
115
|
+
#{@validated.path} validates `required: ...` but all required arguments were hidden.
|
|
116
|
+
|
|
117
|
+
Update your schema definition to allow the client to see some fields or skip validation by adding `required: { ..., allow_all_hidden: true }`
|
|
118
|
+
ERR
|
|
119
|
+
end
|
|
120
|
+
end
|
|
121
|
+
|
|
91
122
|
if fully_matched_conditions == 1 && partially_matched_conditions == 0
|
|
92
123
|
nil # OK
|
|
93
124
|
else
|
|
@@ -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)
|
|
@@ -341,6 +306,60 @@ module GraphQL
|
|
|
341
306
|
|
|
342
307
|
private
|
|
343
308
|
|
|
309
|
+
def compute_type(type_name)
|
|
310
|
+
t = @visibility.get_type(type_name) # rubocop:disable Development/ContextIsPassedCop
|
|
311
|
+
if t
|
|
312
|
+
if t.is_a?(Array)
|
|
313
|
+
vis_t = nil
|
|
314
|
+
t.each do |t_defn|
|
|
315
|
+
if @cached_visible[t_defn] && referenced?(t_defn)
|
|
316
|
+
if vis_t.nil?
|
|
317
|
+
vis_t = t_defn
|
|
318
|
+
else
|
|
319
|
+
raise_duplicate_definition(vis_t, t_defn)
|
|
320
|
+
end
|
|
321
|
+
end
|
|
322
|
+
end
|
|
323
|
+
vis_t
|
|
324
|
+
else
|
|
325
|
+
if t && @cached_visible[t] && referenced?(t)
|
|
326
|
+
t
|
|
327
|
+
else
|
|
328
|
+
nil
|
|
329
|
+
end
|
|
330
|
+
end
|
|
331
|
+
end
|
|
332
|
+
end
|
|
333
|
+
|
|
334
|
+
def compute_field(owner, field_name)
|
|
335
|
+
f = if owner.kind.fields? && (field = @cached_parent_fields[owner][field_name])
|
|
336
|
+
field
|
|
337
|
+
elsif owner == query_root && (entry_point_field = @schema.introspection_system.entry_point(name: field_name))
|
|
338
|
+
entry_point_field
|
|
339
|
+
elsif (dynamic_field = @schema.introspection_system.dynamic_field(name: field_name))
|
|
340
|
+
dynamic_field
|
|
341
|
+
else
|
|
342
|
+
nil
|
|
343
|
+
end
|
|
344
|
+
if f.is_a?(Array)
|
|
345
|
+
visible_f = nil
|
|
346
|
+
f.each do |f_defn|
|
|
347
|
+
if @cached_visible_fields[owner][f_defn]
|
|
348
|
+
if visible_f.nil?
|
|
349
|
+
visible_f = f_defn
|
|
350
|
+
else
|
|
351
|
+
raise_duplicate_definition(visible_f, f_defn)
|
|
352
|
+
end
|
|
353
|
+
end
|
|
354
|
+
end
|
|
355
|
+
visible_f&.ensure_loaded
|
|
356
|
+
elsif f && @cached_visible_fields[owner][f.ensure_loaded]
|
|
357
|
+
f
|
|
358
|
+
else
|
|
359
|
+
nil
|
|
360
|
+
end
|
|
361
|
+
end
|
|
362
|
+
|
|
344
363
|
def non_duplicate_items(definitions, visibility_cache)
|
|
345
364
|
non_dups = []
|
|
346
365
|
names = Set.new
|
|
@@ -10,9 +10,9 @@ module GraphQL
|
|
|
10
10
|
class Visibility
|
|
11
11
|
# @param schema [Class<GraphQL::Schema>]
|
|
12
12
|
# @param profiles [Hash<Symbol => Hash>] A hash of `name => context` pairs for preloading visibility profiles
|
|
13
|
-
# @param preload [Boolean] if `true`, load the default schema profile and all named profiles immediately (defaults to `true` for `Rails.env.production?`)
|
|
13
|
+
# @param preload [Boolean] if `true`, load the default schema profile and all named profiles immediately (defaults to `true` for `Rails.env.production?` and `Rails.env.staging?`)
|
|
14
14
|
# @param migration_errors [Boolean] if `true`, raise an error when `Visibility` and `Warden` return different results
|
|
15
|
-
def self.use(schema, dynamic: false, profiles: EmptyObjects::EMPTY_HASH, preload: (defined?(Rails.env) ? Rails.env.production? : nil), migration_errors: false)
|
|
15
|
+
def self.use(schema, dynamic: false, profiles: EmptyObjects::EMPTY_HASH, preload: (defined?(Rails.env) ? (Rails.env.production? || Rails.env.staging?) : nil), migration_errors: false)
|
|
16
16
|
profiles&.each { |name, ctx|
|
|
17
17
|
ctx[:visibility_profile] = name
|
|
18
18
|
ctx.freeze
|
|
@@ -191,7 +191,7 @@ module GraphQL
|
|
|
191
191
|
if refresh
|
|
192
192
|
@top_level_profile = nil
|
|
193
193
|
end
|
|
194
|
-
@top_level_profile ||= @schema.visibility_profile_class.new(context:
|
|
194
|
+
@top_level_profile ||= @schema.visibility_profile_class.new(context: @schema.null_context, schema: @schema, visibility: self)
|
|
195
195
|
end
|
|
196
196
|
|
|
197
197
|
private
|