diff --git a/lib/values.rb b/lib/values.rb index c762350..e59736c 100644 --- a/lib/values.rb +++ b/lib/values.rb @@ -10,6 +10,17 @@ # p.y # #=> 0 # +module Values + class FieldError < ::ArgumentError + attr_reader :missing_fields, :unexpected_fields + + def initialize(message, missing_fields=[], unexpected_fields=[]) + @missing_fields, @unexpected_fields = missing_fields, unexpected_fields + super(message) + end + end +end + class Value # Create a new value class. # @@ -24,7 +35,9 @@ def self.new(*fields, &block) attr_reader(:hash, *fields) define_method(:initialize) do |*values| - raise ArgumentError.new("wrong number of arguments, #{values.size} for #{fields.size}") if fields.size != values.size + if (fields.size != values.size) + raise Values::FieldError.new("wrong number of arguments, #{values.size} for #{fields.size}", fields[values.size..-1]) + end fields.zip(values) do |field, value| instance_variable_set(:"@#{field}", value) @@ -38,14 +51,13 @@ def self.new(*fields, &block) const_set :VALUE_ATTRS, fields def self.with(hash) - unexpected_keys = hash.keys - self::VALUE_ATTRS - if unexpected_keys.any? - raise ArgumentError.new("Unexpected hash keys: #{unexpected_keys}") - end + unexpected_fields = hash.keys - self::VALUE_ATTRS + missing_fields = self::VALUE_ATTRS - hash.keys - missing_keys = self::VALUE_ATTRS - hash.keys - if missing_keys.any? - raise ArgumentError.new("Missing hash keys: #{missing_keys} (got keys #{hash.keys})") + if unexpected_fields.any? + raise Values::FieldError.new("Unexpected hash keys: #{unexpected_fields}", missing_fields, unexpected_fields) + elsif missing_fields.any? + raise Values::FieldError.new("Missing hash keys: #{missing_fields} (got keys #{hash.keys})", missing_fields, unexpected_fields) end new(*hash.values_at(*self::VALUE_ATTRS)) diff --git a/spec/values_spec.rb b/spec/values_spec.rb index e62ce56..1a95dc1 100644 --- a/spec/values_spec.rb +++ b/spec/values_spec.rb @@ -42,7 +42,12 @@ end it 'raises argument errors if not given the right number of arguments' do - expect { Point.new }.to raise_error(ArgumentError, 'wrong number of arguments, 0 for 2') + expect { Point.new }.to raise_error do |error| + expect(error).to be_a(Values::FieldError) + expect(error.message).to eq('wrong number of arguments, 0 for 2') + expect(error.missing_fields).to contain_exactly(:x, :y) + expect(error.unexpected_fields).to be_empty + end end end @@ -100,11 +105,19 @@ def change_color(new_color) end it 'errors if you instantiate it from a hash with unrecognised fields' do - expect { Money.with(:unrecognized_field => 1, :amount => 2, :denomination => 'USD') }.to raise_error(ArgumentError) + expect { Money.with(:unrecognized_field => 1, :amount => 2, :denomination => 'USD') }.to raise_error do |error| + expect(error).to be_a(Values::FieldError) + expect(error.missing_fields).to be_empty + expect(error.unexpected_fields).to contain_exactly(:unrecognized_field) + end end it 'errors if you instantiate it from a hash with missing fields' do - expect { Money.with({}) }.to raise_error(ArgumentError) + expect { Money.with({}) }.to raise_error do |error| + expect(error).to be_a(Values::FieldError) + expect(error.missing_fields).to contain_exactly(:amount, :denomination) + expect(error.unexpected_fields).to be_empty + end end it 'does not error when fields are explicitly nil' do @@ -200,7 +213,11 @@ def change_color(new_color) end it 'raises argument error if unknown field' do - expect { p.with({ :foo => 3 }) }.to raise_error(ArgumentError) + expect { p.with({ :foo => 3 , :bar => "baz" }) }.to raise_error do |error| + expect(error).to be_a(Values::FieldError) + expect(error.unexpected_fields).to contain_exactly(:foo, :bar) + expect(error.missing_fields).to be_empty + end end end end