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
@@ -7,22 +7,30 @@ module GraphQL
7
7
  "a name, potentially a list of arguments, and a return type."
8
8
  field :name, String, null: false
9
9
  field :description, String
10
- field :args, [GraphQL::Schema::LateBoundType.new("__InputValue")], null: false, scope: false do
10
+ field :args, [GraphQL::Schema::LateBoundType.new("__InputValue")], null: false, scope: false, resolve_each: :resolve_args do
11
11
  argument :include_deprecated, Boolean, required: false, default_value: false
12
12
  end
13
13
  field :type, GraphQL::Schema::LateBoundType.new("__Type"), null: false
14
- field :is_deprecated, Boolean, null: false
14
+ field :is_deprecated, Boolean, null: false, resolve_each: :resolve_is_deprecated
15
15
  field :deprecation_reason, String
16
16
 
17
+ def self.resolve_is_deprecated(object, _context)
18
+ !!object.deprecation_reason
19
+ end
20
+
17
21
  def is_deprecated
18
- !!@object.deprecation_reason
22
+ self.class.resolve_is_deprecated(object, context)
19
23
  end
20
24
 
21
- def args(include_deprecated:)
22
- args = @context.types.arguments(@object)
25
+ def self.resolve_args(object, context, include_deprecated:)
26
+ args = context.types.arguments(object)
23
27
  args = args.reject(&:deprecation_reason) unless include_deprecated
24
28
  args
25
29
  end
30
+
31
+ def args(include_deprecated:)
32
+ self.class.resolve_args(object, context, include_deprecated: include_deprecated)
33
+ end
26
34
  end
27
35
  end
28
36
  end
@@ -9,53 +9,61 @@ module GraphQL
9
9
  field :name, String, null: false
10
10
  field :description, String
11
11
  field :type, GraphQL::Schema::LateBoundType.new("__Type"), null: false
12
- field :default_value, String, "A GraphQL-formatted string representing the default value for this input value."
13
- field :is_deprecated, Boolean, null: false
12
+ field :default_value, String, "A GraphQL-formatted string representing the default value for this input value.", resolve_each: :resolve_default_value
13
+ field :is_deprecated, Boolean, null: false, resolve_each: :resolve_is_deprecated
14
14
  field :deprecation_reason, String
15
15
 
16
+ def self.resolve_is_deprecated(object, context)
17
+ !!object.deprecation_reason
18
+ end
19
+
16
20
  def is_deprecated
17
- !!@object.deprecation_reason
21
+ self.class.resolve_is_deprecated(object, context)
18
22
  end
19
23
 
20
- def default_value
21
- if @object.default_value?
22
- value = @object.default_value
24
+ def self.resolve_default_value(object, context)
25
+ if object.default_value?
26
+ value = object.default_value
23
27
  if value.nil?
24
28
  'null'
25
29
  else
26
- if (@object.type.kind.list? || (@object.type.kind.non_null? && @object.type.of_type.kind.list?)) && !value.respond_to?(:map)
30
+ if (object.type.kind.list? || (object.type.kind.non_null? && object.type.of_type.kind.list?)) && !value.respond_to?(:map)
27
31
  # This is a bit odd -- we expect the default value to be an application-style value, so we use coerce result below.
28
32
  # But coerce_result doesn't wrap single-item lists, which are valid inputs to list types.
29
33
  # So, apply that wrapper here if needed.
30
34
  value = [value]
31
35
  end
32
- coerced_default_value = @object.type.coerce_result(value, @context)
33
- serialize_default_value(coerced_default_value, @object.type)
36
+ coerced_default_value = object.type.coerce_result(value, context)
37
+ serialize_default_value(coerced_default_value, object.type, context)
34
38
  end
35
39
  else
36
40
  nil
37
41
  end
38
42
  end
39
43
 
44
+ def default_value
45
+ self.class.resolve_default_value(object, context)
46
+ end
47
+
40
48
 
41
49
  private
42
50
 
43
51
  # Recursively serialize, taking care not to add quotes to enum values
44
- def serialize_default_value(value, type)
52
+ def self.serialize_default_value(value, type, context)
45
53
  if value.nil?
46
54
  'null'
47
55
  elsif type.kind.list?
48
56
  inner_type = type.of_type
49
- "[" + value.map { |v| serialize_default_value(v, inner_type) }.join(", ") + "]"
57
+ "[" + value.map { |v| serialize_default_value(v, inner_type, context) }.join(", ") + "]"
50
58
  elsif type.kind.non_null?
51
- serialize_default_value(value, type.of_type)
59
+ serialize_default_value(value, type.of_type, context)
52
60
  elsif type.kind.enum?
53
61
  value
54
62
  elsif type.kind.input_object?
55
63
  "{" +
56
64
  value.map do |k, v|
57
65
  arg_defn = type.get_argument(k, context)
58
- "#{k}: #{serialize_default_value(v, arg_defn.type)}"
66
+ "#{k}: #{serialize_default_value(v, arg_defn.type, context)}"
59
67
  end.join(", ") +
60
68
  "}"
61
69
  else
@@ -11,32 +11,36 @@ module GraphQL
11
11
  "they describe. Abstract types, Union and Interface, provide the Object types "\
12
12
  "possible at runtime. List and NonNull types compose other types."
13
13
 
14
- field :kind, GraphQL::Schema::LateBoundType.new("__TypeKind"), null: false
14
+ field :kind, GraphQL::Schema::LateBoundType.new("__TypeKind"), null: false, resolve_each: :resolve_kind
15
15
  field :name, String, method: :graphql_name
16
16
  field :description, String
17
- field :fields, [GraphQL::Schema::LateBoundType.new("__Field")], scope: false do
17
+ field :fields, [GraphQL::Schema::LateBoundType.new("__Field")], scope: false, resolve_each: :resolve_fields do
18
18
  argument :include_deprecated, Boolean, required: false, default_value: false
19
19
  end
20
- field :interfaces, [GraphQL::Schema::LateBoundType.new("__Type")], scope: false
21
- field :possible_types, [GraphQL::Schema::LateBoundType.new("__Type")], scope: false
22
- field :enum_values, [GraphQL::Schema::LateBoundType.new("__EnumValue")], scope: false do
20
+ field :interfaces, [GraphQL::Schema::LateBoundType.new("__Type")], scope: false, resolve_each: :resolve_interfaces
21
+ field :possible_types, [GraphQL::Schema::LateBoundType.new("__Type")], scope: false, resolve_each: :resolve_possible_types
22
+ field :enum_values, [GraphQL::Schema::LateBoundType.new("__EnumValue")], scope: false, resolve_each: :resolve_enum_values do
23
23
  argument :include_deprecated, Boolean, required: false, default_value: false
24
24
  end
25
- field :input_fields, [GraphQL::Schema::LateBoundType.new("__InputValue")], scope: false do
25
+ field :input_fields, [GraphQL::Schema::LateBoundType.new("__InputValue")], scope: false, resolve_each: :resolve_input_fields do
26
26
  argument :include_deprecated, Boolean, required: false, default_value: false
27
27
  end
28
- field :of_type, GraphQL::Schema::LateBoundType.new("__Type")
28
+ field :of_type, GraphQL::Schema::LateBoundType.new("__Type"), resolve_each: :resolve_of_type
29
29
 
30
- field :specifiedByURL, String, resolver_method: :specified_by_url
30
+ field :specifiedByURL, String, resolve_each: :resolve_specified_by_url, resolver_method: :specified_by_url
31
31
 
32
- field :is_one_of, Boolean, null: false
32
+ field :is_one_of, Boolean, null: false, resolve_each: :resolve_is_one_of
33
33
 
34
- def is_one_of
34
+ def self.resolve_is_one_of(object, _ctx)
35
35
  object.kind.input_object? &&
36
36
  object.directives.any? { |d| d.graphql_name == "oneOf" }
37
37
  end
38
38
 
39
- def specified_by_url
39
+ def is_one_of
40
+ self.class.resolve_is_one_of(object, context)
41
+ end
42
+
43
+ def self.resolve_specified_by_url(object, _ctx)
40
44
  if object.kind.scalar?
41
45
  object.specified_by_url
42
46
  else
@@ -44,15 +48,23 @@ module GraphQL
44
48
  end
45
49
  end
46
50
 
51
+ def specified_by_url
52
+ self.class.resolve_specified_by_url(object, context)
53
+ end
54
+
55
+ def self.resolve_kind(object, context)
56
+ object.kind.name
57
+ end
58
+
47
59
  def kind
48
- @object.kind.name
60
+ self.class.resolve_kind(object, context)
49
61
  end
50
62
 
51
- def enum_values(include_deprecated:)
52
- if !@object.kind.enum?
63
+ def self.resolve_enum_values(object, context, include_deprecated:)
64
+ if !object.kind.enum?
53
65
  nil
54
66
  else
55
- enum_values = @context.types.enum_values(@object)
67
+ enum_values = context.types.enum_values(object)
56
68
 
57
69
  if !include_deprecated
58
70
  enum_values = enum_values.select {|f| !f.deprecation_reason }
@@ -62,17 +74,25 @@ module GraphQL
62
74
  end
63
75
  end
64
76
 
65
- def interfaces
66
- if @object.kind.object? || @object.kind.interface?
67
- @context.types.interfaces(@object).sort_by(&:graphql_name)
77
+ def enum_values(include_deprecated:)
78
+ self.class.resolve_enum_values(object, context, include_deprecated: include_deprecated)
79
+ end
80
+
81
+ def self.resolve_interfaces(object, context)
82
+ if object.kind.object? || object.kind.interface?
83
+ context.types.interfaces(object).sort_by(&:graphql_name)
68
84
  else
69
85
  nil
70
86
  end
71
87
  end
72
88
 
73
- def input_fields(include_deprecated:)
74
- if @object.kind.input_object?
75
- args = @context.types.arguments(@object)
89
+ def interfaces
90
+ self.class.resolve_interfaces(object, context)
91
+ end
92
+
93
+ def self.resolve_input_fields(object, context, include_deprecated:)
94
+ if object.kind.input_object?
95
+ args = context.types.arguments(object)
76
96
  args = args.reject(&:deprecation_reason) unless include_deprecated
77
97
  args
78
98
  else
@@ -80,19 +100,27 @@ module GraphQL
80
100
  end
81
101
  end
82
102
 
83
- def possible_types
84
- if @object.kind.abstract?
85
- @context.types.possible_types(@object).sort_by(&:graphql_name)
103
+ def input_fields(include_deprecated:)
104
+ self.class.resolve_input_fields(object, context, include_deprecated: include_deprecated)
105
+ end
106
+
107
+ def self.resolve_possible_types(object, context)
108
+ if object.kind.abstract?
109
+ context.types.possible_types(object).sort_by(&:graphql_name)
86
110
  else
87
111
  nil
88
112
  end
89
113
  end
90
114
 
91
- def fields(include_deprecated:)
92
- if !@object.kind.fields?
115
+ def possible_types
116
+ self.class.resolve_possible_types(object, context)
117
+ end
118
+
119
+ def self.resolve_fields(object, context, include_deprecated:)
120
+ if !object.kind.fields?
93
121
  nil
94
122
  else
95
- fields = @context.types.fields(@object)
123
+ fields = context.types.fields(object)
96
124
  if !include_deprecated
97
125
  fields = fields.select {|f| !f.deprecation_reason }
98
126
  end
@@ -100,8 +128,16 @@ module GraphQL
100
128
  end
101
129
  end
102
130
 
131
+ def fields(include_deprecated:)
132
+ self.class.resolve_fields(object, context, include_deprecated: include_deprecated)
133
+ end
134
+
135
+ def self.resolve_of_type(object, _ctx)
136
+ object.kind.wraps? ? object.of_type : nil
137
+ end
138
+
103
139
  def of_type
104
- @object.kind.wraps? ? @object.of_type : nil
140
+ self.class.resolve_of_type(object, context)
105
141
  end
106
142
  end
107
143
  end
@@ -2,7 +2,7 @@
2
2
  module GraphQL
3
3
  # Raised automatically when a field's resolve function returns `nil`
4
4
  # for a non-null field.
5
- class InvalidNullError < GraphQL::Error
5
+ class InvalidNullError < GraphQL::RuntimeError
6
6
  # @return [GraphQL::BaseType] The owner of {#field}
7
7
  attr_reader :parent_type
8
8
 
@@ -10,17 +10,23 @@ module GraphQL
10
10
  attr_reader :field
11
11
 
12
12
  # @return [GraphQL::Language::Nodes::Field] the field where the error occurred
13
- attr_reader :ast_node
13
+ def ast_node
14
+ @ast_nodes.first
15
+ end
16
+
17
+ attr_reader :ast_nodes
14
18
 
15
19
  # @return [Boolean] indicates an array result caused the error
16
20
  attr_reader :is_from_array
17
21
 
18
- def initialize(parent_type, field, ast_node, is_from_array: false)
22
+ attr_accessor :path
23
+
24
+ def initialize(parent_type, field, ast_node_or_nodes, is_from_array: false, path: nil)
19
25
  @parent_type = parent_type
20
26
  @field = field
21
- @ast_node = ast_node
27
+ @ast_nodes = Array(ast_node_or_nodes)
22
28
  @is_from_array = is_from_array
23
-
29
+ @path = path
24
30
  # For List elements, identify the non-null error is for an
25
31
  # element and the required element type so it's not ambiguous
26
32
  # whether it was caused by a null instead of the list or a
@@ -52,8 +52,9 @@ module GraphQL
52
52
 
53
53
  def build_object_type_node(object_type)
54
54
  ints = @types.interfaces(object_type)
55
+
55
56
  if !ints.empty?
56
- ints.sort_by!(&:graphql_name)
57
+ ints = ints.sort_by(&:graphql_name)
57
58
  ints.map! { |iface| build_type_name_node(iface) }
58
59
  end
59
60
 
@@ -20,10 +20,23 @@ module GraphQL
20
20
  @finished
21
21
  end
22
22
 
23
+ def freeze
24
+ @scanner = nil
25
+ super
26
+ end
27
+
23
28
  attr_reader :pos, :tokens_count
24
29
 
25
30
  def advance
26
- @scanner.skip(IGNORE_REGEXP)
31
+ loop do
32
+ @scanner.skip(IGNORE_REGEXP)
33
+ if @scanner.skip(COMMENT_REGEXP)
34
+ @tokens_count += 1
35
+ next
36
+ end
37
+ break
38
+ end
39
+
27
40
  if @scanner.eos?
28
41
  @finished = true
29
42
  return false
@@ -173,12 +186,8 @@ module GraphQL
173
186
  raise GraphQL::ParseError.new(message, line, col, @string, filename: @filename)
174
187
  end
175
188
 
176
- IGNORE_REGEXP = %r{
177
- (?:
178
- [, \c\r\n\t]+ |
179
- \#.*$
180
- )*
181
- }x
189
+ IGNORE_REGEXP = /[, \c\r\n\t]+/
190
+ COMMENT_REGEXP = /\#[^\n]*/
182
191
  IDENTIFIER_REGEXP = /[_A-Za-z][_0-9A-Za-z]*/
183
192
  INT_REGEXP = /-?(?:[0]|[1-9][0-9]*)/
184
193
  FLOAT_DECIMAL_REGEXP = /[.][0-9]+/
@@ -242,7 +251,7 @@ module GraphQL
242
251
  :SCALAR,
243
252
  nil,
244
253
  :FRAGMENT
245
- ]
254
+ ].freeze
246
255
 
247
256
  # This produces a unique integer for bytes 2 and 3 of each keyword string
248
257
  # See https://tenderlovemaking.com/2023/09/02/fast-tokenizers-with-stringscanner.html
@@ -271,7 +280,8 @@ module GraphQL
271
280
  PUNCTUATION_NAME_FOR_BYTE = Punctuation.constants.each_with_object([]) { |name, arr|
272
281
  punct = Punctuation.const_get(name)
273
282
  arr[punct.ord] = name
274
- }
283
+ }.freeze
284
+
275
285
 
276
286
  QUOTE = '"'
277
287
  UNICODE_DIGIT = /[0-9A-Za-z]/
@@ -321,6 +331,7 @@ module GraphQL
321
331
  punct = Punctuation.const_get(punct_name)
322
332
  FIRST_BYTES[punct.ord] = ByteFor::PUNCTUATION
323
333
  end
334
+ FIRST_BYTES.freeze
324
335
 
325
336
 
326
337
  # Replace any escaped unicode or whitespace with the _actual_ characters
@@ -83,7 +83,11 @@ module GraphQL
83
83
 
84
84
  def to_query_string(printer: GraphQL::Language::Printer.new)
85
85
  if printer.is_a?(GraphQL::Language::Printer)
86
- @query_string ||= printer.print(self)
86
+ if frozen?
87
+ @query_string || printer.print(self)
88
+ else
89
+ @query_string ||= printer.print(self)
90
+ end
87
91
  else
88
92
  printer.print(self)
89
93
  end
@@ -2,6 +2,7 @@
2
2
 
3
3
  require "strscan"
4
4
  require "graphql/language/nodes"
5
+ require "graphql/tracing/null_trace"
5
6
 
6
7
  module GraphQL
7
8
  module Language
@@ -77,21 +77,30 @@ module GraphQL
77
77
  new_query_str || query_str
78
78
  end
79
79
 
80
+ LEADING_REGEX = Regexp.union(" ", *Lexer::Punctuation.constants.map { |const| Lexer::Punctuation.const_get(const) })
81
+
82
+ # Optimized pattern using:
83
+ # - Possessive quantifiers (*+, ++) to prevent backtracking in number patterns
84
+ # - Atomic group (?>...) for IGNORE to prevent backtracking
85
+ # - Single unified number pattern instead of three alternatives
86
+ EFFICIENT_NUMBER_REGEXP = /-?(?:0|[1-9][0-9]*+)(?:\.[0-9]++)?(?:[eE][+-]?[0-9]++)?/
87
+ EFFICIENT_IGNORE_REGEXP = /(?>[, \r\n\t]+|\#[^\n]*$)*/
88
+
89
+ MAYBE_INVALID_NUMBER = /\d[_a-zA-Z]/
90
+
80
91
  INVALID_NUMBER_FOLLOWED_BY_NAME_REGEXP = %r{
81
- (
82
- ((?<num>#{Lexer::INT_REGEXP}(#{Lexer::FLOAT_EXP_REGEXP})?)(?<name>#{Lexer::IDENTIFIER_REGEXP})#{Lexer::IGNORE_REGEXP}:)
83
- |
84
- ((?<num>#{Lexer::INT_REGEXP}#{Lexer::FLOAT_DECIMAL_REGEXP}#{Lexer::FLOAT_EXP_REGEXP})(?<name>#{Lexer::IDENTIFIER_REGEXP})#{Lexer::IGNORE_REGEXP}:)
85
- |
86
- ((?<num>#{Lexer::INT_REGEXP}#{Lexer::FLOAT_DECIMAL_REGEXP})(?<name>#{Lexer::IDENTIFIER_REGEXP})#{Lexer::IGNORE_REGEXP}:)
87
- )}x
92
+ (?<leading>#{LEADING_REGEX})
93
+ (?<num>#{EFFICIENT_NUMBER_REGEXP})
94
+ (?<name>#{Lexer::IDENTIFIER_REGEXP})
95
+ #{EFFICIENT_IGNORE_REGEXP}
96
+ :
97
+ }x
88
98
 
89
99
  def self.add_space_between_numbers_and_names(query_str)
90
- if query_str.match?(INVALID_NUMBER_FOLLOWED_BY_NAME_REGEXP)
91
- query_str.gsub(INVALID_NUMBER_FOLLOWED_BY_NAME_REGEXP, "\\k<num> \\k<name>:")
92
- else
93
- query_str
94
- end
100
+ # Fast check for digit followed by identifier char. If this doesn't match, skip the more expensive regexp entirely.
101
+ return query_str unless query_str.match?(MAYBE_INVALID_NUMBER)
102
+ return query_str unless query_str.match?(INVALID_NUMBER_FOLLOWED_BY_NAME_REGEXP)
103
+ query_str.gsub(INVALID_NUMBER_FOLLOWED_BY_NAME_REGEXP, "\\k<leading>\\k<num> \\k<name>:")
95
104
  end
96
105
  end
97
106
  end
@@ -94,6 +94,8 @@ module GraphQL
94
94
  @was_authorized_by_scope_items = detect_was_authorized_by_scope_items
95
95
  end
96
96
 
97
+ attr_writer :was_authorized_by_scope_items
98
+
97
99
  def was_authorized_by_scope_items?
98
100
  @was_authorized_by_scope_items
99
101
  end
@@ -83,6 +83,38 @@ module GraphQL
83
83
  end
84
84
  end
85
85
 
86
+ def populate_connection(field, object, value, original_arguments, context)
87
+ if value.is_a? GraphQL::ExecutionError
88
+ # This isn't even going to work because context doesn't have ast_node anymore
89
+ context.add_error(value)
90
+ nil
91
+ elsif value.nil?
92
+ nil
93
+ elsif value.is_a?(GraphQL::Pagination::Connection)
94
+ # update the connection with some things that may not have been provided
95
+ value.context ||= context
96
+ value.parent ||= object
97
+ value.first_value ||= original_arguments[:first]
98
+ value.after_value ||= original_arguments[:after]
99
+ value.last_value ||= original_arguments[:last]
100
+ value.before_value ||= original_arguments[:before]
101
+ value.arguments ||= original_arguments # rubocop:disable Development/ContextIsPassedCop -- unrelated .arguments method
102
+ value.field ||= field
103
+ if field.has_max_page_size? && !value.has_max_page_size_override?
104
+ value.max_page_size = field.max_page_size
105
+ end
106
+ if field.has_default_page_size? && !value.has_default_page_size_override?
107
+ value.default_page_size = field.default_page_size
108
+ end
109
+ if (custom_t = context.schema.connections.edge_class_for_field(field))
110
+ value.edge_class = custom_t
111
+ end
112
+ value
113
+ else
114
+ context.namespace(:connections)[:all_wrappers] ||= context.schema.connections.all_wrappers
115
+ context.schema.connections.wrap(field, object, value, original_arguments, context)
116
+ end
117
+ end
86
118
  # use an override if there is one
87
119
  # @api private
88
120
  def edge_class_for_field(field)
@@ -29,6 +29,7 @@ module GraphQL
29
29
  end
30
30
 
31
31
  extend Forwardable
32
+ include Schema::Member::HasDataloader
32
33
 
33
34
  # @return [Array<GraphQL::ExecutionError>] errors returned during execution
34
35
  attr_reader :errors
@@ -84,7 +85,7 @@ module GraphQL
84
85
 
85
86
  attr_writer :types
86
87
 
87
- RUNTIME_METADATA_KEYS = Set.new([:current_object, :current_arguments, :current_field, :current_path])
88
+ RUNTIME_METADATA_KEYS = Set.new([:current_object, :current_arguments, :current_field, :current_path]).freeze
88
89
  # @!method []=(key, value)
89
90
  # Reassign `key` to the hash passed to {Schema#execute} as `context:`
90
91
 
@@ -111,20 +112,26 @@ module GraphQL
111
112
  # Return this value to tell the runtime
112
113
  # to exclude this field from the response altogether
113
114
  def skip
114
- GraphQL::Execution::SKIP
115
+ GraphQL::Execution::Skip.new
115
116
  end
116
117
 
117
118
  # Add error at query-level.
118
119
  # @param error [GraphQL::ExecutionError] an execution error
119
120
  # @return [void]
120
121
  def add_error(error)
121
- if !error.is_a?(ExecutionError)
122
- raise TypeError, "expected error to be a ExecutionError, but was #{error.class}"
122
+ if !error.is_a?(GraphQL::RuntimeError)
123
+ raise TypeError, "expected error to be a GraphQL::RuntimeError, but was #{error.class}"
123
124
  end
124
125
  errors << error
125
126
  nil
126
127
  end
127
128
 
129
+ # @param value [Object] Any object to be inserted directly into the final response
130
+ # @return [GraphQL::Execution::Interpreter::RawValue] Return this from the field
131
+ def raw_value(value)
132
+ GraphQL::Execution::Interpreter::RawValue.new(value)
133
+ end
134
+
128
135
  # @example Print the GraphQL backtrace during field resolution
129
136
  # puts ctx.backtrace
130
137
  #
@@ -4,7 +4,13 @@ module GraphQL
4
4
  class Query
5
5
  # This object can be `ctx` in places where there is no query
6
6
  class NullContext < Context
7
- include Singleton
7
+ def self.instance
8
+ @instance ||= self.new
9
+ end
10
+
11
+ def self.instance=(new_inst)
12
+ @instance = new_inst
13
+ end
8
14
 
9
15
  class NullQuery
10
16
  def after_lazy(value)
@@ -20,10 +26,10 @@ module GraphQL
20
26
  attr_reader :schema, :query, :warden, :dataloader
21
27
  def_delegators GraphQL::EmptyObjects::EMPTY_HASH, :[], :fetch, :dig, :key?, :to_h
22
28
 
23
- def initialize
29
+ def initialize(schema: NullSchema)
24
30
  @query = NullQuery.new
25
31
  @dataloader = GraphQL::Dataloader::NullDataloader.new
26
- @schema = NullSchema
32
+ @schema = schema
27
33
  @warden = Schema::Warden::NullWarden.new(context: self, schema: @schema)
28
34
  @types = @warden.visibility_profile
29
35
  freeze
@@ -32,6 +32,7 @@ module GraphQL
32
32
  @multiplex = nil
33
33
  @result_values = nil
34
34
  @result = nil
35
+ @finalizers = @top_level_finalizers = nil
35
36
 
36
37
  if fragment_node
37
38
  @ast_nodes = [fragment_node]
@@ -51,6 +52,10 @@ module GraphQL
51
52
  @leaf
52
53
  end
53
54
 
55
+ def root_value
56
+ object
57
+ end
58
+
54
59
  attr_reader :context, :query, :ast_nodes, :root_type, :object, :field_definition, :path, :schema
55
60
 
56
61
  attr_accessor :multiplex, :result_values
@@ -90,10 +95,22 @@ module GraphQL
90
95
  @query.fragments
91
96
  end
92
97
 
98
+ def validate
99
+ @query.validate
100
+ end
101
+
93
102
  def valid?
94
103
  @query.valid?
95
104
  end
96
105
 
106
+ def query?
107
+ true
108
+ end
109
+
110
+ def run_partials(...)
111
+ @query.run_partials(...)
112
+ end
113
+
97
114
  def analyzers
98
115
  EmptyObjects::EMPTY_ARRAY
99
116
  end
@@ -107,7 +124,7 @@ module GraphQL
107
124
  end
108
125
 
109
126
  def selected_operation
110
- ast_nodes.first
127
+ Language::Nodes::OperationDefinition.new(selections: ast_nodes.flat_map(&:selections))
111
128
  end
112
129
 
113
130
  def static_errors
@@ -123,7 +140,6 @@ module GraphQL
123
140
  def set_type_info_from_path
124
141
  selections = [@query.selected_operation]
125
142
  type = @query.root_type
126
- parent_type = nil
127
143
  field_defn = nil
128
144
 
129
145
  @path.each do |name_in_doc|
@@ -162,7 +178,6 @@ module GraphQL
162
178
  end
163
179
  field_name = next_selections.first.name
164
180
  field_defn = @schema.get_field(type, field_name, @query.context) || raise("Invariant: no field called #{field_name} on #{type.graphql_name}")
165
- parent_type = type
166
181
  type = field_defn.type
167
182
  if type.non_null?
168
183
  type = type.of_type
data/lib/graphql/query.rb CHANGED
@@ -159,6 +159,7 @@ module GraphQL
159
159
  @root_value = root_value
160
160
  @fragments = nil
161
161
  @operations = nil
162
+ @finalizers = @top_level_finalizers = nil
162
163
  @validate = validate
163
164
  self.static_validator = static_validator if static_validator
164
165
  context_tracers = (context ? context.fetch(:tracers, []) : [])
@@ -262,6 +263,10 @@ module GraphQL
262
263
  with_prepared_ast { @operations }
263
264
  end
264
265
 
266
+ def path
267
+ EmptyObjects::EMPTY_ARRAY
268
+ end
269
+
265
270
  # Run subtree partials of this query and return their results.
266
271
  # Each partial is identified with a `path:` and `object:`
267
272
  # where the path references a field in the AST and the object will be treated
@@ -271,7 +276,11 @@ module GraphQL
271
276
  # @return [Array<GraphQL::Query::Result>]
272
277
  def run_partials(partials_hashes)
273
278
  partials = partials_hashes.map { |partial_options| Partial.new(query: self, **partial_options) }
274
- Execution::Interpreter.run_all(@schema, partials, context: @context)
279
+ if context[:__graphql_execute_next]
280
+ Execution::Next.run_all(@schema, partials, context: @context)
281
+ else
282
+ Execution::Interpreter.run_all(@schema, partials, context: @context)
283
+ end
275
284
  end
276
285
 
277
286
  # Get the result for this query, executing it once