96 lines
3.2 KiB
Ruby
96 lines
3.2 KiB
Ruby
|
# frozen_string_literal: true
|
||
|
|
||
|
class FeatureFlagStrategiesValidator < ActiveModel::EachValidator
|
||
|
STRATEGY_DEFAULT = 'default'.freeze
|
||
|
STRATEGY_GRADUALROLLOUTUSERID = 'gradualRolloutUserId'.freeze
|
||
|
STRATEGY_USERWITHID = 'userWithId'.freeze
|
||
|
# Order key names alphabetically
|
||
|
STRATEGIES = {
|
||
|
STRATEGY_DEFAULT => [].freeze,
|
||
|
STRATEGY_GRADUALROLLOUTUSERID => %w[groupId percentage].freeze,
|
||
|
STRATEGY_USERWITHID => ['userIds'].freeze
|
||
|
}.freeze
|
||
|
USERID_MAX_LENGTH = 256
|
||
|
|
||
|
def validate_each(record, attribute, value)
|
||
|
return unless value
|
||
|
|
||
|
if value.is_a?(Array) && value.all? { |s| s.is_a?(Hash) }
|
||
|
value.each do |strategy|
|
||
|
strategy_validations(record, attribute, strategy)
|
||
|
end
|
||
|
else
|
||
|
error(record, attribute, 'must be an array of strategy hashes')
|
||
|
end
|
||
|
end
|
||
|
|
||
|
private
|
||
|
|
||
|
def strategy_validations(record, attribute, strategy)
|
||
|
validate_name(record, attribute, strategy) &&
|
||
|
validate_parameters_type(record, attribute, strategy) &&
|
||
|
validate_parameters_keys(record, attribute, strategy) &&
|
||
|
validate_parameters_values(record, attribute, strategy)
|
||
|
end
|
||
|
|
||
|
def validate_name(record, attribute, strategy)
|
||
|
STRATEGIES.key?(strategy['name']) || error(record, attribute, 'strategy name is invalid')
|
||
|
end
|
||
|
|
||
|
def validate_parameters_type(record, attribute, strategy)
|
||
|
strategy['parameters'].is_a?(Hash) || error(record, attribute, 'parameters are invalid')
|
||
|
end
|
||
|
|
||
|
def validate_parameters_keys(record, attribute, strategy)
|
||
|
name, parameters = strategy.values_at('name', 'parameters')
|
||
|
actual_keys = parameters.keys.sort
|
||
|
expected_keys = STRATEGIES[name]
|
||
|
expected_keys == actual_keys || error(record, attribute, 'parameters are invalid')
|
||
|
end
|
||
|
|
||
|
def validate_parameters_values(record, attribute, strategy)
|
||
|
case strategy['name']
|
||
|
when STRATEGY_GRADUALROLLOUTUSERID
|
||
|
gradual_rollout_user_id_parameters_validation(record, attribute, strategy)
|
||
|
when STRATEGY_USERWITHID
|
||
|
user_with_id_parameters_validation(record, attribute, strategy)
|
||
|
end
|
||
|
end
|
||
|
|
||
|
def gradual_rollout_user_id_parameters_validation(record, attribute, strategy)
|
||
|
percentage = strategy.dig('parameters', 'percentage')
|
||
|
group_id = strategy.dig('parameters', 'groupId')
|
||
|
|
||
|
unless percentage.is_a?(String) && percentage.match(/\A[1-9]?[0-9]\z|\A100\z/)
|
||
|
error(record, attribute, 'percentage must be a string between 0 and 100 inclusive')
|
||
|
end
|
||
|
|
||
|
unless group_id.is_a?(String) && group_id.match(/\A[a-z]{1,32}\z/)
|
||
|
error(record, attribute, 'groupId parameter is invalid')
|
||
|
end
|
||
|
end
|
||
|
|
||
|
def user_with_id_parameters_validation(record, attribute, strategy)
|
||
|
user_ids = strategy.dig('parameters', 'userIds')
|
||
|
unless user_ids.is_a?(String) && !user_ids.match(/[\n\r\t]|,,/) && valid_ids?(user_ids.split(","))
|
||
|
error(record, attribute, "userIds must be a string of unique comma separated values each #{USERID_MAX_LENGTH} characters or less")
|
||
|
end
|
||
|
end
|
||
|
|
||
|
def valid_ids?(user_ids)
|
||
|
user_ids.uniq.length == user_ids.length &&
|
||
|
user_ids.all? { |id| valid_id?(id) }
|
||
|
end
|
||
|
|
||
|
def valid_id?(user_id)
|
||
|
user_id.present? &&
|
||
|
user_id.strip == user_id &&
|
||
|
user_id.length <= USERID_MAX_LENGTH
|
||
|
end
|
||
|
|
||
|
def error(record, attribute, msg)
|
||
|
record.errors.add(attribute, msg)
|
||
|
false
|
||
|
end
|
||
|
end
|