graphql 1.6.8 → 1.7.0

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