class Dry::Schema::MessageCompiler

Compiles rule results AST into human-readable format

@api private

@api private

Constants

DEFAULT_PREDICATE_RESOLVERS
EMPTY_MESSAGE_SET
EMPTY_OPTS
FULL_MESSAGE_WHITESPACE

Attributes

default_lookup_options[R]
options[R]

Public Class Methods

new(messages, **options) click to toggle source

@api private

Calls superclass method
# File lib/dry/schema/message_compiler.rb, line 53
def initialize(messages, **options)
  super
  @options = options
  @default_lookup_options = options[:locale] ? {locale: locale} : EMPTY_HASH
end

Public Instance Methods

append_mapped_size_tokens(tokens) click to toggle source

@api private

# File lib/dry/schema/message_compiler.rb, line 229
def append_mapped_size_tokens(tokens)
  # this is a temporary fix for the inconsistency in the "size" errors arguments
  mapped_hash = tokens.each_with_object({}) { |(k, v), h| h[k.to_s.gsub("size", "num").to_sym] = v }
  tokens.merge(mapped_hash)
end
call(ast) click to toggle source

@api private

# File lib/dry/schema/message_compiler.rb, line 71
def call(ast)
  return EMPTY_MESSAGE_SET if ast.empty?

  current_messages = EMPTY_ARRAY.dup
  compiled_messages = ast.map { |node| visit(node, EMPTY_OPTS.dup(current_messages)) }

  MessageSet[compiled_messages, failures: options.fetch(:failures, true)]
end
lookup_options(arg_vals:, input:) click to toggle source

@api private

# File lib/dry/schema/message_compiler.rb, line 195
def lookup_options(arg_vals:, input:)
  default_lookup_options.merge(
    arg_type: arg_vals.size == 1 && arg_vals[0].class,
    val_type: input.equal?(Undefined) ? NilClass : input.class
  )
end
message_text(template, tokens, options) click to toggle source

@api private

# File lib/dry/schema/message_compiler.rb, line 203
def message_text(template, tokens, options)
  text = template[template.data(tokens)]

  return text if !text || !full

  rule = options[:path]
  [messages.rule(rule, options) || rule, text].join(FULL_MESSAGE_WHITESPACE[template.options[:locale]])
end
message_tokens(args) click to toggle source

@api private

# File lib/dry/schema/message_compiler.rb, line 213
def message_tokens(args)
  tokens = args.each_with_object({}) do |arg, hash|
    case arg[1]
    when Array
      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]
    end
  end
  args.any? { |e| e.first == :size } ? append_mapped_size_tokens(tokens) : tokens
end
message_type(*) click to toggle source

@api private

# File lib/dry/schema/message_compiler.rb, line 167
def message_type(*)
  Message
end
or_translator() click to toggle source

@api private

# File lib/dry/schema/message_compiler.rb, line 124
def or_translator
  @or_translator ||= proc { |k|
    message = messages.translate(k, **default_lookup_options)
    message.is_a?(Hash) ? message[:text] : message
  }
end
visit(node, opts = EMPTY_OPTS.dup) click to toggle source

@api private

# File lib/dry/schema/message_compiler.rb, line 81
def visit(node, opts = EMPTY_OPTS.dup)
  __send__(:"visit_#{node[0]}", node[1], opts)
end
visit_and(node, opts) click to toggle source

@api private

# File lib/dry/schema/message_compiler.rb, line 102
def visit_and(node, opts)
  left, right = node.map { |n| visit(n, opts) }

  if right
    [left, right]
  else
    left
  end
end
visit_failure(node, opts) click to toggle source

@api private

# File lib/dry/schema/message_compiler.rb, line 86
def visit_failure(node, opts)
  rule, other = node
  visit(other, opts.(rule: rule))
end
visit_hint(*) click to toggle source

@api private

# File lib/dry/schema/message_compiler.rb, line 92
def visit_hint(*)
  nil
end
visit_implication(node, *args) click to toggle source

@api private

# File lib/dry/schema/message_compiler.rb, line 183
def visit_implication(node, *args)
  _, right = node
  visit(right, *args)
end
visit_key(node, opts) click to toggle source

@api private

# File lib/dry/schema/message_compiler.rb, line 172
def visit_key(node, opts)
  name, other = node
  visit(other, opts.(path: name))
end
visit_namespace(node, opts) click to toggle source

@api private

# File lib/dry/schema/message_compiler.rb, line 132
def visit_namespace(node, opts)
  ns, rest = node
  self.class.new(messages.namespaced(ns), **options).visit(rest, opts)
end
visit_not(node, opts) click to toggle source

@api private

# File lib/dry/schema/message_compiler.rb, line 97
def visit_not(node, opts)
  visit(node, opts.(not: true))
end
visit_or(node, opts) click to toggle source

@api private

# File lib/dry/schema/message_compiler.rb, line 118
def visit_or(node, opts)
  left, right = node.map { |n| visit(n, opts) }
  Message::Or[left, right, or_translator]
end
visit_predicate(node, opts) click to toggle source

@api private

# File lib/dry/schema/message_compiler.rb, line 138
def visit_predicate(node, opts)
  predicate, args = node

  tokens = message_tokens(args)
  path, *arg_vals, input = predicate_resolvers[predicate].(args, opts)

  options = opts.dup.update(
    path: path.last, **tokens, **lookup_options(arg_vals: arg_vals, input: input)
  ).to_h

  template, meta = messages[predicate, options]

  unless template
    raise MissingMessageError.new(path, messages.looked_up_paths(predicate, options))
  end

  text = message_text(template, tokens, options)

  message_type(options).new(
    text: text,
    meta: meta,
    path: path,
    predicate: predicate,
    args: arg_vals,
    input: input
  )
end
visit_set(node, opts) click to toggle source

@api private

# File lib/dry/schema/message_compiler.rb, line 178
def visit_set(node, opts)
  node.map { |el| visit(el, opts) }
end
visit_unexpected_key(node, opts) click to toggle source

@api private

# File lib/dry/schema/message_compiler.rb, line 113
def visit_unexpected_key(node, opts)
  visit_predicate([:unexpected_key, []], opts.dup.update(path: Path[node.first]))
end
visit_xor(node, opts) click to toggle source

@api private

# File lib/dry/schema/message_compiler.rb, line 189
def visit_xor(node, opts)
  left, right = node
  [visit(left, opts), visit(right, opts)].uniq
end
with(new_options) click to toggle source

@api private

# File lib/dry/schema/message_compiler.rb, line 60
def with(new_options)
  return self if new_options.empty?

  updated_opts = options.merge(new_options)

  return self if updated_opts.eql?(options)

  self.class.new(messages, **updated_opts)
end