mirror of
https://github.com/discourse/discourse.git
synced 2024-11-30 13:05:17 +08:00
f8964f8f8f
Properties of schema theme settings that are not marked `required: true` should accept nil as a value.
250 lines
6.4 KiB
Ruby
250 lines
6.4 KiB
Ruby
# frozen_string_literal: true
|
|
|
|
class ThemeSettingsObjectValidator
|
|
class << self
|
|
def validate_objects(schema:, objects:)
|
|
error_messages = []
|
|
|
|
objects.each_with_index do |object, index|
|
|
humanize_error_messages(
|
|
self.new(schema: schema, object: object).validate,
|
|
index:,
|
|
error_messages:,
|
|
)
|
|
end
|
|
|
|
error_messages
|
|
end
|
|
|
|
private
|
|
|
|
def humanize_error_messages(errors, index:, error_messages:)
|
|
errors.each do |property_json_pointer, error_details|
|
|
error_messages.push(*error_details.humanize_messages("/#{index}#{property_json_pointer}"))
|
|
end
|
|
end
|
|
end
|
|
|
|
class ThemeSettingsObjectErrors
|
|
def initialize
|
|
@errors = []
|
|
end
|
|
|
|
def add_error(error, i18n_opts = {})
|
|
@errors << ThemeSettingsObjectError.new(error, i18n_opts)
|
|
end
|
|
|
|
def humanize_messages(property_json_pointer)
|
|
@errors.map { |error| error.humanize_messages(property_json_pointer) }
|
|
end
|
|
|
|
def full_messages
|
|
@errors.map(&:error_message)
|
|
end
|
|
end
|
|
class ThemeSettingsObjectError
|
|
def initialize(error, i18n_opts = {})
|
|
@error = error
|
|
@i18n_opts = i18n_opts
|
|
end
|
|
|
|
def humanize_messages(property_json_pointer)
|
|
I18n.t(
|
|
"themes.settings_errors.objects.humanize_#{@error}",
|
|
@i18n_opts.merge(property_json_pointer:),
|
|
)
|
|
end
|
|
|
|
def error_message
|
|
I18n.t("themes.settings_errors.objects.#{@error}", @i18n_opts)
|
|
end
|
|
end
|
|
|
|
def initialize(schema:, object:, json_pointer_prefix: "", errors: {}, valid_ids_lookup: {})
|
|
@object = object.with_indifferent_access
|
|
@schema_name = schema[:name]
|
|
@properties = schema[:properties]
|
|
@errors = errors
|
|
@json_pointer_prefix = json_pointer_prefix
|
|
@valid_ids_lookup = valid_ids_lookup
|
|
end
|
|
|
|
def validate
|
|
@properties.each do |property_name, property_attributes|
|
|
if property_attributes[:type] == "objects"
|
|
validate_child_objects(
|
|
@object[property_name],
|
|
property_name:,
|
|
schema: property_attributes[:schema],
|
|
)
|
|
else
|
|
validate_property(property_name, property_attributes)
|
|
end
|
|
end
|
|
|
|
@errors
|
|
end
|
|
|
|
private
|
|
|
|
def validate_child_objects(objects, property_name:, schema:)
|
|
return if objects.blank?
|
|
|
|
objects.each_with_index do |object, index|
|
|
self
|
|
.class
|
|
.new(
|
|
schema:,
|
|
object:,
|
|
valid_ids_lookup:,
|
|
json_pointer_prefix: "#{@json_pointer_prefix}#{property_name}/#{index}/",
|
|
errors: @errors,
|
|
)
|
|
.validate
|
|
end
|
|
end
|
|
|
|
def validate_property(property_name, property_attributes)
|
|
return if property_attributes[:required] && !is_property_present?(property_name)
|
|
return if !has_valid_property_value_type?(property_attributes, property_name)
|
|
!has_valid_property_value?(property_attributes, property_name)
|
|
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", "topic", "post", "group", "upload", "tag"
|
|
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, :invalid_type, type:)
|
|
return false
|
|
end
|
|
|
|
if is_value_valid
|
|
true
|
|
else
|
|
add_error(property_name, "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]
|
|
|
|
return true if value.nil?
|
|
|
|
case type
|
|
when "topic", "category", "upload", "post", "group", "tag"
|
|
if !valid_ids(type).include?(value)
|
|
add_error(property_name, :"not_valid_#{type}_value")
|
|
return false
|
|
end
|
|
when "string"
|
|
if (min = validations&.dig(:min_length)) && value.length < min
|
|
add_error(property_name, :string_value_not_valid_min, min:)
|
|
return false
|
|
end
|
|
|
|
if (max = validations&.dig(:max_length)) && value.length > max
|
|
add_error(property_name, :string_value_not_valid_max, max:)
|
|
return false
|
|
end
|
|
|
|
if validations&.dig(:url) && !UrlHelper.relaxed_parse(value)
|
|
add_error(property_name, :string_value_not_valid_url)
|
|
return false
|
|
end
|
|
when "integer", "float"
|
|
if (min = validations&.dig(:min)) && value < min
|
|
add_error(property_name, :number_value_not_valid_min, min:)
|
|
return false
|
|
end
|
|
|
|
if (max = validations&.dig(:max)) && value > max
|
|
add_error(property_name, :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, :required)
|
|
false
|
|
else
|
|
true
|
|
end
|
|
end
|
|
|
|
def add_error(property_name, key, i18n_opts = {})
|
|
pointer = json_pointer(property_name)
|
|
@errors[pointer] ||= ThemeSettingsObjectErrors.new
|
|
@errors[pointer].add_error(key, i18n_opts)
|
|
end
|
|
|
|
def json_pointer(property_name)
|
|
"/#{@json_pointer_prefix}#{property_name}"
|
|
end
|
|
|
|
def valid_ids_lookup
|
|
@valid_ids_lookup ||= {}
|
|
end
|
|
|
|
TYPE_TO_MODEL_MAP = {
|
|
"category" => Category,
|
|
"topic" => Topic,
|
|
"post" => Post,
|
|
"group" => Group,
|
|
"upload" => Upload,
|
|
"tag" => Tag,
|
|
}
|
|
private_constant :TYPE_TO_MODEL_MAP
|
|
|
|
def valid_ids(type)
|
|
valid_ids_lookup[type] ||= Set.new(
|
|
TYPE_TO_MODEL_MAP[type].where(
|
|
id: fetch_property_values_of_type(@properties, @object, type),
|
|
).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
|