Skip to content

Commit

Permalink
feat: Add Integer and Float coercions (#26)
Browse files Browse the repository at this point in the history
  • Loading branch information
maxveldink authored Mar 5, 2024
1 parent 5c12b65 commit 537f0fc
Show file tree
Hide file tree
Showing 6 changed files with 101 additions and 1 deletion.
8 changes: 8 additions & 0 deletions lib/typed/coercion.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,20 @@ module Typed
module Coercion
extend T::Sig

# TODO: We can definitely improve how we select which coercer to use
# Related issues:
# * https://github.com/maxveldink/sorbet-schema/issues/9
# * https://github.com/maxveldink/sorbet-schema/issues/10
sig { type_parameters(:U).params(field: Field, value: Value).returns(Result[Value, CoercionError]) }
def self.coerce(field:, value:)
if field.type < T::Struct
StructCoercer.coerce(field: field, value: value)
elsif field.type == String
StringCoercer.coerce(field: field, value: value)
elsif field.type == Integer
IntegerCoercer.coerce(field: field, value: value)
elsif field.type == Float
FloatCoercer.coerce(field: field, value: value)
else
Failure.new(CoercionNotSupportedError.new)
end
Expand Down
21 changes: 21 additions & 0 deletions lib/typed/coercion/float_coercer.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# typed: strict

module Typed
module Coercion
class FloatCoercer
extend T::Sig
extend T::Generic

extend Coercer

Target = type_template { {fixed: Float} }

sig { override.params(field: Field, value: Value).returns(Result[Target, CoercionError]) }
def self.coerce(field:, value:)
Success.new(Float(value))
rescue ArgumentError, TypeError
Failure.new(CoercionError.new("'#{value}' cannot be coerced into Float."))
end
end
end
end
21 changes: 21 additions & 0 deletions lib/typed/coercion/integer_coercer.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# typed: strict

module Typed
module Coercion
class IntegerCoercer
extend T::Sig
extend T::Generic

extend Coercer

Target = type_template { {fixed: Integer} }

sig { override.params(field: Field, value: Value).returns(Result[Target, CoercionError]) }
def self.coerce(field:, value:)
Success.new(Integer(value))
rescue ArgumentError, TypeError
Failure.new(CoercionError.new("'#{value}' cannot be coerced into Integer."))
end
end
end
end
17 changes: 17 additions & 0 deletions test/typed/coercion/float_coercer_test.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# typed: true

class FloatCoercerTest < Minitest::Test
def setup
@field = Typed::Field.new(name: :testing, type: Float)
end

def test_when_coercable_returns_success
assert_payload(1.1, Typed::Coercion::FloatCoercer.coerce(field: @field, value: "1.1"))
assert_payload(1.0, Typed::Coercion::FloatCoercer.coerce(field: @field, value: 1))
end

def test_when_not_coercable_returns_failure
assert_error(Typed::Coercion::CoercionError.new("'a' cannot be coerced into Float."), Typed::Coercion::FloatCoercer.coerce(field: @field, value: "a"))
assert_error(Typed::Coercion::CoercionError.new("'true' cannot be coerced into Float."), Typed::Coercion::FloatCoercer.coerce(field: @field, value: true))
end
end
17 changes: 17 additions & 0 deletions test/typed/coercion/integer_coercer_test.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# typed: true

class IntegerCoercerTest < Minitest::Test
def setup
@field = Typed::Field.new(name: :testing, type: Integer)
end

def test_when_coercable_returns_success
assert_payload(1, Typed::Coercion::IntegerCoercer.coerce(field: @field, value: "1"))
assert_payload(1, Typed::Coercion::IntegerCoercer.coerce(field: @field, value: 1.1))
end

def test_when_not_coercable_returns_failure
assert_error(Typed::Coercion::CoercionError.new("'a' cannot be coerced into Integer."), Typed::Coercion::IntegerCoercer.coerce(field: @field, value: "a"))
assert_error(Typed::Coercion::CoercionError.new("'true' cannot be coerced into Integer."), Typed::Coercion::IntegerCoercer.coerce(field: @field, value: true))
end
end
18 changes: 17 additions & 1 deletion test/typed/coercion_test.rb
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
# typed: true

require "date"

class CoercionTest < Minitest::Test
def test_coercion_coerces_structs
result = Typed::Coercion.coerce(field: Typed::Field.new(name: :job, type: Job), value: {"title" => "Software Developer", "salary" => 90_000_00})
Expand All @@ -15,8 +17,22 @@ def test_coercion_coerces_strings
assert_payload("1", result)
end

def test_coercion_coerces_integers
result = Typed::Coercion.coerce(field: Typed::Field.new(name: :name, type: Integer), value: "1")

assert_success(result)
assert_payload(1, result)
end

def test_coercion_coerces_floats
result = Typed::Coercion.coerce(field: Typed::Field.new(name: :name, type: Float), value: "1.1")

assert_success(result)
assert_payload(1.1, result)
end

def test_when_coercer_isnt_matched_returns_failure
result = Typed::Coercion.coerce(field: Typed::Field.new(name: :testing, type: Integer), value: "testing")
result = Typed::Coercion.coerce(field: Typed::Field.new(name: :testing, type: Date), value: "testing")

assert_failure(result)
assert_error(Typed::Coercion::CoercionNotSupportedError.new, result)
Expand Down

0 comments on commit 537f0fc

Please sign in to comment.