graphql 1.7.6 → 1.7.7

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 (66) hide show
  1. checksums.yaml +4 -4
  2. data/lib/generators/graphql/function_generator.rb +1 -1
  3. data/lib/generators/graphql/loader_generator.rb +1 -1
  4. data/lib/generators/graphql/mutation_generator.rb +6 -1
  5. data/lib/generators/graphql/templates/function.erb +2 -2
  6. data/lib/generators/graphql/templates/loader.erb +2 -2
  7. data/lib/graphql/execution.rb +1 -0
  8. data/lib/graphql/execution/instrumentation.rb +82 -0
  9. data/lib/graphql/execution/multiplex.rb +11 -28
  10. data/lib/graphql/field.rb +5 -0
  11. data/lib/graphql/internal_representation/node.rb +1 -1
  12. data/lib/graphql/language.rb +1 -0
  13. data/lib/graphql/language/document_from_schema_definition.rb +185 -0
  14. data/lib/graphql/language/lexer.rb +3 -3
  15. data/lib/graphql/language/lexer.rl +2 -2
  16. data/lib/graphql/language/token.rb +9 -2
  17. data/lib/graphql/query.rb +4 -0
  18. data/lib/graphql/relay/relation_connection.rb +13 -18
  19. data/lib/graphql/schema.rb +6 -0
  20. data/lib/graphql/schema/build_from_definition.rb +2 -0
  21. data/lib/graphql/subscriptions/action_cable_subscriptions.rb +6 -4
  22. data/lib/graphql/tracing.rb +1 -0
  23. data/lib/graphql/tracing/data_dog_tracing.rb +45 -0
  24. data/lib/graphql/tracing/platform_tracing.rb +20 -7
  25. data/lib/graphql/version.rb +1 -1
  26. data/readme.md +1 -1
  27. data/spec/dummy/app/channels/graphql_channel.rb +22 -1
  28. data/spec/dummy/log/development.log +239 -0
  29. data/spec/dummy/log/test.log +204 -0
  30. data/spec/dummy/test/system/action_cable_subscription_test.rb +4 -0
  31. data/spec/dummy/tmp/screenshots/failures_test_it_handles_subscriptions.png +0 -0
  32. data/spec/generators/graphql/function_generator_spec.rb +26 -0
  33. data/spec/generators/graphql/loader_generator_spec.rb +24 -0
  34. data/spec/graphql/analysis/max_query_complexity_spec.rb +3 -3
  35. data/spec/graphql/analysis/max_query_depth_spec.rb +3 -3
  36. data/spec/graphql/boolean_type_spec.rb +3 -3
  37. data/spec/graphql/execution/execute_spec.rb +1 -1
  38. data/spec/graphql/execution/instrumentation_spec.rb +165 -0
  39. data/spec/graphql/execution/multiplex_spec.rb +1 -1
  40. data/spec/graphql/float_type_spec.rb +2 -2
  41. data/spec/graphql/id_type_spec.rb +1 -1
  42. data/spec/graphql/input_object_type_spec.rb +2 -2
  43. data/spec/graphql/int_type_spec.rb +2 -2
  44. data/spec/graphql/internal_representation/rewrite_spec.rb +2 -2
  45. data/spec/graphql/introspection/schema_type_spec.rb +1 -0
  46. data/spec/graphql/language/document_from_schema_definition_spec.rb +337 -0
  47. data/spec/graphql/language/lexer_spec.rb +12 -1
  48. data/spec/graphql/language/parser_spec.rb +1 -1
  49. data/spec/graphql/query/arguments_spec.rb +3 -3
  50. data/spec/graphql/query/variables_spec.rb +1 -1
  51. data/spec/graphql/query_spec.rb +4 -4
  52. data/spec/graphql/relay/base_connection_spec.rb +1 -1
  53. data/spec/graphql/relay/connection_resolve_spec.rb +1 -1
  54. data/spec/graphql/relay/connection_type_spec.rb +1 -1
  55. data/spec/graphql/relay/mutation_spec.rb +3 -3
  56. data/spec/graphql/relay/relation_connection_spec.rb +58 -0
  57. data/spec/graphql/schema/build_from_definition_spec.rb +14 -0
  58. data/spec/graphql/schema/validation_spec.rb +1 -1
  59. data/spec/graphql/schema/warden_spec.rb +11 -11
  60. data/spec/graphql/schema_spec.rb +8 -1
  61. data/spec/graphql/string_type_spec.rb +3 -3
  62. data/spec/graphql/subscriptions_spec.rb +1 -1
  63. data/spec/graphql/tracing/platform_tracing_spec.rb +59 -0
  64. data/spec/support/dummy/schema.rb +19 -0
  65. data/spec/support/star_wars/data.rb +1 -2
  66. metadata +9 -2
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 4ee2881b8335c9299d2dd2b707810856bb34d956
4
- data.tar.gz: d23544e543addd64583dbcf82280fc92454a515c
3
+ metadata.gz: 38483eef219e223ada69c9b3abf6be4b21b18631
4
+ data.tar.gz: 6e7b06898fbc81c29dd817ee9817ae54e4a44ada
5
5
  SHA512:
6
- metadata.gz: db14299e06ef27b4f177980705c4f70d6b1d2a6defef6efad0a0b15db493ee1501c820c6fd298a60bc5ffdaf817d50abe4e7c2c145278da5e1ac46866e0a3648
7
- data.tar.gz: 4776fe2f611c0cb93778fbf6101dfb81a3f08244bce650dccfbc2c999886ac0aac3d26f06533c6722c2e6aceab371ef9aa419f8d9b192301f2674519d5f66848
6
+ metadata.gz: 8c1813ee4f6758846e9f5620d1952517c9d7a3d714e265d18da05fcd68425f152afdae56f8c400fd55d4e455e03443f26d90e04fd69578d85c71b3130f5eaf0f
7
+ data.tar.gz: 41480a626d5acf9db70ea864438e192bab03ec56ad89c137f9f5e63e9ada426c3fd812b9099e8f4c621e3705740fd7daf1d1f74019c632ebd19f54e62608eb90
@@ -11,7 +11,7 @@ module Graphql
11
11
  source_root File.expand_path('../templates', __FILE__)
12
12
 
13
13
  def create_function_file
14
- template "function.erb", "#{options[:directory]}/functions/#{file_name}.rb"
14
+ template "function.erb", "#{options[:directory]}/functions/#{file_path}.rb"
15
15
  end
16
16
  end
17
17
  end
@@ -13,7 +13,7 @@ module Graphql
13
13
  source_root File.expand_path('../templates', __FILE__)
14
14
 
15
15
  def create_loader_file
16
- template "loader.erb", "#{options[:directory]}/loaders/#{file_name}.rb"
16
+ template "loader.erb", "#{options[:directory]}/loaders/#{file_path}.rb"
17
17
  end
18
18
  end
19
19
  end
@@ -27,7 +27,12 @@ module Graphql
27
27
  attr_reader :file_name, :mutation_name, :field_name
28
28
 
29
29
  def create_mutation_file
30
- create_mutation_root_type
30
+ unless @behavior == :revoke
31
+ create_mutation_root_type
32
+ else
33
+ log :gsub, "#{options[:directory]}/types/mutation_type.rb"
34
+ end
35
+
31
36
  template "mutation.erb", "#{options[:directory]}/mutations/#{file_name}.rb"
32
37
 
33
38
  sentinel = /name "Mutation"\s*\n/m
@@ -1,7 +1,7 @@
1
- class Functions::<%= name %> < GraphQL::Function
1
+ class Functions::<%= class_name %> < GraphQL::Function
2
2
  # Define `initialize` to store field-level options, eg
3
3
  #
4
- # field :myField, function: Functions::<%= name %>.new(type: MyType)
4
+ # field :myField, function: Functions::<%= class_name %>.new(type: MyType)
5
5
  #
6
6
  # attr_reader :type
7
7
  # def initialize(type:)
@@ -1,7 +1,7 @@
1
- class Loaders::<%= name %> < GraphQL::Batch::Loader
1
+ class Loaders::<%= class_name %> < GraphQL::Batch::Loader
2
2
  # Define `initialize` to store grouping arguments, eg
3
3
  #
4
- # Loaders::<%= name %>.for(group).load(value)
4
+ # Loaders::<%= class_name %>.for(group).load(value)
5
5
  #
6
6
  # def initialize()
7
7
  # end
@@ -2,6 +2,7 @@
2
2
  require "graphql/execution/directive_checks"
3
3
  require "graphql/execution/execute"
4
4
  require "graphql/execution/flatten"
5
+ require "graphql/execution/instrumentation"
5
6
  require "graphql/execution/lazy"
6
7
  require "graphql/execution/multiplex"
7
8
  require "graphql/execution/typecast"
@@ -0,0 +1,82 @@
1
+ # frozen_string_literal: true
2
+ module GraphQL
3
+ module Execution
4
+ module Instrumentation
5
+ # This function implements the instrumentation policy:
6
+ #
7
+ # - Instrumenters are a stack; the first `before_query` will have the last `after_query`
8
+ # - If a `before_` hook returned without an error, its corresponding `after_` hook will run.
9
+ # - If the `before_` hook did _not_ run, the `after_` hook will not be called.
10
+ #
11
+ # When errors are raised from `after_` hooks:
12
+ # - Subsequent `after_` hooks _are_ called
13
+ # - The first raised error is captured; later errors are ignored
14
+ # - If an error was capture, it's re-raised after all hooks are finished
15
+ #
16
+ # Partial runs of instrumentation are possible:
17
+ # - If a `before_multiplex` hook raises an error, no `before_query` hooks will run
18
+ # - If a `before_query` hook raises an error, subsequent `before_query` hooks will not run (on any query)
19
+ def self.apply_instrumenters(multiplex)
20
+ schema = multiplex.schema
21
+ queries = multiplex.queries
22
+ query_instrumenters = schema.instrumenters[:query]
23
+ multiplex_instrumenters = schema.instrumenters[:multiplex]
24
+
25
+ # First, run multiplex instrumentation, then query instrumentation for each query
26
+ call_hooks(multiplex_instrumenters, multiplex, :before_multiplex, :after_multiplex) do
27
+ each_query_call_hooks(query_instrumenters, queries) do
28
+ # Let them be executed
29
+ yield
30
+ end
31
+ end
32
+ end
33
+
34
+ class << self
35
+ private
36
+ # Call the before_ hooks of each query,
37
+ # Then yield if no errors.
38
+ # `call_hooks` takes care of appropriate cleanup.
39
+ def each_query_call_hooks(instrumenters, queries, i = 0)
40
+ if i >= queries.length
41
+ yield
42
+ else
43
+ query = queries[i]
44
+ call_hooks(instrumenters, query, :before_query, :after_query) {
45
+ each_query_call_hooks(instrumenters, queries, i + 1) {
46
+ yield
47
+ }
48
+ }
49
+ end
50
+ end
51
+
52
+ # Call each before hook, and if they all succeed, yield.
53
+ # If they don't all succeed, call after_ for each one that succeeded.
54
+ def call_hooks(instrumenters, object, before_hook_name, after_hook_name, i = 0)
55
+ if i >= instrumenters.length
56
+ # We've reached the end of the instrumenters, so start exiting the call stack.
57
+ # (This will eventually call the originally-passed block.)
58
+ yield
59
+ else
60
+ # Get the next instrumenter and call the before_hook.
61
+ instrumenter = instrumenters[i]
62
+ instrumenter.public_send(before_hook_name, object)
63
+ # At this point, the before_hook did _not_ raise an error.
64
+ # (If it did raise an error, we wouldn't reach this point.)
65
+ # So we should guarantee that we run the after_hook.
66
+ begin
67
+ # Call the next instrumenter on the list,
68
+ # basically passing along the original block
69
+ call_hooks(instrumenters, object, before_hook_name, after_hook_name, i + 1) {
70
+ yield
71
+ }
72
+ ensure
73
+ # Regardless of how the next instrumenter in the list behaves,
74
+ # call the after_hook of this instrumenter
75
+ instrumenter.public_send(after_hook_name, object)
76
+ end
77
+ end
78
+ end
79
+ end
80
+ end
81
+ end
82
+ end
@@ -60,12 +60,12 @@ module GraphQL
60
60
  if queries.length != 1
61
61
  raise ArgumentError, "Multiplexing doesn't support custom execution strategies, run one query at a time instead"
62
62
  else
63
- with_instrumentation(multiplex, max_complexity: max_complexity) do
63
+ instrument_and_analyze(multiplex, max_complexity: max_complexity) do
64
64
  [run_one_legacy(schema, queries.first)]
65
65
  end
66
66
  end
67
67
  else
68
- with_instrumentation(multiplex, max_complexity: max_complexity) do
68
+ instrument_and_analyze(multiplex, max_complexity: max_complexity) do
69
69
  run_as_multiplex(multiplex)
70
70
  end
71
71
  end
@@ -162,34 +162,17 @@ module GraphQL
162
162
  # Apply multiplex & query instrumentation to `queries`.
163
163
  #
164
164
  # It yields when the queries should be executed, then runs teardown.
165
- def with_instrumentation(multiplex, max_complexity:)
166
- schema = multiplex.schema
167
- queries = multiplex.queries
168
- query_instrumenters = schema.instrumenters[:query]
169
- multiplex_instrumenters = schema.instrumenters[:multiplex]
170
-
171
- # First, run multiplex instrumentation, then query instrumentation for each query
172
- multiplex_instrumenters.each { |i| i.before_multiplex(multiplex) }
173
- queries.each do |query|
174
- query_instrumenters.each { |i| i.before_query(query) }
175
- end
176
-
177
- multiplex_analyzers = schema.multiplex_analyzers
178
- if max_complexity
179
- multiplex_analyzers += [GraphQL::Analysis::MaxQueryComplexity.new(max_complexity)]
180
- end
181
-
182
- GraphQL::Analysis.analyze_multiplex(multiplex, multiplex_analyzers)
165
+ def instrument_and_analyze(multiplex, max_complexity:)
166
+ GraphQL::Execution::Instrumentation.apply_instrumenters(multiplex) do
167
+ schema = multiplex.schema
168
+ multiplex_analyzers = schema.multiplex_analyzers
169
+ if max_complexity
170
+ multiplex_analyzers += [GraphQL::Analysis::MaxQueryComplexity.new(max_complexity)]
171
+ end
183
172
 
184
- # Let them be executed
185
- yield
186
- ensure
187
- # Finally, run teardown instrumentation for each query + the multiplex
188
- # Use `reverse_each` so instrumenters are treated like a stack
189
- queries.each do |query|
190
- query_instrumenters.reverse_each { |i| i.after_query(query) }
173
+ GraphQL::Analysis.analyze_multiplex(multiplex, multiplex_analyzers)
174
+ yield
191
175
  end
192
- multiplex_instrumenters.reverse_each { |i| i.after_multiplex(multiplex) }
193
176
  end
194
177
  end
195
178
  end
data/lib/graphql/field.rb CHANGED
@@ -130,6 +130,7 @@ module GraphQL
130
130
  :relay_node_field,
131
131
  :relay_nodes_field,
132
132
  :subscription_scope,
133
+ :trace,
133
134
  argument: GraphQL::Define::AssignArgument
134
135
 
135
136
  ensure_defined(
@@ -186,6 +187,9 @@ module GraphQL
186
187
  # @return [nil, String] Prefix for subscription names from this field
187
188
  attr_accessor :subscription_scope
188
189
 
190
+ # @return [Boolean] True if this field should be traced. By default, fields are only traced if they are not a ScalarType or EnumType.
191
+ attr_accessor :trace
192
+
189
193
  # @return [Boolean]
190
194
  def connection?
191
195
  @connection
@@ -212,6 +216,7 @@ module GraphQL
212
216
  @connection = false
213
217
  @connection_max_page_size = nil
214
218
  @edge_class = nil
219
+ @trace = nil
215
220
  end
216
221
 
217
222
  def initialize_copy(other)
@@ -16,7 +16,7 @@ module GraphQL
16
16
  # This value is derived from {#scoped_children} after the rewrite is finished.
17
17
  # @return [Hash<GraphQL::ObjectType, Hash<String => Node>>]
18
18
  def typed_children
19
- @typed_childen ||= begin
19
+ @typed_children ||= begin
20
20
  new_tc = Hash.new(&DEFAULT_TYPED_CHILDREN)
21
21
  if @scoped_children.any?
22
22
  all_object_types = Set.new
@@ -1,5 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
  require "graphql/language/definition_slice"
3
+ require "graphql/language/document_from_schema_definition"
3
4
  require "graphql/language/generation"
4
5
  require "graphql/language/lexer"
5
6
  require "graphql/language/nodes"
@@ -0,0 +1,185 @@
1
+ # frozen_string_literal: true
2
+ module GraphQL
3
+ module Language
4
+ # @api private
5
+ #
6
+ # {GraphQL::Language::DocumentFromSchemaDefinition} is used to convert a {GraphQL::Schema} object
7
+ # To a {GraphQL::Language::Document} AST node.
8
+ #
9
+ class DocumentFromSchemaDefinition
10
+ def initialize(schema)
11
+ @schema = schema
12
+ @types = GraphQL::Schema::Traversal.new(schema, introspection: true).type_map.values
13
+ end
14
+
15
+ def document
16
+ GraphQL::Language::Nodes::Document.new(
17
+ definitions: build_definition_nodes
18
+ )
19
+ end
20
+
21
+ protected
22
+
23
+ def build_schema_node(schema)
24
+ schema_node = GraphQL::Language::Nodes::SchemaDefinition.new(
25
+ query: schema.query.name
26
+ )
27
+
28
+ if schema.mutation
29
+ schema_node.mutation = schema.mutation.name
30
+ end
31
+
32
+ if schema.subscription
33
+ schema_node.subscription = schema.subscription.name
34
+ end
35
+
36
+ schema_node
37
+ end
38
+
39
+ def build_object_type_node(object_type)
40
+ GraphQL::Language::Nodes::ObjectTypeDefinition.new(
41
+ name: object_type.name,
42
+ interfaces: object_type.interfaces.map { |iface| build_type_name_node(iface) },
43
+ fields: build_field_nodes(object_type.fields.values),
44
+ description: object_type.description,
45
+ )
46
+ end
47
+
48
+ def build_field_node(field)
49
+ GraphQL::Language::Nodes::FieldDefinition.new(
50
+ name: field.name,
51
+ arguments: build_argument_nodes(field.arguments.values),
52
+ type: build_type_name_node(field.type),
53
+ description: field.description,
54
+ )
55
+ end
56
+
57
+ def build_union_type_node(union_type)
58
+ GraphQL::Language::Nodes::UnionTypeDefinition.new(
59
+ name: union_type.name,
60
+ description: union_type.description,
61
+ types: union_type.possible_types.map { |type| build_type_name_node(type) }
62
+ )
63
+ end
64
+
65
+ def build_interface_type_node(interface_type)
66
+ GraphQL::Language::Nodes::InterfaceTypeDefinition.new(
67
+ name: interface_type.name,
68
+ description: interface_type.description,
69
+ fields: build_field_nodes(interface_type.fields.values)
70
+ )
71
+ end
72
+
73
+ def build_enum_type_node(enum_type)
74
+ GraphQL::Language::Nodes::EnumTypeDefinition.new(
75
+ name: enum_type.name,
76
+ values: enum_type.values.values.map do |enum_value|
77
+ build_enum_value_node(enum_value)
78
+ end,
79
+ description: enum_type.description,
80
+ )
81
+ end
82
+
83
+ def build_enum_value_node(enum_value)
84
+ GraphQL::Language::Nodes::EnumValueDefinition.new(
85
+ name: enum_value.name,
86
+ description: enum_value.description,
87
+ )
88
+ end
89
+
90
+ def build_scalar_type_node(scalar_type)
91
+ GraphQL::Language::Nodes::ScalarTypeDefinition.new(
92
+ name: scalar_type.name,
93
+ description: scalar_type.description,
94
+ )
95
+ end
96
+
97
+ def build_argument_node(argument)
98
+ GraphQL::Language::Nodes::InputValueDefinition.new(
99
+ name: argument.name,
100
+ description: argument.description,
101
+ type: build_type_name_node(argument.type),
102
+ default_value: argument.default_value,
103
+ )
104
+ end
105
+
106
+ def build_input_object_node(input_object)
107
+ GraphQL::Language::Nodes::InputObjectTypeDefinition.new(
108
+ name: input_object.name,
109
+ fields: build_argument_nodes(input_object.arguments.values),
110
+ description: input_object.description,
111
+ )
112
+ end
113
+
114
+ def build_directive_node(directive)
115
+ GraphQL::Language::Nodes::DirectiveDefinition.new(
116
+ name: directive.name,
117
+ arguments: build_argument_nodes(directive.arguments.values),
118
+ locations: directive.locations.map(&:to_s),
119
+ description: directive.description,
120
+ )
121
+ end
122
+
123
+ def build_type_name_node(type)
124
+ case type
125
+ when GraphQL::ListType
126
+ GraphQL::Language::Nodes::ListType.new(
127
+ of_type: build_type_name_node(type.of_type)
128
+ )
129
+ when GraphQL::NonNullType
130
+ GraphQL::Language::Nodes::NonNullType.new(
131
+ of_type: build_type_name_node(type.of_type)
132
+ )
133
+ else
134
+ GraphQL::Language::Nodes::TypeName.new(name: type.name)
135
+ end
136
+ end
137
+
138
+ def build_type_definition_node(type)
139
+ case type
140
+ when GraphQL::ObjectType
141
+ build_object_type_node(type)
142
+ when GraphQL::UnionType
143
+ build_union_type_node(type)
144
+ when GraphQL::InterfaceType
145
+ build_interface_type_node(type)
146
+ when GraphQL::ScalarType
147
+ build_scalar_type_node(type)
148
+ when GraphQL::EnumType
149
+ build_enum_type_node(type)
150
+ when GraphQL::InputObjectType
151
+ build_input_object_node(type)
152
+ else
153
+ raise TypeError
154
+ end
155
+ end
156
+
157
+ def build_argument_nodes(arguments)
158
+ arguments.map { |arg| build_argument_node(arg) }
159
+ end
160
+
161
+ def build_directive_nodes(directives)
162
+ directives.map { |directive| build_directive_node(directive) }
163
+ end
164
+
165
+ def build_definition_nodes
166
+ definitions = build_type_definition_nodes(types)
167
+ definitions += build_directive_nodes(schema.directives.values)
168
+ definitions << build_schema_node(schema)
169
+ definitions
170
+ end
171
+
172
+ def build_type_definition_nodes(types)
173
+ types.map { |type| build_type_definition_node(type) }
174
+ end
175
+
176
+ def build_field_nodes(fields)
177
+ fields.map { |field| build_field_node(field) }
178
+ end
179
+
180
+ private
181
+
182
+ attr_reader :schema, :types
183
+ end
184
+ end
185
+ end