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
|
# frozen_string_literal: true
require "graphql"
require "graphql/client/document_types"
module GraphQL
class Client
# Internal: Insert __typename field selections into query.
module QueryTypename
# Internal: Insert __typename field selections into query.
#
# Skips known types when schema is provided.
#
# document - GraphQL::Language::Nodes::Document to modify
# schema - Optional Map of GraphQL::Language::Nodes::Node to GraphQL::Type
#
# Returns the document with `__typename` added to it
if GraphQL::Language::Nodes::AbstractNode.method_defined?(:merge)
# GraphQL 1.9 introduces a new visitor class
# and doesn't expose writer methods for node attributes.
# So, use the node mutation API instead.
class InsertTypenameVisitor < GraphQL::Language::Visitor
def initialize(document, types:)
@types = types
super(document)
end
def add_typename(node, parent)
type = @types[node]
type = type && type.unwrap
if (node.selections.any? && (type.nil? || type.kind.interface? || type.kind.union?)) ||
(node.selections.none? && (type && type.kind.object?))
names = QueryTypename.node_flatten_selections(node.selections).map { |s| s.respond_to?(:name) ? s.name : nil }
names = Set.new(names.compact)
if names.include?("__typename")
yield(node, parent)
else
node_with_typename = node.merge(selections: [GraphQL::Language::Nodes::Field.new(name: "__typename")] + node.selections)
yield(node_with_typename, parent)
end
else
yield(node, parent)
end
end
def on_operation_definition(node, parent)
add_typename(node, parent) { |n, p| super(n, p) }
end
def on_field(node, parent)
add_typename(node, parent) { |n, p| super(n, p) }
end
def on_fragment_definition(node, parent)
add_typename(node, parent) { |n, p| super(n, p) }
end
end
def self.insert_typename_fields(document, types: {})
visitor = InsertTypenameVisitor.new(document, types: types)
visitor.visit
visitor.result
end
else
def self.insert_typename_fields(document, types: {})
on_selections = ->(node, _parent) do
type = types[node]
if node.selections.any?
case type && type.unwrap
when NilClass, GraphQL::InterfaceType, GraphQL::UnionType
names = node_flatten_selections(node.selections).map { |s| s.respond_to?(:name) ? s.name : nil }
names = Set.new(names.compact)
unless names.include?("__typename")
node.selections = [GraphQL::Language::Nodes::Field.new(name: "__typename")] + node.selections
end
end
elsif type && type.unwrap.is_a?(GraphQL::ObjectType)
node.selections = [GraphQL::Language::Nodes::Field.new(name: "__typename")]
end
end
visitor = GraphQL::Language::Visitor.new(document)
visitor[GraphQL::Language::Nodes::Field].leave << on_selections
visitor[GraphQL::Language::Nodes::FragmentDefinition].leave << on_selections
visitor[GraphQL::Language::Nodes::OperationDefinition].leave << on_selections
visitor.visit
document
end
end
def self.node_flatten_selections(selections)
selections.flat_map do |selection|
case selection
when GraphQL::Language::Nodes::Field
selection
when GraphQL::Language::Nodes::InlineFragment
node_flatten_selections(selection.selections)
else
[]
end
end
end
end
end
end
|