sorbet-schema 0.2.2 → 0.4.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +38 -0
- data/Gemfile.lock +1 -1
- data/lib/sorbet-schema/hash_transformer.rb +27 -2
- data/lib/sorbet-schema/version.rb +1 -1
- data/lib/typed/coercion/boolean_coercer.rb +31 -0
- data/lib/typed/coercion/coercer.rb +3 -3
- data/lib/typed/coercion/coercer_registry.rb +13 -2
- data/lib/typed/coercion/enum_coercer.rb +25 -0
- data/lib/typed/coercion/float_coercer.rb +4 -4
- data/lib/typed/coercion/integer_coercer.rb +3 -3
- data/lib/typed/coercion/string_coercer.rb +3 -3
- data/lib/typed/coercion/struct_coercer.rb +7 -7
- data/lib/typed/coercion/typed_array_coercer.rb +34 -0
- data/lib/typed/coercion.rb +4 -4
- data/lib/typed/deserialize_error.rb +1 -1
- data/lib/typed/field.rb +10 -1
- data/lib/typed/hash_serializer.rb +20 -6
- data/lib/typed/json_serializer.rb +4 -2
- data/lib/typed/schema.rb +21 -0
- data/lib/typed/serialization_error.rb +17 -0
- data/lib/typed/serialize_error.rb +6 -0
- data/lib/typed/serializer.rb +5 -7
- data/lib/typed/validations/field_type_validator.rb +1 -1
- data/lib/typed/validations/type_mismatch_error.rb +1 -1
- metadata +7 -3
- data/lib/sorbet-schema/struct_ext.rb +0 -37
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 9af17198e776435ac97075ce33b365c34fd539aa7998ca9f60f9eef1a57ddd95
|
4
|
+
data.tar.gz: 8afbc08bc51a6cdc849803273af0c44e9f8a86e04991d9cd70767e565b3c68ca
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 260e1ee1603ef6ae6ddd5cc20544a874a097b65f95736e42dc97f489b979df5a6a41d48124d2a5f5c2643e9f0989de0e67ed7a7f787adabf39fd12168bc05b65
|
7
|
+
data.tar.gz: 94836f2ea97f2c4ac158562f2a6b9ffdd05d4ad2a0f34423e77d42b989c894caf08e6fdc28693491d8903d826aa3da860a4d85ceb14ba37b0a448542a41b51e7
|
data/CHANGELOG.md
CHANGED
@@ -4,6 +4,44 @@ All notable changes to this project will be documented in this file.
|
|
4
4
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
5
5
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
6
6
|
|
7
|
+
## [0.4.0](https://github.com/maxveldink/sorbet-schema/compare/v0.3.0...v0.4.0) (2024-03-14)
|
8
|
+
|
9
|
+
|
10
|
+
### ⚠ BREAKING CHANGES
|
11
|
+
|
12
|
+
* Have coercers take in a type instead of the full field
|
13
|
+
* Update Field's types to a support Sorbets T::Types::Base classes
|
14
|
+
* Changed serialize return value to a Result
|
15
|
+
* adds SerializationError ancestor
|
16
|
+
|
17
|
+
### Features
|
18
|
+
|
19
|
+
* Add BooleanCoercer ([acd220f](https://github.com/maxveldink/sorbet-schema/commit/acd220f55fe0ebc823de6cc43776a1f510a4acd0))
|
20
|
+
* Add EnumCoercer ([5c0e2b5](https://github.com/maxveldink/sorbet-schema/commit/5c0e2b51990dfb72218129b0935f881622324194))
|
21
|
+
* Add from_hash and from_json helpers to Schemas ([#44](https://github.com/maxveldink/sorbet-schema/issues/44)) ([55c2da7](https://github.com/maxveldink/sorbet-schema/commit/55c2da77b7c2636339c684560bf15c51ff3ff4b1))
|
22
|
+
* Add idempotency to Struct coercer ([3a42957](https://github.com/maxveldink/sorbet-schema/commit/3a42957836afee55b074fc5c8a41eac6d31ada70))
|
23
|
+
* Add option to serialize values to HashSerializer ([710d365](https://github.com/maxveldink/sorbet-schema/commit/710d365d477bd6c6bff20929fcdcc1d1cc95bdb3))
|
24
|
+
* Adds TypedArray coercer ([795ddd9](https://github.com/maxveldink/sorbet-schema/commit/795ddd97f05502b154d4cb165878c1f78935cb3c))
|
25
|
+
|
26
|
+
|
27
|
+
### Code Refactoring
|
28
|
+
|
29
|
+
* adds SerializationError ancestor ([f8ea753](https://github.com/maxveldink/sorbet-schema/commit/f8ea75304613cbab17f698006698a2524a44b538))
|
30
|
+
* Changed serialize return value to a Result ([948c678](https://github.com/maxveldink/sorbet-schema/commit/948c67815dab19793dd2f321a90797d123740e0e))
|
31
|
+
* Have coercers take in a type instead of the full field ([c06169e](https://github.com/maxveldink/sorbet-schema/commit/c06169e0fa1cf7c8f645ecabef19cc2f5facffe4))
|
32
|
+
* Update Field's types to a support Sorbets T::Types::Base classes ([9ef1cd5](https://github.com/maxveldink/sorbet-schema/commit/9ef1cd5caf09396d8dae6a4db1feeb4f88dd25e9))
|
33
|
+
|
34
|
+
## [0.3.0](https://github.com/maxveldink/sorbet-schema/compare/v0.2.2...v0.3.0) (2024-03-12)
|
35
|
+
|
36
|
+
|
37
|
+
### ⚠ BREAKING CHANGES
|
38
|
+
|
39
|
+
* Remove struct_ext for now
|
40
|
+
|
41
|
+
### Code Refactoring
|
42
|
+
|
43
|
+
* Remove struct_ext for now ([4a505cb](https://github.com/maxveldink/sorbet-schema/commit/4a505cba47b7fd0ae96d76a543f97228a3cd00d7))
|
44
|
+
|
7
45
|
## [0.2.2](https://github.com/maxveldink/sorbet-schema/compare/v0.2.1...v0.2.2) (2024-03-12)
|
8
46
|
|
9
47
|
|
data/Gemfile.lock
CHANGED
@@ -6,17 +6,42 @@
|
|
6
6
|
class HashTransformer
|
7
7
|
extend T::Sig
|
8
8
|
|
9
|
+
sig { params(should_serialize_values: T::Boolean).void }
|
10
|
+
def initialize(should_serialize_values: false)
|
11
|
+
@should_serialize_values = should_serialize_values
|
12
|
+
end
|
13
|
+
|
9
14
|
sig { params(hash: T::Hash[T.untyped, T.untyped]).returns(T::Hash[Symbol, T.untyped]) }
|
10
15
|
def deep_symbolize_keys(hash)
|
11
16
|
hash.each_with_object({}) do |(key, value), result|
|
12
|
-
result[key.to_sym] = value
|
17
|
+
result[key.to_sym] = transform_value(value, hash_transformation_method: :deep_symbolize_keys)
|
13
18
|
end
|
14
19
|
end
|
15
20
|
|
16
21
|
sig { params(hash: T::Hash[T.untyped, T.untyped]).returns(T::Hash[String, T.untyped]) }
|
17
22
|
def deep_stringify_keys(hash)
|
18
23
|
hash.each_with_object({}) do |(key, value), result|
|
19
|
-
result[key.to_s] = value
|
24
|
+
result[key.to_s] = transform_value(value, hash_transformation_method: :deep_stringify_keys)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
private
|
29
|
+
|
30
|
+
sig { returns(T::Boolean) }
|
31
|
+
attr_reader :should_serialize_values
|
32
|
+
|
33
|
+
sig { params(value: T.untyped, hash_transformation_method: Symbol).returns(T.untyped) }
|
34
|
+
def transform_value(value, hash_transformation_method:)
|
35
|
+
if value.is_a?(Hash)
|
36
|
+
send(hash_transformation_method, value)
|
37
|
+
elsif value.is_a?(Array)
|
38
|
+
value.map { |inner_val| transform_value(inner_val, hash_transformation_method: hash_transformation_method) }
|
39
|
+
elsif value.is_a?(T::Struct) && should_serialize_values
|
40
|
+
deep_symbolize_keys(value.serialize)
|
41
|
+
elsif value.respond_to?(:serialize) && should_serialize_values
|
42
|
+
value.serialize
|
43
|
+
else
|
44
|
+
value
|
20
45
|
end
|
21
46
|
end
|
22
47
|
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
# typed: strict
|
2
|
+
|
3
|
+
module Typed
|
4
|
+
module Coercion
|
5
|
+
class BooleanCoercer < Coercer
|
6
|
+
extend T::Generic
|
7
|
+
|
8
|
+
Target = type_member { {fixed: T::Boolean} }
|
9
|
+
|
10
|
+
sig { override.params(type: Field::Type).returns(T::Boolean) }
|
11
|
+
def used_for_type?(type)
|
12
|
+
type == T::Utils.coerce(T::Boolean)
|
13
|
+
end
|
14
|
+
|
15
|
+
sig { override.params(type: Field::Type, value: Value).returns(Result[Target, CoercionError]) }
|
16
|
+
def coerce(type:, value:)
|
17
|
+
if T.cast(type, T::Types::Base).recursively_valid?(value)
|
18
|
+
Success.new(value)
|
19
|
+
elsif value == "true"
|
20
|
+
Success.new(true)
|
21
|
+
elsif value == "false"
|
22
|
+
Success.new(false)
|
23
|
+
else
|
24
|
+
Failure.new(CoercionError.new)
|
25
|
+
end
|
26
|
+
rescue TypeError
|
27
|
+
Failure.new(CoercionError.new("Field type must be a T::Boolean."))
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -10,12 +10,12 @@ module Typed
|
|
10
10
|
|
11
11
|
Target = type_member(:out)
|
12
12
|
|
13
|
-
sig { abstract.params(type:
|
13
|
+
sig { abstract.params(type: Field::Type).returns(T::Boolean) }
|
14
14
|
def used_for_type?(type)
|
15
15
|
end
|
16
16
|
|
17
|
-
sig { abstract.params(
|
18
|
-
def coerce(
|
17
|
+
sig { abstract.params(type: Field::Type, value: Value).returns(Result[Target, CoercionError]) }
|
18
|
+
def coerce(type:, value:)
|
19
19
|
end
|
20
20
|
end
|
21
21
|
end
|
@@ -11,7 +11,18 @@ module Typed
|
|
11
11
|
|
12
12
|
Registry = T.type_alias { T::Array[T.class_of(Coercer)] }
|
13
13
|
|
14
|
-
DEFAULT_COERCERS = T.let(
|
14
|
+
DEFAULT_COERCERS = T.let(
|
15
|
+
[
|
16
|
+
StringCoercer,
|
17
|
+
BooleanCoercer,
|
18
|
+
IntegerCoercer,
|
19
|
+
FloatCoercer,
|
20
|
+
EnumCoercer,
|
21
|
+
StructCoercer,
|
22
|
+
TypedArrayCoercer
|
23
|
+
],
|
24
|
+
Registry
|
25
|
+
)
|
15
26
|
|
16
27
|
sig { void }
|
17
28
|
def initialize
|
@@ -28,7 +39,7 @@ module Typed
|
|
28
39
|
@available = DEFAULT_COERCERS.clone
|
29
40
|
end
|
30
41
|
|
31
|
-
sig { params(type:
|
42
|
+
sig { params(type: Field::Type).returns(T.nilable(T.class_of(Coercer))) }
|
32
43
|
def select_coercer_by(type:)
|
33
44
|
@available.find { |coercer| coercer.new.used_for_type?(type) }
|
34
45
|
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
# typed: strict
|
2
|
+
|
3
|
+
module Typed
|
4
|
+
module Coercion
|
5
|
+
class EnumCoercer < Coercer
|
6
|
+
extend T::Generic
|
7
|
+
|
8
|
+
Target = type_member { {fixed: T::Enum} }
|
9
|
+
|
10
|
+
sig { override.params(type: Field::Type).returns(T::Boolean) }
|
11
|
+
def used_for_type?(type)
|
12
|
+
type.is_a?(Class) && !!(type < T::Enum)
|
13
|
+
end
|
14
|
+
|
15
|
+
sig { override.params(type: Field::Type, value: Value).returns(Result[Target, CoercionError]) }
|
16
|
+
def coerce(type:, value:)
|
17
|
+
return Failure.new(CoercionError.new("Field type must inherit from T::Enum for Enum coercion.")) unless type.is_a?(Class) && !!(type < T::Enum)
|
18
|
+
|
19
|
+
Success.new(type.from_serialized(value))
|
20
|
+
rescue KeyError => e
|
21
|
+
Failure.new(CoercionError.new(e.message))
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -7,13 +7,13 @@ module Typed
|
|
7
7
|
|
8
8
|
Target = type_member { {fixed: Float} }
|
9
9
|
|
10
|
-
sig { override.params(type:
|
10
|
+
sig { override.params(type: Field::Type).returns(T::Boolean) }
|
11
11
|
def used_for_type?(type)
|
12
|
-
type == Float
|
12
|
+
T::Utils.coerce(type) == T::Utils.coerce(Float)
|
13
13
|
end
|
14
14
|
|
15
|
-
sig { override.params(
|
16
|
-
def coerce(
|
15
|
+
sig { override.params(type: Field::Type, value: Value).returns(Result[Target, CoercionError]) }
|
16
|
+
def coerce(type:, value:)
|
17
17
|
Success.new(Float(value))
|
18
18
|
rescue ArgumentError, TypeError
|
19
19
|
Failure.new(CoercionError.new("'#{value}' cannot be coerced into Float."))
|
@@ -7,13 +7,13 @@ module Typed
|
|
7
7
|
|
8
8
|
Target = type_member { {fixed: Integer} }
|
9
9
|
|
10
|
-
sig { override.params(type:
|
10
|
+
sig { override.params(type: Field::Type).returns(T::Boolean) }
|
11
11
|
def used_for_type?(type)
|
12
12
|
type == Integer
|
13
13
|
end
|
14
14
|
|
15
|
-
sig { override.params(
|
16
|
-
def coerce(
|
15
|
+
sig { override.params(type: Field::Type, value: Value).returns(Result[Target, CoercionError]) }
|
16
|
+
def coerce(type:, value:)
|
17
17
|
Success.new(Integer(value))
|
18
18
|
rescue ArgumentError, TypeError
|
19
19
|
Failure.new(CoercionError.new("'#{value}' cannot be coerced into Integer."))
|
@@ -7,13 +7,13 @@ module Typed
|
|
7
7
|
|
8
8
|
Target = type_member { {fixed: String} }
|
9
9
|
|
10
|
-
sig { override.params(type:
|
10
|
+
sig { override.params(type: Field::Type).returns(T::Boolean) }
|
11
11
|
def used_for_type?(type)
|
12
12
|
type == String
|
13
13
|
end
|
14
14
|
|
15
|
-
sig { override.params(
|
16
|
-
def coerce(
|
15
|
+
sig { override.params(type: Field::Type, value: Value).returns(Result[Target, CoercionError]) }
|
16
|
+
def coerce(type:, value:)
|
17
17
|
Success.new(String(value))
|
18
18
|
end
|
19
19
|
end
|
@@ -7,17 +7,17 @@ module Typed
|
|
7
7
|
|
8
8
|
Target = type_member { {fixed: T::Struct} }
|
9
9
|
|
10
|
-
sig { override.params(type:
|
10
|
+
sig { override.params(type: Field::Type).returns(T::Boolean) }
|
11
11
|
def used_for_type?(type)
|
12
|
-
!!(type < T::Struct)
|
12
|
+
type.is_a?(Class) && !!(type < T::Struct)
|
13
13
|
end
|
14
14
|
|
15
|
-
sig { override.params(
|
16
|
-
def coerce(
|
17
|
-
type
|
15
|
+
sig { override.params(type: Field::Type, value: Value).returns(Result[Target, CoercionError]) }
|
16
|
+
def coerce(type:, value:)
|
17
|
+
return Failure.new(CoercionError.new("Field type must inherit from T::Struct for Struct coercion.")) unless type.is_a?(Class) && type < T::Struct
|
18
|
+
return Success.new(value) if value.instance_of?(type)
|
18
19
|
|
19
|
-
return Failure.new(CoercionError.new("
|
20
|
-
return Failure.new(CoercionError.new("Value must be a Hash for Struct coercion.")) unless value.is_a?(Hash)
|
20
|
+
return Failure.new(CoercionError.new("Value of type '#{value.class}' cannot be coerced to #{type} Struct.")) unless value.is_a?(Hash)
|
21
21
|
|
22
22
|
Success.new(type.from_hash!(HashTransformer.new.deep_stringify_keys(value)))
|
23
23
|
rescue ArgumentError => e
|
@@ -0,0 +1,34 @@
|
|
1
|
+
# typed: strict
|
2
|
+
|
3
|
+
module Typed
|
4
|
+
module Coercion
|
5
|
+
class TypedArrayCoercer < Coercer
|
6
|
+
extend T::Generic
|
7
|
+
|
8
|
+
Target = type_member { {fixed: T::Array[T.untyped]} }
|
9
|
+
|
10
|
+
sig { override.params(type: Field::Type).returns(T::Boolean) }
|
11
|
+
def used_for_type?(type)
|
12
|
+
type.is_a?(T::Types::TypedArray)
|
13
|
+
end
|
14
|
+
|
15
|
+
sig { override.params(type: Field::Type, value: Value).returns(Result[Target, CoercionError]) }
|
16
|
+
def coerce(type:, value:)
|
17
|
+
return Failure.new(CoercionError.new("Field type must be a T::Array.")) unless type.is_a?(T::Types::TypedArray)
|
18
|
+
return Failure.new(CoercionError.new("Value must be an Array.")) unless value.is_a?(Array)
|
19
|
+
|
20
|
+
return Success.new(value) if type.recursively_valid?(value)
|
21
|
+
|
22
|
+
coerced_results = value.map do |item|
|
23
|
+
Coercion.coerce(type: type.type.raw_type, value: item)
|
24
|
+
end
|
25
|
+
|
26
|
+
if coerced_results.all?(&:success?)
|
27
|
+
Success.new(coerced_results.map(&:payload))
|
28
|
+
else
|
29
|
+
Failure.new(CoercionError.new(coerced_results.select(&:failure?).map(&:error).map(&:message).join(" | ")))
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
data/lib/typed/coercion.rb
CHANGED
@@ -9,13 +9,13 @@ module Typed
|
|
9
9
|
CoercerRegistry.instance.register(coercer)
|
10
10
|
end
|
11
11
|
|
12
|
-
sig { type_parameters(:U).params(
|
13
|
-
def self.coerce(
|
14
|
-
coercer = CoercerRegistry.instance.select_coercer_by(type:
|
12
|
+
sig { type_parameters(:U).params(type: Field::Type, value: Value).returns(Result[Value, CoercionError]) }
|
13
|
+
def self.coerce(type:, value:)
|
14
|
+
coercer = CoercerRegistry.instance.select_coercer_by(type: type)
|
15
15
|
|
16
16
|
return Failure.new(CoercionNotSupportedError.new) unless coercer
|
17
17
|
|
18
|
-
coercer.new.coerce(
|
18
|
+
coercer.new.coerce(type: type, value: value)
|
19
19
|
end
|
20
20
|
end
|
21
21
|
end
|
data/lib/typed/field.rb
CHANGED
@@ -6,8 +6,10 @@ module Typed
|
|
6
6
|
|
7
7
|
include ActsAsComparable
|
8
8
|
|
9
|
+
Type = T.type_alias { T.any(T::Class[T.anything], T::Types::Base) }
|
10
|
+
|
9
11
|
const :name, Symbol
|
10
|
-
const :type,
|
12
|
+
const :type, Type
|
11
13
|
const :required, T::Boolean, default: true
|
12
14
|
|
13
15
|
sig { returns(T::Boolean) }
|
@@ -24,5 +26,12 @@ module Typed
|
|
24
26
|
def validate(value)
|
25
27
|
Validations::FieldTypeValidator.new.validate(field: self, value: value)
|
26
28
|
end
|
29
|
+
|
30
|
+
sig { params(value: Value).returns(T::Boolean) }
|
31
|
+
def works_with?(value)
|
32
|
+
value.class == type || T.cast(type, T::Types::Base).recursively_valid?(value) # standard:disable Style/ClassEqualityComparison
|
33
|
+
rescue TypeError
|
34
|
+
false
|
35
|
+
end
|
27
36
|
end
|
28
37
|
end
|
@@ -3,19 +3,33 @@
|
|
3
3
|
module Typed
|
4
4
|
class HashSerializer < Serializer
|
5
5
|
InputHash = T.type_alias { T::Hash[T.any(Symbol, String), T.untyped] }
|
6
|
-
OutputHash = T.type_alias { Params }
|
7
|
-
|
8
6
|
Input = type_member { {fixed: InputHash} }
|
9
|
-
Output = type_member { {fixed:
|
7
|
+
Output = type_member { {fixed: Params} }
|
8
|
+
|
9
|
+
sig { params(schema: Schema, should_serialize_values: T::Boolean).void }
|
10
|
+
def initialize(schema:, should_serialize_values: false)
|
11
|
+
@should_serialize_values = should_serialize_values
|
12
|
+
|
13
|
+
super(schema: schema)
|
14
|
+
end
|
10
15
|
|
11
16
|
sig { override.params(source: Input).returns(Result[T::Struct, DeserializeError]) }
|
12
17
|
def deserialize(source)
|
13
|
-
deserialize_from_creation_params(HashTransformer.new.deep_symbolize_keys(source))
|
18
|
+
deserialize_from_creation_params(HashTransformer.new(should_serialize_values: should_serialize_values).deep_symbolize_keys(source))
|
14
19
|
end
|
15
20
|
|
16
|
-
sig { override.params(struct: T::Struct).returns(Output) }
|
21
|
+
sig { override.params(struct: T::Struct).returns(Result[Output, SerializeError]) }
|
17
22
|
def serialize(struct)
|
18
|
-
|
23
|
+
return Failure.new(SerializeError.new("'#{struct.class}' cannot be serialized to target type of '#{schema.target}'.")) if struct.class != schema.target
|
24
|
+
|
25
|
+
hsh = schema.fields.each_with_object({}) { |field, hsh| hsh[field.name] = struct.send(field.name) }
|
26
|
+
|
27
|
+
Success.new(HashTransformer.new(should_serialize_values: should_serialize_values).deep_symbolize_keys(hsh.compact))
|
19
28
|
end
|
29
|
+
|
30
|
+
private
|
31
|
+
|
32
|
+
sig { returns(T::Boolean) }
|
33
|
+
attr_reader :should_serialize_values
|
20
34
|
end
|
21
35
|
end
|
@@ -20,9 +20,11 @@ module Typed
|
|
20
20
|
Failure.new(ParseError.new(format: :json))
|
21
21
|
end
|
22
22
|
|
23
|
-
sig { override.params(struct: T::Struct).returns(Output) }
|
23
|
+
sig { override.params(struct: T::Struct).returns(Result[Output, SerializeError]) }
|
24
24
|
def serialize(struct)
|
25
|
-
|
25
|
+
return Failure.new(SerializeError.new("'#{struct.class}' cannot be serialized to target type of '#{schema.target}'.")) if struct.class != schema.target
|
26
|
+
|
27
|
+
Success.new(JSON.generate(struct.serialize))
|
26
28
|
end
|
27
29
|
end
|
28
30
|
end
|
data/lib/typed/schema.rb
CHANGED
@@ -2,9 +2,30 @@
|
|
2
2
|
|
3
3
|
module Typed
|
4
4
|
class Schema < T::Struct
|
5
|
+
extend T::Sig
|
5
6
|
include ActsAsComparable
|
6
7
|
|
7
8
|
const :fields, T::Array[Field], default: []
|
8
9
|
const :target, T.class_of(T::Struct)
|
10
|
+
|
11
|
+
sig { params(struct: T.class_of(T::Struct)).returns(Typed::Schema) }
|
12
|
+
def self.from_struct(struct)
|
13
|
+
Typed::Schema.new(
|
14
|
+
target: struct,
|
15
|
+
fields: struct.props.map do |name, properties|
|
16
|
+
Typed::Field.new(name: name, type: properties[:type], required: !properties[:fully_optional])
|
17
|
+
end
|
18
|
+
)
|
19
|
+
end
|
20
|
+
|
21
|
+
sig { params(hash: Typed::HashSerializer::InputHash).returns(Typed::Serializer::DeserializeResult) }
|
22
|
+
def from_hash(hash)
|
23
|
+
Typed::HashSerializer.new(schema: self).deserialize(hash)
|
24
|
+
end
|
25
|
+
|
26
|
+
sig { params(json: String).returns(Typed::Serializer::DeserializeResult) }
|
27
|
+
def from_json(json)
|
28
|
+
Typed::JSONSerializer.new(schema: self).deserialize(json)
|
29
|
+
end
|
9
30
|
end
|
10
31
|
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
# typed: strict
|
2
|
+
|
3
|
+
module Typed
|
4
|
+
class SerializationError < StandardError
|
5
|
+
extend T::Sig
|
6
|
+
|
7
|
+
sig { returns({error: String}) }
|
8
|
+
def to_h
|
9
|
+
{error: message}
|
10
|
+
end
|
11
|
+
|
12
|
+
sig { params(_options: T::Hash[T.untyped, T.untyped]).returns(String) }
|
13
|
+
def to_json(_options = {})
|
14
|
+
JSON.generate(to_h)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
data/lib/typed/serializer.rb
CHANGED
@@ -20,11 +20,11 @@ module Typed
|
|
20
20
|
@schema = schema
|
21
21
|
end
|
22
22
|
|
23
|
-
sig { abstract.params(source:
|
23
|
+
sig { abstract.params(source: Input).returns(DeserializeResult) }
|
24
24
|
def deserialize(source)
|
25
25
|
end
|
26
26
|
|
27
|
-
sig { abstract.params(struct: T::Struct).returns(Output) }
|
27
|
+
sig { abstract.params(struct: T::Struct).returns(Result[Output, SerializeError]) }
|
28
28
|
def serialize(struct)
|
29
29
|
end
|
30
30
|
|
@@ -35,18 +35,16 @@ module Typed
|
|
35
35
|
results = schema.fields.map do |field|
|
36
36
|
value = creation_params[field.name]
|
37
37
|
|
38
|
-
if value.nil?
|
38
|
+
if value.nil? || field.works_with?(value)
|
39
39
|
field.validate(value)
|
40
|
-
|
41
|
-
coercion_result = Coercion.coerce(
|
40
|
+
else
|
41
|
+
coercion_result = Coercion.coerce(type: field.type, value: value)
|
42
42
|
|
43
43
|
if coercion_result.success?
|
44
44
|
field.validate(coercion_result.payload)
|
45
45
|
else
|
46
46
|
Failure.new(Validations::ValidationError.new(coercion_result.error.message))
|
47
47
|
end
|
48
|
-
else
|
49
|
-
field.validate(value)
|
50
48
|
end
|
51
49
|
end
|
52
50
|
|
@@ -9,7 +9,7 @@ module Typed
|
|
9
9
|
|
10
10
|
sig { override.params(field: Field, value: Value).returns(ValidationResult) }
|
11
11
|
def validate(field:, value:)
|
12
|
-
if field.
|
12
|
+
if field.works_with?(value)
|
13
13
|
Success.new(ValidatedValue.new(name: field.name, value: value))
|
14
14
|
elsif field.required? && value.nil?
|
15
15
|
Failure.new(RequiredFieldError.new(field_name: field.name))
|
@@ -5,7 +5,7 @@ module Typed
|
|
5
5
|
class TypeMismatchError < ValidationError
|
6
6
|
extend T::Sig
|
7
7
|
|
8
|
-
sig { params(field_name: Symbol, field_type:
|
8
|
+
sig { params(field_name: Symbol, field_type: Field::Type, given_type: T::Class[T.anything]).void }
|
9
9
|
def initialize(field_name:, field_type:, given_type:)
|
10
10
|
super("Invalid type given to #{field_name}. Expected #{field_type}, got #{given_type}.")
|
11
11
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: sorbet-schema
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.4.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Max VelDink
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2024-03-
|
11
|
+
date: 2024-03-14 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: sorbet-result
|
@@ -85,23 +85,27 @@ files:
|
|
85
85
|
- Rakefile
|
86
86
|
- lib/sorbet-schema.rb
|
87
87
|
- lib/sorbet-schema/hash_transformer.rb
|
88
|
-
- lib/sorbet-schema/struct_ext.rb
|
89
88
|
- lib/sorbet-schema/version.rb
|
90
89
|
- lib/typed/coercion.rb
|
90
|
+
- lib/typed/coercion/boolean_coercer.rb
|
91
91
|
- lib/typed/coercion/coercer.rb
|
92
92
|
- lib/typed/coercion/coercer_registry.rb
|
93
93
|
- lib/typed/coercion/coercion_error.rb
|
94
94
|
- lib/typed/coercion/coercion_not_supported_error.rb
|
95
|
+
- lib/typed/coercion/enum_coercer.rb
|
95
96
|
- lib/typed/coercion/float_coercer.rb
|
96
97
|
- lib/typed/coercion/integer_coercer.rb
|
97
98
|
- lib/typed/coercion/string_coercer.rb
|
98
99
|
- lib/typed/coercion/struct_coercer.rb
|
100
|
+
- lib/typed/coercion/typed_array_coercer.rb
|
99
101
|
- lib/typed/deserialize_error.rb
|
100
102
|
- lib/typed/field.rb
|
101
103
|
- lib/typed/hash_serializer.rb
|
102
104
|
- lib/typed/json_serializer.rb
|
103
105
|
- lib/typed/parse_error.rb
|
104
106
|
- lib/typed/schema.rb
|
107
|
+
- lib/typed/serialization_error.rb
|
108
|
+
- lib/typed/serialize_error.rb
|
105
109
|
- lib/typed/serializer.rb
|
106
110
|
- lib/typed/validations.rb
|
107
111
|
- lib/typed/validations/field_type_validator.rb
|
@@ -1,37 +0,0 @@
|
|
1
|
-
# typed: strict
|
2
|
-
|
3
|
-
module T
|
4
|
-
class Struct
|
5
|
-
extend T::Sig
|
6
|
-
|
7
|
-
sig { returns(Typed::Schema) }
|
8
|
-
def self.create_schema
|
9
|
-
Typed::Schema.new(
|
10
|
-
target: self,
|
11
|
-
fields: props.map do |name, properties|
|
12
|
-
Typed::Field.new(name: name, type: properties[:type], required: !properties[:fully_optional])
|
13
|
-
end
|
14
|
-
)
|
15
|
-
end
|
16
|
-
|
17
|
-
sig { params(hash: Typed::HashSerializer::InputHash).returns(Typed::Serializer::DeserializeResult) }
|
18
|
-
def self.from_hash(hash)
|
19
|
-
Typed::HashSerializer.new(schema: create_schema).deserialize(hash)
|
20
|
-
end
|
21
|
-
|
22
|
-
sig { params(json: String).returns(Typed::Serializer::DeserializeResult) }
|
23
|
-
def self.from_json(json)
|
24
|
-
Typed::JSONSerializer.new(schema: create_schema).deserialize(json)
|
25
|
-
end
|
26
|
-
|
27
|
-
sig { returns(Typed::HashSerializer::OutputHash) }
|
28
|
-
def to_hash
|
29
|
-
Typed::HashSerializer.new(schema: self.class.create_schema).serialize(self)
|
30
|
-
end
|
31
|
-
|
32
|
-
sig { params(_options: T::Hash[T.untyped, T.untyped]).returns(String) }
|
33
|
-
def to_json(_options = {})
|
34
|
-
Typed::JSONSerializer.new(schema: self.class.create_schema).serialize(self)
|
35
|
-
end
|
36
|
-
end
|
37
|
-
end
|