mirror of
https://github.com/discourse/discourse.git
synced 2024-11-26 23:23:49 +08:00
cac60a2c6b
Why this change? This change supports a property of `type: category` in the schema that is declared for a theme setting object. Example: ``` sections: type: objects schema: name: section properties: category_property: type: category ``` The value of a property declared as `type: category` will have to be a valid id of a row in the `categories` table. What does this change do? Adds a property value validation step for `type: category`. Care has been taken to ensure that we do not spam the database with a ton of requests if there are alot of category typed properties. This is done by walking through the entire object and collecting all the values for properties typed category. After which, a single database query is executed to validate which values are valid.
184 lines
4.7 KiB
Ruby
184 lines
4.7 KiB
Ruby
# frozen_string_literal: true
|
|
|
|
class ThemeSettingsObjectValidator
|
|
def initialize(schema:, object:, valid_category_ids: nil)
|
|
@object = object
|
|
@schema_name = schema[:name]
|
|
@properties = schema[:properties]
|
|
@errors = {}
|
|
@valid_category_ids = valid_category_ids
|
|
end
|
|
|
|
def validate
|
|
validate_properties
|
|
|
|
@properties.each do |property_name, property_attributes|
|
|
if property_attributes[:type] == "objects"
|
|
@object[property_name]&.each do |child_object|
|
|
@errors[property_name] ||= []
|
|
|
|
@errors[property_name].push(
|
|
self
|
|
.class
|
|
.new(schema: property_attributes[:schema], object: child_object, valid_category_ids:)
|
|
.validate,
|
|
)
|
|
end
|
|
end
|
|
end
|
|
|
|
@errors
|
|
end
|
|
|
|
private
|
|
|
|
def validate_properties
|
|
@properties.each do |property_name, property_attributes|
|
|
next if property_attributes[:type] == "objects"
|
|
next if property_attributes[:required] && !is_property_present?(property_name)
|
|
next if !has_valid_property_value_type?(property_attributes, property_name)
|
|
next if !has_valid_property_value?(property_attributes, property_name)
|
|
end
|
|
end
|
|
|
|
def has_valid_property_value_type?(property_attributes, property_name)
|
|
value = @object[property_name]
|
|
type = property_attributes[:type]
|
|
|
|
return true if (value.nil? && type != "enum")
|
|
|
|
is_value_valid =
|
|
case type
|
|
when "string"
|
|
value.is_a?(String)
|
|
when "integer", "category"
|
|
value.is_a?(Integer)
|
|
when "float"
|
|
value.is_a?(Float) || value.is_a?(Integer)
|
|
when "boolean"
|
|
[true, false].include?(value)
|
|
when "enum"
|
|
property_attributes[:choices].include?(value)
|
|
else
|
|
add_error(property_name, I18n.t("themes.settings_errors.objects.invalid_type", type:))
|
|
return false
|
|
end
|
|
|
|
if is_value_valid
|
|
true
|
|
else
|
|
add_error(
|
|
property_name,
|
|
I18n.t("themes.settings_errors.objects.not_valid_#{type}_value", property_attributes),
|
|
)
|
|
false
|
|
end
|
|
end
|
|
|
|
def has_valid_property_value?(property_attributes, property_name)
|
|
validations = property_attributes[:validations]
|
|
type = property_attributes[:type]
|
|
value = @object[property_name]
|
|
|
|
case type
|
|
when "category"
|
|
if !valid_category_ids.include?(value)
|
|
add_error(property_name, I18n.t("themes.settings_errors.objects.not_valid_category_value"))
|
|
return false
|
|
end
|
|
when "string"
|
|
if (min = validations&.dig(:min_length)) && value.length < min
|
|
add_error(
|
|
property_name,
|
|
I18n.t("themes.settings_errors.objects.string_value_not_valid_min", min:),
|
|
)
|
|
|
|
return false
|
|
end
|
|
|
|
if (max = validations&.dig(:max_length)) && value.length > max
|
|
add_error(
|
|
property_name,
|
|
I18n.t("themes.settings_errors.objects.string_value_not_valid_max", max: max),
|
|
)
|
|
|
|
return false
|
|
end
|
|
|
|
if validations&.dig(:url) && !value.match?(URI.regexp)
|
|
add_error(
|
|
property_name,
|
|
I18n.t("themes.settings_errors.objects.string_value_not_valid_url"),
|
|
)
|
|
|
|
return false
|
|
end
|
|
when "integer", "float"
|
|
if (min = validations&.dig(:min)) && value < min
|
|
add_error(
|
|
property_name,
|
|
I18n.t("themes.settings_errors.objects.number_value_not_valid_min", min:),
|
|
)
|
|
|
|
return false
|
|
end
|
|
|
|
if (max = validations&.dig(:max)) && value > max
|
|
add_error(
|
|
property_name,
|
|
I18n.t("themes.settings_errors.objects.number_value_not_valid_max", max:),
|
|
)
|
|
|
|
return false
|
|
end
|
|
end
|
|
|
|
true
|
|
end
|
|
|
|
def is_property_present?(property_name)
|
|
if @object[property_name].nil?
|
|
add_error(property_name, I18n.t("themes.settings_errors.objects.required"))
|
|
false
|
|
else
|
|
true
|
|
end
|
|
end
|
|
|
|
def add_error(property_name, error)
|
|
@errors[property_name] ||= []
|
|
@errors[property_name] << error
|
|
end
|
|
|
|
def valid_category_ids
|
|
@valid_category_ids ||=
|
|
Set.new(
|
|
Category.where(id: fetch_property_values_of_type(@properties, @object, "category")).pluck(
|
|
:id,
|
|
),
|
|
)
|
|
end
|
|
|
|
def fetch_property_values_of_type(properties, object, type)
|
|
values = Set.new
|
|
|
|
properties.each do |property_name, property_attributes|
|
|
if property_attributes[:type] == type
|
|
values << object[property_name]
|
|
elsif property_attributes[:type] == "objects"
|
|
object[property_name]&.each do |child_object|
|
|
values.merge(
|
|
fetch_property_values_of_type(
|
|
property_attributes[:schema][:properties],
|
|
child_object,
|
|
type,
|
|
),
|
|
)
|
|
end
|
|
end
|
|
end
|
|
|
|
values
|
|
end
|
|
end
|