A library for defining and validating data structures.
An Errors
object documents the failures for a constraint or contract match.
When a constraint or contract fails to match an object, Stannum can return the reasons for that failure in the form of an errors object. Specifically, a Stannum::Errors
object is returned by calling #errors_for
or #negated_errors_for
with a failing object, or as part of the result of calling #match
or #negated_match
.
contract.matches?(nil)
#=> false
contract.errors_for(nil)
#=> an instance of Stannum::Errors
status, errors = contract.match(nil)
status
#=> false
errors
#=> an instance of Stannum::Errors
A Stannum::Errors
object is an Enumerable
collection, while each error is a Hash
with the following properties:
#type
: A unique value that defines what kind of error was encountered. Each error’s type should be a namespaced String
, e.g. "stannum.constraints.invalid"
.#data
: A Hash
of additional data about the error. For example, a failed type validation will include the expected type for the value; a failed range validation might include the minimum and maximum allowable values.#path
: The path of the error relative to the top-level object that was validated. The path is always an Array
, and each item in the array is either an Integer
or a non-empty Symbol
. Some examples:
[]
.3
of an array would have a path of [3]
.#name
property of an object would have a path of [:name]
.[:manufacturers, 3, :address]
.#message
: A human-readable description of the error. Error messages are not generated by default; either specify a message when defining the constraint, or call #with_messages
to generate the error messages based on the error types and data. See Generating Messages, below.The simplest way to access the errors in a Stannum::Errors
object is via the #each
method, which will yield each error in the collection to the given block. Because each Stannum::Errors
is enumerable, you can use the standard Enumerable
methods such as #map
, #reduce
, #select
, and so on. You can also use #count
to return the number of errors, or #empty?
to check if there are any errors in the collection.
errors.count
#=> 3
errors.empty?
#=> false
errors.first
#=> {
# data: {},
# message: nil,
# path: [:name],
# type: 'stannum.constraints.invalid'
# }
errors.map(&:type)
#=> [
# 'stannum.constraints.invalid',
# 'stannum.constraints.absent',
# 'stannum.constraints.is_not_type'
# ]
Usually, an errors object is generated automatically by a constraint or contract with its errors already defined. If you want to add custom errors to an errors object, use the #add
method, which takes the error type
as one required argument. You can also specify the message
keyword, which sets the message of the error. Finally, any additional keywords are added to the error data
.
errors = Stannum::Errors.new
errors.count
#=> 0
errors.empty?
#=> true
errors.add('example.constraints.out_of_range', message: 'out of range', min: 0, max: 10)
#=> the errors object
errors.count
#=> 1
errors.empty?
#=> false
errors.first
#=> {
# data: { min: 0, max: 10 },
# message: 'out of range',
# path: [],
# type: 'example.constraints.out_of_range'
# }
Conveniently, #add
returns the errors object itself, so you can chain together multiple #add
calls.
To represent the properties of an object or the values in a data structure, Stannum::Errors
can be nested together. Nested error objects are accessed using the #[]
operator.
errors = Stannum::Errors.new
errors[:manufacturers][0][:address].add('stannum.constraints.invalid')
errors[:manufacturers][0][:address]
#=> an instance of Stannum::Errors
errors[:manufacturers][0][:address].count
#=> 1
errors[:manufacturers][0][:address].first
#=> {
# data: {},
# message: nil,
# path: [],
# type: 'stannum.constraints.invalid'
# }
errors.count
#=> 1
errors.first
#=> {
# data: {},
# message: nil,
# path: [:manufacturers, 0, :address],
# type: 'stannum.constraints.invalid'
# }
You can also use the #dig
method to access nested errors:
errors.dig(:manufacturers, 0, :address).first
#=> {
# data: {},
# message: nil,
# path: [],
# type: 'stannum.constraints.invalid'
# }
By default, errors objects do not generate messages. Stannum::Errors
defines the #with_messages
method to generate messages for a given errors object. If the :force
keyword is set to true, then #with_messages
will overwrite any messages that are already set on an error, whether from a constraint or generated by a different strategy.
errors.first.message
#=> nil
errors = errors.with_messages.first.message
errors.first.message
#=> 'is invalid'
Stannum uses the strategy pattern to determine how error messages are generated. You can pass the strategy:
keyword to #with_messages
to force Stannum to use the specified strategy, or set the Stannum::Messages.strategy
property to define the default for your application. The default strategy for Stannum uses an I18n-like configuration file to define messages based on the type and optionally the data for each error.
Each errors object defines a #summary
method that collects the path and message for each error into a single comma-separated string.
errors = Stannum::Errors.new
errors['rocket'].add('already_launched', message: 'has already launched')
errors['rocket'].add('wrong_direction', message: 'not pointed toward space')
errors['rocket']['fuel'].add('empty', message: 'is empty')
errors.summary
#=> "rocket: has already launched, rocket: not pointed toward space, rocket.fuel: is empty"
Use the error summary to display a human-readable representation of the errors, such as in a displayed failure message.
Back to Documentation