graphql 2.0.28 → 2.2.11

Sign up to get free protection for your applications and to get access to all the features.
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