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.
- checksums.yaml +4 -4
- data/lib/graphql.rb +5 -0
- data/lib/graphql/analysis/analyze_query.rb +21 -17
- data/lib/graphql/argument.rb +6 -2
- data/lib/graphql/backtrace.rb +50 -0
- data/lib/graphql/backtrace/inspect_result.rb +51 -0
- data/lib/graphql/backtrace/table.rb +120 -0
- data/lib/graphql/backtrace/traced_error.rb +55 -0
- data/lib/graphql/backtrace/tracer.rb +50 -0
- data/lib/graphql/enum_type.rb +1 -10
- data/lib/graphql/execution.rb +1 -2
- data/lib/graphql/execution/execute.rb +98 -89
- data/lib/graphql/execution/flatten.rb +40 -0
- data/lib/graphql/execution/lazy/resolve.rb +7 -7
- data/lib/graphql/execution/multiplex.rb +29 -29
- data/lib/graphql/field.rb +5 -1
- data/lib/graphql/internal_representation/node.rb +16 -0
- data/lib/graphql/invalid_name_error.rb +11 -0
- data/lib/graphql/language/parser.rb +11 -5
- data/lib/graphql/language/parser.y +11 -5
- data/lib/graphql/name_validator.rb +16 -0
- data/lib/graphql/object_type.rb +5 -0
- data/lib/graphql/query.rb +28 -7
- data/lib/graphql/query/context.rb +155 -52
- data/lib/graphql/query/literal_input.rb +36 -9
- data/lib/graphql/query/null_context.rb +7 -1
- data/lib/graphql/query/result.rb +63 -0
- data/lib/graphql/query/serial_execution/field_resolution.rb +3 -4
- data/lib/graphql/query/serial_execution/value_resolution.rb +3 -4
- data/lib/graphql/query/variables.rb +1 -1
- data/lib/graphql/schema.rb +31 -0
- data/lib/graphql/schema/traversal.rb +16 -1
- data/lib/graphql/schema/warden.rb +40 -4
- data/lib/graphql/static_validation/validator.rb +20 -18
- data/lib/graphql/subscriptions.rb +129 -0
- data/lib/graphql/subscriptions/action_cable_subscriptions.rb +122 -0
- data/lib/graphql/subscriptions/event.rb +52 -0
- data/lib/graphql/subscriptions/instrumentation.rb +68 -0
- data/lib/graphql/tracing.rb +80 -0
- data/lib/graphql/tracing/active_support_notifications_tracing.rb +31 -0
- data/lib/graphql/version.rb +1 -1
- data/readme.md +1 -1
- data/spec/graphql/analysis/analyze_query_spec.rb +19 -0
- data/spec/graphql/argument_spec.rb +28 -0
- data/spec/graphql/backtrace_spec.rb +144 -0
- data/spec/graphql/define/assign_argument_spec.rb +12 -0
- data/spec/graphql/enum_type_spec.rb +1 -1
- data/spec/graphql/execution/execute_spec.rb +66 -0
- data/spec/graphql/execution/lazy_spec.rb +4 -3
- data/spec/graphql/language/parser_spec.rb +16 -0
- data/spec/graphql/object_type_spec.rb +14 -0
- data/spec/graphql/query/context_spec.rb +134 -27
- data/spec/graphql/query/result_spec.rb +29 -0
- data/spec/graphql/query/variables_spec.rb +13 -0
- data/spec/graphql/query_spec.rb +22 -0
- data/spec/graphql/schema/build_from_definition_spec.rb +2 -0
- data/spec/graphql/schema/traversal_spec.rb +70 -12
- data/spec/graphql/schema/warden_spec.rb +67 -1
- data/spec/graphql/schema_spec.rb +29 -0
- data/spec/graphql/static_validation/validator_spec.rb +16 -0
- data/spec/graphql/subscriptions_spec.rb +331 -0
- data/spec/graphql/tracing/active_support_notifications_tracing_spec.rb +57 -0
- data/spec/graphql/tracing_spec.rb +47 -0
- data/spec/spec_helper.rb +32 -0
- data/spec/support/star_wars/schema.rb +39 -0
- metadata +27 -4
- data/lib/graphql/execution/field_result.rb +0 -54
- data/lib/graphql/execution/selection_result.rb +0 -90
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 143f5c1d1fd648361bc54f779e9e160d5953a439
|
4
|
+
data.tar.gz: a17369262755499d3adfa17be3465ac4d68e6c97
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
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
|
-
|
18
|
-
|
18
|
+
multiplex_results = reducer_states.map(&:finalize_reducer)
|
19
|
+
multiplex_errors = analysis_errors(multiplex_results)
|
19
20
|
|
20
|
-
|
21
|
-
|
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
|
-
|
40
|
+
GraphQL::Tracing.trace("analyze_query", { query: query }) do
|
41
|
+
reducer_states = analyzers.map { |r| ReducerState.new(r, query) } + multiplex_states
|
39
42
|
|
40
|
-
|
43
|
+
irep = query.internal_representation
|
41
44
|
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
+
irep.operation_definitions.each do |name, op_node|
|
46
|
+
reduce_node(op_node, reducer_states)
|
47
|
+
end
|
45
48
|
|
46
|
-
|
49
|
+
reducer_states.map(&:finalize_reducer)
|
50
|
+
end
|
47
51
|
end
|
48
52
|
|
49
53
|
private
|
data/lib/graphql/argument.rb
CHANGED
@@ -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,
|
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
|
-
|
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
|