dry-validation/lib/dry/validation/result.rb

212 lines
4.5 KiB
Ruby

# frozen_string_literal: true
require "concurrent/map"
require "dry/equalizer"
require "dry/validation/constants"
require "dry/validation/message_set"
require "dry/validation/values"
module Dry
module Validation
# Result objects are returned by contracts
#
# @api public
class Result
include Dry::Equalizer(:schema_result, :context, :errors, inspect: false)
# Build a new result
#
# @param [Dry::Schema::Result] schema_result
#
# @api private
def self.new(schema_result, context = ::Concurrent::Map.new, options = EMPTY_HASH)
result = super
yield(result) if block_given?
result.freeze
end
# Context that's shared between rules
#
# @return [Concurrent::Map]
#
# @api public
attr_reader :context
# Result from contract's schema
#
# @return [Dry::Schema::Result]
#
# @api private
attr_reader :schema_result
# Result options
#
# @return [Hash]
#
# @api private
attr_reader :options
# Initialize a new result
#
# @api private
def initialize(schema_result, context, options)
@schema_result = schema_result
@context = context
@options = options
@errors = initialize_errors
end
# Return values wrapper with the input processed by schema
#
# @return [Values]
#
# @api public
def values
@values ||= Values.new(schema_result.to_h)
end
# Get error set
#
# @!macro errors-options
# @param [Hash] new_options
# @option new_options [Symbol] :locale Set locale for messages
# @option new_options [Boolean] :hints Enable/disable hints
# @option new_options [Boolean] :full Get messages that include key names
#
# @return [MessageSet]
#
# @api public
def errors(new_options = EMPTY_HASH)
new_options.empty? ? @errors : @errors.with(schema_errors(new_options), new_options)
end
# Check if result is successful
#
# @return [Bool]
#
# @api public
def success?
@errors.empty?
end
# Check if result is not successful
#
# @return [Bool]
#
# @api public
def failure?
!success?
end
# Check if values include an error for the provided key
#
# @api private
def error?(key)
schema_result.error?(key)
end
# Check if there's any error for the provided key
#
# This does not consider errors from the nested values
#
# @api private
def base_error?(key)
schema_result.errors.any? { |error|
key_path = Schema::Path[key]
err_path = Schema::Path[error.path]
return false unless key_path.same_root?(err_path)
key_path == err_path
}
end
# Add a new error for the provided key
#
# @api private
def add_error(error)
@errors.add(error)
self
end
# Read a value under provided key
#
# @param [Symbol] key
#
# @return [Object]
#
# @api public
def [](key)
values[key]
end
# Check if a key was set
#
# @param [Symbol] key
#
# @return [Bool]
#
# @api public
def key?(key)
values.key?(key)
end
# Coerce to a hash
#
# @api public
def to_h
values.to_h
end
# Return a string representation
#
# @api public
def inspect
if context.empty?
"#<#{self.class}#{to_h} errors=#{errors.to_h}>"
else
"#<#{self.class}#{to_h} errors=#{errors.to_h} context=#{context.each.to_h}>"
end
end
# Freeze result and its error set
#
# @api private
def freeze
values.freeze
errors.freeze
super
end
if RUBY_VERSION >= "2.7"
# Pattern matching
#
# @api private
def deconstruct_keys(keys)
values.deconstruct_keys(keys)
end
# Pattern matching
#
# @api private
def deconstruct
[values, context.each.to_h]
end
end
private
# @api private
def initialize_errors(options = self.options)
MessageSet.new(schema_errors(options), options)
end
# @api private
def schema_errors(options)
schema_result.message_set(options).to_a
end
end
end
end