diff --git a/config/locales/server.en.yml b/config/locales/server.en.yml index 0550183d2e7..54216cc196d 100644 --- a/config/locales/server.en.yml +++ b/config/locales/server.en.yml @@ -142,6 +142,11 @@ en: not_valid_float_value: "must be a float" not_valid_boolean_value: "must be a boolean" not_valid_enum_value: "must be one of the following: %{choices}" + string_value_not_valid_min: "must be at least %{min} characters long" + string_value_not_valid_max: "must be at most %{max} characters long" + number_value_not_valid_min: "must be larger than or equal to %{min}" + number_value_not_valid_max: "must be smaller than or equal to %{max}" + string_value_not_valid_url: "must be a valid URL" locale_errors: top_level_locale: "The top level key in a locale file must match the locale name" invalid_yaml: "Translation YAML invalid" diff --git a/lib/theme_settings_object_validator.rb b/lib/theme_settings_object_validator.rb index 9c795a8a73e..09dee338ea2 100644 --- a/lib/theme_settings_object_validator.rb +++ b/lib/theme_settings_object_validator.rb @@ -30,17 +30,18 @@ class ThemeSettingsObjectValidator def validate_properties @properties.each do |property_name, property_attributes| - next if property_attributes[:required] && validate_required_property(property_name) - validate_property_type(property_attributes, property_name) + 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 validate_property_type(property_attributes, property_name) + def has_valid_property_value_type?(property_attributes, property_name) value = @object[property_name] type = property_attributes[:type] - return if (value.nil? && type != "enum") - return if type == "objects" + return true if (value.nil? && type != "enum") + return true if type == "objects" is_value_valid = case type @@ -56,23 +57,97 @@ class ThemeSettingsObjectValidator property_attributes[:choices].include?(value) else add_error(property_name, I18n.t("themes.settings_errors.objects.invalid_type", type:)) - return + return false end - if !is_value_valid + 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 validate_required_property(property_name) + def has_valid_property_value?(property_attributes, property_name) + validations = property_attributes[:validations] + + return true if validations.blank? + + type = property_attributes[:type] + value = @object[property_name] + + case type + when "string" + if validations[:min_length] && value.length < validations[:min_length] + add_error( + property_name, + I18n.t( + "themes.settings_errors.objects.string_value_not_valid_min", + min: validations[:min_length], + ), + ) + + return false + end + + if validations[:max_length] && value.length > validations[:max_length] + add_error( + property_name, + I18n.t( + "themes.settings_errors.objects.string_value_not_valid_max", + max: validations[:max_length], + ), + ) + + return false + end + + if validations[: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 validations[:min] && value < validations[:min] + add_error( + property_name, + I18n.t( + "themes.settings_errors.objects.number_value_not_valid_min", + min: validations[:min], + ), + ) + + return false + end + + if validations[:max] && value > validations[:max] + add_error( + property_name, + I18n.t( + "themes.settings_errors.objects.number_value_not_valid_max", + max: validations[: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")) - true - else false + else + true end end diff --git a/spec/lib/theme_settings_object_validator_spec.rb b/spec/lib/theme_settings_object_validator_spec.rb index 7f42600dfb4..c245718ad75 100644 --- a/spec/lib/theme_settings_object_validator_spec.rb +++ b/spec/lib/theme_settings_object_validator_spec.rb @@ -139,6 +139,29 @@ RSpec.describe ThemeSettingsObjectValidator do described_class.new(schema: schema, object: { float_property: "string" }).validate, ).to eq(float_property: ["must be a float"]) end + + it "should return the right hash of error messages when integer property does not satisfy min or max validations" do + schema = { + name: "section", + properties: { + float_property: { + type: "float", + validations: { + min: 5.5, + max: 11.5, + }, + }, + }, + } + + expect(described_class.new(schema: schema, object: { float_property: 4.5 }).validate).to eq( + float_property: ["must be larger than or equal to 5.5"], + ) + + expect( + described_class.new(schema: schema, object: { float_property: 12.5 }).validate, + ).to eq(float_property: ["must be smaller than or equal to 11.5"]) + end end context "for integer properties" do @@ -159,6 +182,48 @@ RSpec.describe ThemeSettingsObjectValidator do described_class.new(schema: schema, object: { integer_property: 1.0 }).validate, ).to eq(integer_property: ["must be an integer"]) end + + it "should not return any error messages when the value of the integer property satisfies min and max validations" do + schema = { + name: "section", + properties: { + integer_property: { + type: "integer", + validations: { + min: 5, + max: 10, + }, + }, + }, + } + + expect(described_class.new(schema: schema, object: { integer_property: 6 }).validate).to eq( + {}, + ) + end + + it "should return the right hash of error messages when integer property does not satisfy min or max validations" do + schema = { + name: "section", + properties: { + integer_property: { + type: "integer", + validations: { + min: 5, + max: 10, + }, + }, + }, + } + + expect(described_class.new(schema: schema, object: { integer_property: 4 }).validate).to eq( + integer_property: ["must be larger than or equal to 5"], + ) + + expect( + described_class.new(schema: schema, object: { integer_property: 11 }).validate, + ).to eq(integer_property: ["must be smaller than or equal to 10"]) + end end context "for string properties" do @@ -171,10 +236,72 @@ RSpec.describe ThemeSettingsObjectValidator do end it "should return the right hash of error messages when value of property is not of type string" do + schema = { name: "section", properties: { string_property: { type: "string" } } } + expect(described_class.new(schema: schema, object: { string_property: 1 }).validate).to eq( string_property: ["must be a string"], ) end + + it "should return the right hash of error messages when string property does not statisfy url validation" do + schema = { + name: "section", + properties: { + string_property: { + type: "string", + validations: { + url: true, + }, + }, + }, + } + + expect( + described_class.new(schema: schema, object: { string_property: "not a url" }).validate, + ).to eq(string_property: ["must be a valid URL"]) + end + + it "should not return any error messages when the value of the string property satisfies min_length and max_length validations" do + schema = { + name: "section", + properties: { + string_property: { + type: "string", + validations: { + min_length: 5, + max_length: 10, + }, + }, + }, + } + + expect( + described_class.new(schema: schema, object: { string_property: "123456" }).validate, + ).to eq({}) + end + + it "should return the right hash of error messages when string property does not satisfy min_length or max_length validations" do + schema = { + name: "section", + properties: { + string_property: { + type: "string", + validations: { + min_length: 5, + max_length: 10, + }, + }, + }, + } + + expect( + described_class.new(schema: schema, object: { string_property: "1234" }).validate, + ).to eq(string_property: ["must be at least 5 characters long"]) + + expect( + described_class.new(schema: schema, object: { string_property: "12345678910" }).validate, + ).to eq(string_property: ["must be at most 10 characters long"]) + end end end end