Skip to content

Commit

Permalink
refactor!: Update Field's types to a support Sorbets T::Types::Base c…
Browse files Browse the repository at this point in the history
…lasses
  • Loading branch information
maxveldink committed Mar 13, 2024
1 parent 948c678 commit 9ef1cd5
Show file tree
Hide file tree
Showing 13 changed files with 39 additions and 19 deletions.
2 changes: 1 addition & 1 deletion lib/typed/coercion/coercer.rb
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ class Coercer

Target = type_member(:out)

sig { abstract.params(type: T::Class[T.anything]).returns(T::Boolean) }
sig { abstract.params(type: Field::Type).returns(T::Boolean) }
def used_for_type?(type)
end

Expand Down
2 changes: 1 addition & 1 deletion lib/typed/coercion/coercer_registry.rb
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ def reset!
@available = DEFAULT_COERCERS.clone
end

sig { params(type: T::Class[T.anything]).returns(T.nilable(T.class_of(Coercer))) }
sig { params(type: Field::Type).returns(T.nilable(T.class_of(Coercer))) }
def select_coercer_by(type:)
@available.find { |coercer| coercer.new.used_for_type?(type) }
end
Expand Down
6 changes: 3 additions & 3 deletions lib/typed/coercion/enum_coercer.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,16 +7,16 @@ class EnumCoercer < Coercer

Target = type_member { {fixed: T::Enum} }

sig { override.params(type: T::Class[T.anything]).returns(T::Boolean) }
sig { override.params(type: Field::Type).returns(T::Boolean) }
def used_for_type?(type)
!!(type < T::Enum)
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

return Failure.new(CoercionError.new("Field type must inherit from T::Enum for Enum coercion.")) unless type < T::Enum
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))
rescue KeyError => e
Expand Down
2 changes: 1 addition & 1 deletion lib/typed/coercion/float_coercer.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ class FloatCoercer < Coercer

Target = type_member { {fixed: Float} }

sig { override.params(type: T::Class[T.anything]).returns(T::Boolean) }
sig { override.params(type: Field::Type).returns(T::Boolean) }
def used_for_type?(type)
type == Float
end
Expand Down
2 changes: 1 addition & 1 deletion lib/typed/coercion/integer_coercer.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ class IntegerCoercer < Coercer

Target = type_member { {fixed: Integer} }

sig { override.params(type: T::Class[T.anything]).returns(T::Boolean) }
sig { override.params(type: Field::Type).returns(T::Boolean) }
def used_for_type?(type)
type == Integer
end
Expand Down
2 changes: 1 addition & 1 deletion lib/typed/coercion/string_coercer.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ class StringCoercer < Coercer

Target = type_member { {fixed: String} }

sig { override.params(type: T::Class[T.anything]).returns(T::Boolean) }
sig { override.params(type: Field::Type).returns(T::Boolean) }
def used_for_type?(type)
type == String
end
Expand Down
6 changes: 3 additions & 3 deletions lib/typed/coercion/struct_coercer.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,16 +7,16 @@ class StructCoercer < Coercer

Target = type_member { {fixed: T::Struct} }

sig { override.params(type: T::Class[T.anything]).returns(T::Boolean) }
sig { override.params(type: Field::Type).returns(T::Boolean) }
def used_for_type?(type)
!!(type < T::Struct)
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

return Failure.new(CoercionError.new("Field type must inherit from T::Struct for Struct coercion.")) unless type < T::Struct
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)

Success.new(type.from_hash!(HashTransformer.new.deep_stringify_keys(value)))
Expand Down
11 changes: 10 additions & 1 deletion lib/typed/field.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,10 @@ class Field < T::Struct

include ActsAsComparable

Type = T.type_alias { T.any(T::Class[T.anything], T::Types::Base) }

const :name, Symbol
const :type, T::Class[T.anything]
const :type, Type
const :required, T::Boolean, default: true

sig { returns(T::Boolean) }
Expand All @@ -24,5 +26,12 @@ def optional?
def validate(value)
Validations::FieldTypeValidator.new.validate(field: self, value: value)
end

sig { params(value: Value).returns(T::Boolean) }
def works_with?(value)
value.class == type || T.cast(type, T::Types::Base).valid?(value)
rescue TypeError
false
end
end
end
6 changes: 2 additions & 4 deletions lib/typed/serializer.rb
Original file line number Diff line number Diff line change
Expand Up @@ -35,18 +35,16 @@ def deserialize_from_creation_params(creation_params)
results = schema.fields.map do |field|
value = creation_params[field.name]

if value.nil?
if value.nil? || field.works_with?(value)
field.validate(value)
elsif value.class != field.type
else
coercion_result = Coercion.coerce(field: field, value: value)

if coercion_result.success?
field.validate(coercion_result.payload)
else
Failure.new(Validations::ValidationError.new(coercion_result.error.message))
end
else
field.validate(value)
end
end

Expand Down
2 changes: 1 addition & 1 deletion lib/typed/validations/field_type_validator.rb
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ class FieldTypeValidator

sig { override.params(field: Field, value: Value).returns(ValidationResult) }
def validate(field:, value:)
if field.type == value.class
if field.works_with?(value)
Success.new(ValidatedValue.new(name: field.name, value: value))
elsif field.required? && value.nil?
Failure.new(RequiredFieldError.new(field_name: field.name))
Expand Down
2 changes: 1 addition & 1 deletion lib/typed/validations/type_mismatch_error.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ module Validations
class TypeMismatchError < ValidationError
extend T::Sig

sig { params(field_name: Symbol, field_type: T::Class[T.anything], given_type: T::Class[T.anything]).void }
sig { params(field_name: Symbol, field_type: Field::Type, given_type: T::Class[T.anything]).void }
def initialize(field_name:, field_type:, given_type:)
super("Invalid type given to #{field_name}. Expected #{field_type}, got #{given_type}.")
end
Expand Down
2 changes: 1 addition & 1 deletion test/support/simple_string_coercer.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ class SimpleStringCoercer < Typed::Coercion::Coercer

Target = type_member { {fixed: String} }

sig { override.params(type: T::Class[T.anything]).returns(T::Boolean) }
sig { override.params(type: Typed::Field::Type).returns(T::Boolean) }
def used_for_type?(type)
type == String
end
Expand Down
13 changes: 13 additions & 0 deletions test/typed/field_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -17,4 +17,17 @@ def test_required_and_optional_helpers_work_when_optional
assert_predicate(@optional_field, :optional?)
refute_predicate(@optional_field, :required?)
end

def test_when_standard_type_work_with_works
assert(@required_field.works_with?("Max"))
refute(@required_field.works_with?(1))
end

def test_when_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
end

0 comments on commit 9ef1cd5

Please sign in to comment.