module DataModel # Error is a class that holds errors. Errors are a tuple of [name, ctx] # - name is a symbol that identifies the error # - ctx is contextual information about the error which can be used to build an error message # # The error object is a structured way to store, modify, and add errors in that intermediary format. # To turn an error into a human readable message, use #to_messages, which delegates to a registry # # Base errors are errors that are related to the object as a whole, and not to any specific child # Child errors are errors that are related to a specific child of the object, which may or may not apply depending on the type class Error include Errors # Create a new error Object # @return [Error] the new error object def initialize @base = [] @children = {} end # errors related to the object as a whole # @return [Array] the base errors def base return @base end # errors related children # @return [Hash{Symbol => Array}] the child errors def children return @children end # all errors # @return [Hash{Symbol => Array}] all errors def all return children.merge(base:) end alias to_h all # Returns true if any errors are present. # @param blk [Proc] an optional block to filter errors, takes an Array(Symbol, untyped) and returns boolean # @return [Boolean] true if any errors are present def any?(&blk) if !blk return !@base.empty? || !@children.empty? end any = T.let(false, T::Boolean) for error_list in all.values any = error_list.any?(&blk) if any break end end return any end # Returns true if no errors are present. # @return [Boolean] true if no errors are present def empty? !any? end # Add an error to the error list. # @param err [Array(Symbol, untyped)] the error to add # @param child [Symbol, Array(Symbol)] the child to add the error to. child can be an array of symbols to specify a path if nested # @return [void] def add(err, child: nil) if child.is_a?(Array) child = child.join(".").to_sym end if child == :base raise "child errors may not be named :base" end errs = child ? @children[child] ||= [] : @base errs.push(err) end # Merge another error object into this one for child Errors # @param name [Symbol] the name of the child # @param child [Error] the child error object # @return [void] def merge_child(name, child) if !child.any? return end for (key, error_list) in child.all for error in error_list add(error, child: [name, key]) end end end # Get human readable error messages from error tuples # @param registry [Registry] the registry to use to get error messages # @return [Hash{Symbol => Array[String]}] the error messages def to_messages(registry: Registry.instance) return registry.error_messages(self) end end end