1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141
|
# frozen_string_literal: true
module GraphQL
class Backtrace
# A class for turning a context into a human-readable table or array
class Table
MIN_COL_WIDTH = 4
MAX_COL_WIDTH = 100
HEADERS = [
"Loc",
"Field",
"Object",
"Arguments",
"Result",
]
def initialize(context, value:)
@context = context
@override_value = value
end
# @return [String] A table layout of backtrace with metadata
def to_table
@to_table ||= render_table(rows)
end
# @return [Array<String>] An array of position + field name entries
def to_backtrace
@to_backtrace ||= begin
backtrace = rows.map { |r| "#{r[0]}: #{r[1]}" }
# skip the header entry
backtrace.shift
backtrace
end
end
private
def rows
@rows ||= build_rows(@context, rows: [HEADERS], top: true)
end
# @return [String]
def render_table(rows)
max = Array.new(HEADERS.length, MIN_COL_WIDTH)
rows.each do |row|
row.each_with_index do |col, idx|
col_len = col.length
max_len = max[idx]
if col_len > max_len
if col_len > MAX_COL_WIDTH
max[idx] = MAX_COL_WIDTH
else
max[idx] = col_len
end
end
end
end
table = "".dup
last_col_idx = max.length - 1
rows.each do |row|
table << row.map.each_with_index do |col, idx|
max_len = max[idx]
if idx < last_col_idx
col = col.ljust(max_len)
end
if col.length > max_len
col = col[0, max_len - 3] + "..."
end
col
end.join(" | ")
table << "\n"
end
table
end
# @return [Array] 5 items for a backtrace table (not `key`)
def build_rows(context_entry, rows:, top: false)
case context_entry
when Backtrace::Frame
field_alias = context_entry.ast_node.respond_to?(:alias) && context_entry.ast_node.alias
value = if top && @override_value
@override_value
else
value_at(@context.query.context.namespace(:interpreter_runtime)[:runtime], context_entry.path)
end
rows << [
"#{context_entry.ast_node ? context_entry.ast_node.position.join(":") : ""}",
"#{context_entry.field.path}#{field_alias ? " as #{field_alias}" : ""}",
"#{context_entry.object.object.inspect}",
context_entry.arguments.to_h.inspect, # rubocop:disable Development/ContextIsPassedCop -- unrelated method
Backtrace::InspectResult.inspect_result(value),
]
if (parent = context_entry.parent_frame)
build_rows(parent, rows: rows)
else
rows
end
when GraphQL::Query::Context
query = context_entry.query
op = query.selected_operation
if op
op_type = op.operation_type
position = "#{op.line}:#{op.col}"
else
op_type = "query"
position = "?:?"
end
op_name = query.selected_operation_name
object = query.root_value
if object.is_a?(GraphQL::Schema::Object)
object = object.object
end
value = value_at(context_entry.namespace(:interpreter_runtime)[:runtime], [])
rows << [
"#{position}",
"#{op_type}#{op_name ? " #{op_name}" : ""}",
"#{object.inspect}",
query.variables.to_h.inspect,
Backtrace::InspectResult.inspect_result(value),
]
else
raise "Unexpected get_rows subject #{context_entry.class} (#{context_entry.inspect})"
end
end
def value_at(runtime, path)
response = runtime.final_result
path.each do |key|
if response && (response = response[key])
next
else
break
end
end
response
end
end
end
end
|