graphql 1.6.8 → 1.7.0

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 (68) hide show
  1. checksums.yaml +4 -4
  2. data/lib/graphql.rb +5 -0
  3. data/lib/graphql/analysis/analyze_query.rb +21 -17
  4. data/lib/graphql/argument.rb +6 -2
  5. data/lib/graphql/backtrace.rb +50 -0
  6. data/lib/graphql/backtrace/inspect_result.rb +51 -0
  7. data/lib/graphql/backtrace/table.rb +120 -0
  8. data/lib/graphql/backtrace/traced_error.rb +55 -0
  9. data/lib/graphql/backtrace/tracer.rb +50 -0
  10. data/lib/graphql/enum_type.rb +1 -10
  11. data/lib/graphql/execution.rb +1 -2
  12. data/lib/graphql/execution/execute.rb +98 -89
  13. data/lib/graphql/execution/flatten.rb +40 -0
  14. data/lib/graphql/execution/lazy/resolve.rb +7 -7
  15. data/lib/graphql/execution/multiplex.rb +29 -29
  16. data/lib/graphql/field.rb +5 -1
  17. data/lib/graphql/internal_representation/node.rb +16 -0
  18. data/lib/graphql/invalid_name_error.rb +11 -0
  19. data/lib/graphql/language/parser.rb +11 -5
  20. data/lib/graphql/language/parser.y +11 -5
  21. data/lib/graphql/name_validator.rb +16 -0
  22. data/lib/graphql/object_type.rb +5 -0
  23. data/lib/graphql/query.rb +28 -7
  24. data/lib/graphql/query/context.rb +155 -52
  25. data/lib/graphql/query/literal_input.rb +36 -9
  26. data/lib/graphql/query/null_context.rb +7 -1
  27. data/lib/graphql/query/result.rb +63 -0
  28. data/lib/graphql/query/serial_execution/field_resolution.rb +3 -4
  29. data/lib/graphql/query/serial_execution/value_resolution.rb +3 -4
  30. data/lib/graphql/query/variables.rb +1 -1
  31. data/lib/graphql/schema.rb +31 -0
  32. data/lib/graphql/schema/traversal.rb +16 -1
  33. data/lib/graphql/schema/warden.rb +40 -4
  34. data/lib/graphql/static_validation/validator.rb +20 -18
  35. data/lib/graphql/subscriptions.rb +129 -0
  36. data/lib/graphql/subscriptions/action_cable_subscriptions.rb +122 -0
  37. data/lib/graphql/subscriptions/event.rb +52 -0
  38. data/lib/graphql/subscriptions/instrumentation.rb +68 -0
  39. data/lib/graphql/tracing.rb +80 -0
  40. data/lib/graphql/tracing/active_support_notifications_tracing.rb +31 -0
  41. data/lib/graphql/version.rb +1 -1
  42. data/readme.md +1 -1
  43. data/spec/graphql/analysis/analyze_query_spec.rb +19 -0
  44. data/spec/graphql/argument_spec.rb +28 -0
  45. data/spec/graphql/backtrace_spec.rb +144 -0
  46. data/spec/graphql/define/assign_argument_spec.rb +12 -0
  47. data/spec/graphql/enum_type_spec.rb +1 -1
  48. data/spec/graphql/execution/execute_spec.rb +66 -0
  49. data/spec/graphql/execution/lazy_spec.rb +4 -3
  50. data/spec/graphql/language/parser_spec.rb +16 -0
  51. data/spec/graphql/object_type_spec.rb +14 -0
  52. data/spec/graphql/query/context_spec.rb +134 -27
  53. data/spec/graphql/query/result_spec.rb +29 -0
  54. data/spec/graphql/query/variables_spec.rb +13 -0
  55. data/spec/graphql/query_spec.rb +22 -0
  56. data/spec/graphql/schema/build_from_definition_spec.rb +2 -0
  57. data/spec/graphql/schema/traversal_spec.rb +70 -12
  58. data/spec/graphql/schema/warden_spec.rb +67 -1
  59. data/spec/graphql/schema_spec.rb +29 -0
  60. data/spec/graphql/static_validation/validator_spec.rb +16 -0
  61. data/spec/graphql/subscriptions_spec.rb +331 -0
  62. data/spec/graphql/tracing/active_support_notifications_tracing_spec.rb +57 -0
  63. data/spec/graphql/tracing_spec.rb +47 -0
  64. data/spec/spec_helper.rb +32 -0
  65. data/spec/support/star_wars/schema.rb +39 -0
  66. metadata +27 -4
  67. data/lib/graphql/execution/field_result.rb +0 -54
  68. data/lib/graphql/execution/selection_result.rb +0 -90
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: '056695dd78098e5adfe501785c8e7e111cb8bc2d'
4
- data.tar.gz: 5ef5aa896020c5b149c1960a5e870d8f13e4a2f8
3
+ metadata.gz: 143f5c1d1fd648361bc54f779e9e160d5953a439
4
+ data.tar.gz: a17369262755499d3adfa17be3465ac4d68e6c97
5
5
  SHA512:
6
- metadata.gz: 50d4e93b57572c9a3cce63468b0e6b23daca8f61a97120e5899613018c1bdfc4fe9ecade9b8906e4243c383a55f636b37d066e52d14310c002911e7e7c3c086f
7
- data.tar.gz: 63d326d4a3ea184e7840308d6841afb7af3336d570f9d8362ffdad5d0e351016e0d67c72bf9ec62e3629a5ce768bf4fe1b92bc0873b7effa47b37aa3f370f7f9
6
+ metadata.gz: d9d383e0fcb02bd527c830c157ce0e6b97a8f3b552f75d6e6be28f51f82296168bfea5e81f4e06463d706d2dc264095aa38b048d35e3afc432379b5482d6afd7
7
+ data.tar.gz: a22957f865c6145c04af2f68b1810f799caa873a60cc3788607505c63be52fbaa9f393a9bf2809a85e64e5625efbe83ceeab2642f7c29ef4d952c1391c9ac3e7
data/lib/graphql.rb CHANGED
@@ -87,6 +87,7 @@ require "graphql/id_type"
87
87
  require "graphql/int_type"
88
88
  require "graphql/string_type"
89
89
  require "graphql/directive"
90
+ require "graphql/name_validator"
90
91
 
91
92
  require "graphql/introspection"
92
93
  require "graphql/language"
@@ -100,6 +101,7 @@ require "graphql/schema/printer"
100
101
  require "graphql/analysis_error"
101
102
  require "graphql/runtime_type_error"
102
103
  require "graphql/invalid_null_error"
104
+ require "graphql/invalid_name_error"
103
105
  require "graphql/unresolved_type_error"
104
106
  require "graphql/string_encoding_error"
105
107
  require "graphql/query"
@@ -109,4 +111,7 @@ require "graphql/version"
109
111
  require "graphql/compatibility"
110
112
  require "graphql/function"
111
113
  require "graphql/filter"
114
+ require "graphql/subscriptions"
112
115
  require "graphql/parse_error"
116
+ require "graphql/tracing"
117
+ require "graphql/backtrace"
@@ -5,20 +5,22 @@ module GraphQL
5
5
 
6
6
  # @return [void]
7
7
  def analyze_multiplex(multiplex, analyzers)
8
- reducer_states = analyzers.map { |r| ReducerState.new(r, multiplex) }
9
- query_results = multiplex.queries.map do |query|
10
- if query.valid?
11
- analyze_query(query, query.analyzers, multiplex_states: reducer_states)
12
- else
13
- []
8
+ GraphQL::Tracing.trace("analyze_multiplex", { multiplex: multiplex }) do
9
+ reducer_states = analyzers.map { |r| ReducerState.new(r, multiplex) }
10
+ query_results = multiplex.queries.map do |query|
11
+ if query.valid?
12
+ analyze_query(query, query.analyzers, multiplex_states: reducer_states)
13
+ else
14
+ []
15
+ end
14
16
  end
15
- end
16
17
 
17
- multiplex_results = reducer_states.map(&:finalize_reducer)
18
- multiplex_errors = analysis_errors(multiplex_results)
18
+ multiplex_results = reducer_states.map(&:finalize_reducer)
19
+ multiplex_errors = analysis_errors(multiplex_results)
19
20
 
20
- multiplex.queries.each_with_index do |query, idx|
21
- query.analysis_errors = multiplex_errors + analysis_errors(query_results[idx])
21
+ multiplex.queries.each_with_index do |query, idx|
22
+ query.analysis_errors = multiplex_errors + analysis_errors(query_results[idx])
23
+ end
22
24
  end
23
25
  nil
24
26
  end
@@ -35,15 +37,17 @@ module GraphQL
35
37
  # @param analyzers [Array<#call>] Objects that respond to `#call(memo, visit_type, irep_node)`
36
38
  # @return [Array<Any>] Results from those analyzers
37
39
  def analyze_query(query, analyzers, multiplex_states: [])
38
- reducer_states = analyzers.map { |r| ReducerState.new(r, query) } + multiplex_states
40
+ GraphQL::Tracing.trace("analyze_query", { query: query }) do
41
+ reducer_states = analyzers.map { |r| ReducerState.new(r, query) } + multiplex_states
39
42
 
40
- irep = query.internal_representation
43
+ irep = query.internal_representation
41
44
 
42
- irep.operation_definitions.each do |name, op_node|
43
- reduce_node(op_node, reducer_states)
44
- end
45
+ irep.operation_definitions.each do |name, op_node|
46
+ reduce_node(op_node, reducer_states)
47
+ end
45
48
 
46
- reducer_states.map(&:finalize_reducer)
49
+ reducer_states.map(&:finalize_reducer)
50
+ end
47
51
  end
48
52
 
49
53
  private
@@ -96,7 +96,11 @@ module GraphQL
96
96
 
97
97
  NO_DEFAULT_VALUE = Object.new
98
98
  # @api private
99
- def self.from_dsl(name, type = nil, description = nil, default_value: NO_DEFAULT_VALUE, as: nil, prepare: DefaultPrepare, **kwargs, &block)
99
+ def self.from_dsl(name, type_or_argument = nil, description = nil, default_value: NO_DEFAULT_VALUE, as: nil, prepare: DefaultPrepare, **kwargs, &block)
100
+ if type_or_argument.is_a?(GraphQL::Argument)
101
+ return type_or_argument.redefine(name: name.to_s)
102
+ end
103
+
100
104
  argument = if block_given?
101
105
  GraphQL::Argument.define(&block)
102
106
  else
@@ -104,7 +108,7 @@ module GraphQL
104
108
  end
105
109
 
106
110
  argument.name = name.to_s
107
- type && argument.type = type
111
+ type_or_argument && argument.type = type_or_argument
108
112
  description && argument.description = description
109
113
  if default_value != NO_DEFAULT_VALUE
110
114
  argument.default_value = default_value
@@ -0,0 +1,50 @@
1
+ # frozen_string_literal: true
2
+ require "graphql/backtrace/inspect_result"
3
+ require "graphql/backtrace/table"
4
+ require "graphql/backtrace/traced_error"
5
+ require "graphql/backtrace/tracer"
6
+ module GraphQL
7
+ # Wrap unhandled errors with {TracedError}.
8
+ #
9
+ # {TracedError} provides a GraphQL backtrace with arguments and return values.
10
+ # The underlying error is available as {TracedError#cause}.
11
+ #
12
+ # WARNING: {.enable} is not threadsafe because {GraphQL::Tracing.install} is not threadsafe.
13
+ #
14
+ # @example toggling backtrace annotation
15
+ # # to enable:
16
+ # GraphQL::Backtrace.enable
17
+ # # later, to disable:
18
+ # GraphQL::Backtrace.disable
19
+ #
20
+ class Backtrace
21
+ include Enumerable
22
+ extend GraphQL::Delegate
23
+
24
+ def_delegators :to_a, :each, :[]
25
+
26
+ def self.enable
27
+ GraphQL::Tracing.install(Backtrace::Tracer)
28
+ nil
29
+ end
30
+
31
+ def self.disable
32
+ GraphQL::Tracing.uninstall(Backtrace::Tracer)
33
+ nil
34
+ end
35
+
36
+ def initialize(context, value: nil)
37
+ @table = Table.new(context, value: value)
38
+ end
39
+
40
+ def inspect
41
+ @table.to_table
42
+ end
43
+
44
+ alias :to_s :inspect
45
+
46
+ def to_a
47
+ @table.to_backtrace
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,51 @@
1
+ # frozen_string_literal: true
2
+ # test_via: ../backtrace.rb
3
+ module GraphQL
4
+ class Backtrace
5
+ module InspectResult
6
+ module_function
7
+
8
+ def inspect(obj)
9
+ case obj
10
+ when Hash
11
+ "{" +
12
+ obj.map do |key, val|
13
+ "#{key}: #{inspect_truncated(val)}"
14
+ end.join(", ") +
15
+ "}"
16
+ when Array
17
+ "[" +
18
+ obj.map { |v| inspect_truncated(v) }.join(", ") +
19
+ "]"
20
+ when Query::Context::SharedMethods
21
+ if obj.invalid_null?
22
+ "nil"
23
+ else
24
+ inspect_truncated(obj.value)
25
+ end
26
+ else
27
+ inspect_truncated(obj)
28
+ end
29
+ end
30
+
31
+ def inspect_truncated(obj)
32
+ case obj
33
+ when Hash
34
+ "{...}"
35
+ when Array
36
+ "[...]"
37
+ when Query::Context::SharedMethods
38
+ if obj.invalid_null?
39
+ "nil"
40
+ else
41
+ inspect_truncated(obj.value)
42
+ end
43
+ when GraphQL::Execution::Lazy
44
+ "(unresolved)"
45
+ else
46
+ obj.inspect
47
+ end
48
+ end
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,120 @@
1
+ # frozen_string_literal: true
2
+ # test_via: ../backtrace.rb
3
+ module GraphQL
4
+ class Backtrace
5
+ # A class for turning a context into a human-readable table or array
6
+ class Table
7
+ MIN_COL_WIDTH = 4
8
+ MAX_COL_WIDTH = 100
9
+ HEADERS = [
10
+ "Loc",
11
+ "Field",
12
+ "Object",
13
+ "Arguments",
14
+ "Result",
15
+ ]
16
+
17
+ def initialize(context, value:)
18
+ @context = context
19
+ @override_value = value
20
+ end
21
+
22
+ # @return [String] A table layout of backtrace with metadata
23
+ def to_table
24
+ @to_table ||= render_table(rows)
25
+ end
26
+
27
+ # @return [Array<String>] An array of position + field name entries
28
+ def to_backtrace
29
+ @to_backtrace ||= begin
30
+ backtrace = rows.map { |r| "#{r[0]}: #{r[1]}" }
31
+ # skip the header entry
32
+ backtrace.shift
33
+ backtrace
34
+ end
35
+ end
36
+
37
+ private
38
+
39
+ def rows
40
+ @rows ||= build_rows(@context, rows: [HEADERS], top: true)
41
+ end
42
+
43
+ # @return [String]
44
+ def render_table(rows)
45
+ max = Array.new(HEADERS.length, MIN_COL_WIDTH)
46
+
47
+ rows.each do |row|
48
+ row.each_with_index do |col, idx|
49
+ col_len = col.length
50
+ max_len = max[idx]
51
+ if col_len > max_len
52
+ if col_len > MAX_COL_WIDTH
53
+ max[idx] = MAX_COL_WIDTH
54
+ else
55
+ max[idx] = col_len
56
+ end
57
+ end
58
+ end
59
+ end
60
+
61
+ table = "".dup
62
+ last_col_idx = max.length - 1
63
+ rows.each do |row|
64
+ table << row.map.each_with_index do |col, idx|
65
+ max_len = max[idx]
66
+ if idx < last_col_idx
67
+ col = col.ljust(max_len)
68
+ end
69
+ if col.length > max_len
70
+ col = col[0, max_len - 3] + "..."
71
+ end
72
+ col
73
+ end.join(" | ")
74
+ table << "\n"
75
+ end
76
+ table
77
+ end
78
+
79
+ # @return [Array] 5 items for a backtrace table (not `key`)
80
+ def build_rows(context_entry, rows:, top: false)
81
+ case context_entry
82
+ when GraphQL::Query::Context::FieldResolutionContext
83
+ ctx = context_entry
84
+ field_name = "#{ctx.irep_node.owner_type.name}.#{ctx.field.name}"
85
+ position = "#{ctx.ast_node.line}:#{ctx.ast_node.col}"
86
+ field_alias = ctx.ast_node.alias
87
+ rows << [
88
+ "#{position}",
89
+ "#{field_name}#{field_alias ? " as #{field_alias}" : ""}",
90
+ ctx.object.inspect,
91
+ ctx.irep_node.arguments.to_h.inspect,
92
+ Backtrace::InspectResult.inspect(top && @override_value ? @override_value : ctx.value),
93
+ ]
94
+
95
+ build_rows(ctx.parent, rows: rows)
96
+ when GraphQL::Query::Context
97
+ query = context_entry.query
98
+ op = query.selected_operation
99
+ if op
100
+ op_type = op.operation_type
101
+ position = "#{op.line}:#{op.col}"
102
+ else
103
+ op_type = "query"
104
+ position = "?:?"
105
+ end
106
+ op_name = query.selected_operation_name
107
+ rows << [
108
+ "#{position}",
109
+ "#{op_type}#{op_name ? " #{op_name}" : ""}",
110
+ query.root_value.inspect,
111
+ query.variables.to_h.inspect,
112
+ Backtrace::InspectResult.inspect(query.context.value),
113
+ ]
114
+ else
115
+ raise "Unexpected get_rows subject #{context_entry.inspect}"
116
+ end
117
+ end
118
+ end
119
+ end
120
+ end
@@ -0,0 +1,55 @@
1
+ # frozen_string_literal: true
2
+ # test_via: ../backtrace.rb
3
+ module GraphQL
4
+ class Backtrace
5
+ # When {Backtrace} is enabled, raised errors are wrapped with {TracedError}.
6
+ class TracedError < GraphQL::Error
7
+ # @return [Array<String>] Printable backtrace of GraphQL error context
8
+ attr_reader :graphql_backtrace
9
+
10
+ # @return [GraphQL::Query::Context] The context at the field where the error was raised
11
+ attr_reader :context
12
+
13
+ MESSAGE_TEMPLATE = <<-MESSAGE
14
+ Unhandled error during GraphQL execution:
15
+
16
+ %{cause_message}
17
+ %{cause_backtrace}
18
+ %{cause_backtrace_more}
19
+ Use #cause to access the original exception (including #cause.backtrace).
20
+
21
+ GraphQL Backtrace:
22
+ %{graphql_table}
23
+ MESSAGE
24
+
25
+ # This many lines of the original Ruby backtrace
26
+ # are included in the message
27
+ CAUSE_BACKTRACE_PREVIEW_LENGTH = 10
28
+
29
+ def initialize(err, current_ctx)
30
+ @context = current_ctx
31
+ backtrace = Backtrace.new(current_ctx, value: err)
32
+ @graphql_backtrace = backtrace.to_a
33
+
34
+ cause_backtrace_preview = err.backtrace.first(CAUSE_BACKTRACE_PREVIEW_LENGTH).join("\n ")
35
+
36
+ cause_backtrace_remainder_length = err.backtrace.length - CAUSE_BACKTRACE_PREVIEW_LENGTH
37
+ cause_backtrace_more = if cause_backtrace_remainder_length < 0
38
+ ""
39
+ elsif cause_backtrace_remainder_length == 1
40
+ "... and 1 more line\n"
41
+ else
42
+ "... and #{cause_backtrace_remainder_length} more lines\n"
43
+ end
44
+
45
+ message = MESSAGE_TEMPLATE % {
46
+ cause_message: err.message,
47
+ cause_backtrace: cause_backtrace_preview,
48
+ cause_backtrace_more: cause_backtrace_more,
49
+ graphql_table: backtrace.inspect,
50
+ }
51
+ super(message)
52
+ end
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,50 @@
1
+ # frozen_string_literal: true
2
+ module GraphQL
3
+ class Backtrace
4
+ module Tracer
5
+ module_function
6
+
7
+ # Implement the {GraphQL::Tracing} API.
8
+ def trace(key, metadata)
9
+ push_data = case key
10
+ when "lex", "parse"
11
+ # No context here, don't have a query yet
12
+ nil
13
+ when "execute_multiplex", "analyze_multiplex"
14
+ metadata[:multiplex].queries
15
+ when "validate", "analyze_query", "execute_query", "execute_query_lazy"
16
+ metadata[:query] || metadata[:queries]
17
+ when "execute_field", "execute_field_lazy"
18
+ metadata[:context]
19
+ else
20
+ # Custom key, no backtrace data for this
21
+ nil
22
+ end
23
+
24
+ if push_data
25
+ execution_context = Thread.current[:graphql_execution_context] ||= []
26
+ if key == "execute_multiplex"
27
+ execution_context.clear
28
+ execution_context.push(push_data)
29
+ begin
30
+ yield
31
+ rescue StandardError => err
32
+ # This is an unhandled error from execution,
33
+ # Re-raise it with a GraphQL trace.
34
+ raise TracedError.new(err, execution_context.last)
35
+ ensure
36
+ execution_context.clear
37
+ end
38
+ else
39
+ execution_context.push(push_data)
40
+ res = yield
41
+ execution_context.pop
42
+ res
43
+ end
44
+ else
45
+ yield
46
+ end
47
+ end
48
+ end
49
+ end
50
+ end