From 9cfa84b1bfd63a3a50db6b6517d15152f00ddc0f Mon Sep 17 00:00:00 2001 From: Max VelDink Date: Thu, 11 Jul 2024 15:40:10 -0400 Subject: [PATCH] feat: add coercion support for DateTime --- lib/typed/coercion/coercer_registry.rb | 1 + lib/typed/coercion/date_time_coercer.rb | 29 ++++++++++++++++ test/typed/coercion/date_coercer_test.rb | 2 ++ test/typed/coercion/date_time_coercer_test.rb | 33 +++++++++++++++++++ 4 files changed, 65 insertions(+) create mode 100644 lib/typed/coercion/date_time_coercer.rb create mode 100644 test/typed/coercion/date_time_coercer_test.rb diff --git a/lib/typed/coercion/coercer_registry.rb b/lib/typed/coercion/coercer_registry.rb index 402cc3a..6c70825 100644 --- a/lib/typed/coercion/coercer_registry.rb +++ b/lib/typed/coercion/coercer_registry.rb @@ -19,6 +19,7 @@ class CoercerRegistry IntegerCoercer, FloatCoercer, DateCoercer, + DateTimeCoercer, EnumCoercer, StructCoercer, TypedArrayCoercer, diff --git a/lib/typed/coercion/date_time_coercer.rb b/lib/typed/coercion/date_time_coercer.rb new file mode 100644 index 0000000..d400dd9 --- /dev/null +++ b/lib/typed/coercion/date_time_coercer.rb @@ -0,0 +1,29 @@ +# typed: strict + +require "date" + +module Typed + module Coercion + class DateTimeCoercer < Coercer + extend T::Generic + + Target = type_member { {fixed: DateTime} } + + sig { override.params(type: T::Types::Base).returns(T::Boolean) } + def used_for_type?(type) + T::Utils.coerce(type) == T::Utils.coerce(DateTime) + end + + sig { override.params(type: T::Types::Base, value: Value).returns(Result[Target, CoercionError]) } + def coerce(type:, value:) + return Failure.new(CoercionError.new("Type must be a DateTime.")) unless used_for_type?(type) + + return Success.new(value) if value.is_a?(DateTime) + + Success.new(DateTime.parse(value)) + rescue Date::Error, TypeError + Failure.new(CoercionError.new("'#{value}' cannot be coerced into DateTime.")) + end + end + end +end diff --git a/test/typed/coercion/date_coercer_test.rb b/test/typed/coercion/date_coercer_test.rb index 8bb5976..9643fb4 100644 --- a/test/typed/coercion/date_coercer_test.rb +++ b/test/typed/coercion/date_coercer_test.rb @@ -1,5 +1,7 @@ # typed: true +require "test_helper" + class DateCoercerTest < Minitest::Test def setup @coercer = Typed::Coercion::DateCoercer.new diff --git a/test/typed/coercion/date_time_coercer_test.rb b/test/typed/coercion/date_time_coercer_test.rb new file mode 100644 index 0000000..2983c40 --- /dev/null +++ b/test/typed/coercion/date_time_coercer_test.rb @@ -0,0 +1,33 @@ +# typed: true + +require "test_helper" + +class DateTimeCoercerTest < Minitest::Test + def setup + @coercer = Typed::Coercion::DateTimeCoercer.new + @type = T::Utils.coerce(DateTime) + end + + def test_used_for_type_works + assert(@coercer.used_for_type?(@type)) + refute(@coercer.used_for_type?(T::Utils.coerce(Integer))) + end + + def test_when_non_date_type_given_returns_failure + result = @coercer.coerce(type: T::Utils.coerce(Integer), value: DateTime.now) + + assert_failure(result) + assert_error(Typed::Coercion::CoercionError.new("Type must be a DateTime."), result) + end + + def test_when_coercable_returns_success + assert_payload(DateTime.new(2024, 7, 11, 19, 31, 30), @coercer.coerce(type: @type, value: "2024-07-11T19:31:30Z")) + assert_payload(DateTime.new(2024, 7, 11, 19, 31, 30), @coercer.coerce(type: @type, value: "2024-07-11T19:31:30")) + assert_payload(DateTime.new(2024, 7, 11), @coercer.coerce(type: @type, value: "2024-07-11")) + end + + def test_when_not_coercable_returns_failure + assert_error(Typed::Coercion::CoercionError.new("'a' cannot be coerced into DateTime."), @coercer.coerce(type: @type, value: "a")) + assert_error(Typed::Coercion::CoercionError.new("'1' cannot be coerced into DateTime."), @coercer.coerce(type: @type, value: 1)) + end +end