91 lines
2.2 KiB
Ruby
91 lines
2.2 KiB
Ruby
|
# frozen_string_literal: true
|
||
|
|
||
|
module Sbom
|
||
|
class PackageUrl
|
||
|
class ArgumentValidator
|
||
|
QUALIFIER_KEY_REGEXP = /^[A-Za-z\d._-]+$/.freeze
|
||
|
START_WITH_NUMBER_REGEXP = /^\d/.freeze
|
||
|
|
||
|
def initialize(package)
|
||
|
@type = package.type
|
||
|
@namespace = package.namespace
|
||
|
@name = package.name
|
||
|
@version = package.version
|
||
|
@qualifiers = package.qualifiers
|
||
|
@errors = []
|
||
|
end
|
||
|
|
||
|
def validate!
|
||
|
validate_type
|
||
|
validate_name
|
||
|
validate_qualifiers
|
||
|
validate_by_type
|
||
|
|
||
|
raise ArgumentError, formatted_errors if invalid?
|
||
|
end
|
||
|
|
||
|
private
|
||
|
|
||
|
def invalid?
|
||
|
errors.present?
|
||
|
end
|
||
|
|
||
|
attr_reader :type, :namespace, :name, :version, :qualifiers, :errors
|
||
|
|
||
|
def formatted_errors
|
||
|
errors.join(', ')
|
||
|
end
|
||
|
|
||
|
def validate_type
|
||
|
errors.push('Type is required') if type.blank?
|
||
|
end
|
||
|
|
||
|
def validate_name
|
||
|
errors.push('Name is required') if name.blank?
|
||
|
end
|
||
|
|
||
|
def validate_qualifiers
|
||
|
return if qualifiers.nil?
|
||
|
|
||
|
keys = qualifiers.keys
|
||
|
errors.push('Qualifier keys must be unique') unless keys.uniq.size == keys.size
|
||
|
|
||
|
keys.each do |key|
|
||
|
errors.push(key_error(key, 'contains illegal characters')) unless key.match?(QUALIFIER_KEY_REGEXP)
|
||
|
errors.push(key_error(key, 'may not start with a number')) if key.match?(START_WITH_NUMBER_REGEXP)
|
||
|
end
|
||
|
end
|
||
|
|
||
|
def key_error(key, text)
|
||
|
"Qualifier key `#{key}` #{text}"
|
||
|
end
|
||
|
|
||
|
def validate_by_type
|
||
|
case type
|
||
|
when 'conan'
|
||
|
validate_conan
|
||
|
when 'cran'
|
||
|
validate_cran
|
||
|
when 'swift'
|
||
|
validate_swift
|
||
|
end
|
||
|
end
|
||
|
|
||
|
def validate_conan
|
||
|
return unless namespace.blank? ^ (qualifiers.nil? || qualifiers.exclude?('channel'))
|
||
|
|
||
|
errors.push('Conan packages require the channel be present if published in a namespace and vice-versa')
|
||
|
end
|
||
|
|
||
|
def validate_cran
|
||
|
errors.push('Cran packages require a version') if version.blank?
|
||
|
end
|
||
|
|
||
|
def validate_swift
|
||
|
errors.push('Swift packages require a namespace') if namespace.blank?
|
||
|
errors.push('Swift packages require a version') if version.blank?
|
||
|
end
|
||
|
end
|
||
|
end
|
||
|
end
|