mirror of
https://github.com/discourse/discourse.git
synced 2024-11-26 19:56:18 +08:00
e2ced85757
This commit changes enum typed theme objects property to be optional. Previously, an enum typed property is always required but we have found that this might not be ideal so we want to change it.
290 lines
7.4 KiB
Ruby
290 lines
7.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
|
|
|
|
def property_values_of_type(type)
|
|
fetch_property_values_of_type(@properties, @object, type)
|
|
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?
|
|
|
|
is_value_valid =
|
|
case type
|
|
when "string"
|
|
value.is_a?(String)
|
|
when "integer", "topic", "post", "upload"
|
|
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)
|
|
when "categories", "groups"
|
|
value.is_a?(Array) && value.all? { |id| id.is_a?(Integer) }
|
|
when "tags"
|
|
value.is_a?(Array) && value.all? { |tag| tag.is_a?(String) }
|
|
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", "upload", "post"
|
|
if !valid_ids(type).include?(value)
|
|
add_error(property_name, :"not_valid_#{type}_value")
|
|
return false
|
|
end
|
|
when "tags", "categories", "groups"
|
|
if !Array(value).to_set.subset?(valid_ids(type))
|
|
add_error(property_name, :"not_valid_#{type}_value")
|
|
return false
|
|
end
|
|
|
|
if (min = validations&.dig(:min)) && value.length < min
|
|
add_error(property_name, :"#{type}_value_not_valid_min", count: min)
|
|
return false
|
|
end
|
|
|
|
if (max = validations&.dig(:max)) && value.length > max
|
|
add_error(property_name, :"#{type}_value_not_valid_max", count: max)
|
|
return false
|
|
end
|
|
when "string"
|
|
if (min = validations&.dig(:min_length)) && value.length < min
|
|
add_error(property_name, :string_value_not_valid_min, count: min)
|
|
return false
|
|
end
|
|
|
|
if (max = validations&.dig(:max_length)) && value.length > max
|
|
add_error(property_name, :string_value_not_valid_max, count: 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].blank?
|
|
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 = {
|
|
"categories" => {
|
|
klass: Category,
|
|
},
|
|
"topic" => {
|
|
klass: Topic,
|
|
},
|
|
"post" => {
|
|
klass: Post,
|
|
},
|
|
"groups" => {
|
|
klass: Group,
|
|
},
|
|
"upload" => {
|
|
klass: Upload,
|
|
},
|
|
"tags" => {
|
|
klass: Tag,
|
|
column: :name,
|
|
},
|
|
}
|
|
private_constant :TYPE_TO_MODEL_MAP
|
|
|
|
def valid_ids(type)
|
|
valid_ids_lookup[type] ||= begin
|
|
column = TYPE_TO_MODEL_MAP[type][:column] || :id
|
|
|
|
Set.new(
|
|
TYPE_TO_MODEL_MAP[type][:klass].where(
|
|
column => fetch_property_values_of_type(@properties, @object, type),
|
|
).pluck(column),
|
|
)
|
|
end
|
|
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.merge(Array(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
|