Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to 'lib/gitlab/utils/delegator_override/validator.rb')
-rw-r--r--lib/gitlab/utils/delegator_override/validator.rb105
1 files changed, 105 insertions, 0 deletions
diff --git a/lib/gitlab/utils/delegator_override/validator.rb b/lib/gitlab/utils/delegator_override/validator.rb
new file mode 100644
index 00000000000..402154b41c2
--- /dev/null
+++ b/lib/gitlab/utils/delegator_override/validator.rb
@@ -0,0 +1,105 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module Utils
+ module DelegatorOverride
+ class Validator
+ UnexpectedDelegatorOverrideError = Class.new(StandardError)
+
+ attr_reader :delegator_class, :target_classes
+
+ OVERRIDE_ERROR_MESSAGE = <<~EOS
+ We've detected that the delegator is overriding a specific method(s) on the target class.
+ Please make sure if it's intentional and handle this error accordingly.
+ See https://gitlab.com/gitlab-org/gitlab/-/blob/master/app/presenters/README.md#validate-accidental-overrides for more information.
+ EOS
+
+ def initialize(delegator_class)
+ @delegator_class = delegator_class
+ @target_classes = []
+ end
+
+ def add_allowlist(names)
+ allowed_method_names.concat(names)
+ end
+
+ def allowed_method_names
+ @allowed_method_names ||= []
+ end
+
+ def add_target(target_class)
+ @target_classes << target_class if target_class
+ end
+
+ # This will make sure allowlist we put into ancestors are all included
+ def expand_on_ancestors(validators)
+ delegator_class.ancestors.each do |ancestor|
+ next if delegator_class == ancestor # ancestor includes itself
+
+ validator_ancestor = validators[ancestor]
+
+ next unless validator_ancestor
+
+ add_allowlist(validator_ancestor.allowed_method_names)
+ end
+ end
+
+ def validate_overrides!
+ return if target_classes.empty?
+
+ errors = []
+
+ # Workaround to fully load the instance methods in the target class.
+ # See https://gitlab.com/gitlab-org/gitlab/-/merge_requests/69823#note_678887402
+ begin
+ target_classes.map(&:new)
+ rescue ArgumentError
+ # Some models might raise ArgumentError here, but it's fine in this case,
+ # because this is enough to force ActiveRecord to generate the methods we
+ # need to verify, so it's safe to ignore it.
+ end
+
+ (delegator_class.instance_methods - allowlist).each do |method_name|
+ target_classes.each do |target_class|
+ next unless target_class.instance_methods.include?(method_name)
+
+ errors << generate_error(method_name, target_class, delegator_class)
+ end
+ end
+
+ return if errors.empty?
+
+ details = errors.map { |error| "- #{error}" }.join("\n")
+
+ raise UnexpectedDelegatorOverrideError,
+ <<~TEXT
+ #{OVERRIDE_ERROR_MESSAGE}
+ Here are the conflict details.
+
+ #{details}
+ TEXT
+ end
+
+ private
+
+ def generate_error(method_name, target_class, delegator_class)
+ target_location = extract_location(target_class, method_name)
+ delegator_location = extract_location(delegator_class, method_name)
+ Error.new(method_name, target_class, target_location, delegator_class, delegator_location)
+ end
+
+ def extract_location(klass, method_name)
+ klass.instance_method(method_name).source_location&.join(':') || 'unknown'
+ end
+
+ def allowlist
+ [].tap do |allowed|
+ allowed.concat(allowed_method_names)
+ allowed.concat(Object.instance_methods)
+ allowed.concat(::Delegator.instance_methods)
+ end
+ end
+ end
+ end
+ end
+end