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 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222
|
# frozen_string_literal: true
require "graphql"
require "graphql/client/collocated_enforcement"
require "graphql/client/schema/object_type"
require "graphql/client/schema/possible_types"
require "set"
module GraphQL
class Client
# Definitions are constructed by Client.parse and wrap a parsed AST of the
# query string as well as hold references to any external query definition
# dependencies.
#
# Definitions MUST be assigned to a constant.
class Definition < Module
def self.for(ast_node:, **kargs)
case ast_node
when Language::Nodes::OperationDefinition
OperationDefinition.new(ast_node: ast_node, **kargs)
when Language::Nodes::FragmentDefinition
FragmentDefinition.new(ast_node: ast_node, **kargs)
else
raise TypeError, "expected node to be a definition type, but was #{ast_node.class}"
end
end
def initialize(client:, document:, source_document:, ast_node:, source_location:)
@client = client
@document = document
@source_document = source_document
@definition_node = ast_node
@source_location = source_location
definition_type = case ast_node
when GraphQL::Language::Nodes::OperationDefinition
case ast_node.operation_type
when "mutation"
@client.schema.mutation
when "subscription"
@client.schema.subscription
when "query", nil
@client.schema.query
else
raise "Unexpected operation_type: #{ast_node.operation_type}"
end
when GraphQL::Language::Nodes::FragmentDefinition
@client.get_type(ast_node.type.name)
else
raise "Unexpected ast_node: #{ast_node}"
end
@schema_class = client.types.define_class(self, [ast_node], definition_type)
# Clear cache only needed during initialization
@indexes = nil
end
# Internal: Get associated owner GraphQL::Client instance.
attr_reader :client
# Internal root schema class for definition. Returns
# GraphQL::Client::Schema::ObjectType or
# GraphQL::Client::Schema::PossibleTypes.
attr_reader :schema_class
# Internal: Get underlying operation or fragment definition AST node for
# definition.
#
# Returns OperationDefinition or FragmentDefinition object.
attr_reader :definition_node
# Internal: Get original document that created this definition, without
# any additional dependencies.
#
# Returns GraphQL::Language::Nodes::Document.
attr_reader :source_document
# Public: Global name of definition in client document.
#
# Returns a GraphQL safe name of the Ruby constant String.
#
# "Users::UserQuery" #=> "Users__UserQuery"
#
# Returns String.
def definition_name
return @definition_name if defined?(@definition_name)
if name
@definition_name = name.gsub("::", "__").freeze
else
"#{self.class.name}_#{object_id}".gsub("::", "__").freeze
end
end
# Public: Get document with only the definitions needed to perform this
# operation.
#
# Returns GraphQL::Language::Nodes::Document with one OperationDefinition
# and any FragmentDefinition dependencies.
attr_reader :document
# Public: Returns the Ruby source filename and line number containing this
# definition was not defined in Ruby.
#
# Returns Array pair of [String, Fixnum].
attr_reader :source_location
def new(obj, errors = Errors.new)
case schema_class
when GraphQL::Client::Schema::PossibleTypes
case obj
when NilClass
obj
else
cast_object(obj)
end
when GraphQL::Client::Schema::ObjectType::WithDefinition
case obj
when schema_class.klass
if obj._definer == schema_class
obj
else
cast_object(obj)
end
when nil
nil
when Hash
schema_class.new(obj, errors)
else
cast_object(obj)
end
when GraphQL::Client::Schema::ObjectType
case obj
when nil, schema_class
obj
when Hash
schema_class.new(obj, errors)
else
cast_object(obj)
end
else
raise TypeError, "unexpected #{schema_class}"
end
end
# Internal: Nodes AST indexes.
def indexes
@indexes ||= begin
visitor = GraphQL::Language::Visitor.new(document)
definitions = index_node_definitions(visitor)
spreads = index_spreads(visitor)
visitor.visit
{ definitions: definitions, spreads: spreads }
end
end
private
def cast_object(obj)
if obj.class.is_a?(GraphQL::Client::Schema::ObjectType)
unless obj._spreads.include?(definition_node.name)
raise TypeError, "#{definition_node.name} is not included in #{obj.source_definition.name}"
end
schema_class.cast(obj.to_h, obj.errors)
else
raise TypeError, "unexpected #{obj.class}"
end
end
EMPTY_SET = Set.new.freeze
def index_spreads(visitor)
spreads = {}
on_node = ->(node, _parent) do
node_spreads = flatten_spreads(node).map(&:name)
spreads[node] = node_spreads.empty? ? EMPTY_SET : Set.new(node_spreads).freeze
end
visitor[GraphQL::Language::Nodes::Field] << on_node
visitor[GraphQL::Language::Nodes::FragmentDefinition] << on_node
visitor[GraphQL::Language::Nodes::OperationDefinition] << on_node
spreads
end
def flatten_spreads(node)
spreads = []
node.selections.each do |selection|
case selection
when Language::Nodes::FragmentSpread
spreads << selection
when Language::Nodes::InlineFragment
spreads.concat(flatten_spreads(selection))
else
# Do nothing, not a spread
end
end
spreads
end
def index_node_definitions(visitor)
current_definition = nil
enter_definition = ->(node, _parent) { current_definition = node }
leave_definition = ->(node, _parent) { current_definition = nil }
visitor[GraphQL::Language::Nodes::FragmentDefinition].enter << enter_definition
visitor[GraphQL::Language::Nodes::FragmentDefinition].leave << leave_definition
visitor[GraphQL::Language::Nodes::OperationDefinition].enter << enter_definition
visitor[GraphQL::Language::Nodes::OperationDefinition].leave << leave_definition
definitions = {}
on_node = ->(node, _parent) { definitions[node] = current_definition }
visitor[GraphQL::Language::Nodes::Field] << on_node
visitor[GraphQL::Language::Nodes::FragmentDefinition] << on_node
visitor[GraphQL::Language::Nodes::InlineFragment] << on_node
visitor[GraphQL::Language::Nodes::OperationDefinition] << on_node
definitions
end
end
end
end
|