lib/dry/validation/message_compiler.rb in dry-validation-0.9.5 vs lib/dry/validation/message_compiler.rb in dry-validation-0.10.0

- old
+ new

@@ -1,102 +1,162 @@ -require 'dry/validation/constants' +require 'dry/core/constants' require 'dry/validation/message' require 'dry/validation/message_set' +require 'dry/validation/message_compiler/visitor_opts' module Dry module Validation class MessageCompiler + include Core::Constants + attr_reader :messages, :options, :locale, :default_lookup_options + EMPTY_OPTS = VisitorOpts.new + LIST_SEPARATOR = ', '.freeze + def initialize(messages, options = {}) @messages = messages @options = options @full = @options.fetch(:full, false) + @hints = @options.fetch(:hints, true) @locale = @options.fetch(:locale, messages.default_locale) - @default_lookup_options = { message_type: message_type, locale: locale } + @default_lookup_options = { locale: locale } end - def call(ast) - MessageSet[ast.map { |node| visit(node) }] - end - def full? @full end + def hints? + @hints + end + def with(new_options) return self if new_options.empty? self.class.new(messages, options.merge(new_options)) end + def call(ast) + MessageSet[ast.map { |node| visit(node) }, failures: options.fetch(:failures, true)] + end + def visit(node, *args) __send__(:"visit_#{node[0]}", node[1], *args) end - def visit_predicate(node, base_opts = EMPTY_HASH) - predicate, args = node + def visit_failure(node, opts = EMPTY_OPTS) + rule, other = node + visit(other, opts.(rule: rule)) + end - *arg_vals, _ = args.map(&:last) + def visit_hint(node, opts = EMPTY_OPTS) + if hints? + visit(node, opts.(message_type: :hint)) + end + end - tokens = message_tokens(args) + def visit_each(node, opts = EMPTY_OPTS) + # TODO: we can still generate a hint for elements here! + [] + end - if base_opts[:message] == false - return [predicate, arg_vals, tokens] + def visit_not(node, opts = EMPTY_OPTS) + visit(node, opts.(not: true)) + end + + def visit_check(node, opts = EMPTY_OPTS) + keys, other = node + visit(other, opts.(path: keys.last, check: true)) + end + + def visit_rule(node, opts = EMPTY_OPTS) + name, other = node + visit(other, opts.(rule: name)) + end + + def visit_schema(node, opts = EMPTY_OPTS) + node.rule_ast.map { |rule| visit(rule, opts) } + end + + def visit_and(node, opts = EMPTY_OPTS) + left, right = node.map { |n| visit(n, opts) } + + if right + [left, right] + else + left end + end - options = base_opts.update(lookup_options(base_opts, arg_vals)) + def visit_or(node, opts = EMPTY_OPTS) + left, right = node.map { |n| visit(n, opts) } + + if [left, right].flatten.map(&:path).uniq.size == 1 + Message::Or.new(left, right, -> k { messages[k, default_lookup_options] }) + elsif right.is_a?(Array) + right + else + [left, right] + end + end + + def visit_predicate(node, base_opts = EMPTY_OPTS) + predicate, args = node + + *arg_vals, val = args.map(&:last) + tokens = message_tokens(args) + + input = val != Undefined ? val : nil + + options = base_opts.update(lookup_options(arg_vals: arg_vals, input: input)) msg_opts = options.update(tokens) - name = msg_opts[:name] - rule = msg_opts[:rule] || name + rule = msg_opts[:rule] + path = msg_opts[:path] - template = messages[predicate, msg_opts] + template = messages[rule] || messages[predicate, msg_opts] unless template raise MissingMessageError, "message for #{predicate} was not found" end text = message_text(rule, template, tokens, options) - path = message_path(msg_opts, name) + message_class = options[:message_type] == :hint ? Hint : Message + message_class[ predicate, path, text, - args: arg_vals, rule: rule, each: base_opts[:each] == true + args: arg_vals, + input: input, + rule: rule, + check: base_opts[:check] ] end - def visit_key(node, opts = EMPTY_HASH) - name, predicate = node - visit(predicate, opts.merge(name: name)) + def visit_key(node, opts = EMPTY_OPTS) + name, other = node + visit(other, opts.(path: name)) end - def visit_val(node, opts = EMPTY_HASH) - visit(node, opts) + def visit_set(node, opts = EMPTY_OPTS) + node.map { |el| visit(el, opts) } end - def visit_set(node, opts = EMPTY_HASH) - node.map { |input| visit(input, opts) } - end - - def visit_el(node, opts = EMPTY_HASH) - idx, el = node - visit(el, opts.merge(path: opts[:path] + [idx])) - end - def visit_implication(node, *args) _, right = node visit(right, *args) end - def visit_xor(node, *args) - _, right = node - visit(right, *args) + def visit_xor(node, opts = EMPTY_OPTS) + left, right = node + [visit(left, opts), visit(right, opts)].uniq end - def lookup_options(_opts, arg_vals = []) + def lookup_options(arg_vals: [], input: nil) default_lookup_options.merge( - arg_type: arg_vals.size == 1 && arg_vals[0].class + arg_type: arg_vals.size == 1 && arg_vals[0].class, + val_type: input.class ) end def message_text(rule, template, tokens, opts) text = template % tokens @@ -107,28 +167,14 @@ else text end end - def message_path(opts, name) - if name.is_a?(Array) - name - else - path = opts[:path] || Array(name) - - if name && path.last != name - path += [name] - end - - path - end - end - def message_tokens(args) args.each_with_object({}) { |arg, hash| case arg[1] when Array - hash[arg[0]] = arg[1].join(', ') + hash[arg[0]] = arg[1].join(LIST_SEPARATOR) when Range hash["#{arg[0]}_left".to_sym] = arg[1].first hash["#{arg[0]}_right".to_sym] = arg[1].last else hash[arg[0]] = arg[1]