dry-validation/lib/dry/validation/messages/resolver.rb

107 lines
3.1 KiB
Ruby

# frozen_string_literal: true
require "dry/validation/message"
module Dry
module Validation
module Messages
# Resolve translated messages from failure arguments
#
# @api public
class Resolver
# @!attribute [r] messages
# @return [Messages::I18n, Messages::YAML] messages backend
# @api private
attr_reader :messages
# @api private
def initialize(messages)
@messages = messages
end
# Resolve Message object from provided args and path
#
# This is used internally by contracts when rules are applied
# If message argument is a Hash, then it MUST have a :text key,
# which value will be used as the message value
#
# @return [Message, Message::Localized]
#
# @api public
def call(message:, tokens:, path:, meta: EMPTY_HASH)
case message
when Symbol
Message[->(**opts) { message(message, path: path, tokens: tokens, **opts) }, path, meta]
when String
Message[message, path, meta]
when Hash
meta = message.dup
text = meta.delete(:text) { |key|
raise ArgumentError, <<~STR
+message+ Hash must contain :#{key} key (#{message.inspect} given)
STR
}
call(message: text, tokens: tokens, path: path, meta: meta)
else
raise ArgumentError, <<~STR
+message+ must be either a Symbol, String or Hash (#{message.inspect} given)
STR
end
end
alias_method :[], :call
# Resolve a message
#
# @return [String]
#
# @api public
#
# rubocop:disable Metrics/AbcSize
def message(rule, tokens: EMPTY_HASH, locale: nil, full: false, path:)
keys = path.to_a.compact
msg_opts = tokens.merge(path: keys, locale: locale || messages.default_locale)
if keys.empty?
template, meta = messages["rules.#{rule}", msg_opts]
else
template, meta = messages[rule, msg_opts.merge(path: keys.join(DOT))]
template, meta = messages[rule, msg_opts.merge(path: keys.last)] unless template
end
unless template
raise MissingMessageError, <<~STR
Message template for #{rule.inspect} under #{keys.join(DOT).inspect} was not found
STR
end
parsed_tokens = parse_tokens(tokens)
text = template.(template.data(parsed_tokens))
[full ? "#{messages.rule(keys.last, msg_opts)} #{text}" : text, meta]
end
# rubocop:enable Metrics/AbcSize
private
def parse_tokens(tokens)
Hash[
tokens.map do |key, token|
[key, parse_token(token)]
end
]
end
def parse_token(token)
case token
when Array
token.join(", ")
else
token
end
end
end
end
end
end