# typed: strict module DataModel # Provide Error building functionality as a mixin module Errors include Kernel extend T::Sig extend self TTemporal = T.type_alias { T.any(::Date, ::Time, ::DateTime) } ## Constructors # Type error applies when a value is not of the expected type sig { params(cls: T.class_of(Object), value: Object).returns(TError) } def type_error(cls, value) [:type, [cls, value]] end # Coerce error applies when a value cannot be coerced to the expected type sig { params(cls: T.class_of(Object), value: Object).returns(TError) } def coerce_error(cls, value) [:coerce, [cls, value]] end # Missing error applies when a value is missing sig { params(cls: T.class_of(Object)).returns(TError) } def missing_error(cls) [:missing, cls] end # Inclusion error applies when a value is not in a set of allowed values sig { params(set: T::Array[T.any(Symbol, String)]).returns(TError) } def inclusion_error(set) [:inclusion, set] end # Exclusive error applies when a value is in a set of disallowed values sig { params(set: T::Array[T.any(Symbol, String)]).returns(TError) } def exclusion_error(set) [:exclusion, set] end # Blank error applies when a value is blank sig { returns(TError) } def blank_error [:blank, nil] end # Extra keys error applies when a hash has extra keys sig { params(keys: T::Array[Symbol]).returns(TError) } def extra_keys_error(keys) [:extra_keys, keys] end # Min applies when value is less then the minimum sig { params(min: Numeric, val: Numeric).returns(TError) } def min_error(min, val) [:min, [min, val]] end # Max applies when value is less then the minimum sig { params(min: Numeric, val: Numeric).returns(TError) } def max_error(min, val) [:max, [min, val]] end # Earliest applies when value is earlier then earliest sig { params(earliest: TTemporal, val: TTemporal).returns(TError) } def earliest_error(earliest, val) [:earliest, [earliest, val]] end # Latest applies when value is earlier then earliest sig { params(latest: TTemporal, val: TTemporal).returns(TError) } def latest_error(latest, val) [:latest, [latest, val]] end # Format applies when value does not match a format sig { params(format: Object, val: String).returns(TError) } def format_error(format, val) [:format, [format, val]] end ## Messages # Generate a message for a type error sig { params(cls: T.class_of(Object), value: Object).returns(String) } def type_error_message(cls, value) "#{value.inspect} is not a #{cls.name}, it is a #{value.class.name}" end # Generate a message for a coerce error sig { params(cls: T.class_of(Object), value: Object).returns(String) } def coerce_error_message(cls, value) "cannot be coerced to #{cls.name}, it is a #{value.class.name}" end # Generate a message for a missing error sig { params(cls: T.class_of(Object)).returns(String) } def missing_error_message(cls) "missing value, expected a #{cls.name}" end # Generate a message for an inclusion error sig { params(set: T::Array[Symbol]).returns(String) } def inclusion_error_message(set) "must be one of #{set.join(', ')}" end # Generate a message for an exclusion error sig { params(set: T::Array[Symbol]).returns(String) } def exclusion_error_message(set) "must not be one of #{set.join(', ')}" end # Generate a message for a blank error sig { returns(String) } def blank_error_message "cannot be blank" end # Generate a message for an extra keys error sig { params(keys: T::Array[Symbol]).returns(String) } def extra_keys_error_message(keys) "more elements found in closed hash then specified children: #{keys.join(', ')}" end # Generate a message for a min error sig { params(min: Numeric, val: Numeric).returns(String) } def min_error_message(min, val) "value is less than the minimum of #{min}, it is #{val}" end # Generate a message for a min error sig { params(max: Numeric, val: Numeric).returns(String) } def max_error_message(max, val) "value is more than the maximum of #{max}, it is #{val}" end # Generate a message for a value that occurs earlier then the specified earliest point sig { params(earliest: TTemporal, val: TTemporal).returns(String) } def early_error_message(earliest, val) "value #{val} is before #{earliest}" end # Generate a message for a value that occurs later then the specified latest point sig { params(latest: TTemporal, val: TTemporal).returns(String) } def late_error_message(latest, val) "value #{val} is after #{latest}" end # Generate a message for a value that does not match the format sig { params(format: Object, val: String).returns(String) } def format_error_message(format, val) "value #{val} does not match format #{format}" end # Builders TErrorMessageBuilder = T.type_alias { T.proc.params(ctx: T.untyped).returns(String) } TErrorMessages = T.type_alias { T::Hash[Symbol, TErrorMessageBuilder] } TClassValueCtx = T.type_alias { [T.class_of(Object), Object] } TClassCtx = T.type_alias { T.class_of(Object) } TSetCtx = T.type_alias { T::Array[Symbol] } TWithinCtx = T.type_alias { [Numeric, Numeric] } TWithinTemporalCtx = T.type_alias { [Errors::TTemporal, Errors::TTemporal] } TFormatCtx = T.type_alias { [Object, String] } # Get the error message builders sig { returns(TErrorMessages) } def self.error_messages return { type: lambda do |ctx| cls, val = T.let(ctx, TClassValueCtx) type_error_message(cls, val) end, coerce: lambda do |ctx| cls, val = T.let(ctx, TClassValueCtx) type_error_message(cls, val) end, missing: lambda do |ctx| cls = T.let(ctx, TClassCtx) missing_error_message(cls) end, inclusion: lambda do |ctx| set = T.let(ctx, TSetCtx) inclusion_error_message(set) end, exclusion: lambda do |ctx| set = T.let(ctx, TSetCtx) exclusion_error_message(set) end, extra_keys: lambda do |ctx| set = T.let(ctx, TSetCtx) extra_keys_error_message(set) end, min: lambda do |ctx| min, val = T.let(ctx, TWithinCtx) min_error_message(min, val) end, max: lambda do |ctx| max, val = T.let(ctx, TWithinCtx) max_error_message(max, val) end, earliest: lambda do |ctx| earliest, val = T.let(ctx, TWithinTemporalCtx) early_error_message(earliest, val) end, latest: lambda do |ctx| latest, val = T.let(ctx, TWithinTemporalCtx) late_error_message(latest, val) end, blank: lambda do blank_error_message end, format: lambda do |ctx| format, val = T.let(ctx, TFormatCtx) format_error_message(format, val) end } end end end