From 9ef1cd5caf09396d8dae6a4db1feeb4f88dd25e9 Mon Sep 17 00:00:00 2001 From: Max VelDink Date: Wed, 13 Mar 2024 15:52:03 -0400 Subject: [PATCH] refactor!: Update Field's types to a support Sorbets T::Types::Base classes --- lib/typed/coercion/coercer.rb | 2 +- lib/typed/coercion/coercer_registry.rb | 2 +- lib/typed/coercion/enum_coercer.rb | 6 +++--- lib/typed/coercion/float_coercer.rb | 2 +- lib/typed/coercion/integer_coercer.rb | 2 +- lib/typed/coercion/string_coercer.rb | 2 +- lib/typed/coercion/struct_coercer.rb | 6 +++--- lib/typed/field.rb | 11 ++++++++++- lib/typed/serializer.rb | 6 ++---- lib/typed/validations/field_type_validator.rb | 2 +- lib/typed/validations/type_mismatch_error.rb | 2 +- test/support/simple_string_coercer.rb | 2 +- test/typed/field_test.rb | 13 +++++++++++++ 13 files changed, 39 insertions(+), 19 deletions(-) diff --git a/lib/typed/coercion/coercer.rb b/lib/typed/coercion/coercer.rb index 0d50345..c7776e2 100644 --- a/lib/typed/coercion/coercer.rb +++ b/lib/typed/coercion/coercer.rb @@ -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 diff --git a/lib/typed/coercion/coercer_registry.rb b/lib/typed/coercion/coercer_registry.rb index 2f8cda2..569702f 100644 --- a/lib/typed/coercion/coercer_registry.rb +++ b/lib/typed/coercion/coercer_registry.rb @@ -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 diff --git a/lib/typed/coercion/enum_coercer.rb b/lib/typed/coercion/enum_coercer.rb index c0537c5..c0070e8 100644 --- a/lib/typed/coercion/enum_coercer.rb +++ b/lib/typed/coercion/enum_coercer.rb @@ -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 diff --git a/lib/typed/coercion/float_coercer.rb b/lib/typed/coercion/float_coercer.rb index 526637f..fb0db82 100644 --- a/lib/typed/coercion/float_coercer.rb +++ b/lib/typed/coercion/float_coercer.rb @@ -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 diff --git a/lib/typed/coercion/integer_coercer.rb b/lib/typed/coercion/integer_coercer.rb index 6a24396..faa4fd1 100644 --- a/lib/typed/coercion/integer_coercer.rb +++ b/lib/typed/coercion/integer_coercer.rb @@ -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 diff --git a/lib/typed/coercion/string_coercer.rb b/lib/typed/coercion/string_coercer.rb index 29e8b41..73c9e71 100644 --- a/lib/typed/coercion/string_coercer.rb +++ b/lib/typed/coercion/string_coercer.rb @@ -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 diff --git a/lib/typed/coercion/struct_coercer.rb b/lib/typed/coercion/struct_coercer.rb index 9b14d9f..be51421 100644 --- a/lib/typed/coercion/struct_coercer.rb +++ b/lib/typed/coercion/struct_coercer.rb @@ -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))) diff --git a/lib/typed/field.rb b/lib/typed/field.rb index 80ffdac..88349b0 100644 --- a/lib/typed/field.rb +++ b/lib/typed/field.rb @@ -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) } @@ -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 diff --git a/lib/typed/serializer.rb b/lib/typed/serializer.rb index 67fbb23..3d4471b 100644 --- a/lib/typed/serializer.rb +++ b/lib/typed/serializer.rb @@ -35,9 +35,9 @@ 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? @@ -45,8 +45,6 @@ def deserialize_from_creation_params(creation_params) else Failure.new(Validations::ValidationError.new(coercion_result.error.message)) end - else - field.validate(value) end end diff --git a/lib/typed/validations/field_type_validator.rb b/lib/typed/validations/field_type_validator.rb index ffda964..3148761 100644 --- a/lib/typed/validations/field_type_validator.rb +++ b/lib/typed/validations/field_type_validator.rb @@ -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)) diff --git a/lib/typed/validations/type_mismatch_error.rb b/lib/typed/validations/type_mismatch_error.rb index 99ca072..8970b77 100644 --- a/lib/typed/validations/type_mismatch_error.rb +++ b/lib/typed/validations/type_mismatch_error.rb @@ -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 diff --git a/test/support/simple_string_coercer.rb b/test/support/simple_string_coercer.rb index 8a49917..496b5fc 100644 --- a/test/support/simple_string_coercer.rb +++ b/test/support/simple_string_coercer.rb @@ -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 diff --git a/test/typed/field_test.rb b/test/typed/field_test.rb index 11fc1b3..ba7345c 100644 --- a/test/typed/field_test.rb +++ b/test/typed/field_test.rb @@ -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