lib/i18n-js/schema.rb in i18n-js-4.2.0 vs lib/i18n-js/schema.rb in i18n-js-4.2.1
- old
+ new
@@ -23,121 +23,194 @@
end
def self.validate!(target)
schema = new(target)
schema.validate!
- I18nJS.plugins.each {|plugin| plugin.validate_schema(config: target) }
+ schema
end
attr_reader :target
def initialize(target)
@target = target
end
def validate!
- expect_type(:root, target, Hash, target)
+ validate_root
- expect_required_keys(self.class.required_root_keys, target)
- reject_extraneous_keys(self.class.root_keys, target)
+ expect_required_keys(
+ keys: self.class.required_root_keys,
+ path: nil
+ )
+
+ reject_extraneous_keys(
+ keys: self.class.root_keys,
+ path: nil
+ )
+
validate_translations
validate_lint_translations
validate_lint_scripts
+ validate_plugins
end
+ def validate_plugins
+ I18nJS.plugins.each do |plugin|
+ next unless target.key?(plugin.config_key)
+
+ expect_type(
+ path: [plugin.config_key, :enabled],
+ types: [TrueClass, FalseClass]
+ )
+
+ plugin.validate_schema
+ end
+ end
+
+ def validate_root
+ return if target.is_a?(Hash)
+
+ message = "Expected config to be \"Hash\"; " \
+ "got #{target.class} instead"
+
+ reject message, target
+ end
+
def validate_lint_translations
key = :lint_translations
return unless target.key?(key)
- config = target[key]
+ expect_type(path: [key], types: Hash)
- expect_type(key, config, Hash, target)
- expect_required_keys(REQUIRED_LINT_TRANSLATIONS_KEYS, config)
- expect_type(:ignore, config[:ignore], Array, config)
+ expect_required_keys(
+ keys: REQUIRED_LINT_TRANSLATIONS_KEYS,
+ path: [key]
+ )
+
+ expect_type(path: [key, :ignore], types: Array)
end
def validate_lint_scripts
key = :lint_scripts
return unless target.key?(key)
- config = target[key]
-
- expect_type(key, config, Hash, target)
- expect_required_keys(REQUIRED_LINT_SCRIPTS_KEYS, config)
- expect_type(:ignore, config[:ignore], Array, config)
- expect_type(:patterns, config[:patterns], Array, config)
+ expect_type(path: [key], types: Hash)
+ expect_required_keys(
+ keys: REQUIRED_LINT_SCRIPTS_KEYS,
+ path: [key]
+ )
+ expect_type(path: [key, :ignore], types: Array)
+ expect_type(path: [key, :patterns], types: Array)
end
def validate_translations
- translations = target[:translations]
+ expect_array_with_items(path: [:translations])
- expect_type(:translations, translations, Array, target)
- expect_array_with_items(:translations, translations)
-
- translations.each do |translation|
- validate_translation(translation)
+ target[:translations].each_with_index do |translation, index|
+ validate_translation(translation, index)
end
end
- def validate_translation(translation)
- expect_required_keys(REQUIRED_TRANSLATION_KEYS, translation)
- reject_extraneous_keys(TRANSLATION_KEYS, translation)
- expect_type(:file, translation[:file], String, translation)
- expect_type(:patterns, translation[:patterns], Array, translation)
- expect_array_with_items(:patterns, translation[:patterns], translation)
+ def validate_translation(_translation, index)
+ expect_required_keys(
+ path: [:translations, index],
+ keys: REQUIRED_TRANSLATION_KEYS
+ )
+
+ reject_extraneous_keys(
+ keys: TRANSLATION_KEYS,
+ path: [:translations, index]
+ )
+
+ expect_type(path: [:translations, index, :file], types: String)
+ expect_array_with_items(path: [:translations, index, :patterns])
end
def reject(error_message, node = nil)
node_json = "\n#{JSON.pretty_generate(node)}" if node
raise InvalidError, "#{error_message}#{node_json}"
end
- def expect_enabled_config(config_key, value)
- return if [TrueClass, FalseClass].include?(value.class)
+ def expect_type(path:, types:)
+ path = prepare_path(path: path)
+ value = value_for(path: path)
+ types = Array(types)
- actual_type = value.class
+ return if types.any? {|type| value.is_a?(type) }
- reject "Expected #{config_key}.enabled to be a boolean; " \
- "got #{actual_type} instead"
- end
-
- def expect_type(attribute, value, expected_type, payload)
- return if value.is_a?(expected_type)
-
actual_type = value.class
+ type_desc = if types.size == 1
+ types[0].to_s.inspect
+ else
+ "one of #{types.inspect}"
+ end
+
message = [
- "Expected #{attribute.inspect} to be #{expected_type};",
+ "Expected #{path.join('.').inspect} to be #{type_desc};",
"got #{actual_type} instead"
].join(" ")
- reject message, payload
+ reject message, target
end
- def expect_array_with_items(attribute, value, payload = value)
+ def expect_array_with_items(path:)
+ expect_type(path: path, types: Array)
+
+ path = prepare_path(path: path)
+ value = value_for(path: path)
+
return unless value.empty?
- reject "Expected #{attribute.inspect} to have at least one item", payload
+ reject "Expected #{path.join('.').inspect} to have at least one item",
+ target
end
- def expect_required_keys(required_keys, value)
- keys = value.keys.map(&:to_sym)
+ def expect_required_keys(keys:, path:)
+ path = prepare_path(path: path)
+ value = value_for(path: path)
+ actual_keys = value.keys.map(&:to_sym)
- required_keys.each do |key|
- next if keys.include?(key)
+ keys.each do |key|
+ next if actual_keys.include?(key)
- reject "Expected #{key.inspect} to be defined", value
+ path_desc = if path.empty?
+ key.to_s.inspect
+ else
+ (path + [key]).join(".").inspect
+ end
+
+ reject "Expected #{path_desc} to be defined", target
end
end
- def reject_extraneous_keys(allowed_keys, value)
- keys = value.keys.map(&:to_sym)
- extraneous = keys.to_a - allowed_keys.to_a
+ def reject_extraneous_keys(keys:, path:)
+ path = prepare_path(path: path)
+ value = value_for(path: path)
+ actual_keys = value.keys.map(&:to_sym)
+ extraneous = actual_keys.to_a - keys.to_a
+
return if extraneous.empty?
- reject "Unexpected keys: #{extraneous.join(', ')}", value
+ path_desc = if path.empty?
+ "config"
+ else
+ path.join(".").inspect
+ end
+
+ reject "#{path_desc} has unexpected keys: #{extraneous.inspect}",
+ target
+ end
+
+ def prepare_path(path:)
+ path = path.to_s.split(".").map(&:to_sym) unless path.is_a?(Array)
+ path
+ end
+
+ def value_for(path:)
+ path.empty? ? target : target.dig(*path)
end
end
end