File: has_fields.rb

package info (click to toggle)
ruby-graphql 2.2.17-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 9,584 kB
  • sloc: ruby: 67,505; ansic: 1,753; yacc: 831; javascript: 331; makefile: 6
file content (223 lines) | stat: -rw-r--r-- 9,414 bytes parent folder | download
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