graphql 2.0.28 → 2.2.11

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 (133) hide show
  1. checksums.yaml +4 -4
  2. data/lib/generators/graphql/install/templates/base_mutation.erb +2 -0
  3. data/lib/generators/graphql/install/templates/mutation_type.erb +2 -0
  4. data/lib/generators/graphql/install_generator.rb +3 -0
  5. data/lib/generators/graphql/templates/base_argument.erb +2 -0
  6. data/lib/generators/graphql/templates/base_connection.erb +2 -0
  7. data/lib/generators/graphql/templates/base_edge.erb +2 -0
  8. data/lib/generators/graphql/templates/base_enum.erb +2 -0
  9. data/lib/generators/graphql/templates/base_field.erb +2 -0
  10. data/lib/generators/graphql/templates/base_input_object.erb +2 -0
  11. data/lib/generators/graphql/templates/base_interface.erb +2 -0
  12. data/lib/generators/graphql/templates/base_object.erb +2 -0
  13. data/lib/generators/graphql/templates/base_resolver.erb +6 -0
  14. data/lib/generators/graphql/templates/base_scalar.erb +2 -0
  15. data/lib/generators/graphql/templates/base_union.erb +2 -0
  16. data/lib/generators/graphql/templates/graphql_controller.erb +2 -0
  17. data/lib/generators/graphql/templates/loader.erb +2 -0
  18. data/lib/generators/graphql/templates/mutation.erb +2 -0
  19. data/lib/generators/graphql/templates/node_type.erb +2 -0
  20. data/lib/generators/graphql/templates/query_type.erb +2 -0
  21. data/lib/generators/graphql/templates/schema.erb +2 -0
  22. data/lib/graphql/analysis/ast/analyzer.rb +7 -0
  23. data/lib/graphql/analysis/ast/field_usage.rb +32 -7
  24. data/lib/graphql/analysis/ast/query_complexity.rb +80 -128
  25. data/lib/graphql/analysis/ast/query_depth.rb +7 -2
  26. data/lib/graphql/analysis/ast/visitor.rb +2 -2
  27. data/lib/graphql/analysis/ast.rb +21 -11
  28. data/lib/graphql/backtrace/trace.rb +12 -15
  29. data/lib/graphql/coercion_error.rb +1 -9
  30. data/lib/graphql/dataloader/async_dataloader.rb +85 -0
  31. data/lib/graphql/dataloader/request.rb +5 -0
  32. data/lib/graphql/dataloader/source.rb +11 -3
  33. data/lib/graphql/dataloader.rb +109 -142
  34. data/lib/graphql/duration_encoding_error.rb +16 -0
  35. data/lib/graphql/execution/interpreter/runtime/graphql_result.rb +170 -0
  36. data/lib/graphql/execution/interpreter/runtime.rb +79 -248
  37. data/lib/graphql/execution/interpreter.rb +91 -157
  38. data/lib/graphql/execution/lookahead.rb +88 -21
  39. data/lib/graphql/introspection/dynamic_fields.rb +1 -1
  40. data/lib/graphql/introspection/entry_points.rb +11 -5
  41. data/lib/graphql/introspection/schema_type.rb +3 -1
  42. data/lib/graphql/language/block_string.rb +34 -18
  43. data/lib/graphql/language/definition_slice.rb +1 -1
  44. data/lib/graphql/language/document_from_schema_definition.rb +37 -37
  45. data/lib/graphql/language/lexer.rb +271 -177
  46. data/lib/graphql/language/nodes.rb +75 -57
  47. data/lib/graphql/language/parser.rb +707 -1986
  48. data/lib/graphql/language/printer.rb +303 -146
  49. data/lib/graphql/language/sanitized_printer.rb +20 -22
  50. data/lib/graphql/language/static_visitor.rb +167 -0
  51. data/lib/graphql/language/visitor.rb +20 -81
  52. data/lib/graphql/language.rb +1 -0
  53. data/lib/graphql/load_application_object_failed_error.rb +5 -1
  54. data/lib/graphql/pagination/array_connection.rb +3 -3
  55. data/lib/graphql/pagination/connection.rb +28 -1
  56. data/lib/graphql/pagination/mongoid_relation_connection.rb +1 -2
  57. data/lib/graphql/pagination/relation_connection.rb +3 -3
  58. data/lib/graphql/query/context/scoped_context.rb +101 -0
  59. data/lib/graphql/query/context.rb +36 -98
  60. data/lib/graphql/query/null_context.rb +4 -11
  61. data/lib/graphql/query/validation_pipeline.rb +2 -2
  62. data/lib/graphql/query/variables.rb +3 -3
  63. data/lib/graphql/query.rb +13 -22
  64. data/lib/graphql/railtie.rb +9 -6
  65. data/lib/graphql/rake_task.rb +3 -12
  66. data/lib/graphql/schema/argument.rb +6 -1
  67. data/lib/graphql/schema/base_64_encoder.rb +3 -5
  68. data/lib/graphql/schema/build_from_definition.rb +0 -11
  69. data/lib/graphql/schema/directive/one_of.rb +12 -0
  70. data/lib/graphql/schema/directive/specified_by.rb +14 -0
  71. data/lib/graphql/schema/directive.rb +1 -1
  72. data/lib/graphql/schema/enum.rb +3 -3
  73. data/lib/graphql/schema/field/connection_extension.rb +1 -15
  74. data/lib/graphql/schema/field/scope_extension.rb +8 -1
  75. data/lib/graphql/schema/field.rb +39 -35
  76. data/lib/graphql/schema/has_single_input_argument.rb +156 -0
  77. data/lib/graphql/schema/input_object.rb +2 -2
  78. data/lib/graphql/schema/interface.rb +15 -11
  79. data/lib/graphql/schema/introspection_system.rb +2 -0
  80. data/lib/graphql/schema/loader.rb +0 -2
  81. data/lib/graphql/schema/member/base_dsl_methods.rb +2 -1
  82. data/lib/graphql/schema/member/has_arguments.rb +61 -38
  83. data/lib/graphql/schema/member/has_fields.rb +8 -5
  84. data/lib/graphql/schema/member/has_interfaces.rb +23 -9
  85. data/lib/graphql/schema/member/scoped.rb +19 -0
  86. data/lib/graphql/schema/member/validates_input.rb +3 -3
  87. data/lib/graphql/schema/object.rb +8 -0
  88. data/lib/graphql/schema/printer.rb +8 -7
  89. data/lib/graphql/schema/relay_classic_mutation.rb +6 -128
  90. data/lib/graphql/schema/resolver.rb +16 -8
  91. data/lib/graphql/schema/scalar.rb +3 -3
  92. data/lib/graphql/schema/subscription.rb +11 -4
  93. data/lib/graphql/schema/union.rb +1 -1
  94. data/lib/graphql/schema/unique_within_type.rb +1 -1
  95. data/lib/graphql/schema/warden.rb +96 -94
  96. data/lib/graphql/schema.rb +252 -78
  97. data/lib/graphql/static_validation/all_rules.rb +1 -1
  98. data/lib/graphql/static_validation/base_visitor.rb +1 -1
  99. data/lib/graphql/static_validation/literal_validator.rb +2 -3
  100. data/lib/graphql/static_validation/rules/fields_will_merge.rb +1 -1
  101. data/lib/graphql/static_validation/rules/required_arguments_are_present.rb +1 -1
  102. data/lib/graphql/static_validation/rules/required_input_object_attributes_are_present.rb +2 -2
  103. data/lib/graphql/static_validation/validation_context.rb +5 -5
  104. data/lib/graphql/static_validation/validator.rb +3 -0
  105. data/lib/graphql/static_validation.rb +0 -1
  106. data/lib/graphql/subscriptions/action_cable_subscriptions.rb +3 -2
  107. data/lib/graphql/subscriptions/event.rb +8 -2
  108. data/lib/graphql/subscriptions/serialize.rb +2 -0
  109. data/lib/graphql/subscriptions.rb +14 -12
  110. data/lib/graphql/testing/helpers.rb +129 -0
  111. data/lib/graphql/testing.rb +2 -0
  112. data/lib/graphql/tracing/appoptics_trace.rb +2 -2
  113. data/lib/graphql/tracing/appoptics_tracing.rb +2 -2
  114. data/lib/graphql/tracing/legacy_hooks_trace.rb +74 -0
  115. data/lib/graphql/tracing/platform_tracing.rb +2 -0
  116. data/lib/graphql/tracing/{prometheus_tracing → prometheus_trace}/graphql_collector.rb +3 -1
  117. data/lib/graphql/tracing/sentry_trace.rb +112 -0
  118. data/lib/graphql/tracing/trace.rb +1 -0
  119. data/lib/graphql/tracing.rb +3 -1
  120. data/lib/graphql/types/iso_8601_duration.rb +77 -0
  121. data/lib/graphql/types/relay/connection_behaviors.rb +32 -2
  122. data/lib/graphql/types/relay/edge_behaviors.rb +7 -0
  123. data/lib/graphql/types.rb +1 -0
  124. data/lib/graphql/version.rb +1 -1
  125. data/lib/graphql.rb +6 -5
  126. data/readme.md +12 -2
  127. metadata +46 -38
  128. data/lib/graphql/deprecation.rb +0 -9
  129. data/lib/graphql/filter.rb +0 -59
  130. data/lib/graphql/language/parser.y +0 -560
  131. data/lib/graphql/schema/base_64_bp.rb +0 -26
  132. data/lib/graphql/static_validation/type_stack.rb +0 -216
  133. data/lib/graphql/subscriptions/instrumentation.rb +0 -28
@@ -138,7 +138,7 @@ module GraphQL
138
138
  # As a last ditch, try to force loading the return type:
139
139
  type.unwrap.name
140
140
  end
141
- @connection = return_type_name.end_with?("Connection")
141
+ @connection = return_type_name.end_with?("Connection") && return_type_name != "Connection"
142
142
  else
143
143
  @connection
144
144
  end
@@ -218,8 +218,8 @@ module GraphQL
218
218
  # @param ast_node [Language::Nodes::FieldDefinition, nil] If this schema was parsed from definition, this AST node defined the field
219
219
  # @param method_conflict_warning [Boolean] If false, skip the warning if this field's method conflicts with a built-in method
220
220
  # @param validates [Array<Hash>] Configurations for validating this field
221
- # @fallback_value [Object] A fallback value if the method is not defined
222
- def initialize(type: nil, name: nil, owner: nil, null: nil, description: NOT_CONFIGURED, deprecation_reason: nil, method: nil, hash_key: nil, dig: nil, resolver_method: nil, connection: nil, max_page_size: NOT_CONFIGURED, default_page_size: NOT_CONFIGURED, scope: nil, introspection: false, camelize: true, trace: nil, complexity: nil, ast_node: nil, extras: EMPTY_ARRAY, extensions: EMPTY_ARRAY, connection_extension: self.class.connection_extension, resolver_class: nil, subscription_scope: nil, relay_node_field: false, relay_nodes_field: false, method_conflict_warning: true, broadcastable: NOT_CONFIGURED, arguments: EMPTY_HASH, directives: EMPTY_HASH, validates: EMPTY_ARRAY, fallback_value: NOT_CONFIGURED, &definition_block)
221
+ # @param fallback_value [Object] A fallback value if the method is not defined
222
+ def initialize(type: nil, name: nil, owner: nil, null: nil, description: NOT_CONFIGURED, deprecation_reason: nil, method: nil, hash_key: nil, dig: nil, resolver_method: nil, connection: nil, max_page_size: NOT_CONFIGURED, default_page_size: NOT_CONFIGURED, scope: nil, introspection: false, camelize: true, trace: nil, complexity: nil, ast_node: nil, extras: EMPTY_ARRAY, extensions: EMPTY_ARRAY, connection_extension: self.class.connection_extension, resolver_class: nil, subscription_scope: nil, relay_node_field: false, relay_nodes_field: false, method_conflict_warning: true, broadcastable: NOT_CONFIGURED, arguments: EMPTY_HASH, directives: EMPTY_HASH, validates: EMPTY_ARRAY, fallback_value: NOT_CONFIGURED, dynamic_introspection: false, &definition_block)
223
223
  if name.nil?
224
224
  raise ArgumentError, "missing first `name` argument or keyword `name:`"
225
225
  end
@@ -267,6 +267,7 @@ module GraphQL
267
267
  @method_sym = method_name.to_sym
268
268
  @resolver_method = (resolver_method || name_s).to_sym
269
269
  @complexity = complexity
270
+ @dynamic_introspection = dynamic_introspection
270
271
  @return_type_expr = type
271
272
  @return_type_null = if !null.nil?
272
273
  null
@@ -351,6 +352,8 @@ module GraphQL
351
352
  @call_after_define = true
352
353
  end
353
354
 
355
+ attr_accessor :dynamic_introspection
356
+
354
357
  # If true, subscription updates with this field can be shared between viewers
355
358
  # @return [Boolean, nil]
356
359
  # @see GraphQL::Subscriptions::BroadcastAnalyzer
@@ -480,41 +483,24 @@ module GraphQL
480
483
  metadata_complexity = 0
481
484
  lookahead = GraphQL::Execution::Lookahead.new(query: query, field: self, ast_nodes: nodes, owner_type: owner)
482
485
 
483
- if (page_info_lookahead = lookahead.selection(:page_info)).selected?
484
- metadata_complexity += 1 # pageInfo
485
- metadata_complexity += page_info_lookahead.selections.size # subfields
486
- end
487
-
488
- if lookahead.selects?(:total) || lookahead.selects?(:total_count) || lookahead.selects?(:count)
489
- metadata_complexity += 1
486
+ lookahead.selections.each do |next_lookahead|
487
+ # this includes `pageInfo`, `nodes` and `edges` and any custom fields
488
+ # TODO this doesn't support procs yet -- unlikely to need it.
489
+ metadata_complexity += next_lookahead.field.complexity
490
+ if next_lookahead.name != :nodes && next_lookahead.name != :edges
491
+ # subfields, eg, for pageInfo -- assumes no subselections
492
+ metadata_complexity += next_lookahead.selections.size
493
+ end
490
494
  end
491
495
 
492
- nodes_edges_complexity = 0
493
- nodes_edges_complexity += 1 if lookahead.selects?(:edges)
494
- nodes_edges_complexity += 1 if lookahead.selects?(:nodes)
495
-
496
496
  # Possible bug: selections on `edges` and `nodes` are _both_ multiplied here. Should they be?
497
- items_complexity = child_complexity - metadata_complexity - nodes_edges_complexity
498
- # Add 1 for _this_ field
499
- 1 + (max_possible_page_size * items_complexity) + metadata_complexity + nodes_edges_complexity
497
+ items_complexity = child_complexity - metadata_complexity
498
+ subfields_complexity = (max_possible_page_size * items_complexity) + metadata_complexity
499
+ # Apply this field's own complexity
500
+ apply_own_complexity_to(subfields_complexity, query, nodes)
500
501
  end
501
502
  else
502
- defined_complexity = complexity
503
- case defined_complexity
504
- when Proc
505
- arguments = query.arguments_for(nodes.first, self)
506
- if arguments.is_a?(GraphQL::ExecutionError)
507
- return child_complexity
508
- elsif arguments.respond_to?(:keyword_arguments)
509
- arguments = arguments.keyword_arguments
510
- end
511
-
512
- defined_complexity.call(query.context, arguments, child_complexity)
513
- when Numeric
514
- defined_complexity + child_complexity
515
- else
516
- raise("Invalid complexity: #{defined_complexity.inspect} on #{path} (#{inspect})")
517
- end
503
+ apply_own_complexity_to(child_complexity, query, nodes)
518
504
  end
519
505
  end
520
506
 
@@ -659,7 +645,7 @@ module GraphQL
659
645
  method_to_call = nil
660
646
  method_args = nil
661
647
 
662
- Schema::Validator.validate!(validators, application_object, query_ctx, args)
648
+ @own_validators && Schema::Validator.validate!(validators, application_object, query_ctx, args)
663
649
 
664
650
  query_ctx.query.after_lazy(self.authorized?(application_object, args, query_ctx)) do |is_authorized|
665
651
  if is_authorized
@@ -701,7 +687,7 @@ module GraphQL
701
687
  inner_object.dig(*@dig_keys)
702
688
  elsif inner_object.key?(@method_sym)
703
689
  inner_object[@method_sym]
704
- elsif inner_object.key?(@method_str)
690
+ elsif inner_object.key?(@method_str) || !inner_object.default_proc.nil?
705
691
  inner_object[@method_str]
706
692
  elsif @fallback_value != NOT_CONFIGURED
707
693
  @fallback_value
@@ -879,6 +865,24 @@ ERR
879
865
  yield(obj, args)
880
866
  end
881
867
  end
868
+
869
+ def apply_own_complexity_to(child_complexity, query, nodes)
870
+ case (own_complexity = complexity)
871
+ when Numeric
872
+ own_complexity + child_complexity
873
+ when Proc
874
+ arguments = query.arguments_for(nodes.first, self)
875
+ if arguments.is_a?(GraphQL::ExecutionError)
876
+ return child_complexity
877
+ elsif arguments.respond_to?(:keyword_arguments)
878
+ arguments = arguments.keyword_arguments
879
+ end
880
+
881
+ own_complexity.call(query.context, arguments, child_complexity)
882
+ else
883
+ raise ArgumentError, "Invalid complexity for #{self.path}: #{own_complexity.inspect}"
884
+ end
885
+ end
882
886
  end
883
887
  end
884
888
  end
@@ -0,0 +1,156 @@
1
+ # frozen_string_literal: true
2
+
3
+ module GraphQL
4
+ class Schema
5
+ module HasSingleInputArgument
6
+ def resolve_with_support(**inputs)
7
+ if inputs[:input].is_a?(InputObject)
8
+ input = inputs[:input].to_kwargs
9
+ else
10
+ input = inputs[:input]
11
+ end
12
+
13
+ new_extras = field ? field.extras : []
14
+ all_extras = self.class.extras + new_extras
15
+
16
+ # Transfer these from the top-level hash to the
17
+ # shortcutted `input:` object
18
+ all_extras.each do |ext|
19
+ # It's possible that the `extra` was not passed along by this point,
20
+ # don't re-add it if it wasn't given here.
21
+ if inputs.key?(ext)
22
+ input[ext] = inputs[ext]
23
+ end
24
+ end
25
+
26
+ if input
27
+ # This is handled by Relay::Mutation::Resolve, a bit hacky, but here we are.
28
+ input_kwargs = input.to_h
29
+ else
30
+ # Relay Classic Mutations with no `argument`s
31
+ # don't require `input:`
32
+ input_kwargs = {}
33
+ end
34
+
35
+ if input_kwargs.any?
36
+ super(**input_kwargs)
37
+ else
38
+ super()
39
+ end
40
+ end
41
+
42
+ def self.included(base)
43
+ base.extend(ClassMethods)
44
+ end
45
+
46
+ module ClassMethods
47
+ def dummy
48
+ @dummy ||= begin
49
+ d = Class.new(GraphQL::Schema::Resolver)
50
+ d.argument_class(self.argument_class)
51
+ # TODO make this lazier?
52
+ d.argument(:input, input_type, description: "Parameters for #{self.graphql_name}")
53
+ d
54
+ end
55
+ end
56
+
57
+ def field_arguments(context = GraphQL::Query::NullContext.instance)
58
+ dummy.arguments(context)
59
+ end
60
+
61
+ def get_field_argument(name, context = GraphQL::Query::NullContext.instance)
62
+ dummy.get_argument(name, context)
63
+ end
64
+
65
+ def own_field_arguments
66
+ dummy.own_arguments
67
+ end
68
+
69
+ def any_field_arguments?
70
+ dummy.any_arguments?
71
+ end
72
+
73
+ def all_field_argument_definitions
74
+ dummy.all_argument_definitions
75
+ end
76
+
77
+ # Also apply this argument to the input type:
78
+ def argument(*args, own_argument: false, **kwargs, &block)
79
+ it = input_type # make sure any inherited arguments are already added to it
80
+ arg = super(*args, **kwargs, &block)
81
+
82
+ # This definition might be overriding something inherited;
83
+ # if it is, remove the inherited definition so it's not confused at runtime as having multiple definitions
84
+ prev_args = it.own_arguments[arg.graphql_name]
85
+ case prev_args
86
+ when GraphQL::Schema::Argument
87
+ if prev_args.owner != self
88
+ it.own_arguments.delete(arg.graphql_name)
89
+ end
90
+ when Array
91
+ prev_args.reject! { |a| a.owner != self }
92
+ if prev_args.empty?
93
+ it.own_arguments.delete(arg.graphql_name)
94
+ end
95
+ end
96
+
97
+ it.add_argument(arg)
98
+ arg
99
+ end
100
+
101
+ # The base class for generated input object types
102
+ # @param new_class [Class] The base class to use for generating input object definitions
103
+ # @return [Class] The base class for this mutation's generated input object (default is {GraphQL::Schema::InputObject})
104
+ def input_object_class(new_class = nil)
105
+ if new_class
106
+ @input_object_class = new_class
107
+ end
108
+ @input_object_class || (superclass.respond_to?(:input_object_class) ? superclass.input_object_class : GraphQL::Schema::InputObject)
109
+ end
110
+
111
+ # @param new_input_type [Class, nil] If provided, it configures this mutation to accept `new_input_type` instead of generating an input type
112
+ # @return [Class] The generated {Schema::InputObject} class for this mutation's `input`
113
+ def input_type(new_input_type = nil)
114
+ if new_input_type
115
+ @input_type = new_input_type
116
+ end
117
+ @input_type ||= generate_input_type
118
+ end
119
+
120
+ private
121
+
122
+ # Generate the input type for the `input:` argument
123
+ # To customize how input objects are generated, override this method
124
+ # @return [Class] a subclass of {.input_object_class}
125
+ def generate_input_type
126
+ mutation_args = all_argument_definitions
127
+ mutation_class = self
128
+ Class.new(input_object_class) do
129
+ class << self
130
+ def default_graphql_name
131
+ "#{self.mutation.graphql_name}Input"
132
+ end
133
+
134
+ def description(new_desc = nil)
135
+ super || "Autogenerated input type of #{self.mutation.graphql_name}"
136
+ end
137
+ end
138
+ mutation(mutation_class)
139
+ # these might be inherited:
140
+ mutation_args.each do |arg|
141
+ add_argument(arg)
142
+ end
143
+ end
144
+ end
145
+ end
146
+
147
+ private
148
+
149
+ def authorize_arguments(args, values)
150
+ # remove the `input` wrapper to match values
151
+ input_args = args["input"].type.unwrap.arguments(context)
152
+ super(input_args, values)
153
+ end
154
+ end
155
+ end
156
+ end
@@ -79,7 +79,7 @@ module GraphQL
79
79
  end
80
80
 
81
81
  def self.one_of?
82
- directives.any? { |d| d.is_a?(GraphQL::Schema::Directive::OneOf) }
82
+ false # Re-defined when `OneOf` is added
83
83
  end
84
84
 
85
85
  def unwrap_value(value)
@@ -145,7 +145,7 @@ module GraphQL
145
145
  end
146
146
 
147
147
  # @api private
148
- INVALID_OBJECT_MESSAGE = "Expected %{object} to be a key-value object responding to `to_h` or `to_unsafe_h`."
148
+ INVALID_OBJECT_MESSAGE = "Expected %{object} to be a key-value object."
149
149
 
150
150
  def validate_non_null_input(input, ctx, max_errors: nil)
151
151
  warden = ctx.warden
@@ -20,6 +20,15 @@ module GraphQL
20
20
  # - Added as class methods to this interface
21
21
  # - Added as class methods to all child interfaces
22
22
  def definition_methods(&block)
23
+ # Use an instance variable to tell whether it's been included previously or not;
24
+ # You can't use constant detection because constants are brought into scope
25
+ # by `include`, which has already happened at this point.
26
+ if !defined?(@_definition_methods)
27
+ defn_methods_module = Module.new
28
+ @_definition_methods = defn_methods_module
29
+ const_set(:DefinitionMethods, defn_methods_module)
30
+ extend(self::DefinitionMethods)
31
+ end
23
32
  self::DefinitionMethods.module_eval(&block)
24
33
  end
25
34
 
@@ -47,20 +56,11 @@ module GraphQL
47
56
 
48
57
  child_class.type_membership_class(self.type_membership_class)
49
58
  child_class.ancestors.reverse_each do |ancestor|
50
- if ancestor.const_defined?(:DefinitionMethods)
59
+ if ancestor.const_defined?(:DefinitionMethods) && ancestor != child_class
51
60
  child_class.extend(ancestor::DefinitionMethods)
52
61
  end
53
62
  end
54
63
 
55
- # Use an instance variable to tell whether it's been included previously or not;
56
- # You can't use constant detection because constants are brought into scope
57
- # by `include`, which has already happened at this point.
58
- if !child_class.instance_variable_defined?(:@_definition_methods)
59
- defn_methods_module = Module.new
60
- child_class.instance_variable_set(:@_definition_methods, defn_methods_module)
61
- child_class.const_set(:DefinitionMethods, defn_methods_module)
62
- child_class.extend(child_class::DefinitionMethods)
63
- end
64
64
  child_class.introspection(introspection)
65
65
  child_class.description(description)
66
66
  # If interfaces are mixed into each other, only define this class once
@@ -69,7 +69,11 @@ module GraphQL
69
69
  end
70
70
  elsif child_class < GraphQL::Schema::Object
71
71
  # This is being included into an object type, make sure it's using `implements(...)`
72
- backtrace_line = caller(0, 10).find { |line| line.include?("schema/member/has_interfaces.rb") && line.include?("in `implements'")}
72
+ backtrace_line = caller_locations(0, 10).find do |location|
73
+ location.base_label == "implements" &&
74
+ location.path.end_with?("schema/member/has_interfaces.rb")
75
+ end
76
+
73
77
  if !backtrace_line
74
78
  raise "Attach interfaces using `implements(#{self})`, not `include(#{self})`"
75
79
  end
@@ -39,7 +39,9 @@ module GraphQL
39
39
  entry_point_fields.delete('__type') if schema.disable_type_introspection_entry_point?
40
40
  entry_point_fields
41
41
  end
42
+ @entry_point_fields.each { |k, v| v.dynamic_introspection = true }
42
43
  @dynamic_fields = get_fields_from_class(class_sym: :DynamicFields)
44
+ @dynamic_fields.each { |k, v| v.dynamic_introspection = true }
43
45
  end
44
46
 
45
47
  def entry_points
@@ -176,7 +176,6 @@ module GraphQL
176
176
  while (of_type = unwrapped_field_hash["ofType"])
177
177
  unwrapped_field_hash = of_type
178
178
  end
179
- type_name = unwrapped_field_hash["name"]
180
179
 
181
180
  type_defn.field(
182
181
  field_hash["name"],
@@ -186,7 +185,6 @@ module GraphQL
186
185
  null: true,
187
186
  camelize: false,
188
187
  connection_extension: nil,
189
- connection: type_name.end_with?("Connection"),
190
188
  ) do
191
189
  if field_hash["args"].any?
192
190
  loader.build_arguments(self, field_hash["args"], type_resolver)
@@ -102,7 +102,8 @@ module GraphQL
102
102
  def default_graphql_name
103
103
  @default_graphql_name ||= begin
104
104
  raise GraphQL::RequiredImplementationMissingError, 'Anonymous class should declare a `graphql_name`' if name.nil?
105
- -name.split("::").last.sub(/Type\Z/, "")
105
+ g_name = -name.split("::").last
106
+ g_name.end_with?("Type") ? g_name.sub(/Type\Z/, "") : g_name
106
107
  end
107
108
  end
108
109
 
@@ -109,7 +109,7 @@ module GraphQL
109
109
  end
110
110
 
111
111
  # @return [Hash<String => GraphQL::Schema::Argument] Arguments defined on this thing, keyed by name. Includes inherited definitions
112
- def arguments(context = GraphQL::Query::NullContext)
112
+ def arguments(context = GraphQL::Query::NullContext.instance)
113
113
  if own_arguments.any?
114
114
  own_arguments_that_apply = {}
115
115
  own_arguments.each do |name, args_entry|
@@ -122,6 +122,10 @@ module GraphQL
122
122
  own_arguments_that_apply || own_arguments
123
123
  end
124
124
 
125
+ def any_arguments?
126
+ own_arguments.any?
127
+ end
128
+
125
129
  module ClassConfigured
126
130
  def inherited(child_class)
127
131
  super
@@ -129,7 +133,7 @@ module GraphQL
129
133
  end
130
134
 
131
135
  module InheritedArguments
132
- def arguments(context = GraphQL::Query::NullContext)
136
+ def arguments(context = GraphQL::Query::NullContext.instance)
133
137
  own_arguments = super
134
138
  inherited_arguments = superclass.arguments(context)
135
139
 
@@ -145,6 +149,10 @@ module GraphQL
145
149
  end
146
150
  end
147
151
 
152
+ def any_arguments?
153
+ super || superclass.any_arguments?
154
+ end
155
+
148
156
  def all_argument_definitions
149
157
  all_defns = {}
150
158
  ancestors.reverse_each do |ancestor|
@@ -158,7 +166,7 @@ module GraphQL
158
166
  end
159
167
 
160
168
 
161
- def get_argument(argument_name, context = GraphQL::Query::NullContext)
169
+ def get_argument(argument_name, context = GraphQL::Query::NullContext.instance)
162
170
  warden = Warden.from_context(context)
163
171
  for ancestor in ancestors
164
172
  if ancestor.respond_to?(:own_arguments) &&
@@ -173,9 +181,9 @@ module GraphQL
173
181
  end
174
182
 
175
183
  module FieldConfigured
176
- def arguments(context = GraphQL::Query::NullContext)
184
+ def arguments(context = GraphQL::Query::NullContext.instance)
177
185
  own_arguments = super
178
- if defined?(@resolver_class) && @resolver_class
186
+ if @resolver_class
179
187
  inherited_arguments = @resolver_class.field_arguments(context)
180
188
  if own_arguments.any?
181
189
  if inherited_arguments.any?
@@ -191,8 +199,12 @@ module GraphQL
191
199
  end
192
200
  end
193
201
 
202
+ def any_arguments?
203
+ super || (@resolver_class && @resolver_class.any_field_arguments?)
204
+ end
205
+
194
206
  def all_argument_definitions
195
- if defined?(@resolver_class) && @resolver_class
207
+ if @resolver_class
196
208
  all_defns = {}
197
209
  @resolver_class.all_field_argument_definitions.each do |arg_defn|
198
210
  key = arg_defn.graphql_name
@@ -224,7 +236,7 @@ module GraphQL
224
236
  end
225
237
 
226
238
  # @return [GraphQL::Schema::Argument, nil] Argument defined on this thing, fetched by name.
227
- def get_argument(argument_name, context = GraphQL::Query::NullContext)
239
+ def get_argument(argument_name, context = GraphQL::Query::NullContext.instance)
228
240
  warden = Warden.from_context(context)
229
241
  if (arg_config = own_arguments[argument_name]) && (visible_arg = Warden.visible_entry?(:visible_argument?, arg_config, context, warden))
230
242
  visible_arg
@@ -367,41 +379,52 @@ module GraphQL
367
379
  def authorize_application_object(argument, id, context, loaded_application_object)
368
380
  context.query.after_lazy(loaded_application_object) do |application_object|
369
381
  if application_object.nil?
370
- err = GraphQL::LoadApplicationObjectFailedError.new(argument: argument, id: id, object: application_object)
371
- load_application_object_failed(err)
382
+ err = GraphQL::LoadApplicationObjectFailedError.new(context: context, argument: argument, id: id, object: application_object)
383
+ application_object = load_application_object_failed(err)
372
384
  end
373
385
  # Double-check that the located object is actually of this type
374
386
  # (Don't want to allow arbitrary access to objects this way)
375
- maybe_lazy_resolve_type = context.schema.resolve_type(argument.loads, application_object, context)
376
- context.query.after_lazy(maybe_lazy_resolve_type) do |resolve_type_result|
377
- if resolve_type_result.is_a?(Array) && resolve_type_result.size == 2
378
- application_object_type, application_object = resolve_type_result
379
- else
380
- application_object_type = resolve_type_result
381
- # application_object is already assigned
382
- end
383
- possible_object_types = context.warden.possible_types(argument.loads)
384
- if !possible_object_types.include?(application_object_type)
385
- err = GraphQL::LoadApplicationObjectFailedError.new(argument: argument, id: id, object: application_object)
386
- load_application_object_failed(err)
387
- else
388
- # This object was loaded successfully
389
- # and resolved to the right type,
390
- # now apply the `.authorized?` class method if there is one
391
- context.query.after_lazy(application_object_type.authorized?(application_object, context)) do |authed|
392
- if authed
393
- application_object
394
- else
395
- err = GraphQL::UnauthorizedError.new(
396
- object: application_object,
397
- type: application_object_type,
398
- context: context,
399
- )
400
- if self.respond_to?(:unauthorized_object)
401
- err.set_backtrace(caller)
402
- unauthorized_object(err)
387
+ if application_object.nil?
388
+ nil
389
+ else
390
+ maybe_lazy_resolve_type = context.schema.resolve_type(argument.loads, application_object, context)
391
+ context.query.after_lazy(maybe_lazy_resolve_type) do |resolve_type_result|
392
+ if resolve_type_result.is_a?(Array) && resolve_type_result.size == 2
393
+ application_object_type, application_object = resolve_type_result
394
+ else
395
+ application_object_type = resolve_type_result
396
+ # application_object is already assigned
397
+ end
398
+
399
+ if !(
400
+ context.warden.possible_types(argument.loads).include?(application_object_type) ||
401
+ context.warden.loadable?(argument.loads, context)
402
+ )
403
+ err = GraphQL::LoadApplicationObjectFailedError.new(context: context, argument: argument, id: id, object: application_object)
404
+ application_object = load_application_object_failed(err)
405
+ end
406
+
407
+ if application_object.nil?
408
+ nil
409
+ else
410
+ # This object was loaded successfully
411
+ # and resolved to the right type,
412
+ # now apply the `.authorized?` class method if there is one
413
+ context.query.after_lazy(application_object_type.authorized?(application_object, context)) do |authed|
414
+ if authed
415
+ application_object
403
416
  else
404
- raise err
417
+ err = GraphQL::UnauthorizedError.new(
418
+ object: application_object,
419
+ type: application_object_type,
420
+ context: context,
421
+ )
422
+ if self.respond_to?(:unauthorized_object)
423
+ err.set_backtrace(caller)
424
+ unauthorized_object(err)
425
+ else
426
+ raise err
427
+ end
405
428
  end
406
429
  end
407
430
  end
@@ -97,7 +97,7 @@ module GraphQL
97
97
  end
98
98
 
99
99
  module InterfaceMethods
100
- def get_field(field_name, context = GraphQL::Query::NullContext)
100
+ def get_field(field_name, context = GraphQL::Query::NullContext.instance)
101
101
  warden = Warden.from_context(context)
102
102
  for ancestor in ancestors
103
103
  if ancestor.respond_to?(:own_fields) &&
@@ -110,7 +110,7 @@ module GraphQL
110
110
  end
111
111
 
112
112
  # @return [Hash<String => GraphQL::Schema::Field>] Fields on this object, keyed by name, including inherited fields
113
- def fields(context = GraphQL::Query::NullContext)
113
+ def fields(context = GraphQL::Query::NullContext.instance)
114
114
  warden = Warden.from_context(context)
115
115
  # Local overrides take precedence over inherited fields
116
116
  visible_fields = {}
@@ -130,22 +130,25 @@ module GraphQL
130
130
  end
131
131
 
132
132
  module ObjectMethods
133
- def get_field(field_name, context = GraphQL::Query::NullContext)
133
+ def get_field(field_name, context = GraphQL::Query::NullContext.instance)
134
134
  # Objects need to check that the interface implementation is visible, too
135
135
  warden = Warden.from_context(context)
136
- for ancestor in ancestors
136
+ ancs = ancestors
137
+ i = 0
138
+ while (ancestor = ancs[i])
137
139
  if ancestor.respond_to?(:own_fields) &&
138
140
  visible_interface_implementation?(ancestor, context, warden) &&
139
141
  (f_entry = ancestor.own_fields[field_name]) &&
140
142
  (f = Warden.visible_entry?(:visible_field?, f_entry, context, warden))
141
143
  return f
142
144
  end
145
+ i += 1
143
146
  end
144
147
  nil
145
148
  end
146
149
 
147
150
  # @return [Hash<String => GraphQL::Schema::Field>] Fields on this object, keyed by name, including inherited fields
148
- def fields(context = GraphQL::Query::NullContext)
151
+ def fields(context = GraphQL::Query::NullContext.instance)
149
152
  # Objects need to check that the interface implementation is visible, too
150
153
  warden = Warden.from_context(context)
151
154
  # Local overrides take precedence over inherited fields