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 223
|
# frozen_string_literal: true
module GraphQL
class Schema
class Member
# Shared code for Objects, Interfaces, Mutations, Subscriptions
module HasFields
# Add a field to this object or interface with the given definition
# @see {GraphQL::Schema::Field#initialize} for method signature
# @return [GraphQL::Schema::Field]
def field(*args, **kwargs, &block)
field_defn = field_class.from_options(*args, owner: self, **kwargs, &block)
add_field(field_defn)
field_defn
end
# A list of Ruby keywords.
#
# @api private
RUBY_KEYWORDS = [:class, :module, :def, :undef, :begin, :rescue, :ensure, :end, :if, :unless, :then, :elsif, :else, :case, :when, :while, :until, :for, :break, :next, :redo, :retry, :in, :do, :return, :yield, :super, :self, :nil, :true, :false, :and, :or, :not, :alias, :defined?, :BEGIN, :END, :__LINE__, :__FILE__]
# A list of GraphQL-Ruby keywords.
#
# @api private
GRAPHQL_RUBY_KEYWORDS = [:context, :object, :raw_value]
# A list of field names that we should advise users to pick a different
# resolve method name.
#
# @api private
CONFLICT_FIELD_NAMES = Set.new(GRAPHQL_RUBY_KEYWORDS + RUBY_KEYWORDS + Object.instance_methods)
# Register this field with the class, overriding a previous one if needed.
# @param field_defn [GraphQL::Schema::Field]
# @return [void]
def add_field(field_defn, method_conflict_warning: field_defn.method_conflict_warning?)
# Check that `field_defn.original_name` equals `resolver_method` and `method_sym` --
# that shows that no override value was given manually.
if method_conflict_warning &&
CONFLICT_FIELD_NAMES.include?(field_defn.resolver_method) &&
field_defn.original_name == field_defn.resolver_method &&
field_defn.original_name == field_defn.method_sym &&
field_defn.hash_key == NOT_CONFIGURED &&
field_defn.dig_keys.nil?
warn(conflict_field_name_warning(field_defn))
end
prev_defn = own_fields[field_defn.name]
case prev_defn
when nil
own_fields[field_defn.name] = field_defn
when Array
prev_defn << field_defn
when GraphQL::Schema::Field
own_fields[field_defn.name] = [prev_defn, field_defn]
else
raise "Invariant: unexpected previous field definition for #{field_defn.name.inspect}: #{prev_defn.inspect}"
end
nil
end
# @return [Class] The class to initialize when adding fields to this kind of schema member
def field_class(new_field_class = nil)
if new_field_class
@field_class = new_field_class
elsif defined?(@field_class) && @field_class
@field_class
else
find_inherited_value(:field_class, GraphQL::Schema::Field)
end
end
def global_id_field(field_name, **kwargs)
type = self
field field_name, "ID", **kwargs, null: false
define_method(field_name) do
context.schema.id_from_object(object, type, context)
end
end
# @return [Hash<String => GraphQL::Schema::Field, Array<GraphQL::Schema::Field>>] Fields defined on this class _specifically_, not parent classes
def own_fields
@own_fields ||= {}
end
def all_field_definitions
all_fields = {}
ancestors.reverse_each do |ancestor|
if ancestor.respond_to?(:own_fields)
all_fields.merge!(ancestor.own_fields)
end
end
all_fields = all_fields.values
all_fields.flatten!
all_fields
end
module InterfaceMethods
def get_field(field_name, context = GraphQL::Query::NullContext.instance)
warden = Warden.from_context(context)
for ancestor in ancestors
if ancestor.respond_to?(:own_fields) &&
(f_entry = ancestor.own_fields[field_name]) &&
(f = Warden.visible_entry?(:visible_field?, f_entry, context, warden))
return f
end
end
nil
end
# @return [Hash<String => GraphQL::Schema::Field>] Fields on this object, keyed by name, including inherited fields
def fields(context = GraphQL::Query::NullContext.instance)
warden = Warden.from_context(context)
# Local overrides take precedence over inherited fields
visible_fields = {}
for ancestor in ancestors
if ancestor.respond_to?(:own_fields)
ancestor.own_fields.each do |field_name, fields_entry|
# Choose the most local definition that passes `.visible?` --
# stop checking for fields by name once one has been found.
if !visible_fields.key?(field_name) && (f = Warden.visible_entry?(:visible_field?, fields_entry, context, warden))
visible_fields[field_name] = f
end
end
end
end
visible_fields
end
end
module ObjectMethods
def get_field(field_name, context = GraphQL::Query::NullContext.instance)
# Objects need to check that the interface implementation is visible, too
warden = Warden.from_context(context)
ancs = ancestors
i = 0
while (ancestor = ancs[i])
if ancestor.respond_to?(:own_fields) &&
visible_interface_implementation?(ancestor, context, warden) &&
(f_entry = ancestor.own_fields[field_name]) &&
(f = Warden.visible_entry?(:visible_field?, f_entry, context, warden))
return f
end
i += 1
end
nil
end
# @return [Hash<String => GraphQL::Schema::Field>] Fields on this object, keyed by name, including inherited fields
def fields(context = GraphQL::Query::NullContext.instance)
# Objects need to check that the interface implementation is visible, too
warden = Warden.from_context(context)
# Local overrides take precedence over inherited fields
visible_fields = {}
for ancestor in ancestors
if ancestor.respond_to?(:own_fields) && visible_interface_implementation?(ancestor, context, warden)
ancestor.own_fields.each do |field_name, fields_entry|
# Choose the most local definition that passes `.visible?` --
# stop checking for fields by name once one has been found.
if !visible_fields.key?(field_name) && (f = Warden.visible_entry?(:visible_field?, fields_entry, context, warden))
visible_fields[field_name] = f
end
end
end
end
visible_fields
end
end
def self.included(child_class)
# Included in an interface definition methods module
child_class.include(InterfaceMethods)
super
end
def self.extended(child_class)
child_class.extend(ObjectMethods)
super
end
private
def inherited(subclass)
super
subclass.class_exec do
@own_fields ||= nil
@field_class ||= nil
end
end
# If `type` is an interface, and `self` has a type membership for `type`, then make sure it's visible.
def visible_interface_implementation?(type, context, warden)
if type.respond_to?(:kind) && type.kind.interface?
implements_this_interface = false
implementation_is_visible = false
warden.interface_type_memberships(self, context).each do |tm|
if tm.abstract_type == type
implements_this_interface ||= true
if warden.visible_type_membership?(tm, context)
implementation_is_visible = true
break
end
end
end
# It's possible this interface came by way of `include` in another interface which this
# object type _does_ implement, and that's ok
implements_this_interface ? implementation_is_visible : true
else
# If there's no implementation, then we're looking at Ruby-style inheritance instead
true
end
end
# @param [GraphQL::Schema::Field]
# @return [String] A warning to give when this field definition might conflict with a built-in method
def conflict_field_name_warning(field_defn)
"#{self.graphql_name}'s `field :#{field_defn.original_name}` conflicts with a built-in method, use `resolver_method:` to pick a different resolver method for this field (for example, `resolver_method: :resolve_#{field_defn.resolver_method}` and `def resolve_#{field_defn.resolver_method}`). Or use `method_conflict_warning: false` to suppress this warning."
end
end
end
end
end
|