diff --git a/lib/typed/coercion.rb b/lib/typed/coercion.rb index 88e2209..68de119 100644 --- a/lib/typed/coercion.rb +++ b/lib/typed/coercion.rb @@ -9,13 +9,13 @@ def self.register_coercer(coercer) CoercerRegistry.instance.register(coercer) end - sig { type_parameters(:U).params(field: Field, value: Value).returns(Result[Value, CoercionError]) } - def self.coerce(field:, value:) - coercer = CoercerRegistry.instance.select_coercer_by(type: field.type) + sig { type_parameters(:U).params(type: Field::Type, value: Value).returns(Result[Value, CoercionError]) } + def self.coerce(type:, value:) + coercer = CoercerRegistry.instance.select_coercer_by(type: type) return Failure.new(CoercionNotSupportedError.new) unless coercer - coercer.new.coerce(field: field, value: value) + coercer.new.coerce(type: type, value: value) end end end diff --git a/lib/typed/coercion/boolean_coercer.rb b/lib/typed/coercion/boolean_coercer.rb index 0ed09d4..0312c13 100644 --- a/lib/typed/coercion/boolean_coercer.rb +++ b/lib/typed/coercion/boolean_coercer.rb @@ -12,9 +12,9 @@ def used_for_type?(type) type == T::Utils.coerce(T::Boolean) end - sig { override.params(field: Field, value: Value).returns(Result[Target, CoercionError]) } - def coerce(field:, value:) - if T.cast(field.type, T::Types::Base).valid?(value) + sig { override.params(type: Field::Type, value: Value).returns(Result[Target, CoercionError]) } + def coerce(type:, value:) + if T.cast(type, T::Types::Base).recursively_valid?(value) Success.new(value) elsif value == "true" Success.new(true) diff --git a/lib/typed/coercion/coercer.rb b/lib/typed/coercion/coercer.rb index c7776e2..79d4570 100644 --- a/lib/typed/coercion/coercer.rb +++ b/lib/typed/coercion/coercer.rb @@ -14,8 +14,8 @@ class Coercer def used_for_type?(type) end - sig { abstract.params(field: Field, value: Value).returns(Result[Target, CoercionError]) } - def coerce(field:, value:) + sig { abstract.params(type: Field::Type, value: Value).returns(Result[Target, CoercionError]) } + def coerce(type:, value:) end end end diff --git a/lib/typed/coercion/coercer_registry.rb b/lib/typed/coercion/coercer_registry.rb index 47a086d..4ab9af6 100644 --- a/lib/typed/coercion/coercer_registry.rb +++ b/lib/typed/coercion/coercer_registry.rb @@ -11,7 +11,18 @@ class CoercerRegistry Registry = T.type_alias { T::Array[T.class_of(Coercer)] } - DEFAULT_COERCERS = T.let([StringCoercer, BooleanCoercer, IntegerCoercer, FloatCoercer, EnumCoercer, StructCoercer], Registry) + DEFAULT_COERCERS = T.let( + [ + StringCoercer, + BooleanCoercer, + IntegerCoercer, + FloatCoercer, + EnumCoercer, + StructCoercer, + TypedArrayCoercer + ], + Registry + ) sig { void } def initialize diff --git a/lib/typed/coercion/enum_coercer.rb b/lib/typed/coercion/enum_coercer.rb index c0070e8..37adc93 100644 --- a/lib/typed/coercion/enum_coercer.rb +++ b/lib/typed/coercion/enum_coercer.rb @@ -12,10 +12,8 @@ def used_for_type?(type) type.is_a?(Class) && !!(type < T::Enum) end - sig { override.params(field: Field, value: Value).returns(Result[Target, CoercionError]) } - def coerce(field:, value:) - type = field.type - + sig { override.params(type: Field::Type, value: Value).returns(Result[Target, CoercionError]) } + def coerce(type:, value:) return Failure.new(CoercionError.new("Field type must inherit from T::Enum for Enum coercion.")) unless type.is_a?(Class) && !!(type < T::Enum) Success.new(type.from_serialized(value)) diff --git a/lib/typed/coercion/float_coercer.rb b/lib/typed/coercion/float_coercer.rb index fb0db82..626ed59 100644 --- a/lib/typed/coercion/float_coercer.rb +++ b/lib/typed/coercion/float_coercer.rb @@ -9,11 +9,11 @@ class FloatCoercer < Coercer sig { override.params(type: Field::Type).returns(T::Boolean) } def used_for_type?(type) - type == Float + T::Utils.coerce(type) == T::Utils.coerce(Float) end - sig { override.params(field: Field, value: Value).returns(Result[Target, CoercionError]) } - def coerce(field:, value:) + sig { override.params(type: Field::Type, value: Value).returns(Result[Target, CoercionError]) } + def coerce(type:, value:) Success.new(Float(value)) rescue ArgumentError, TypeError Failure.new(CoercionError.new("'#{value}' cannot be coerced into Float.")) diff --git a/lib/typed/coercion/integer_coercer.rb b/lib/typed/coercion/integer_coercer.rb index faa4fd1..232073d 100644 --- a/lib/typed/coercion/integer_coercer.rb +++ b/lib/typed/coercion/integer_coercer.rb @@ -12,8 +12,8 @@ def used_for_type?(type) type == Integer end - sig { override.params(field: Field, value: Value).returns(Result[Target, CoercionError]) } - def coerce(field:, value:) + sig { override.params(type: Field::Type, value: Value).returns(Result[Target, CoercionError]) } + def coerce(type:, value:) Success.new(Integer(value)) rescue ArgumentError, TypeError Failure.new(CoercionError.new("'#{value}' cannot be coerced into Integer.")) diff --git a/lib/typed/coercion/string_coercer.rb b/lib/typed/coercion/string_coercer.rb index 73c9e71..958446c 100644 --- a/lib/typed/coercion/string_coercer.rb +++ b/lib/typed/coercion/string_coercer.rb @@ -12,8 +12,8 @@ def used_for_type?(type) type == String end - sig { override.params(field: Field, value: Value).returns(Result[Target, CoercionError]) } - def coerce(field:, value:) + sig { override.params(type: Field::Type, value: Value).returns(Result[Target, CoercionError]) } + def coerce(type:, value:) Success.new(String(value)) end end diff --git a/lib/typed/coercion/struct_coercer.rb b/lib/typed/coercion/struct_coercer.rb index be51421..b0d7ecb 100644 --- a/lib/typed/coercion/struct_coercer.rb +++ b/lib/typed/coercion/struct_coercer.rb @@ -12,12 +12,12 @@ def used_for_type?(type) type.is_a?(Class) && !!(type < T::Struct) end - sig { override.params(field: Field, value: Value).returns(Result[Target, CoercionError]) } - def coerce(field:, value:) - type = field.type - + sig { override.params(type: Field::Type, value: Value).returns(Result[Target, CoercionError]) } + def coerce(type:, value:) return Failure.new(CoercionError.new("Field type must inherit from T::Struct for Struct coercion.")) unless type.is_a?(Class) && type < T::Struct - return Failure.new(CoercionError.new("Value must be a Hash for Struct coercion.")) unless value.is_a?(Hash) + return Success.new(value) if value.instance_of?(type) + + return Failure.new(CoercionError.new("Value of type '#{value.class}' cannot be coerced to #{type} Struct.")) unless value.is_a?(Hash) Success.new(type.from_hash!(HashTransformer.new.deep_stringify_keys(value))) rescue ArgumentError => e diff --git a/lib/typed/coercion/typed_array_coercer.rb b/lib/typed/coercion/typed_array_coercer.rb new file mode 100644 index 0000000..30489df --- /dev/null +++ b/lib/typed/coercion/typed_array_coercer.rb @@ -0,0 +1,34 @@ +# typed: strict + +module Typed + module Coercion + class TypedArrayCoercer < Coercer + extend T::Generic + + Target = type_member { {fixed: T::Array[T.untyped]} } + + sig { override.params(type: Field::Type).returns(T::Boolean) } + def used_for_type?(type) + type.is_a?(T::Types::TypedArray) + end + + sig { override.params(type: Field::Type, value: Value).returns(Result[Target, CoercionError]) } + def coerce(type:, value:) + return Failure.new(CoercionError.new("Field type must be a T::Array.")) unless type.is_a?(T::Types::TypedArray) + return Failure.new(CoercionError.new("Value must be an Array.")) unless value.is_a?(Array) + + return Success.new(value) if type.recursively_valid?(value) + + coerced_results = value.map do |item| + Coercion.coerce(type: type.type.raw_type, value: item) + end + + if coerced_results.all?(&:success?) + Success.new(coerced_results.map(&:payload)) + else + Failure.new(CoercionError.new(coerced_results.select(&:failure?).map(&:error).map(&:message).join(" | "))) + end + end + end + end +end diff --git a/lib/typed/field.rb b/lib/typed/field.rb index b22ad28..5c19153 100644 --- a/lib/typed/field.rb +++ b/lib/typed/field.rb @@ -29,7 +29,7 @@ def validate(value) sig { params(value: Value).returns(T::Boolean) } def works_with?(value) - value.class == type || T.cast(type, T::Types::Base).valid?(value) # standard:disable Style/ClassEqualityComparison + value.class == type || T.cast(type, T::Types::Base).recursively_valid?(value) # standard:disable Style/ClassEqualityComparison rescue TypeError false end diff --git a/lib/typed/serializer.rb b/lib/typed/serializer.rb index 3d4471b..f91fa06 100644 --- a/lib/typed/serializer.rb +++ b/lib/typed/serializer.rb @@ -38,7 +38,7 @@ def deserialize_from_creation_params(creation_params) if value.nil? || field.works_with?(value) field.validate(value) else - coercion_result = Coercion.coerce(field: field, value: value) + coercion_result = Coercion.coerce(type: field.type, value: value) if coercion_result.success? field.validate(coercion_result.payload) diff --git a/test/support/simple_string_coercer.rb b/test/support/simple_string_coercer.rb index 496b5fc..be53922 100644 --- a/test/support/simple_string_coercer.rb +++ b/test/support/simple_string_coercer.rb @@ -10,8 +10,8 @@ def used_for_type?(type) type == String end - sig { override.params(field: Typed::Field, value: Typed::Value).returns(Typed::Result[Target, Typed::Coercion::CoercionError]) } - def coerce(field:, value:) + sig { override.params(type: Typed::Field::Type, value: Typed::Value).returns(Typed::Result[Target, Typed::Coercion::CoercionError]) } + def coerce(type:, value:) Typed::Success.new("always this value") end end diff --git a/test/support/structs/country.rb b/test/support/structs/country.rb index e4a7e3f..8347f43 100644 --- a/test/support/structs/country.rb +++ b/test/support/structs/country.rb @@ -3,6 +3,8 @@ require_relative "city" class Country < T::Struct + include ActsAsComparable + const :name, String const :cities, T::Array[City] end diff --git a/test/typed/coercion/boolean_coercer_test.rb b/test/typed/coercion/boolean_coercer_test.rb index 8d661fd..18dedd8 100644 --- a/test/typed/coercion/boolean_coercer_test.rb +++ b/test/typed/coercion/boolean_coercer_test.rb @@ -3,37 +3,37 @@ class BooleanCoercerTest < Minitest::Test def setup @coercer = Typed::Coercion::BooleanCoercer.new - @field = Typed::Field.new(name: :capital, type: T::Utils.coerce(T::Boolean)) + @type = T::Utils.coerce(T::Boolean) end def test_used_for_type_works - assert(@coercer.used_for_type?(T::Utils.coerce(T::Boolean))) + assert(@coercer.used_for_type?(@type)) refute(@coercer.used_for_type?(Integer)) end - def test_when_boolean_field_given_returns_failure - result = @coercer.coerce(field: Typed::Field.new(name: :testing, type: Integer), value: "testing") + def test_when_non_boolean_field_given_returns_failure + result = @coercer.coerce(type: Integer, value: "testing") assert_failure(result) assert_error(Typed::Coercion::CoercionError.new("Field type must be a T::Boolean."), result) end def test_when_true_boolean_can_be_coerced_returns_success - result = @coercer.coerce(field: @field, value: "true") + result = @coercer.coerce(type: @type, value: "true") assert_success(result) assert_payload(true, result) end def test_when_false_boolean_can_be_coerced_returns_success - result = @coercer.coerce(field: @field, value: "false") + result = @coercer.coerce(type: @type, value: "false") assert_success(result) assert_payload(false, result) end def test_when_enum_cannot_be_coerced_returns_failure - result = @coercer.coerce(field: @field, value: "bad") + result = @coercer.coerce(type: @type, value: "bad") assert_failure(result) assert_error(Typed::Coercion::CoercionError.new, result) diff --git a/test/typed/coercion/enum_coercer_test.rb b/test/typed/coercion/enum_coercer_test.rb index 84f5fbb..1d23e02 100644 --- a/test/typed/coercion/enum_coercer_test.rb +++ b/test/typed/coercion/enum_coercer_test.rb @@ -12,21 +12,21 @@ def test_used_for_type_works end def test_when_non_enum_field_given_returns_failure - result = @coercer.coerce(field: Typed::Field.new(name: :testing, type: Integer), value: "testing") + result = @coercer.coerce(type: Integer, value: "testing") assert_failure(result) assert_error(Typed::Coercion::CoercionError.new("Field type must inherit from T::Enum for Enum coercion."), result) end def test_when_enum_can_be_coerced_returns_success - result = @coercer.coerce(field: Typed::Field.new(name: :ruby_rank, type: RubyRank), value: "shiny") + result = @coercer.coerce(type: RubyRank, value: "shiny") assert_success(result) assert_payload(RubyRank::Luminary, result) end def test_when_enum_cannot_be_coerced_returns_failure - result = @coercer.coerce(field: Typed::Field.new(name: :ruby_rank, type: RubyRank), value: "bad") + result = @coercer.coerce(type: RubyRank, value: "bad") assert_failure(result) assert_error(Typed::Coercion::CoercionError.new("Enum RubyRank key not found: \"bad\""), result) diff --git a/test/typed/coercion/float_coercer_test.rb b/test/typed/coercion/float_coercer_test.rb index f263d57..21cecff 100644 --- a/test/typed/coercion/float_coercer_test.rb +++ b/test/typed/coercion/float_coercer_test.rb @@ -3,7 +3,7 @@ class FloatCoercerTest < Minitest::Test def setup @coercer = Typed::Coercion::FloatCoercer.new - @field = Typed::Field.new(name: :testing, type: Float) + @type = Float end def test_used_for_type_works @@ -12,12 +12,12 @@ def test_used_for_type_works end def test_when_coercable_returns_success - assert_payload(1.1, @coercer.coerce(field: @field, value: "1.1")) - assert_payload(1.0, @coercer.coerce(field: @field, value: 1)) + assert_payload(1.1, @coercer.coerce(type: @type, value: "1.1")) + assert_payload(1.0, @coercer.coerce(type: @type, value: 1)) end def test_when_not_coercable_returns_failure - assert_error(Typed::Coercion::CoercionError.new("'a' cannot be coerced into Float."), @coercer.coerce(field: @field, value: "a")) - assert_error(Typed::Coercion::CoercionError.new("'true' cannot be coerced into Float."), @coercer.coerce(field: @field, value: true)) + assert_error(Typed::Coercion::CoercionError.new("'a' cannot be coerced into Float."), @coercer.coerce(type: @type, value: "a")) + assert_error(Typed::Coercion::CoercionError.new("'true' cannot be coerced into Float."), @coercer.coerce(type: @type, value: true)) end end diff --git a/test/typed/coercion/integer_coercer_test.rb b/test/typed/coercion/integer_coercer_test.rb index b2add99..cdbd49b 100644 --- a/test/typed/coercion/integer_coercer_test.rb +++ b/test/typed/coercion/integer_coercer_test.rb @@ -3,7 +3,7 @@ class IntegerCoercerTest < Minitest::Test def setup @coercer = Typed::Coercion::IntegerCoercer.new - @field = Typed::Field.new(name: :testing, type: Integer) + @type = Integer end def test_used_for_type_works @@ -12,12 +12,12 @@ def test_used_for_type_works end def test_when_coercable_returns_success - assert_payload(1, @coercer.coerce(field: @field, value: "1")) - assert_payload(1, @coercer.coerce(field: @field, value: 1.1)) + assert_payload(1, @coercer.coerce(type: @type, value: "1")) + assert_payload(1, @coercer.coerce(type: @type, value: 1.1)) end def test_when_not_coercable_returns_failure - assert_error(Typed::Coercion::CoercionError.new("'a' cannot be coerced into Integer."), @coercer.coerce(field: @field, value: "a")) - assert_error(Typed::Coercion::CoercionError.new("'true' cannot be coerced into Integer."), @coercer.coerce(field: @field, value: true)) + assert_error(Typed::Coercion::CoercionError.new("'a' cannot be coerced into Integer."), @coercer.coerce(type: @type, value: "a")) + assert_error(Typed::Coercion::CoercionError.new("'true' cannot be coerced into Integer."), @coercer.coerce(type: @type, value: true)) end end diff --git a/test/typed/coercion/string_coercer_test.rb b/test/typed/coercion/string_coercer_test.rb index da6e680..a95de3f 100644 --- a/test/typed/coercion/string_coercer_test.rb +++ b/test/typed/coercion/string_coercer_test.rb @@ -3,7 +3,6 @@ class StringCoercerTest < Minitest::Test def setup @coercer = Typed::Coercion::StringCoercer.new - @field = Typed::Field.new(name: :testing, type: String) end def test_used_for_type_works @@ -12,7 +11,7 @@ def test_used_for_type_works end def test_returns_success - assert_payload("1", @coercer.coerce(field: @field, value: 1)) - assert_payload("[1, 2]", @coercer.coerce(field: @field, value: [1, 2])) + assert_payload("1", @coercer.coerce(type: String, value: 1)) + assert_payload("[1, 2]", @coercer.coerce(type: String, value: [1, 2])) end end diff --git a/test/typed/coercion/struct_coercer_test.rb b/test/typed/coercion/struct_coercer_test.rb index 828dd04..7cc60c6 100644 --- a/test/typed/coercion/struct_coercer_test.rb +++ b/test/typed/coercion/struct_coercer_test.rb @@ -12,23 +12,39 @@ def test_used_for_type_works end def test_when_non_struct_field_given_returns_failure - result = @coercer.coerce(field: Typed::Field.new(name: :testing, type: Integer), value: "testing") + result = @coercer.coerce(type: Integer, value: "testing") assert_failure(result) assert_error(Typed::Coercion::CoercionError.new("Field type must inherit from T::Struct for Struct coercion."), result) end + def test_when_struct_of_correct_type_given_returns_success + job = Job.new(title: "Software Developer", salary: 90_000_00) + + result = @coercer.coerce(type: Job, value: job) + + assert_success(result) + assert_payload(job, result) + end + + def test_when_struct_of_incorrect_type_given_returns_failure + result = @coercer.coerce(type: Job, value: Country.new(name: "Canada", cities: [])) + + assert_failure(result) + assert_error(Typed::Coercion::CoercionError.new("Value of type 'Country' cannot be coerced to Job Struct."), result) + end + def test_when_struct_can_be_coerced_returns_success - result = @coercer.coerce(field: Typed::Field.new(name: :job, type: Job), value: {"title" => "Software Developer", "salary" => 90_000_00}) + result = @coercer.coerce(type: Job, value: {"title" => "Software Developer", "salary" => 90_000_00}) assert_success(result) assert_payload(Job.new(title: "Software Developer", salary: 90_000_00), result) end def test_when_struct_cannot_be_coerced_returns_failure - result = @coercer.coerce(field: Typed::Field.new(name: :job, type: Job), value: "bad") + result = @coercer.coerce(type: Job, value: "bad") assert_failure(result) - assert_error(Typed::Coercion::CoercionError.new("Value must be a Hash for Struct coercion."), result) + assert_error(Typed::Coercion::CoercionError.new("Value of type 'String' cannot be coerced to Job Struct."), result) end end diff --git a/test/typed/coercion/typed_array_coercer_test.rb b/test/typed/coercion/typed_array_coercer_test.rb new file mode 100644 index 0000000..9dd5926 --- /dev/null +++ b/test/typed/coercion/typed_array_coercer_test.rb @@ -0,0 +1,50 @@ +# typed: true + +class TypedArrayCoercerTest < Minitest::Test + def setup + @coercer = Typed::Coercion::TypedArrayCoercer.new + @type = T::Utils.coerce(T::Array[City]) + end + + def test_used_for_type_works + assert(@coercer.used_for_type?(T::Utils.coerce(T::Array[City]))) + assert(@coercer.used_for_type?(T::Utils.coerce(T::Array[String]))) + refute(@coercer.used_for_type?(Integer)) + refute(@coercer.used_for_type?(Array)) + end + + def test_when_non_array_field_given_returns_failure + result = @coercer.coerce(type: Integer, value: "testing") + + assert_failure(result) + assert_error(Typed::Coercion::CoercionError.new("Field type must be a T::Array."), result) + end + + def test_when_non_array_value_given_returns_failure + result = @coercer.coerce(type: @type, value: "testing") + + assert_failure(result) + assert_error(Typed::Coercion::CoercionError.new("Value must be an Array."), result) + end + + def test_when_already_of_type_returns_success + result = @coercer.coerce(type: @type, value: [DC_CITY, NEW_YORK_CITY]) + + assert_success(result) + assert_payload([DC_CITY, NEW_YORK_CITY], result) + end + + def test_when_coercable_array_can_be_coerced_returns_success + result = @coercer.coerce(type: @type, value: [DC_CITY, {name: "New York", capital: false}]) + + assert_success(result) + assert_payload([DC_CITY, NEW_YORK_CITY], result) + end + + def test_when_array_cannot_be_coerced_returns_failure + result = @coercer.coerce(type: @type, value: [1, DC_CITY]) + + assert_failure(result) + assert_error(Typed::Coercion::CoercionError.new("Value of type 'Integer' cannot be coerced to City Struct."), result) + end +end diff --git a/test/typed/coercion_test.rb b/test/typed/coercion_test.rb index 2c7a363..b367668 100644 --- a/test/typed/coercion_test.rb +++ b/test/typed/coercion_test.rb @@ -14,14 +14,14 @@ def test_new_coercers_can_be_registered end def test_when_coercer_is_matched_coerce_coerces - result = Typed::Coercion.coerce(field: Typed::Field.new(name: :name, type: String), value: 1) + result = Typed::Coercion.coerce(type: String, value: 1) assert_success(result) assert_payload("1", result) end def test_when_coercer_isnt_matched_coerce_returns_failure - result = Typed::Coercion.coerce(field: Typed::Field.new(name: :testing, type: Date), value: "testing") + result = Typed::Coercion.coerce(type: Date, value: "testing") assert_failure(result) assert_error(Typed::Coercion::CoercionNotSupportedError.new, result) diff --git a/test/typed/field_test.rb b/test/typed/field_test.rb index ba7345c..de8da85 100644 --- a/test/typed/field_test.rb +++ b/test/typed/field_test.rb @@ -23,11 +23,21 @@ def test_when_standard_type_work_with_works refute(@required_field.works_with?(1)) end - def test_when_base_type_works_with_works + def test_when_simple_base_type_works_with_works field = Typed::Field.new(name: :bools, type: T::Utils.coerce(T::Boolean)) assert(field.works_with?(true)) assert(field.works_with?(false)) refute(field.works_with?("Max")) end + + def test_when_recursive_base_type_works_with_works + field = Typed::Field.new(name: :typed_array, type: T::Utils.coerce(T::Array[String])) + + assert(field.works_with?([])) + assert(field.works_with?(["Max"])) + refute(field.works_with?("Max")) + refute(field.works_with?([1])) + refute(field.works_with?([1, "Max"])) + end end diff --git a/test/typed/hash_serializer_test.rb b/test/typed/hash_serializer_test.rb index c96702b..41c8d9f 100644 --- a/test/typed/hash_serializer_test.rb +++ b/test/typed/hash_serializer_test.rb @@ -37,6 +37,20 @@ def test_with_boolean_it_can_serialize assert_payload({name: "New York", capital: false}, result) end + def test_with_array_it_can_serialize + result = Typed::HashSerializer.new(schema: Typed::Schema.from_struct(Country)).serialize(US_COUNTRY) + + assert_success(result) + assert_payload({name: "US", cities: [NEW_YORK_CITY, DC_CITY]}, result) + end + + def test_with_array_it_can_deep_serialize + result = Typed::HashSerializer.new(schema: Typed::Schema.from_struct(Country), should_serialize_values: true).serialize(US_COUNTRY) + + assert_success(result) + assert_payload({name: "US", cities: [{name: "New York", capital: false}, {name: "DC", capital: true}]}, result) + end + def test_when_struct_given_is_not_of_target_type_returns_failure result = @serializer.serialize(Job.new(title: "Testing", salary: 90_00)) @@ -67,8 +81,22 @@ def test_with_boolean_it_can_deserialize assert_payload(NEW_YORK_CITY, result) end + def test_with_array_it_can_deserialize + result = Typed::HashSerializer.new(schema: Typed::Schema.from_struct(Country)).deserialize({name: "US", cities: [NEW_YORK_CITY, DC_CITY]}) + + assert_success(result) + assert_payload(US_COUNTRY, result) + end + + def test_with_array_it_can_deep_deserialize + result = Typed::HashSerializer.new(schema: Typed::Schema.from_struct(Country)).deserialize({name: "US", cities: [{name: "New York", capital: false}, {name: "DC", capital: true}]}) + + assert_success(result) + assert_payload(US_COUNTRY, result) + end + def test_it_can_deserialize_with_nested_object - result = @serializer.deserialize({name: "Alex", age: 31, ruby_rank: RubyRank::Brilliant, job: {title: "Software Developer", salary: 1_000_000_00}}) + result = @serializer.deserialize({name: "Alex", age: 31, ruby_rank: "pretty", job: {title: "Software Developer", salary: 1_000_000_00}}) assert_success(result) assert_payload(ALEX_PERSON, result) diff --git a/test/typed/json_serializer_test.rb b/test/typed/json_serializer_test.rb index 0954bed..f05aca1 100644 --- a/test/typed/json_serializer_test.rb +++ b/test/typed/json_serializer_test.rb @@ -30,6 +30,13 @@ def test_with_boolean_it_can_serialize assert_payload('{"name":"New York","capital":false}', result) end + def test_with_array_it_can_serialize + result = Typed::JSONSerializer.new(schema: Typed::Schema.from_struct(Country)).serialize(US_COUNTRY) + + assert_success(result) + assert_payload('{"name":"US","cities":[{"name":"New York","capital":false},{"name":"DC","capital":true}]}', result) + end + # Deserialize Tests def test_it_can_simple_deserialize @@ -46,6 +53,13 @@ def test_with_boolean_it_can_deserialize assert_payload(NEW_YORK_CITY, result) end + def test_with_array_it_can_deep_deserialize + result = Typed::JSONSerializer.new(schema: Typed::Schema.from_struct(Country)).deserialize('{"name":"US","cities":[{"name":"New York","capital":false},{"name":"DC","capital":true}]}') + + assert_success(result) + assert_payload(US_COUNTRY, result) + end + def test_it_can_deserialize_with_nested_object result = @serializer.deserialize('{"name":"Alex","age":31,"ruby_rank":"pretty","job":{"title":"Software Developer","salary":100000000}}')