graphql 2.5.9 → 2.5.26

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.
Files changed (127) hide show
  1. checksums.yaml +4 -4
  2. data/lib/generators/graphql/detailed_trace_generator.rb +77 -0
  3. data/lib/generators/graphql/templates/create_graphql_detailed_traces.erb +10 -0
  4. data/lib/graphql/analysis.rb +20 -13
  5. data/lib/graphql/dashboard/application_controller.rb +41 -0
  6. data/lib/graphql/dashboard/landings_controller.rb +9 -0
  7. data/lib/graphql/dashboard/statics_controller.rb +31 -0
  8. data/lib/graphql/dashboard/subscriptions.rb +2 -1
  9. data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/clients/_form.html.erb +1 -0
  10. data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/clients/edit.html.erb +2 -2
  11. data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/clients/index.html.erb +1 -1
  12. data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/clients/new.html.erb +1 -1
  13. data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/index_entries/index.html.erb +1 -1
  14. data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/operations/show.html.erb +1 -1
  15. data/lib/graphql/dashboard/views/graphql/dashboard/subscriptions/topics/show.html.erb +1 -1
  16. data/lib/graphql/dashboard/views/layouts/graphql/dashboard/application.html.erb +7 -7
  17. data/lib/graphql/dashboard.rb +11 -73
  18. data/lib/graphql/dataloader/active_record_association_source.rb +14 -2
  19. data/lib/graphql/dataloader/async_dataloader.rb +22 -11
  20. data/lib/graphql/dataloader/null_dataloader.rb +54 -9
  21. data/lib/graphql/dataloader.rb +75 -23
  22. data/lib/graphql/date_encoding_error.rb +1 -1
  23. data/lib/graphql/execution/field_resolve_step.rb +631 -0
  24. data/lib/graphql/execution/finalize.rb +217 -0
  25. data/lib/graphql/execution/input_values.rb +261 -0
  26. data/lib/graphql/execution/interpreter/handles_raw_value.rb +6 -0
  27. data/lib/graphql/execution/interpreter/resolve.rb +10 -16
  28. data/lib/graphql/execution/interpreter/runtime/graphql_result.rb +13 -0
  29. data/lib/graphql/execution/interpreter/runtime.rb +28 -33
  30. data/lib/graphql/execution/interpreter.rb +8 -22
  31. data/lib/graphql/execution/lazy.rb +1 -1
  32. data/lib/graphql/execution/load_argument_step.rb +64 -0
  33. data/lib/graphql/execution/multiplex.rb +1 -1
  34. data/lib/graphql/execution/next.rb +90 -0
  35. data/lib/graphql/execution/prepare_object_step.rb +128 -0
  36. data/lib/graphql/execution/runner.rb +410 -0
  37. data/lib/graphql/execution/selections_step.rb +91 -0
  38. data/lib/graphql/execution.rb +8 -4
  39. data/lib/graphql/execution_error.rb +17 -10
  40. data/lib/graphql/introspection/directive_type.rb +7 -3
  41. data/lib/graphql/introspection/dynamic_fields.rb +5 -1
  42. data/lib/graphql/introspection/entry_points.rb +11 -3
  43. data/lib/graphql/introspection/enum_value_type.rb +5 -5
  44. data/lib/graphql/introspection/field_type.rb +13 -5
  45. data/lib/graphql/introspection/input_value_type.rb +21 -13
  46. data/lib/graphql/introspection/type_type.rb +64 -28
  47. data/lib/graphql/invalid_null_error.rb +11 -5
  48. data/lib/graphql/language/document_from_schema_definition.rb +2 -1
  49. data/lib/graphql/language/lexer.rb +20 -9
  50. data/lib/graphql/language/nodes.rb +5 -1
  51. data/lib/graphql/language/parser.rb +1 -0
  52. data/lib/graphql/language.rb +21 -12
  53. data/lib/graphql/pagination/connection.rb +2 -0
  54. data/lib/graphql/pagination/connections.rb +32 -0
  55. data/lib/graphql/query/context.rb +11 -4
  56. data/lib/graphql/query/null_context.rb +9 -3
  57. data/lib/graphql/query/partial.rb +18 -3
  58. data/lib/graphql/query.rb +10 -1
  59. data/lib/graphql/runtime_error.rb +6 -0
  60. data/lib/graphql/schema/addition.rb +3 -1
  61. data/lib/graphql/schema/argument.rb +17 -0
  62. data/lib/graphql/schema/build_from_definition.rb +15 -2
  63. data/lib/graphql/schema/directive.rb +45 -13
  64. data/lib/graphql/schema/field/connection_extension.rb +4 -37
  65. data/lib/graphql/schema/field/scope_extension.rb +18 -13
  66. data/lib/graphql/schema/field.rb +87 -48
  67. data/lib/graphql/schema/field_extension.rb +11 -8
  68. data/lib/graphql/schema/interface.rb +26 -0
  69. data/lib/graphql/schema/list.rb +5 -1
  70. data/lib/graphql/schema/member/base_dsl_methods.rb +1 -11
  71. data/lib/graphql/schema/member/has_arguments.rb +43 -14
  72. data/lib/graphql/schema/member/has_authorization.rb +35 -0
  73. data/lib/graphql/schema/member/has_dataloader.rb +37 -0
  74. data/lib/graphql/schema/member/has_fields.rb +86 -5
  75. data/lib/graphql/schema/member/has_interfaces.rb +2 -2
  76. data/lib/graphql/schema/member/type_system_helpers.rb +16 -2
  77. data/lib/graphql/schema/member.rb +5 -0
  78. data/lib/graphql/schema/non_null.rb +1 -1
  79. data/lib/graphql/schema/object.rb +1 -0
  80. data/lib/graphql/schema/ractor_shareable.rb +79 -0
  81. data/lib/graphql/schema/resolver.rb +60 -1
  82. data/lib/graphql/schema/subscription.rb +0 -2
  83. data/lib/graphql/schema/validator/required_validator.rb +45 -5
  84. data/lib/graphql/schema/visibility/migration.rb +2 -2
  85. data/lib/graphql/schema/visibility/profile.rb +140 -56
  86. data/lib/graphql/schema/visibility.rb +31 -18
  87. data/lib/graphql/schema/wrapper.rb +7 -1
  88. data/lib/graphql/schema.rb +108 -32
  89. data/lib/graphql/static_validation/all_rules.rb +1 -1
  90. data/lib/graphql/static_validation/base_visitor.rb +90 -66
  91. data/lib/graphql/static_validation/rules/argument_literals_are_compatible.rb +1 -1
  92. data/lib/graphql/static_validation/rules/argument_names_are_unique.rb +18 -6
  93. data/lib/graphql/static_validation/rules/arguments_are_defined.rb +5 -2
  94. data/lib/graphql/static_validation/rules/directives_are_defined.rb +5 -2
  95. data/lib/graphql/static_validation/rules/fields_are_defined_on_type.rb +4 -3
  96. data/lib/graphql/static_validation/rules/fields_have_appropriate_selections.rb +14 -4
  97. data/lib/graphql/static_validation/rules/fields_will_merge.rb +322 -256
  98. data/lib/graphql/static_validation/rules/fields_will_merge_error.rb +4 -4
  99. data/lib/graphql/static_validation/rules/fragment_spreads_are_possible.rb +3 -3
  100. data/lib/graphql/static_validation/rules/fragment_types_exist.rb +10 -7
  101. data/lib/graphql/static_validation/rules/required_arguments_are_present.rb +27 -7
  102. data/lib/graphql/static_validation/rules/unique_directives_per_location.rb +6 -2
  103. data/lib/graphql/static_validation/rules/variables_are_input_types.rb +12 -9
  104. data/lib/graphql/static_validation/validation_context.rb +1 -1
  105. data/lib/graphql/subscriptions/action_cable_subscriptions.rb +1 -0
  106. data/lib/graphql/subscriptions/default_subscription_resolve_extension.rb +34 -10
  107. data/lib/graphql/subscriptions/event.rb +1 -0
  108. data/lib/graphql/subscriptions.rb +36 -1
  109. data/lib/graphql/testing/helpers.rb +12 -9
  110. data/lib/graphql/testing/mock_action_cable.rb +111 -0
  111. data/lib/graphql/testing.rb +1 -0
  112. data/lib/graphql/tracing/detailed_trace/active_record_backend.rb +74 -0
  113. data/lib/graphql/tracing/detailed_trace.rb +70 -7
  114. data/lib/graphql/tracing/null_trace.rb +1 -1
  115. data/lib/graphql/tracing/perfetto_trace.rb +209 -79
  116. data/lib/graphql/tracing/sentry_trace.rb +3 -1
  117. data/lib/graphql/tracing/trace.rb +6 -0
  118. data/lib/graphql/type_kinds.rb +1 -0
  119. data/lib/graphql/types/relay/connection_behaviors.rb +8 -6
  120. data/lib/graphql/types/relay/edge_behaviors.rb +4 -3
  121. data/lib/graphql/types/relay/has_node_field.rb +13 -8
  122. data/lib/graphql/types/relay/has_nodes_field.rb +13 -8
  123. data/lib/graphql/types/relay/node_behaviors.rb +13 -2
  124. data/lib/graphql/unauthorized_error.rb +9 -1
  125. data/lib/graphql/version.rb +1 -1
  126. data/lib/graphql.rb +7 -3
  127. metadata +21 -3
@@ -14,29 +14,52 @@ module GraphQL
14
14
  cls.extend(ClassConfigured)
15
15
  end
16
16
 
17
- # @see {GraphQL::Schema::Argument#initialize} for parameters
18
- # @return [GraphQL::Schema::Argument] An instance of {argument_class}, created from `*args`
19
- def argument(*args, **kwargs, &block)
20
- kwargs[:owner] = self
21
- loads = kwargs[:loads]
22
- if loads
23
- name = args[0]
24
- name_as_string = name.to_s
25
-
26
- inferred_arg_name = case name_as_string
17
+ # @param arg_name [Symbol] The underscore-cased name of this argument, `name:` keyword also accepted
18
+ # @param type_expr The GraphQL type of this argument; `type:` keyword also accepted
19
+ # @param desc [String] Argument description, `description:` keyword also accepted
20
+ # @option kwargs [Boolean, :nullable] :required if true, this argument is non-null; if false, this argument is nullable. If `:nullable`, then the argument must be provided, though it may be `null`.
21
+ # @option kwargs [String] :description Positional argument also accepted
22
+ # @option kwargs [Class, Array<Class>] :type Input type; positional argument also accepted
23
+ # @option kwargs [Symbol] :name positional argument also accepted
24
+ # @option kwargs [Object] :default_value
25
+ # @option kwargs [Class, Array<Class>] :loads A GraphQL type to load for the given ID when one is present
26
+ # @option kwargs [Symbol] :as Override the keyword name when passed to a method
27
+ # @option kwargs [Symbol] :prepare A method to call to transform this argument's valuebefore sending it to field resolution
28
+ # @option kwargs [Boolean] :camelize if true, the name will be camelized when building the schema
29
+ # @option kwargs [Boolean] :from_resolver if true, a Resolver class defined this argument
30
+ # @option kwargs [Hash{Class => Hash}] :directives
31
+ # @option kwargs [String] :deprecation_reason
32
+ # @option kwargs [String] :comment Private, used by GraphQL-Ruby when parsing GraphQL schema files
33
+ # @option kwargs [GraphQL::Language::Nodes::InputValueDefinition] :ast_node Private, used by GraphQL-Ruby when parsing schema files
34
+ # @option kwargs [Hash, nil] :validates Options for building validators, if any should be applied
35
+ # @option kwargs [Boolean] :replace_null_with_default if `true`, incoming values of `null` will be replaced with the configured `default_value`
36
+ # @param definition_block [Proc] Called with the newly-created {Argument}
37
+ # @param kwargs [Hash] Keywords for defining an argument. Any keywords not documented here must be handled by your base Argument class.
38
+ # @return [GraphQL::Schema::Argument] An instance of {argument_class} created from these arguments
39
+ def argument(arg_name = nil, type_expr = nil, desc = nil, **kwargs, &definition_block)
40
+ if kwargs[:loads]
41
+ loads_name = arg_name || kwargs[:name]
42
+ loads_name_as_string = loads_name.to_s
43
+
44
+ inferred_arg_name = case loads_name_as_string
27
45
  when /_id$/
28
- name_as_string.sub(/_id$/, "").to_sym
46
+ loads_name_as_string.sub(/_id$/, "").to_sym
29
47
  when /_ids$/
30
- name_as_string.sub(/_ids$/, "")
48
+ loads_name_as_string.sub(/_ids$/, "")
31
49
  .sub(/([^s])$/, "\\1s")
32
50
  .to_sym
33
51
  else
34
- name
52
+ loads_name
35
53
  end
36
54
 
37
55
  kwargs[:as] ||= inferred_arg_name
38
56
  end
39
- arg_defn = self.argument_class.new(*args, **kwargs, &block)
57
+ kwargs[:owner] = self
58
+ arg_defn = self.argument_class.new(
59
+ arg_name, type_expr, desc,
60
+ **kwargs,
61
+ &definition_block
62
+ )
40
63
  add_argument(arg_defn)
41
64
  arg_defn
42
65
  end
@@ -413,6 +436,12 @@ module GraphQL
413
436
  end
414
437
  end
415
438
 
439
+ # Called when an argument's `loads:` configuration fails to fetch an application object.
440
+ # By default, this method raises the given error, but you can override it to handle failures differently.
441
+ #
442
+ # @param err [GraphQL::LoadApplicationObjectFailedError] The error that occurred
443
+ # @return [Object, nil] If a value is returned, it will be used instead of the failed load
444
+ # @api public
416
445
  def load_application_object_failed(err)
417
446
  raise err
418
447
  end
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+ module GraphQL
3
+ class Schema
4
+ class Member
5
+ module HasAuthorization
6
+ def self.included(child_class)
7
+ child_class.include(InstanceConfigured)
8
+ end
9
+
10
+ def self.extended(child_class)
11
+ child_class.extend(ClassConfigured)
12
+ child_class.class_exec do
13
+ @authorizes = false
14
+ end
15
+ end
16
+
17
+ def authorized?(object, context)
18
+ true
19
+ end
20
+
21
+ module InstanceConfigured
22
+ def authorizes?(context)
23
+ raise RequiredImplementationMissingError, "#{self.class} must implement #authorizes?(context)"
24
+ end
25
+ end
26
+
27
+ module ClassConfigured
28
+ def authorizes?(context)
29
+ method(:authorized?).owner != GraphQL::Schema::Member::HasAuthorization
30
+ end
31
+ end
32
+ end
33
+ end
34
+ end
35
+ end
@@ -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
- # @see {GraphQL::Schema::Field#initialize} for method signature
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(*args, **kwargs, &block)
12
- field_defn = field_class.from_options(*args, owner: self, **kwargs, &block)
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."
@@ -8,8 +8,8 @@ module GraphQL
8
8
  new_memberships = []
9
9
  new_interfaces.each do |int|
10
10
  if int.is_a?(Module)
11
- unless int.include?(GraphQL::Schema::Interface)
12
- raise "#{int} cannot be implemented since it's not a GraphQL Interface. Use `include` for plain Ruby modules."
11
+ unless int.include?(GraphQL::Schema::Interface) && !int.is_a?(Class)
12
+ raise "#{int.respond_to?(:graphql_name) ? "#{int.graphql_name} (#{int})" : int.inspect} cannot be implemented since it's not a GraphQL Interface. Use `include` for plain Ruby modules."
13
13
  end
14
14
 
15
15
  new_memberships << int.type_membership_class.new(int, self, **options)
@@ -12,12 +12,26 @@ module GraphQL
12
12
 
13
13
  # @return [Schema::NonNull] Make a non-null-type representation of this type
14
14
  def to_non_null_type
15
- @to_non_null_type ||= GraphQL::Schema::NonNull.new(self)
15
+ @to_non_null_type || begin
16
+ t = GraphQL::Schema::NonNull.new(self)
17
+ if frozen?
18
+ t
19
+ else
20
+ @to_non_null_type = t
21
+ end
22
+ end
16
23
  end
17
24
 
18
25
  # @return [Schema::List] Make a list-type representation of this type
19
26
  def to_list_type
20
- @to_list_type ||= GraphQL::Schema::List.new(self)
27
+ @to_list_type || begin
28
+ t = GraphQL::Schema::List.new(self)
29
+ if frozen?
30
+ t
31
+ else
32
+ @to_list_type = t
33
+ end
34
+ end
21
35
  end
22
36
 
23
37
  # @return [Boolean] true if this is a non-nullable type. A nullable list of non-nullables is considered nullable.
@@ -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
@@ -24,7 +24,7 @@ module GraphQL
24
24
  end
25
25
 
26
26
  def to_type_signature
27
- "#{@of_type.to_type_signature}!"
27
+ @type_signature ||= -"#{@of_type.to_type_signature}!"
28
28
  end
29
29
 
30
30
  def inspect
@@ -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
@@ -0,0 +1,79 @@
1
+ # frozen_string_literal: true
2
+ module GraphQL
3
+ class Schema
4
+ module RactorShareable
5
+ def self.extended(schema_class)
6
+ schema_class.extend(SchemaExtension)
7
+ schema_class.freeze_schema
8
+ end
9
+
10
+ module SchemaExtension
11
+
12
+ def freeze_error_handlers(handlers)
13
+ handlers[:subclass_handlers].default_proc = nil
14
+ handlers[:subclass_handlers].each do |_class, subclass_handlers|
15
+ freeze_error_handlers(subclass_handlers)
16
+ end
17
+ Ractor.make_shareable(handlers)
18
+ end
19
+
20
+ def freeze_schema
21
+ # warm some ivars:
22
+ default_analysis_engine
23
+ default_execution_strategy
24
+ GraphQL.default_parser
25
+ default_logger
26
+ freeze_error_handlers(error_handlers)
27
+ # TODO: this freezes errors of parent classes which could cause trouble
28
+ parent_class = superclass
29
+ while parent_class.respond_to?(:error_handlers)
30
+ freeze_error_handlers(parent_class.error_handlers)
31
+ parent_class = parent_class.superclass
32
+ end
33
+
34
+ own_tracers.freeze
35
+ @frozen_tracers = tracers.freeze
36
+ own_trace_modes.each do |m|
37
+ trace_options_for(m)
38
+ build_trace_mode(m)
39
+ end
40
+ build_trace_mode(:default)
41
+ Ractor.make_shareable(@trace_options_for_mode)
42
+ Ractor.make_shareable(own_trace_modes)
43
+ Ractor.make_shareable(own_multiplex_analyzers)
44
+ @frozen_multiplex_analyzers = Ractor.make_shareable(multiplex_analyzers)
45
+ Ractor.make_shareable(own_query_analyzers)
46
+ @frozen_query_analyzers = Ractor.make_shareable(query_analyzers)
47
+ Ractor.make_shareable(own_plugins)
48
+ own_plugins.each do |(plugin, options)|
49
+ Ractor.make_shareable(plugin)
50
+ Ractor.make_shareable(options)
51
+ end
52
+ @frozen_plugins = Ractor.make_shareable(plugins)
53
+ Ractor.make_shareable(own_references_to)
54
+ @frozen_directives = Ractor.make_shareable(directives)
55
+
56
+ Ractor.make_shareable(visibility)
57
+ Ractor.make_shareable(introspection_system)
58
+ extend(FrozenMethods)
59
+
60
+ Ractor.make_shareable(self)
61
+ superclass.respond_to?(:freeze_schema) && superclass.freeze_schema
62
+ end
63
+
64
+ module FrozenMethods
65
+ def tracers; @frozen_tracers; end
66
+ def multiplex_analyzers; @frozen_multiplex_analyzers; end
67
+ def query_analyzers; @frozen_query_analyzers; end
68
+ def plugins; @frozen_plugins; end
69
+ def directives; @frozen_directives; end
70
+
71
+ # This actually accumulates info during execution...
72
+ # How to support it?
73
+ def lazy?(_obj); false; end
74
+ def sync_lazy(obj); obj; end
75
+ end
76
+ end
77
+ end
78
+ end
79
+ end
@@ -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
- attr_reader :object
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:`. TODO extract this into subscription-related code somehow?
84
+ @original_arguments = @field_resolve_step.runner.input_values[q].argument_values(@field, @field_resolve_step.ast_node.arguments, nil)
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