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