diff options
Diffstat (limited to 'lib/release_highlights')
-rw-r--r-- | lib/release_highlights/validator.rb | 54 | ||||
-rw-r--r-- | lib/release_highlights/validator/entry.rb | 74 |
2 files changed, 128 insertions, 0 deletions
diff --git a/lib/release_highlights/validator.rb b/lib/release_highlights/validator.rb new file mode 100644 index 00000000000..6f3f90b5f30 --- /dev/null +++ b/lib/release_highlights/validator.rb @@ -0,0 +1,54 @@ +# frozen_string_literal: true + +module ReleaseHighlights + class Validator + attr_reader :errors, :file + + def initialize(file:) + @file = file + @errors = [] + end + + def valid? + document = YAML.parse(File.read(file)) + + document.root.children.each do |entry| + entry = ReleaseHighlights::Validator::Entry.new(entry) + + errors.push(entry.errors.full_messages) unless entry.valid? + end + + errors.none? + end + + def self.validate_all! + @all_errors = [] + + ReleaseHighlight.file_paths.each do |file_path| + instance = self.new(file: file_path) + + @all_errors.push([instance.errors, instance.file]) unless instance.valid? + end + + @all_errors.none? + end + + def self.error_message + io = StringIO.new + + @all_errors.each do |errors, file| + message = "Validation failed for #{file}" + line = -> { io.puts "-" * message.length } + + line.call + io.puts message + line.call + + errors.flatten.each { |error| io.puts "* #{error}" } + io.puts + end + + io.string + end + end +end diff --git a/lib/release_highlights/validator/entry.rb b/lib/release_highlights/validator/entry.rb new file mode 100644 index 00000000000..0dbe0cdf882 --- /dev/null +++ b/lib/release_highlights/validator/entry.rb @@ -0,0 +1,74 @@ +# frozen_string_literal: true + +module ReleaseHighlights + class Validator::Entry + include ActiveModel::Validations + include ActiveModel::Validations::Callbacks + + PACKAGES = %w(Core Starter Premium Ultimate).freeze + + attr_reader :entry + + validates :title, :body, :stage, presence: true + validates :'self-managed', :'gitlab-com', inclusion: { in: [true, false], message: "must be a boolean" } + validates :url, :image_url, format: { with: URI::DEFAULT_PARSER.make_regexp, message: 'must be a URL' } + validates :release, numericality: true + validate :validate_published_at + validate :validate_packages + + after_validation :add_line_numbers_to_errors! + + def initialize(entry) + @entry = entry + end + + def validate_published_at + published_at = value_for('published_at') + + return if published_at.is_a?(Date) + + errors.add(:published_at, 'must be valid Date') + end + + def validate_packages + packages = value_for('packages') + + if !packages.is_a?(Array) || packages.empty? || packages.any? { |p| PACKAGES.exclude?(p) } + errors.add(:packages, "must be one of #{PACKAGES}") + end + end + + def read_attribute_for_validation(key) + value_for(key) + end + + private + + def add_line_numbers_to_errors! + errors.messages.each do |attribute, messages| + messages.map! { |m| "#{m} (line #{line_number_for(attribute)})" } + end + end + + def line_number_for(key) + node = find_node(key) + + (node&.start_line || @entry.start_line) + 1 + end + + def value_for(key) + node = find_node(key) + + return if node.nil? + + index = entry.children.find_index(node) + + next_node = entry.children[index + 1] + next_node&.to_ruby + end + + def find_node(key) + entry.children.find {|node| node.try(:value) == key.to_s } + end + end +end |