Commit be537d2
Changed files (5)
lib
scim
kit
spec
scim
lib/scim/kit/v2/attribute.rb
@@ -21,8 +21,12 @@ module Scim
define_attributes_for(type.attributes)
end
+ def _assign(new_value, coerce: true)
+ @_value = coerce ? type.coerce(new_value) : new_value
+ end
+
def _value=(new_value)
- @_value = type.coerce(new_value)
+ _assign(new_value, coerce: true)
end
private
lib/scim/kit/v2/attribute_type.rb
@@ -63,13 +63,17 @@ module Scim
end
def coerce(value)
- if type_is?(:boolean) && !BOOLEAN_VALUES.include?(value)
- raise ArgumentError, value
- end
- return value if multi_valued
+ return value if complex?
+
+ if multi_valued
+ return value unless value.respond_to?(:to_a)
- coercion = COERCION[type]
- coercion ? coercion.call(value) : value
+ value.to_a.map do |x|
+ COERCION.fetch(type, ->(y) { y }).call(x)
+ end
+ else
+ COERCION.fetch(type, ->(x) { x }).call(value)
+ end
end
def valid?(value)
lib/scim/kit/v2.rb
@@ -36,11 +36,17 @@ module Scim
complex: 'complex'
}.freeze
COERCION = {
- string: ->(x) { x.to_s },
+ binary: ->(x) { Base64.strict_encode64(x) },
+ boolean: lambda { |x|
+ return true if x == 'true'
+ return false if x == 'false'
+
+ x
+ },
+ datetime: ->(x) { x.is_a?(::String) ? DateTime.parse(x) : x },
decimal: ->(x) { x.to_f },
integer: ->(x) { x.to_i },
- datetime: ->(x) { x.is_a?(::String) ? DateTime.parse(x) : x },
- binary: ->(x) { Base64.strict_encode64(x) }
+ string: ->(x) { x.to_s }
}.freeze
VALIDATIONS = {
binary: ->(x) { x.is_a?(String) && x.match?(BASE64) },
spec/scim/kit/v2/attribute_spec.rb
@@ -39,7 +39,7 @@ RSpec.describe Scim::Kit::V2::Attribute do
context 'when the wrong type is used' do
before do
type.multi_valued = true
- subject._value = [1.0, 2.0]
+ subject._assign([1.0, 2.0], coerce: false)
subject.valid?
end
@@ -101,8 +101,11 @@ RSpec.describe Scim::Kit::V2::Attribute do
specify { expect(subject.as_json[:hungry]).to be(false) }
end
- context 'when string' do
- specify { expect { subject._value = 'hello' }.to raise_error(ArgumentError) }
+ context 'when invalid string' do
+ before { subject._value = 'hello' }
+
+ specify { expect(subject._value).to eql('hello') }
+ specify { expect(subject).not_to be_valid }
end
end
spec/scim/kit/v2/attribute_type_spec.rb
@@ -1,6 +1,9 @@
# frozen_string_literal: true
RSpec.describe Scim::Kit::V2::AttributeType do
+ let(:image) { Base64.strict_encode64(raw_image) }
+ let(:raw_image) { IO.read('./spec/fixtures/avatar.png', mode: 'rb') }
+
specify { expect { described_class.new(name: 'displayName', type: :string) }.not_to raise_error }
specify { expect { described_class.new(name: 'primary', type: :boolean) }.not_to raise_error }
specify { expect { described_class.new(name: 'salary', type: :decimal) }.not_to raise_error }
@@ -65,8 +68,6 @@ RSpec.describe Scim::Kit::V2::AttributeType do
end
describe '#valid?' do
- let(:image) { Base64.strict_encode64(IO.read('./spec/fixtures/avatar.png', mode: 'rb')) }
-
specify { expect(described_class.new(name: :x, type: :binary)).to be_valid(image) }
specify { expect(described_class.new(name: :x, type: :binary)).not_to be_valid('hello') }
specify { expect(described_class.new(name: :x, type: :binary)).not_to be_valid(1) }
@@ -150,4 +151,76 @@ RSpec.describe Scim::Kit::V2::AttributeType do
specify { expect(subject).not_to be_valid([value: email, primary: 'true']) }
end
end
+
+ describe '#coerce' do
+ let(:now) { DateTime.now }
+ let(:uri) { FFaker::Internet.uri('https') }
+
+ specify { expect(described_class.new(name: :x, type: :binary).coerce(raw_image)).to eql(image) }
+
+ specify { expect(described_class.new(name: :x, type: :boolean).coerce(true)).to be(true) }
+ specify { expect(described_class.new(name: :x, type: :boolean).coerce('true')).to be(true) }
+ specify { expect(described_class.new(name: :x, type: :boolean).coerce(false)).to be(false) }
+ specify { expect(described_class.new(name: :x, type: :boolean).coerce('false')).to be(false) }
+ specify { expect(described_class.new(name: :x, type: :boolean).coerce('invalid')).to eql('invalid') }
+
+ specify { expect(described_class.new(name: :x, type: :datetime).coerce(now)).to eql(now) }
+ specify { expect(described_class.new(name: :x, type: :datetime).coerce(now.iso8601)).to be_within(1).of(now) }
+
+ specify { expect(described_class.new(name: :x, type: :decimal).coerce(1.0)).to be(1.0) }
+ specify { expect(described_class.new(name: :x, type: :decimal).coerce(1)).to be(1.0) }
+ specify { expect(described_class.new(name: :x, type: :decimal).coerce('1.0')).to be(1.0) }
+ specify { expect(described_class.new(name: :x, type: :decimal).coerce('1')).to be(1.0) }
+
+ specify { expect(described_class.new(name: :x, type: :integer).coerce(1)).to be(1) }
+ specify { expect(described_class.new(name: :x, type: :integer).coerce(1.0)).to be(1) }
+ specify { expect(described_class.new(name: :x, type: :integer).coerce('1.0')).to be(1) }
+ specify { expect(described_class.new(name: :x, type: :integer).coerce('1')).to be(1) }
+
+ specify { expect(described_class.new(name: :x, type: :reference).coerce(uri)).to eql(uri) }
+ specify { expect(described_class.new(name: :x, type: :reference).coerce('hello')).to eql('hello') }
+
+ specify { expect(described_class.new(name: :x, type: :string).coerce('name')).to eql('name') }
+ specify { expect(described_class.new(name: :x, type: :string).coerce(1)).to eql('1') }
+
+ context 'when multi valued string type' do
+ subject do
+ x = described_class.new(name: :x, type: :string)
+ x.multi_valued = true
+ x
+ end
+
+ specify { expect(subject.coerce(['1'])).to match_array(['1']) }
+ specify { expect(subject.coerce([1])).to match_array(['1']) }
+ end
+
+ context 'when single valued complex type' do
+ subject { described_class.new(name: :location, type: :complex) }
+
+ before do
+ subject.multi_valued = false
+ subject.add_attribute(name: :name, type: :string)
+ subject.add_attribute(name: :latitude, type: :integer)
+ subject.add_attribute(name: :longitude, type: :integer)
+ end
+
+ specify { expect(subject.coerce(name: 'work', latitude: 100, longitude: 100)).to eql(name: 'work', latitude: 100, longitude: 100) }
+ end
+
+ context 'when multi valued complex type' do
+ subject { described_class.new(name: :emails, type: :complex) }
+
+ let(:email) { FFaker::Internet.email }
+ let(:other_email) { FFaker::Internet.email }
+
+ before do
+ subject.multi_valued = true
+ subject.add_attribute(name: 'value', type: :string)
+ subject.add_attribute(name: 'primary', type: :boolean)
+ end
+
+ specify { expect(subject.coerce([value: email, primary: true])).to match_array([value: email, primary: true]) }
+ specify { expect(subject.coerce([{ value: email, primary: true }, { value: other_email, primary: false }])).to match_array([{ value: email, primary: true }, { value: other_email, primary: false }]) }
+ end
+ end
end