1# frozen_string_literal: true 2 3module Resolvers 4 class BaseResolver < GraphQL::Schema::Resolver 5 extend ::Gitlab::Utils::Override 6 include ::Gitlab::Utils::StrongMemoize 7 include ::Gitlab::Graphql::GlobalIDCompatibility 8 9 argument_class ::Types::BaseArgument 10 11 def self.requires_argument! 12 @requires_argument = true 13 end 14 15 def self.calls_gitaly! 16 @calls_gitaly = true 17 end 18 19 def self.field_options 20 extra_options = { 21 requires_argument: @requires_argument, 22 calls_gitaly: @calls_gitaly 23 }.compact 24 25 super.merge(extra_options) 26 end 27 28 def self.singular_type 29 return unless type 30 31 unwrapped = type.unwrap 32 33 %i[node_type relay_node_type of_type itself].reduce(nil) do |t, m| 34 t || unwrapped.try(m) 35 end 36 end 37 38 def self.when_single(&block) 39 as_single << block 40 41 # Have we been called after defining the single version of this resolver? 42 @single.instance_exec(&block) if @single.present? 43 end 44 45 def self.as_single 46 @as_single ||= [] 47 end 48 49 def self.single_definition_blocks 50 ancestors.flat_map { |klass| klass.try(:as_single) || [] } 51 end 52 53 def self.single 54 @single ||= begin 55 parent = self 56 klass = Class.new(self) do 57 type parent.singular_type, null: true 58 59 def ready?(**args) 60 ready, early_return = super 61 [ready, select_result(early_return)] 62 end 63 64 def resolve(**args) 65 select_result(super) 66 end 67 68 def single? 69 true 70 end 71 72 def select_result(results) 73 results&.first 74 end 75 76 define_singleton_method :to_s do 77 "#{parent}.single" 78 end 79 end 80 81 single_definition_blocks.each do |definition| 82 klass.instance_exec(&definition) 83 end 84 85 klass 86 end 87 end 88 89 def self.last 90 parent = self 91 @last ||= Class.new(single) do 92 type parent.singular_type, null: true 93 94 def select_result(results) 95 results&.last 96 end 97 98 define_singleton_method :to_s do 99 "#{parent}.last" 100 end 101 end 102 end 103 104 def self.complexity 105 0 106 end 107 108 def self.resolver_complexity(args, child_complexity:) 109 complexity = 1 110 complexity += 1 if args[:sort] 111 complexity += 5 if args[:search] 112 113 complexity 114 end 115 116 def self.complexity_multiplier(args) 117 # When fetching many items, additional complexity is added to the field 118 # depending on how many items is fetched. For each item we add 1% of the 119 # original complexity - this means that loading 100 items (our default 120 # maxp_age_size limit) doubles the original complexity. 121 # 122 # Complexity is not increased when searching by specific ID(s), because 123 # complexity difference is minimal in this case. 124 [args[:iid], args[:iids]].any? ? 0 : 0.01 125 end 126 127 def self.before_connection_authorization(&block) 128 @before_connection_authorization_block = block 129 end 130 131 # rubocop: disable Style/TrivialAccessors 132 def self.before_connection_authorization_block 133 @before_connection_authorization_block 134 end 135 # rubocop: enable Style/TrivialAccessors 136 137 def offset_pagination(relation) 138 ::Gitlab::Graphql::Pagination::OffsetPaginatedRelation.new(relation) 139 end 140 141 override :object 142 def object 143 super.tap do |obj| 144 # If the field this resolver is used in is wrapped in a presenter, unwrap its subject 145 break obj.subject if obj.is_a?(Gitlab::View::Presenter::Base) 146 end 147 end 148 149 def single? 150 false 151 end 152 153 def current_user 154 context[:current_user] 155 end 156 157 # Overridden in sub-classes (see .single, .last) 158 def select_result(results) 159 results 160 end 161 162 def self.authorization 163 @authorization ||= ::Gitlab::Graphql::Authorize::ObjectAuthorization.new(try(:required_permissions)) 164 end 165 166 def self.authorized?(object, context) 167 authorization.ok?(object, context[:current_user]) 168 end 169 end 170end 171