From aa300f2e492f29825e56e76f93d2d5ba4e21586a Mon Sep 17 00:00:00 2001 From: Alexey Belousov Date: Thu, 18 Jan 2024 21:31:11 +1000 Subject: [PATCH 1/4] Expose failed mapper location --- .../package/ree_dao/wrappers/pg_array.rb | 20 ++-- .../package/ree_dao/wrappers/pg_jsonb.rb | 20 ++-- .../package/ree_enum/base_enum_mapper.rb | 10 +- .../ree_enum/integer_value_enum_mapper.rb | 10 +- .../ree_enum/string_value_enum_mapper.rb | 10 +- .../packages/ree_mapper/package/ree_mapper.rb | 1 + .../ree_mapper/errors/coercion_error.rb | 2 +- .../ree_mapper/errors/error_with_location.rb | 20 ++++ .../package/ree_mapper/errors/type_error.rb | 2 +- .../ree_mapper/package/ree_mapper/field.rb | 16 +++- .../ree_mapper/package/ree_mapper/mapper.rb | 18 ++-- .../package/ree_mapper/mapper_factory.rb | 21 ++++- .../package/ree_mapper/types/any.rb | 16 ++-- .../package/ree_mapper/types/bool.rb | 24 ++--- .../package/ree_mapper/types/date.rb | 26 ++--- .../package/ree_mapper/types/date_time.rb | 26 ++--- .../package/ree_mapper/types/float.rb | 26 ++--- .../package/ree_mapper/types/integer.rb | 26 ++--- .../package/ree_mapper/types/rational.rb | 26 ++--- .../package/ree_mapper/types/string.rb | 24 ++--- .../package/ree_mapper/types/time.rb | 26 ++--- .../package/ree_mapper/wrappers/array.rb | 94 ++++++++++++++----- .../spec/ree_mapper/mapper_factory_spec.rb | 16 ++-- .../ree_mapper/spec/ree_mapper/mapper_spec.rb | 7 ++ 24 files changed, 303 insertions(+), 184 deletions(-) create mode 100644 ree_lib/lib/ree_lib/packages/ree_mapper/package/ree_mapper/errors/error_with_location.rb diff --git a/ree_lib/lib/ree_lib/packages/ree_dao/package/ree_dao/wrappers/pg_array.rb b/ree_lib/lib/ree_lib/packages/ree_dao/package/ree_dao/wrappers/pg_array.rb index 4e7360b0..9f2da61c 100644 --- a/ree_lib/lib/ree_lib/packages/ree_dao/package/ree_dao/wrappers/pg_array.rb +++ b/ree_lib/lib/ree_lib/packages/ree_dao/package/ree_dao/wrappers/pg_array.rb @@ -8,12 +8,13 @@ class ReeDao::PgArray < ReeMapper::AbstractWrapper Kwargs[ name: String, role: Nilor[Symbol, ArrayOf[Symbol]], - fields_filters: ArrayOf[ReeMapper::FieldsFilter] + fields_filters: ArrayOf[ReeMapper::FieldsFilter], + location: Nilor[String], ] => Or[Sequel::Postgres::PGArray, String] ) - def db_dump(value, name:, role: nil, fields_filters: []) + def db_dump(value, name:, role: nil, fields_filters: [], location: nil) if !value.is_a?(Array) - raise ReeMapper::TypeError, "`#{name}` should be an array, got `#{truncate(value.inspect)}`" + raise ReeMapper::TypeError.new("`#{name}` should be an array, got `#{truncate(value.inspect)}`", location) end value = value.map.with_index do |el, index| @@ -21,7 +22,8 @@ def db_dump(value, name:, role: nil, fields_filters: []) el, name: "#{name}[#{index}]", role: role, - fields_filters: fields_filters + [subject.fields_filter] + fields_filters: fields_filters + [subject.fields_filter], + location: subject.location, ) end @@ -37,12 +39,13 @@ def db_dump(value, name:, role: nil, fields_filters: []) Kwargs[ name: String, role: Nilor[Symbol, ArrayOf[Symbol]], - fields_filters: ArrayOf[ReeMapper::FieldsFilter] + fields_filters: ArrayOf[ReeMapper::FieldsFilter], + location: Nilor[String], ] => Array ).throws(ReeMapper::TypeError) - def db_load(value, name:, role: nil, fields_filters: []) + def db_load(value, name:, role: nil, fields_filters: [], location: nil) if !value.is_a?(Sequel::Postgres::PGArray) - raise ReeMapper::TypeError, "`#{name}` should be a Sequel::Postgres::PGArray, got `#{truncate(value.inspect)}`" + raise ReeMapper::TypeError.new("`#{name}` should be a Sequel::Postgres::PGArray, got `#{truncate(value.inspect)}`", location) end value.map.with_index do |val, index| @@ -50,7 +53,8 @@ def db_load(value, name:, role: nil, fields_filters: []) val, name: "#{name}[#{index}]", role: role, - fields_filters: fields_filters + [subject.fields_filter] + fields_filters: fields_filters + [subject.fields_filter], + location: subject.location, ) end end diff --git a/ree_lib/lib/ree_lib/packages/ree_dao/package/ree_dao/wrappers/pg_jsonb.rb b/ree_lib/lib/ree_lib/packages/ree_dao/package/ree_dao/wrappers/pg_jsonb.rb index 046834e0..21f987f8 100644 --- a/ree_lib/lib/ree_lib/packages/ree_dao/package/ree_dao/wrappers/pg_jsonb.rb +++ b/ree_lib/lib/ree_lib/packages/ree_dao/package/ree_dao/wrappers/pg_jsonb.rb @@ -8,7 +8,8 @@ class ReeDao::PgJsonb < ReeMapper::AbstractWrapper Kwargs[ name: String, role: Nilor[Symbol, ArrayOf[Symbol]], - fields_filters: ArrayOf[ReeMapper::FieldsFilter] + fields_filters: ArrayOf[ReeMapper::FieldsFilter], + location: Nilor[String], ] => Or[ Sequel::Postgres::JSONBHash, Sequel::Postgres::JSONBArray, @@ -20,18 +21,19 @@ class ReeDao::PgJsonb < ReeMapper::AbstractWrapper Sequel::Postgres::JSONBNull ] ).throws(ReeMapper::TypeError) - def db_dump(value, name:, role: nil, fields_filters: []) + def db_dump(value, name:, role: nil, fields_filters: [], location: nil) value = subject.type.db_dump( value, name: name, role: role, - fields_filters: fields_filters + [subject.fields_filter] + fields_filters: fields_filters + [subject.fields_filter], + location: subject.location, ) begin Sequel.pg_jsonb_wrap(value) rescue Sequel::Error - raise ReeMapper::TypeError, "`#{name}` should be an jsonb primitive, got `#{truncate(value.inspect)}`" + raise ReeMapper::TypeError.new("`#{name}` should be an jsonb primitive, got `#{truncate(value.inspect)}`", location) end end @@ -40,7 +42,8 @@ def db_dump(value, name:, role: nil, fields_filters: []) Kwargs[ name: String, role: Nilor[Symbol, ArrayOf[Symbol]], - fields_filters: ArrayOf[ReeMapper::FieldsFilter] + fields_filters: ArrayOf[ReeMapper::FieldsFilter], + location: Nilor[String], ] => Or[ Hash, Array, @@ -52,7 +55,7 @@ def db_dump(value, name:, role: nil, fields_filters: []) Rational, ] ).throws(ReeMapper::TypeError) - def db_load(value, name:, role: nil, fields_filters: []) + def db_load(value, name:, role: nil, fields_filters: [], location: nil) value = case value when Sequel::Postgres::JSONBHash ReeObject::ToHash.new.call(value.to_h) @@ -61,14 +64,15 @@ def db_load(value, name:, role: nil, fields_filters: []) when Numeric, String, TrueClass, FalseClass, NilClass value else - raise ReeMapper::TypeError, "`#{name}` should be a Sequel::Postgres::JSONB, got `#{truncate(value.inspect)}`" + raise ReeMapper::TypeError.new("`#{name}` should be a Sequel::Postgres::JSONB, got `#{truncate(value.inspect)}`", location) end subject.type.db_load( value, name: name, role: role, - fields_filters: fields_filters + [subject.fields_filter] + fields_filters: fields_filters + [subject.fields_filter], + location: subject.location, ) end end \ No newline at end of file diff --git a/ree_lib/lib/ree_lib/packages/ree_enum/package/ree_enum/base_enum_mapper.rb b/ree_lib/lib/ree_lib/packages/ree_enum/package/ree_enum/base_enum_mapper.rb index fbc2b38a..3b3dd41b 100644 --- a/ree_lib/lib/ree_lib/packages/ree_enum/package/ree_enum/base_enum_mapper.rb +++ b/ree_lib/lib/ree_lib/packages/ree_enum/package/ree_enum/base_enum_mapper.rb @@ -12,10 +12,10 @@ def initialize(enum) ReeEnum::Value, Kwargs[ name: String, - role: Nilor[Symbol, ArrayOf[Symbol]] + location: Nilor[String], ] => Or[Integer, String] ) - def db_dump(value, name:, role: nil) + def db_dump(value, name:, location: nil) value.mapped_value end @@ -23,14 +23,14 @@ def db_dump(value, name:, role: nil) Or[Integer, String], Kwargs[ name: String, - role: Nilor[Symbol, ArrayOf[Symbol]] + location: Nilor[String], ] => ReeEnum::Value ).throws(ReeMapper::CoercionError) - def db_load(value, name:, role: nil) + def db_load(value, name:, location: nil) enum_val = @enum.get_values.by_mapped_value(value) if !enum_val - raise ReeMapper::CoercionError, "`#{name}` should be one of #{enum_inspection}, got `#{truncate(value.inspect)}`" + raise ReeMapper::CoercionError.new("`#{name}` should be one of #{enum_inspection}, got `#{truncate(value.inspect)}`", location) end enum_val diff --git a/ree_lib/lib/ree_lib/packages/ree_enum/package/ree_enum/integer_value_enum_mapper.rb b/ree_lib/lib/ree_lib/packages/ree_enum/package/ree_enum/integer_value_enum_mapper.rb index 036d34de..6ea738dc 100644 --- a/ree_lib/lib/ree_lib/packages/ree_enum/package/ree_enum/integer_value_enum_mapper.rb +++ b/ree_lib/lib/ree_lib/packages/ree_enum/package/ree_enum/integer_value_enum_mapper.rb @@ -6,10 +6,10 @@ class ReeEnum::IntegerValueEnumMapper < ReeEnum::BaseEnumMapper ReeEnum::Value, Kwargs[ name: String, - role: Nilor[Symbol, ArrayOf[Symbol]] + location: Nilor[String], ] => Integer ) - def serialize(value, name:, role: nil) + def serialize(value, name:, location: nil) value.value end @@ -17,10 +17,10 @@ def serialize(value, name:, role: nil) Any, Kwargs[ name: String, - role: Nilor[Symbol, ArrayOf[Symbol]] + location: Nilor[String], ] => ReeEnum::Value ).throws(ReeMapper::CoercionError) - def cast(value, name:, role: nil) + def cast(value, name:, location: nil) enum_value = case value when Integer @enum.get_values.by_value(value) @@ -34,7 +34,7 @@ def cast(value, name:, role: nil) end if enum_value.nil? - raise ReeMapper::CoercionError, "`#{name}` should be one of #{enum_inspection}, got `#{truncate(value.inspect)}`" + raise ReeMapper::CoercionError.new("`#{name}` should be one of #{enum_inspection}, got `#{truncate(value.inspect)}`", location) end enum_value diff --git a/ree_lib/lib/ree_lib/packages/ree_enum/package/ree_enum/string_value_enum_mapper.rb b/ree_lib/lib/ree_lib/packages/ree_enum/package/ree_enum/string_value_enum_mapper.rb index 4099b5c7..4695eb09 100644 --- a/ree_lib/lib/ree_lib/packages/ree_enum/package/ree_enum/string_value_enum_mapper.rb +++ b/ree_lib/lib/ree_lib/packages/ree_enum/package/ree_enum/string_value_enum_mapper.rb @@ -6,10 +6,10 @@ class ReeEnum::StringValueEnumMapper < ReeEnum::BaseEnumMapper ReeEnum::Value, Kwargs[ name: String, - role: Nilor[Symbol, ArrayOf[Symbol]] + location: Nilor[String], ] => String ) - def serialize(value, name:, role: nil) + def serialize(value, name:, location: nil) value.value end @@ -17,10 +17,10 @@ def serialize(value, name:, role: nil) Any, Kwargs[ name: String, - role: Nilor[Symbol, ArrayOf[Symbol]] + location: Nilor[String], ] => ReeEnum::Value ).throws(ReeMapper::CoercionError) - def cast(value, name:, role: nil) + def cast(value, name:, location: nil) enum_value = case value when String @enum.get_values.by_value(value) @@ -29,7 +29,7 @@ def cast(value, name:, role: nil) end if enum_value.nil? - raise ReeMapper::CoercionError, "`#{name}` should be one of #{enum_inspection}, got `#{truncate(value.inspect)}`" + raise ReeMapper::CoercionError.new("`#{name}` should be one of #{enum_inspection}, got `#{truncate(value.inspect)}`", location) end enum_value diff --git a/ree_lib/lib/ree_lib/packages/ree_mapper/package/ree_mapper.rb b/ree_lib/lib/ree_lib/packages/ree_mapper/package/ree_mapper.rb index 9955f1ed..efff72f0 100644 --- a/ree_lib/lib/ree_lib/packages/ree_mapper/package/ree_mapper.rb +++ b/ree_lib/lib/ree_lib/packages/ree_mapper/package/ree_mapper.rb @@ -14,6 +14,7 @@ module ReeMapper require_relative 'ree_mapper/types/abstract_type' require_relative 'ree_mapper/errors/error' + require_relative 'ree_mapper/errors/error_with_location' require_relative 'ree_mapper/errors/coercion_error' require_relative 'ree_mapper/errors/type_error' require_relative 'ree_mapper/errors/unsupported_type_error' diff --git a/ree_lib/lib/ree_lib/packages/ree_mapper/package/ree_mapper/errors/coercion_error.rb b/ree_lib/lib/ree_lib/packages/ree_mapper/package/ree_mapper/errors/coercion_error.rb index 55dc7f02..dd73e78a 100644 --- a/ree_lib/lib/ree_lib/packages/ree_mapper/package/ree_mapper/errors/coercion_error.rb +++ b/ree_lib/lib/ree_lib/packages/ree_mapper/package/ree_mapper/errors/coercion_error.rb @@ -1,4 +1,4 @@ # frozen_string_literal: true -class ReeMapper::CoercionError < ReeMapper::Error +class ReeMapper::CoercionError < ReeMapper::ErrorWithLocation end diff --git a/ree_lib/lib/ree_lib/packages/ree_mapper/package/ree_mapper/errors/error_with_location.rb b/ree_lib/lib/ree_lib/packages/ree_mapper/package/ree_mapper/errors/error_with_location.rb new file mode 100644 index 00000000..9bb6a4c9 --- /dev/null +++ b/ree_lib/lib/ree_lib/packages/ree_mapper/package/ree_mapper/errors/error_with_location.rb @@ -0,0 +1,20 @@ +# frozen_string_literal: true + +class ReeMapper::ErrorWithLocation < ReeMapper::Error + attr_reader :location + + def initialize(message, location = nil) + super(message) + @location = location + end + + def full_message(...) + msg = super + return msg if location.nil? + + idx = msg.index(/\).*\n/) + return msg if idx.nil? + + msg.insert(idx + 1, "\nlocated at #{location}") + end +end diff --git a/ree_lib/lib/ree_lib/packages/ree_mapper/package/ree_mapper/errors/type_error.rb b/ree_lib/lib/ree_lib/packages/ree_mapper/package/ree_mapper/errors/type_error.rb index 3fa730c9..40e434d9 100644 --- a/ree_lib/lib/ree_lib/packages/ree_mapper/package/ree_mapper/errors/type_error.rb +++ b/ree_lib/lib/ree_lib/packages/ree_mapper/package/ree_mapper/errors/type_error.rb @@ -1,4 +1,4 @@ # frozen_string_literal: true -class ReeMapper::TypeError < ReeMapper::Error +class ReeMapper::TypeError < ReeMapper::ErrorWithLocation end diff --git a/ree_lib/lib/ree_lib/packages/ree_mapper/package/ree_mapper/field.rb b/ree_lib/lib/ree_lib/packages/ree_mapper/package/ree_mapper/field.rb index 233c4b4a..5f621e56 100644 --- a/ree_lib/lib/ree_lib/packages/ree_mapper/package/ree_mapper/field.rb +++ b/ree_lib/lib/ree_lib/packages/ree_mapper/package/ree_mapper/field.rb @@ -3,7 +3,7 @@ class ReeMapper::Field attr_reader :type, :name, :from, :doc, :optional, :null, :roles, :default, :name_as_str, :name_as_instance_var_name, :from_as_str, - :fields_filter + :fields_filter, :location NO_DEFAULT = Object.new.freeze @@ -18,11 +18,12 @@ class ReeMapper::Field role: Nilor[ArrayOf[Symbol], Symbol], default: Any, only: Nilor[ReeMapper::FilterFieldsContract], - except: Nilor[ReeMapper::FilterFieldsContract] + except: Nilor[ReeMapper::FilterFieldsContract], + location: Nilor[String], ] => Any ).throws(ArgumentError) - def initialize(type, name = nil, from: nil, doc: nil, optional: false, null: false, role: nil, default: NO_DEFAULT, - only: nil, except: nil) + def initialize(type, name = nil, from: nil, doc: nil, optional: false, null: false, role: nil, default: NO_DEFAULT, + only: nil, except: nil, location: nil) @type = type @name = name @from = from || name @@ -38,6 +39,13 @@ def initialize(type, name = nil, from: nil, doc: nil, optional: false, null: fal @name_as_instance_var_name = :"@#{@name}" @from_as_str = @from.to_s + @location = location + if @location + @location = @location + .sub(Ree.root_dir, ".") + .sub(/:in.+/, "") + end + raise ArgumentError, 'required fields do not support defaults' if has_default? && !optional end diff --git a/ree_lib/lib/ree_lib/packages/ree_mapper/package/ree_mapper/mapper.rb b/ree_lib/lib/ree_lib/packages/ree_mapper/package/ree_mapper/mapper.rb index 3f4dbe90..5a1fd0a0 100644 --- a/ree_lib/lib/ree_lib/packages/ree_mapper/package/ree_mapper/mapper.rb +++ b/ree_lib/lib/ree_lib/packages/ree_mapper/package/ree_mapper/mapper.rb @@ -23,19 +23,19 @@ def self.build(strategies, type = nil) if type class_eval(<<~RUBY, __FILE__, __LINE__ + 1) - def #{method}(obj, name: nil, role: nil, only: nil, except: nil, fields_filters: []) + def #{method}(obj, name: nil, role: nil, only: nil, except: nil, fields_filters: [], location: nil) #{ if type.is_a?(ReeMapper::AbstractWrapper) - "@type.#{method}(obj, name: name, role: role, fields_filters: fields_filters)" + "@type.#{method}(obj, name: name, role: role, fields_filters: fields_filters, location: location)" else - "@type.#{method}(obj, name: name, role: role)" + "@type.#{method}(obj, name: name, location: location)" end } end RUBY else class_eval(<<~RUBY, __FILE__, __LINE__ + 1) - def #{method}(obj, name: nil, role: nil, only: nil, except: nil, fields_filters: []) + def #{method}(obj, name: nil, role: nil, only: nil, except: nil, fields_filters: [], location: nil) if only && !ReeMapper::FilterFieldsContract.valid?(only) raise ReeMapper::ArgumentError, "Invalid `only` format" end @@ -56,7 +56,7 @@ def #{method}(obj, name: nil, role: nil, only: nil, except: nil, fields_filters: is_optional = field.optional || @#{method}_strategy.always_optional if !is_with_value && !is_optional - raise ReeMapper::TypeError, "Missing required field `\#{field.from_as_str}` for `\#{name || 'root'}`" + raise ReeMapper::TypeError.new("Missing required field `\#{field.from_as_str}` for `\#{name || 'root'}`", field.location) end next if !is_with_value && !field.has_default? @@ -73,7 +73,13 @@ def #{method}(obj, name: nil, role: nil, only: nil, except: nil, fields_filters: nested_fields_filters = field_fields_filters.map { _1.filter_for(field.name) } nested_fields_filters += [field.fields_filter] - value = field.type.#{method}(value, name: nested_name, role: role, fields_filters: nested_fields_filters) + value = field.type.#{method}( + value, + name: nested_name, + role: role, + fields_filters: nested_fields_filters, + location: field.location, + ) end @#{method}_strategy.assign_value(acc, field, value) diff --git a/ree_lib/lib/ree_lib/packages/ree_mapper/package/ree_mapper/mapper_factory.rb b/ree_lib/lib/ree_lib/packages/ree_mapper/package/ree_mapper/mapper_factory.rb index ac26faba..7245eb29 100644 --- a/ree_lib/lib/ree_lib/packages/ree_mapper/package/ree_mapper/mapper_factory.rb +++ b/ree_lib/lib/ree_lib/packages/ree_mapper/package/ree_mapper/mapper_factory.rb @@ -43,7 +43,13 @@ def #{name}(field_name = nil, optional: false, **opts) raise ReeMapper::UnsupportedTypeError, "type :#{name} should implement `\#{@mapper.strategy_methods.join(', ')}`" end - field = ReeMapper::Field.new(type, field_name, optional: optional, **opts) + field = ReeMapper::Field.new( + type, + field_name, + optional: optional, + **opts, + location: caller_locations&.first&.to_s + ) return field unless field_name @@ -90,7 +96,8 @@ def #{name}(field_name = nil, subject = nil, optional: false, dto: nil, **opts, if blk subject = ReeMapper::Field.new( - hash_from_blk(dto: dto, &blk) + hash_from_blk(dto: dto, &blk), + location: caller_locations&.first&.to_s, ) end @@ -105,7 +112,13 @@ def #{name}(field_name = nil, subject = nil, optional: false, dto: nil, **opts, type = ReeMapper::Mapper.build(@mapper.strategies, wrapper.new(subject)) type.name = :#{name} - field = ReeMapper::Field.new(type, field_name, optional: optional, **opts) + field = ReeMapper::Field.new( + type, + field_name, + optional: optional, + **opts, + location: caller_locations&.first&.to_s, + ) return field unless field_name @@ -141,7 +154,7 @@ def hash(field_name, dto: nil, **opts, &blk) type = hash_from_blk(dto: dto, &blk) - field = ReeMapper::Field.new(type, field_name, **opts) + field = ReeMapper::Field.new(type, field_name, **opts, location: caller_locations&.first&.to_s) @mapper.add_field(field) end diff --git a/ree_lib/lib/ree_lib/packages/ree_mapper/package/ree_mapper/types/any.rb b/ree_lib/lib/ree_lib/packages/ree_mapper/package/ree_mapper/types/any.rb index 7389696d..60cd8417 100644 --- a/ree_lib/lib/ree_lib/packages/ree_mapper/package/ree_mapper/types/any.rb +++ b/ree_lib/lib/ree_lib/packages/ree_mapper/package/ree_mapper/types/any.rb @@ -1,23 +1,23 @@ # frozen_string_literal: true class ReeMapper::Any < ReeMapper::AbstractType - contract(Any, Kwargs[name: String, role: Nilor[Symbol, ArrayOf[Symbol]]] => Any) - def serialize(value, name:, role: nil) + contract(Any, Kwargs[name: String, location: Nilor[String]] => Any) + def serialize(value, name:, location: nil) value end - contract(Any , Kwargs[name: String, role: Nilor[Symbol, ArrayOf[Symbol]]] => Any) - def cast(value, name:, role: nil) + contract(Any, Kwargs[name: String, location: Nilor[String]] => Any) + def cast(value, name:, location: nil) value end - contract(Any, Kwargs[name: String, role: Nilor[Symbol, ArrayOf[Symbol]]] => Any) - def db_dump(value, name:, role: nil) + contract(Any, Kwargs[name: String, location: Nilor[String]] => Any) + def db_dump(value, name:, location: nil) value end - contract(Any, Kwargs[name: String, role: Nilor[Symbol, ArrayOf[Symbol]]] => Any) - def db_load(value, name:, role: nil) + contract(Any, Kwargs[name: String, location: Nilor[String]] => Any) + def db_load(value, name:, location: nil) value end end diff --git a/ree_lib/lib/ree_lib/packages/ree_mapper/package/ree_mapper/types/bool.rb b/ree_lib/lib/ree_lib/packages/ree_mapper/package/ree_mapper/types/bool.rb index ac3b5666..b954533e 100644 --- a/ree_lib/lib/ree_lib/packages/ree_mapper/package/ree_mapper/types/bool.rb +++ b/ree_lib/lib/ree_lib/packages/ree_mapper/package/ree_mapper/types/bool.rb @@ -4,33 +4,33 @@ class ReeMapper::Bool < ReeMapper::AbstractType TRUE_CAST_VALUES = ['1', 'true', 'on', 1, true].freeze FALSE_CAST_VALUES = ['0', 'false', 'off', 0, false].freeze - contract(Any, Kwargs[name: String, role: Nilor[Symbol, ArrayOf[Symbol]]] => Bool).throws(ReeMapper::TypeError) - def serialize(value, name:, role: nil) + contract(Any, Kwargs[name: String, location: Nilor[String]] => Bool).throws(ReeMapper::TypeError) + def serialize(value, name:, location: nil) if value.is_a?(TrueClass) || value.is_a?(FalseClass) value else - raise ReeMapper::TypeError, "`#{name}` should be a boolean, got `#{truncate(value.inspect)}`" + raise ReeMapper::TypeError.new("`#{name}` should be a boolean, got `#{truncate(value.inspect)}`", location) end end - contract(Any, Kwargs[name: String, role: Nilor[Symbol, ArrayOf[Symbol]]] => Bool).throws(ReeMapper::CoercionError) - def cast(value, name:, role: nil) + contract(Any, Kwargs[name: String, location: Nilor[String]] => Bool).throws(ReeMapper::CoercionError) + def cast(value, name:, location: nil) if TRUE_CAST_VALUES.include?(value) true elsif FALSE_CAST_VALUES.include?(value) false else - raise ReeMapper::CoercionError, "`#{name}` is invalid boolean, got `#{truncate(value.inspect)}`" + raise ReeMapper::CoercionError.new("`#{name}` is invalid boolean, got `#{truncate(value.inspect)}`", location) end end - contract(Any, Kwargs[name: String, role: Nilor[Symbol, ArrayOf[Symbol]]] => Bool).throws(ReeMapper::TypeError) - def db_dump(value, name:, role: nil) - serialize(value, name: name, role: role) + contract(Any, Kwargs[name: String, location: Nilor[String]] => Bool).throws(ReeMapper::TypeError) + def db_dump(value, name:, location: nil) + serialize(value, name: name, location: location) end - contract(Any, Kwargs[name: String, role: Nilor[Symbol, ArrayOf[Symbol]]] => Bool).throws(ReeMapper::CoercionError) - def db_load(value, name:, role: nil) - cast(value, name: name, role: role) + contract(Any, Kwargs[name: String, location: Nilor[String]] => Bool).throws(ReeMapper::CoercionError) + def db_load(value, name:, location: nil) + cast(value, name: name, location: location) end end diff --git a/ree_lib/lib/ree_lib/packages/ree_mapper/package/ree_mapper/types/date.rb b/ree_lib/lib/ree_lib/packages/ree_mapper/package/ree_mapper/types/date.rb index 7dae4cde..b206a71e 100644 --- a/ree_lib/lib/ree_lib/packages/ree_mapper/package/ree_mapper/types/date.rb +++ b/ree_lib/lib/ree_lib/packages/ree_mapper/package/ree_mapper/types/date.rb @@ -3,17 +3,17 @@ require 'date' class ReeMapper::Date < ReeMapper::AbstractType - contract(Any, Kwargs[name: String, role: Nilor[Symbol, ArrayOf[Symbol]]] => Date).throws(ReeMapper::TypeError) - def serialize(value, name:, role: nil) + contract(Any, Kwargs[name: String, location: Nilor[String]] => Date).throws(ReeMapper::TypeError) + def serialize(value, name:, location: nil) if value.class == Date value else - raise ReeMapper::TypeError, "`#{name}` should be a date, got `#{truncate(value.inspect)}`" + raise ReeMapper::TypeError.new("`#{name}` should be a date, got `#{truncate(value.inspect)}`", location) end end - contract(Any, Kwargs[name: String, role: Nilor[Symbol, ArrayOf[Symbol]]] => Date).throws(ReeMapper::TypeError, ReeMapper::CoercionError) - def cast(value, name:, role: nil) + contract(Any, Kwargs[name: String, location: Nilor[String]] => Date).throws(ReeMapper::TypeError, ReeMapper::CoercionError) + def cast(value, name:, location: nil) if value.class == Date value elsif value.class == DateTime || value.class == Time @@ -22,20 +22,20 @@ def cast(value, name:, role: nil) begin Date.parse(value) rescue ArgumentError => e - raise ReeMapper::CoercionError, "`#{name}` is invalid date, got `#{truncate(value.inspect)}`" + raise ReeMapper::CoercionError.new("`#{name}` is invalid date, got `#{truncate(value.inspect)}`", location) end else - raise ReeMapper::TypeError, "`#{name}` should be a date, got `#{truncate(value.inspect)}`" + raise ReeMapper::TypeError.new("`#{name}` should be a date, got `#{truncate(value.inspect)}`", location) end end - contract(Any, Kwargs[name: String, role: Nilor[Symbol, ArrayOf[Symbol]]] => Date).throws(ReeMapper::TypeError) - def db_dump(value, name:, role: nil) - serialize(value, name: name, role: role) + contract(Any, Kwargs[name: String, location: Nilor[String]] => Date).throws(ReeMapper::TypeError) + def db_dump(value, name:, location: nil) + serialize(value, name: name, location: location) end - contract(Any, Kwargs[name: String, role: Nilor[Symbol, ArrayOf[Symbol]]] => Date).throws(ReeMapper::TypeError, ReeMapper::CoercionError) - def db_load(value, name:, role: nil) - cast(value, name: name, role: role) + contract(Any, Kwargs[name: String, location: Nilor[String]] => Date).throws(ReeMapper::TypeError, ReeMapper::CoercionError) + def db_load(value, name:, location: nil) + cast(value, name: name, location: location) end end diff --git a/ree_lib/lib/ree_lib/packages/ree_mapper/package/ree_mapper/types/date_time.rb b/ree_lib/lib/ree_lib/packages/ree_mapper/package/ree_mapper/types/date_time.rb index 755dc0ed..00138925 100644 --- a/ree_lib/lib/ree_lib/packages/ree_mapper/package/ree_mapper/types/date_time.rb +++ b/ree_lib/lib/ree_lib/packages/ree_mapper/package/ree_mapper/types/date_time.rb @@ -3,17 +3,17 @@ require 'date' class ReeMapper::DateTime < ReeMapper::AbstractType - contract(Any, Kwargs[name: String, role: Nilor[Symbol, ArrayOf[Symbol]]] => DateTime).throws(ReeMapper::TypeError) - def serialize(value, name:, role: nil) + contract(Any, Kwargs[name: String, location: Nilor[String]] => DateTime).throws(ReeMapper::TypeError) + def serialize(value, name:, location: nil) if value.class == DateTime value else - raise ReeMapper::TypeError, "`#{name}` should be a datetime, got `#{truncate(value.inspect)}`" + raise ReeMapper::TypeError.new("`#{name}` should be a datetime, got `#{truncate(value.inspect)}`", location) end end - contract(Any, Kwargs[name: String, role: Nilor[Symbol, ArrayOf[Symbol]]] => DateTime).throws(ReeMapper::CoercionError, ReeMapper::TypeError) - def cast(value, name:, role: nil) + contract(Any, Kwargs[name: String, location: Nilor[String]] => DateTime).throws(ReeMapper::CoercionError, ReeMapper::TypeError) + def cast(value, name:, location: nil) if value.class == DateTime value elsif value.class == Time @@ -22,20 +22,20 @@ def cast(value, name:, role: nil) begin ReeDatetime::InDefaultTimeZone.new.call(DateTime.parse(value)) rescue ArgumentError - raise ReeMapper::CoercionError, "`#{name}` is invalid datetime, got `#{truncate(value.inspect)}`" + raise ReeMapper::CoercionError.new("`#{name}` is invalid datetime, got `#{truncate(value.inspect)}`", location) end else - raise ReeMapper::TypeError, "`#{name}` should be a datetime, got `#{truncate(value.inspect)}`" + raise ReeMapper::TypeError.new("`#{name}` should be a datetime, got `#{truncate(value.inspect)}`", location) end end - contract(Any, Kwargs[name: String, role: Nilor[Symbol, ArrayOf[Symbol]]] => DateTime).throws(ReeMapper::TypeError) - def db_dump(value, name:, role: nil) - serialize(value, name: name, role: role) + contract(Any, Kwargs[name: String, location: Nilor[String]] => DateTime).throws(ReeMapper::TypeError) + def db_dump(value, name:, location: nil) + serialize(value, name: name, location: location) end - contract(Any, Kwargs[name: String, role: Nilor[Symbol, ArrayOf[Symbol]]] => DateTime).throws(ReeMapper::CoercionError, ReeMapper::TypeError) - def db_load(value, name:, role: nil) - cast(value, name: name, role: role) + contract(Any, Kwargs[name: String, location: Nilor[String]] => DateTime).throws(ReeMapper::CoercionError, ReeMapper::TypeError) + def db_load(value, name:, location: nil) + cast(value, name: name, location: location) end end diff --git a/ree_lib/lib/ree_lib/packages/ree_mapper/package/ree_mapper/types/float.rb b/ree_lib/lib/ree_lib/packages/ree_mapper/package/ree_mapper/types/float.rb index ef12fd86..95ee5bf3 100644 --- a/ree_lib/lib/ree_lib/packages/ree_mapper/package/ree_mapper/types/float.rb +++ b/ree_lib/lib/ree_lib/packages/ree_mapper/package/ree_mapper/types/float.rb @@ -1,39 +1,39 @@ # frozen_string_literal: true class ReeMapper::Float < ReeMapper::AbstractType - contract(Any, Kwargs[name: String, role: Nilor[Symbol, ArrayOf[Symbol]]] => Float).throws(ReeMapper::TypeError) - def serialize(value, name:, role: nil) + contract(Any, Kwargs[name: String, location: Nilor[String]] => Float).throws(ReeMapper::TypeError) + def serialize(value, name:, location: nil) if value.is_a?(Float) value else - raise ReeMapper::TypeError, "`#{name}` should be a float, got `#{truncate(value.inspect)}`" + raise ReeMapper::TypeError.new("`#{name}` should be a float, got `#{truncate(value.inspect)}`", location) end end - contract(Any, Kwargs[name: String, role: Nilor[Symbol, ArrayOf[Symbol]]] => Float).throws(ReeMapper::CoercionError, ReeMapper::TypeError) - def cast(value, name:, role: nil) + contract(Any, Kwargs[name: String, location: Nilor[String]] => Float).throws(ReeMapper::CoercionError, ReeMapper::TypeError) + def cast(value, name:, location: nil) if value.is_a?(Numeric) value.to_f elsif value.is_a?(String) begin Float(value) rescue ArgumentError => e - raise ReeMapper::CoercionError, "`#{name}` is invalid float, got `#{truncate(value.inspect)}`" + raise ReeMapper::CoercionError.new("`#{name}` is invalid float, got `#{truncate(value.inspect)}`", location) end elsif defined?(BigDecimal) && value.is_a?(BigDecimal) value.to_f else - raise ReeMapper::TypeError, "`#{name}` should be a float, got `#{truncate(value.inspect)}`" + raise ReeMapper::TypeError.new("`#{name}` should be a float, got `#{truncate(value.inspect)}`", location) end end - contract(Any, Kwargs[name: String, role: Nilor[Symbol, ArrayOf[Symbol]]] => Float).throws(ReeMapper::CoercionError) - def db_dump(value, name:, role: nil) - serialize(value, name: name, role: role) + contract(Any, Kwargs[name: String, location: Nilor[String]] => Float).throws(ReeMapper::CoercionError) + def db_dump(value, name:, location: nil) + serialize(value, name: name, location: location) end - contract(Any, Kwargs[name: String, role: Nilor[Symbol, ArrayOf[Symbol]]] => Float).throws(ReeMapper::CoercionError, ReeMapper::TypeError) - def db_load(value, name:, role: nil) - cast(value, name: name, role: role) + contract(Any, Kwargs[name: String, location: Nilor[String]] => Float).throws(ReeMapper::CoercionError, ReeMapper::TypeError) + def db_load(value, name:, location: nil) + cast(value, name: name, location: location) end end diff --git a/ree_lib/lib/ree_lib/packages/ree_mapper/package/ree_mapper/types/integer.rb b/ree_lib/lib/ree_lib/packages/ree_mapper/package/ree_mapper/types/integer.rb index f192cb9b..5b283115 100644 --- a/ree_lib/lib/ree_lib/packages/ree_mapper/package/ree_mapper/types/integer.rb +++ b/ree_lib/lib/ree_lib/packages/ree_mapper/package/ree_mapper/types/integer.rb @@ -1,37 +1,37 @@ # frozen_string_literal: true class ReeMapper::Integer < ReeMapper::AbstractType - contract(Any, Kwargs[name: String, role: Nilor[Symbol, ArrayOf[Symbol]]] => Integer).throws(ReeMapper::TypeError) - def serialize(value, name:, role: nil) + contract(Any, Kwargs[name: String, location: Nilor[String]] => Integer).throws(ReeMapper::TypeError) + def serialize(value, name:, location: nil) if value.is_a? Integer value else - raise ReeMapper::TypeError, "`#{name}` should be an integer, got `#{truncate(value.inspect)}`" + raise ReeMapper::TypeError.new("`#{name}` should be an integer, got `#{truncate(value.inspect)}`", location) end end - contract(Any , Kwargs[name: String, role: Nilor[Symbol, ArrayOf[Symbol]]]=> Integer).throws(ReeMapper::CoercionError, ReeMapper::TypeError) - def cast(value, name:, role: nil) + contract(Any, Kwargs[name: String, location: Nilor[String]] => Integer).throws(ReeMapper::CoercionError, ReeMapper::TypeError) + def cast(value, name:, location: nil) if value.is_a?(Integer) value elsif value.is_a?(String) coerced_value = Integer(value, exception: false) if coerced_value.nil? - raise ReeMapper::CoercionError, "`#{name}` is invalid integer, got `#{truncate(value.inspect)}`" + raise ReeMapper::CoercionError.new("`#{name}` is invalid integer, got `#{truncate(value.inspect)}`", location) end coerced_value else - raise ReeMapper::TypeError, "`#{name}` should be an integer, got `#{truncate(value.inspect)}`" + raise ReeMapper::TypeError.new("`#{name}` should be an integer, got `#{truncate(value.inspect)}`", location) end end - contract(Any, Kwargs[name: String, role: Nilor[Symbol, ArrayOf[Symbol]]] => Integer).throws(ReeMapper::TypeError) - def db_dump(value, name:, role: nil) - serialize(value, name: name, role: role) + contract(Any, Kwargs[name: String, location: Nilor[String]] => Integer).throws(ReeMapper::TypeError) + def db_dump(value, name:, location: nil) + serialize(value, name: name, location: location) end - contract(Any, Kwargs[name: String, role: Nilor[Symbol, ArrayOf[Symbol]]] => Integer).throws(ReeMapper::CoercionError, ReeMapper::TypeError) - def db_load(value, name:, role: nil) - cast(value, name: name, role: role) + contract(Any, Kwargs[name: String, location: Nilor[String]] => Integer).throws(ReeMapper::CoercionError, ReeMapper::TypeError) + def db_load(value, name:, location: nil) + cast(value, name: name, location: location) end end diff --git a/ree_lib/lib/ree_lib/packages/ree_mapper/package/ree_mapper/types/rational.rb b/ree_lib/lib/ree_lib/packages/ree_mapper/package/ree_mapper/types/rational.rb index 48843ed9..bc748bb8 100644 --- a/ree_lib/lib/ree_lib/packages/ree_mapper/package/ree_mapper/types/rational.rb +++ b/ree_lib/lib/ree_lib/packages/ree_mapper/package/ree_mapper/types/rational.rb @@ -1,39 +1,39 @@ # frozen_string_literal: true class ReeMapper::Rational < ReeMapper::AbstractType - contract(Any, Kwargs[name: String, role: Nilor[Symbol, ArrayOf[Symbol]]] => Rational).throws(ReeMapper::TypeError) - def serialize(value, name:, role: nil) + contract(Any, Kwargs[name: String, location: Nilor[String]] => Rational).throws(ReeMapper::TypeError) + def serialize(value, name:, location: nil) if value.is_a?(Rational) value else - raise ReeMapper::TypeError, "`#{name}` should be a rational, got `#{truncate(value.inspect)}`" + raise ReeMapper::TypeError.new("`#{name}` should be a rational, got `#{truncate(value.inspect)}`", location) end end - contract(Any, Kwargs[name: String, role: Nilor[Symbol, ArrayOf[Symbol]]] => Rational).throws(ReeMapper::CoercionError, ReeMapper::TypeError) - def cast(value, name:, role: nil) + contract(Any, Kwargs[name: String, location: Nilor[String]] => Rational).throws(ReeMapper::CoercionError, ReeMapper::TypeError) + def cast(value, name:, location: nil) if value.is_a?(Rational) value elsif value.is_a?(String) begin Rational(value) rescue ArgumentError, ZeroDivisionError => e - raise ReeMapper::CoercionError, "`#{name}` is invalid rational, got `#{truncate(value.inspect)}`" + raise ReeMapper::CoercionError.new("`#{name}` is invalid rational, got `#{truncate(value.inspect)}`", location) end elsif value.is_a?(Numeric) Rational(value) else - raise ReeMapper::TypeError, "`#{name}` should be a rational, got `#{truncate(value.inspect)}`" + raise ReeMapper::TypeError.new("`#{name}` should be a rational, got `#{truncate(value.inspect)}`", location) end end - contract(Any, Kwargs[name: String, role: Nilor[Symbol, ArrayOf[Symbol]]] => String) - def db_dump(value, name:, role: nil) - serialize(value, name: name, role: role).to_s + contract(Any, Kwargs[name: String, location: Nilor[String]] => String) + def db_dump(value, name:, location: nil) + serialize(value, name: name, location: location).to_s end - contract(Any, Kwargs[name: String, role: Nilor[Symbol, ArrayOf[Symbol]]] => Rational) - def db_load(value, name:, role: nil) - cast(value, name: name, role: role) + contract(Any, Kwargs[name: String, location: Nilor[String]] => Rational) + def db_load(value, name:, location: nil) + cast(value, name: name, location: location) end end diff --git a/ree_lib/lib/ree_lib/packages/ree_mapper/package/ree_mapper/types/string.rb b/ree_lib/lib/ree_lib/packages/ree_mapper/package/ree_mapper/types/string.rb index 19c9fc93..e93d22c2 100644 --- a/ree_lib/lib/ree_lib/packages/ree_mapper/package/ree_mapper/types/string.rb +++ b/ree_lib/lib/ree_lib/packages/ree_mapper/package/ree_mapper/types/string.rb @@ -1,27 +1,27 @@ # frozen_string_literal: true class ReeMapper::String < ReeMapper::AbstractType - contract(Any, Kwargs[name: String, role: Nilor[Symbol, ArrayOf[Symbol]]] => String).throws(ReeMapper::TypeError) - def serialize(value, name:, role: nil) + contract(Any, Kwargs[name: String, location: Nilor[String]] => String).throws(ReeMapper::TypeError) + def serialize(value, name:, location: nil) if value.is_a? String value else - raise ReeMapper::TypeError, "`#{name}` should be a string, got `#{truncate(value.inspect)}`" + raise ReeMapper::TypeError.new("`#{name}` should be a string, got `#{truncate(value.inspect)}`", location) end end - contract(Any, Kwargs[name: String, role: Nilor[Symbol, ArrayOf[Symbol]]] => String).throws(ReeMapper::TypeError) - def cast(value, name:, role: nil) - serialize(value, name: name, role: role) + contract(Any, Kwargs[name: String, location: Nilor[String]] => String).throws(ReeMapper::TypeError) + def cast(value, name:, location: nil) + serialize(value, name: name, location: location) end - contract(Any, Kwargs[name: String, role: Nilor[Symbol, ArrayOf[Symbol]]] => String).throws(ReeMapper::TypeError) - def db_dump(value, name:, role: nil) - serialize(value, name: name, role: role) + contract(Any, Kwargs[name: String, location: Nilor[String]] => String).throws(ReeMapper::TypeError) + def db_dump(value, name:, location: nil) + serialize(value, name: name, location: location) end - contract(Any, Kwargs[name: String, role: Nilor[Symbol, ArrayOf[Symbol]]] => String).throws(ReeMapper::TypeError) - def db_load(value, name:, role: nil) - serialize(value, name: name, role: role) + contract(Any, Kwargs[name: String, location: Nilor[String]] => String).throws(ReeMapper::TypeError) + def db_load(value, name:, location: nil) + serialize(value, name: name, location: location) end end diff --git a/ree_lib/lib/ree_lib/packages/ree_mapper/package/ree_mapper/types/time.rb b/ree_lib/lib/ree_lib/packages/ree_mapper/package/ree_mapper/types/time.rb index bb2e9028..aa79f157 100644 --- a/ree_lib/lib/ree_lib/packages/ree_mapper/package/ree_mapper/types/time.rb +++ b/ree_lib/lib/ree_lib/packages/ree_mapper/package/ree_mapper/types/time.rb @@ -3,17 +3,17 @@ require 'time' class ReeMapper::Time < ReeMapper::AbstractType - contract(Any, Kwargs[name: String, role: Nilor[Symbol, ArrayOf[Symbol]]] => Time).throws(ReeMapper::TypeError) - def serialize(value, name:, role: nil) + contract(Any, Kwargs[name: String, location: Nilor[String]] => Time).throws(ReeMapper::TypeError) + def serialize(value, name:, location: nil) if value.class == Time value else - raise ReeMapper::TypeError, "`#{name}` should be a time, got `#{truncate(value.inspect)}`" + raise ReeMapper::TypeError.new("`#{name}` should be a time, got `#{truncate(value.inspect)}`", location) end end - contract(Any, Kwargs[name: String, role: Nilor[Symbol, ArrayOf[Symbol]]] => Time).throws(ReeMapper::CoercionError, ReeMapper::TypeError) - def cast(value, name:, role: nil) + contract(Any, Kwargs[name: String, location: Nilor[String]] => Time).throws(ReeMapper::CoercionError, ReeMapper::TypeError) + def cast(value, name:, location: nil) if value.class == Time value elsif value.class == DateTime @@ -22,20 +22,20 @@ def cast(value, name:, role: nil) begin Time.parse(value) rescue ArgumentError - raise ReeMapper::CoercionError, "`#{name}` is invalid time, got `#{truncate(value.inspect)}`" + raise ReeMapper::CoercionError.new("`#{name}` is invalid time, got `#{truncate(value.inspect)}`", location) end else - raise ReeMapper::TypeError, "`#{name}` should be a time, got `#{truncate(value.inspect)}`" + raise ReeMapper::TypeError.new("`#{name}` should be a time, got `#{truncate(value.inspect)}`", location) end end - contract(Any, Kwargs[name: String, role: Nilor[Symbol, ArrayOf[Symbol]]] => Time).throws(ReeMapper::TypeError) - def db_dump(value, name:, role: nil) - serialize(value, name: name, role: role) + contract(Any, Kwargs[name: String, location: Nilor[String]] => Time).throws(ReeMapper::TypeError) + def db_dump(value, name:, location: nil) + serialize(value, name: name, location: location) end - contract(Any, Kwargs[name: String, role: Nilor[Symbol, ArrayOf[Symbol]]] => Time).throws(ReeMapper::CoercionError, ReeMapper::TypeError) - def db_load(value, name:, role: nil) - cast(value, name: name, role: role) + contract(Any, Kwargs[name: String, location: Nilor[String]] => Time).throws(ReeMapper::CoercionError, ReeMapper::TypeError) + def db_load(value, name:, location: nil) + cast(value, name: name, location: location) end end diff --git a/ree_lib/lib/ree_lib/packages/ree_mapper/package/ree_mapper/wrappers/array.rb b/ree_lib/lib/ree_lib/packages/ree_mapper/package/ree_mapper/wrappers/array.rb index f8c7bb72..9a34c5f7 100644 --- a/ree_lib/lib/ree_lib/packages/ree_mapper/package/ree_mapper/wrappers/array.rb +++ b/ree_lib/lib/ree_lib/packages/ree_mapper/package/ree_mapper/wrappers/array.rb @@ -1,67 +1,119 @@ # frozen_string_literal: true class ReeMapper::Array < ReeMapper::AbstractWrapper - contract(Any, Kwargs[name: String, role: Nilor[Symbol, ArrayOf[Symbol]], fields_filters: ArrayOf[ReeMapper::FieldsFilter]] => Array) - .throws(ReeMapper::TypeError) - def serialize(value, name:, role: nil, fields_filters: []) + contract( + Any, + Kwargs[ + name: String, + role: Nilor[Symbol, ArrayOf[Symbol]], + fields_filters: ArrayOf[ReeMapper::FieldsFilter], + location: Nilor[String], + ] => Array + ).throws(ReeMapper::TypeError) + def serialize(value, name:, role: nil, fields_filters: [], location: nil) if value.is_a?(Array) value.map.with_index { if _1.nil? && subject.null _1 else - subject.type.serialize(_1, name: "#{name}[#{_2}]", role: role, fields_filters: fields_filters + [subject.fields_filter]) + subject.type.serialize( + _1, + name: "#{name}[#{_2}]", + role: role, + fields_filters: fields_filters + [subject.fields_filter], + location: subject.location, + ) end } else - raise ReeMapper::TypeError, "`#{name}` should be an array, got `#{truncate(value.inspect)}`" + raise ReeMapper::TypeError.new("`#{name}` should be an array, got `#{truncate(value.inspect)}`", location) end end - contract(Any, Kwargs[name: String, role: Nilor[Symbol, ArrayOf[Symbol]], fields_filters: ArrayOf[ReeMapper::FieldsFilter]] => Array) - .throws(ReeMapper::TypeError) - def cast(value, name:, role: nil, fields_filters: []) + contract( + Any, + Kwargs[ + name: String, + role: Nilor[Symbol, ArrayOf[Symbol]], + fields_filters: ArrayOf[ReeMapper::FieldsFilter], + location: Nilor[String], + ] => Array + ).throws(ReeMapper::TypeError) + def cast(value, name:, role: nil, fields_filters: [], location: nil) if value.is_a?(Array) value.map.with_index { if _1.nil? && subject.null _1 else - subject.type.cast(_1, name: "#{name}[#{_2}]", role: role, fields_filters: fields_filters + [subject.fields_filter]) + subject.type.cast( + _1, + name: "#{name}[#{_2}]", + role: role, + fields_filters: fields_filters + [subject.fields_filter], + location: subject.location, + ) end } else - raise ReeMapper::TypeError, "`#{name}` should be an array, got `#{truncate(value.inspect)}`" + raise ReeMapper::TypeError.new("`#{name}` should be an array, got `#{truncate(value.inspect)}`", location) end end - contract(Any, Kwargs[name: String, role: Nilor[Symbol, ArrayOf[Symbol]], fields_filters: ArrayOf[ReeMapper::FieldsFilter]] => Array) - .throws(ReeMapper::TypeError) - def db_dump(value, name:, role: nil, fields_filters: []) + contract( + Any, + Kwargs[ + name: String, + role: Nilor[Symbol, ArrayOf[Symbol]], + fields_filters: ArrayOf[ReeMapper::FieldsFilter], + location: Nilor[String], + ] => Array + ).throws(ReeMapper::TypeError) + def db_dump(value, name:, role: nil, fields_filters: [], location: nil) if value.is_a?(Array) value.map.with_index { if _1.nil? && subject.null _1 else - subject.type.db_dump(_1, name: "#{name}[#{_2}]", role: role, fields_filters: fields_filters + [subject.fields_filter]) + subject.type.db_dump( + _1, + name: "#{name}[#{_2}]", + role: role, + fields_filters: fields_filters + [subject.fields_filter], + location: subject.location, + ) end } else - raise ReeMapper::TypeError, "`#{name}` should be an array, got `#{truncate(value.inspect)}`" + raise ReeMapper::TypeError.new("`#{name}` should be an array, got `#{truncate(value.inspect)}`", location) end end - contract(Any, Kwargs[name: String, role: Nilor[Symbol, ArrayOf[Symbol]], fields_filters: ArrayOf[ReeMapper::FieldsFilter]] => Array) - .throws(ReeMapper::TypeError) - def db_load(value, name:, role: nil, fields_filters: []) + contract( + Any, + Kwargs[ + name: String, + role: Nilor[Symbol, ArrayOf[Symbol]], + fields_filters: ArrayOf[ReeMapper::FieldsFilter], + location: Nilor[String], + ] => Array + ).throws(ReeMapper::TypeError) + def db_load(value, name:, role: nil, fields_filters: [], location: nil) if value.is_a?(Array) value.map.with_index { if _1.nil? && subject.null _1 else - subject.type.db_load(_1, name: "#{name}[#{_2}]", role: role, fields_filters: fields_filters + [subject.fields_filter]) + subject.type.db_load( + _1, + name: "#{name}[#{_2}]", + role: role, + fields_filters: fields_filters + [subject.fields_filter], + location: subject.location, + ) end } else - raise ReeMapper::TypeError, "`#{name}` should be an array, got `#{truncate(value.inspect)}`" + raise ReeMapper::TypeError.new("`#{name}` should be an array, got `#{truncate(value.inspect)}`", location) end end -end \ No newline at end of file +end diff --git a/ree_lib/lib/ree_lib/packages/ree_mapper/spec/ree_mapper/mapper_factory_spec.rb b/ree_lib/lib/ree_lib/packages/ree_mapper/spec/ree_mapper/mapper_factory_spec.rb index b80db3d8..22e50d14 100644 --- a/ree_lib/lib/ree_lib/packages/ree_mapper/spec/ree_mapper/mapper_factory_spec.rb +++ b/ree_lib/lib/ree_lib/packages/ree_mapper/spec/ree_mapper/mapper_factory_spec.rb @@ -82,10 +82,12 @@ def serialize(*); end describe '.register_wrapper' do let(:round_wrapper) { Class.new(ReeMapper::AbstractWrapper) do - def serialize(value, name:, **opts) - raise ReeMapper::TypeError, "`#{name}` should be a number, got `#{truncate(value.inspect)}`" if !value.is_a?(Numeric) + def serialize(value, name:, location: nil, **opts) + if !value.is_a?(Numeric) + raise ReeMapper::TypeError.new("`#{name}` should be a number, got `#{truncate(value.inspect)}`", location) + end - subject.type.serialize(value.round, name: name, **opts) + subject.type.serialize(value.round, name: name, location: subject.location, **opts) end end } @@ -100,10 +102,12 @@ def serialize(value, name:, **opts) it 'allow to register caster and serializer with the same name' do caster_round_wrapper = Class.new(ReeMapper::AbstractWrapper) do - def cast(value, name:, **opts) - value = subject.type.cast(value, name: name, **opts) + def cast(value, name:, location: nil, **opts) + value = subject.type.cast(value, name: name, location: subject.location, **opts) - raise ReeMapper::TypeError, "`#{name}` should be a number, got `#{truncate(value.inspect)}`" if !value.is_a?(Numeric) + if !value.is_a?(Numeric) + raise ReeMapper::TypeError.new("`#{name}` should be a number, got `#{truncate(value.inspect)}`", location) + end value.round end end diff --git a/ree_lib/lib/ree_lib/packages/ree_mapper/spec/ree_mapper/mapper_spec.rb b/ree_lib/lib/ree_lib/packages/ree_mapper/spec/ree_mapper/mapper_spec.rb index f17eec08..fbc983fe 100644 --- a/ree_lib/lib/ree_lib/packages/ree_mapper/spec/ree_mapper/mapper_spec.rb +++ b/ree_lib/lib/ree_lib/packages/ree_mapper/spec/ree_mapper/mapper_spec.rb @@ -1,4 +1,5 @@ # frozen_string_literal: true +package_require "ree_mapper" RSpec.describe ReeMapper::Mapper do link :build_mapper_factory, from: :ree_mapper @@ -32,6 +33,12 @@ obj.define_singleton_method(:my_field) { 1 } expect(mapper.cast(obj)).to eq({ my_field: 1 }) } + + it "add mapper location to error full message" do + mapper.cast({ my_field: "not number" }) + rescue => e + expect(e.full_message).to include("located at") + end end describe 'hash dto' do From 33efa9bf0419ff619f905b97cae9147f15b7f330 Mon Sep 17 00:00:00 2001 From: Alexey Belousov Date: Thu, 18 Jan 2024 22:15:48 +1000 Subject: [PATCH 2/4] Format message. Add mapper location for rspec. --- .../package/ree_mapper/errors/error_with_location.rb | 9 +++++++-- ree_lib/lib/ree_lib/spec.init.rb | 4 +++- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/ree_lib/lib/ree_lib/packages/ree_mapper/package/ree_mapper/errors/error_with_location.rb b/ree_lib/lib/ree_lib/packages/ree_mapper/package/ree_mapper/errors/error_with_location.rb index 9bb6a4c9..a6a1bd8d 100644 --- a/ree_lib/lib/ree_lib/packages/ree_mapper/package/ree_mapper/errors/error_with_location.rb +++ b/ree_lib/lib/ree_lib/packages/ree_mapper/package/ree_mapper/errors/error_with_location.rb @@ -4,6 +4,10 @@ class ReeMapper::ErrorWithLocation < ReeMapper::Error attr_reader :location def initialize(message, location = nil) + if message.is_a?(String) && location && ENV["RUBY_ENV"] == "test" + message = "#{message}, located at #{location}" + end + super(message) @location = location end @@ -14,7 +18,8 @@ def full_message(...) idx = msg.index(/\).*\n/) return msg if idx.nil? - - msg.insert(idx + 1, "\nlocated at #{location}") + return msg if ENV["RUBY_ENV"] == "test" + + msg.insert(idx + 1, ", located at #{location}") end end diff --git a/ree_lib/lib/ree_lib/spec.init.rb b/ree_lib/lib/ree_lib/spec.init.rb index 3c93b2b4..57ab1ed8 100644 --- a/ree_lib/lib/ree_lib/spec.init.rb +++ b/ree_lib/lib/ree_lib/spec.init.rb @@ -11,4 +11,6 @@ require 'rspec' require 'ree' -Ree.init(__dir__) \ No newline at end of file +ENV["RUBY_ENV"] = "test" + +Ree.init(__dir__) From ed1984499c2e3594b18f2ad70c75cde0e07dd713 Mon Sep 17 00:00:00 2001 From: Alexey Belousov Date: Thu, 18 Jan 2024 22:21:36 +1000 Subject: [PATCH 3/4] Fix spec --- .../ree_dao/spec/ree_dao/wrappers/pg_array_spec.rb | 6 +++--- .../ree_dao/spec/ree_dao/wrappers/pg_jsonb_spec.rb | 8 ++++---- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/ree_lib/lib/ree_lib/packages/ree_dao/spec/ree_dao/wrappers/pg_array_spec.rb b/ree_lib/lib/ree_lib/packages/ree_dao/spec/ree_dao/wrappers/pg_array_spec.rb index 45181ff0..5ac02471 100644 --- a/ree_lib/lib/ree_lib/packages/ree_dao/spec/ree_dao/wrappers/pg_array_spec.rb +++ b/ree_lib/lib/ree_lib/packages/ree_dao/spec/ree_dao/wrappers/pg_array_spec.rb @@ -46,7 +46,7 @@ it { expect { mapper.db_dump({ tags: 1 }) - }.to raise_error(ReeMapper::TypeError, "`tags` should be an array, got `1`") + }.to raise_error(ReeMapper::TypeError, /`tags` should be an array, got `1`/) } end @@ -66,7 +66,7 @@ mapper.db_load({ tags: 1 }) - }.to raise_error(ReeMapper::TypeError, "`tags` should be a Sequel::Postgres::PGArray, got `1`") + }.to raise_error(ReeMapper::TypeError, /`tags` should be a Sequel::Postgres::PGArray, got `1`/) } end -end \ No newline at end of file +end diff --git a/ree_lib/lib/ree_lib/packages/ree_dao/spec/ree_dao/wrappers/pg_jsonb_spec.rb b/ree_lib/lib/ree_lib/packages/ree_dao/spec/ree_dao/wrappers/pg_jsonb_spec.rb index 4aaa1364..1439ebff 100644 --- a/ree_lib/lib/ree_lib/packages/ree_dao/spec/ree_dao/wrappers/pg_jsonb_spec.rb +++ b/ree_lib/lib/ree_lib/packages/ree_dao/spec/ree_dao/wrappers/pg_jsonb_spec.rb @@ -55,14 +55,14 @@ it { expect { mapper.db_dump({ numbers: ['1'] }) - }.to raise_error(ReeMapper::TypeError, "`numbers[0]` should be an integer, got `\"1\"`") + }.to raise_error(ReeMapper::TypeError, /`numbers\[0\]` should be an integer, got `\"1\"`/) } it { object = Object.new expect { mapper.db_dump({ any: object }) - }.to raise_error(ReeMapper::TypeError, "`any` should be an jsonb primitive, got `#{object.inspect}`") + }.to raise_error(ReeMapper::TypeError, /`any` should be an jsonb primitive, got `#{object.inspect}`/) } end @@ -88,14 +88,14 @@ it { expect { mapper.db_load({ numbers: Sequel::Postgres::JSONBArray.new([1.1]) }) - }.to raise_error(ReeMapper::TypeError, "`numbers[0]` should be an integer, got `1.1`") + }.to raise_error(ReeMapper::TypeError, /`numbers\[0\]` should be an integer, got `1.1`/) } it { object = Object.new expect { mapper.db_load({ numbers: object }) - }.to raise_error(ReeMapper::TypeError, "`numbers` should be a Sequel::Postgres::JSONB, got `#{object.inspect}`") + }.to raise_error(ReeMapper::TypeError, /`numbers` should be a Sequel::Postgres::JSONB, got `#{object.inspect}`/) } end end From f1d583f003eb62461dc15a9008ed80541dbeb427 Mon Sep 17 00:00:00 2001 From: Alexey Belousov Date: Thu, 18 Jan 2024 22:48:23 +1000 Subject: [PATCH 4/4] Fix spec --- .../lib/ree_lib/packages/ree_enum/spec/ree_enum/dsl_spec.rb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/ree_lib/lib/ree_lib/packages/ree_enum/spec/ree_enum/dsl_spec.rb b/ree_lib/lib/ree_lib/packages/ree_enum/spec/ree_enum/dsl_spec.rb index 4e85873d..1da9dca5 100644 --- a/ree_lib/lib/ree_lib/packages/ree_enum/spec/ree_enum/dsl_spec.rb +++ b/ree_lib/lib/ree_lib/packages/ree_enum/spec/ree_enum/dsl_spec.rb @@ -152,7 +152,7 @@ def call(value) type: 'invalid', number: 0, }) - }.to raise_error(ReeMapper::CoercionError, '`type` should be one of ["account"], got `"invalid"`') + }.to raise_error(ReeMapper::CoercionError, /`type` should be one of \["account"\], got `"invalid"`/) expect { mapper.db_load({ @@ -160,7 +160,7 @@ def call(value) type: 'invalid', number: 0, }) - }.to raise_error(ReeMapper::CoercionError, '`type` should be one of ["account"], got `"invalid"`') + }.to raise_error(ReeMapper::CoercionError, /`type` should be one of \["account"\], got `"invalid"`/) expect( mapper.cast({ @@ -242,4 +242,4 @@ def call(value) swagger_definition_fetcher.call(TestReeEnum::ContentTypes.type_for_mapper, -> {}) ) } -end \ No newline at end of file +end