sorbet-schema 0.3.0 → 0.4.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +35 -0
- data/Gemfile.lock +17 -17
- 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 +14 -2
- data/lib/typed/coercion/date_coercer.rb +29 -0
- data/lib/typed/coercion/enum_coercer.rb +27 -0
- data/lib/typed/coercion/float_coercer.rb +6 -4
- data/lib/typed/coercion/integer_coercer.rb +6 -4
- data/lib/typed/coercion/string_coercer.rb +6 -4
- data/lib/typed/coercion/struct_coercer.rb +12 -10
- 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 +49 -5
- data/lib/typed/hash_serializer.rb +18 -6
- data/lib/typed/json_serializer.rb +4 -2
- data/lib/typed/schema.rb +10 -0
- data/lib/typed/serialization_error.rb +17 -0
- data/lib/typed/serialize_error.rb +6 -0
- data/lib/typed/serializer.rb +12 -7
- data/lib/typed/validations/field_type_validator.rb +1 -1
- data/lib/typed/validations/type_mismatch_error.rb +1 -1
- data/sorbet/rbi/gems/{minitest@5.22.2.rbi → minitest@5.22.3.rbi} +19 -18
- data/sorbet/rbi/gems/{rubocop-ast@1.31.1.rbi → rubocop-ast@1.31.2.rbi} +70 -70
- data/sorbet/rbi/gems/{rubocop@1.61.0.rbi → rubocop@1.62.1.rbi} +276 -233
- data/sorbet/rbi/gems/{standard@1.34.0.rbi → standard@1.35.1.rbi} +56 -56
- metadata +13 -7
- /data/sorbet/rbi/gems/{rubocop-sorbet@0.7.7.rbi → rubocop-sorbet@0.7.8.rbi} +0 -0
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: bfe8fbdabbb2b4f12be947525a1e6b36136a06ef0a3065f843e49aa990eb423a
|
4
|
+
data.tar.gz: eab4268bbdfcdd85ec57fe381b01e143d9c1cb21cf04d988d309e4e8f4282f36
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: c45aa65c93bf22858a0034bd66320a060e8b4fa3e693623fbd948d37dc348e232003e6c5d459b0dfc7a3621d82b74a6a799087061b5b146b4895ae9585edd0d8
|
7
|
+
data.tar.gz: 540114ebcb2c1f1c5fd3061d25401d77b7c30a36d57481732bf92928e01733342027b63d94b42ba1e188ec2546c42946be49c97c521b1eaf21819cdd6afe6a5d
|
data/CHANGELOG.md
CHANGED
@@ -4,6 +4,41 @@ 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.1](https://github.com/maxveldink/sorbet-schema/compare/v0.4.0...v0.4.1) (2024-03-21)
|
8
|
+
|
9
|
+
|
10
|
+
### Features
|
11
|
+
|
12
|
+
* Add DateCoercer ([ef8c1db](https://github.com/maxveldink/sorbet-schema/commit/ef8c1dbdf3bd87ab2e64102fbd1434811aa353d8))
|
13
|
+
* Add inline serializer to fields ([dd8042d](https://github.com/maxveldink/sorbet-schema/commit/dd8042d8d88d67d530c7619ee3cbb108957990d1))
|
14
|
+
|
15
|
+
## [0.4.0](https://github.com/maxveldink/sorbet-schema/compare/v0.3.0...v0.4.0) (2024-03-14)
|
16
|
+
|
17
|
+
|
18
|
+
### ⚠ BREAKING CHANGES
|
19
|
+
|
20
|
+
* Have coercers take in a type instead of the full field
|
21
|
+
* Update Field's types to a support Sorbets T::Types::Base classes
|
22
|
+
* Changed serialize return value to a Result
|
23
|
+
* adds SerializationError ancestor
|
24
|
+
|
25
|
+
### Features
|
26
|
+
|
27
|
+
* Add BooleanCoercer ([acd220f](https://github.com/maxveldink/sorbet-schema/commit/acd220f55fe0ebc823de6cc43776a1f510a4acd0))
|
28
|
+
* Add EnumCoercer ([5c0e2b5](https://github.com/maxveldink/sorbet-schema/commit/5c0e2b51990dfb72218129b0935f881622324194))
|
29
|
+
* 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))
|
30
|
+
* Add idempotency to Struct coercer ([3a42957](https://github.com/maxveldink/sorbet-schema/commit/3a42957836afee55b074fc5c8a41eac6d31ada70))
|
31
|
+
* Add option to serialize values to HashSerializer ([710d365](https://github.com/maxveldink/sorbet-schema/commit/710d365d477bd6c6bff20929fcdcc1d1cc95bdb3))
|
32
|
+
* Adds TypedArray coercer ([795ddd9](https://github.com/maxveldink/sorbet-schema/commit/795ddd97f05502b154d4cb165878c1f78935cb3c))
|
33
|
+
|
34
|
+
|
35
|
+
### Code Refactoring
|
36
|
+
|
37
|
+
* adds SerializationError ancestor ([f8ea753](https://github.com/maxveldink/sorbet-schema/commit/f8ea75304613cbab17f698006698a2524a44b538))
|
38
|
+
* Changed serialize return value to a Result ([948c678](https://github.com/maxveldink/sorbet-schema/commit/948c67815dab19793dd2f321a90797d123740e0e))
|
39
|
+
* Have coercers take in a type instead of the full field ([c06169e](https://github.com/maxveldink/sorbet-schema/commit/c06169e0fa1cf7c8f645ecabef19cc2f5facffe4))
|
40
|
+
* Update Field's types to a support Sorbets T::Types::Base classes ([9ef1cd5](https://github.com/maxveldink/sorbet-schema/commit/9ef1cd5caf09396d8dae6a4db1feeb4f88dd25e9))
|
41
|
+
|
7
42
|
## [0.3.0](https://github.com/maxveldink/sorbet-schema/compare/v0.2.2...v0.3.0) (2024-03-12)
|
8
43
|
|
9
44
|
|
data/Gemfile.lock
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
sorbet-schema (0.
|
4
|
+
sorbet-schema (0.4.1)
|
5
5
|
sorbet-result (~> 1.1)
|
6
6
|
sorbet-runtime (~> 0.5)
|
7
7
|
sorbet-struct-comparable (~> 1.3)
|
@@ -18,13 +18,13 @@ GEM
|
|
18
18
|
reline (>= 0.3.8)
|
19
19
|
erubi (1.12.0)
|
20
20
|
io-console (0.7.2)
|
21
|
-
irb (1.
|
21
|
+
irb (1.12.0)
|
22
22
|
rdoc
|
23
23
|
reline (>= 0.4.2)
|
24
24
|
json (2.7.1)
|
25
25
|
language_server-protocol (3.17.0.3)
|
26
26
|
lint_roller (1.1.0)
|
27
|
-
minitest (5.22.
|
27
|
+
minitest (5.22.3)
|
28
28
|
minitest-focus (1.4.0)
|
29
29
|
minitest (>= 4, < 6)
|
30
30
|
minitest-reporters (1.6.1)
|
@@ -53,7 +53,7 @@ GEM
|
|
53
53
|
reline (0.4.3)
|
54
54
|
io-console (~> 0.5)
|
55
55
|
rexml (3.2.6)
|
56
|
-
rubocop (1.
|
56
|
+
rubocop (1.62.1)
|
57
57
|
json (~> 2.3)
|
58
58
|
language_server-protocol (>= 3.17.0)
|
59
59
|
parallel (~> 1.10)
|
@@ -61,27 +61,27 @@ GEM
|
|
61
61
|
rainbow (>= 2.2.2, < 4.0)
|
62
62
|
regexp_parser (>= 1.8, < 3.0)
|
63
63
|
rexml (>= 3.2.5, < 4.0)
|
64
|
-
rubocop-ast (>= 1.
|
64
|
+
rubocop-ast (>= 1.31.1, < 2.0)
|
65
65
|
ruby-progressbar (~> 1.7)
|
66
66
|
unicode-display_width (>= 2.4.0, < 3.0)
|
67
|
-
rubocop-ast (1.31.
|
67
|
+
rubocop-ast (1.31.2)
|
68
68
|
parser (>= 3.3.0.4)
|
69
69
|
rubocop-performance (1.20.2)
|
70
70
|
rubocop (>= 1.48.1, < 2.0)
|
71
71
|
rubocop-ast (>= 1.30.0, < 2.0)
|
72
|
-
rubocop-sorbet (0.7.
|
72
|
+
rubocop-sorbet (0.7.8)
|
73
73
|
rubocop (>= 0.90.0)
|
74
74
|
ruby-progressbar (1.13.0)
|
75
|
-
sorbet (0.5.
|
76
|
-
sorbet-static (= 0.5.
|
75
|
+
sorbet (0.5.11295)
|
76
|
+
sorbet-static (= 0.5.11295)
|
77
77
|
sorbet-result (1.1.0)
|
78
78
|
sorbet-runtime (~> 0.5)
|
79
|
-
sorbet-runtime (0.5.
|
80
|
-
sorbet-static (0.5.
|
81
|
-
sorbet-static (0.5.
|
82
|
-
sorbet-static-and-runtime (0.5.
|
83
|
-
sorbet (= 0.5.
|
84
|
-
sorbet-runtime (= 0.5.
|
79
|
+
sorbet-runtime (0.5.11295)
|
80
|
+
sorbet-static (0.5.11295-universal-darwin)
|
81
|
+
sorbet-static (0.5.11295-x86_64-linux)
|
82
|
+
sorbet-static-and-runtime (0.5.11295)
|
83
|
+
sorbet (= 0.5.11295)
|
84
|
+
sorbet-runtime (= 0.5.11295)
|
85
85
|
sorbet-struct-comparable (1.3.0)
|
86
86
|
sorbet-runtime (>= 0.5)
|
87
87
|
spoom (1.2.4)
|
@@ -89,10 +89,10 @@ GEM
|
|
89
89
|
sorbet-static-and-runtime (>= 0.5.10187)
|
90
90
|
syntax_tree (>= 6.1.1)
|
91
91
|
thor (>= 0.19.2)
|
92
|
-
standard (1.
|
92
|
+
standard (1.35.1)
|
93
93
|
language_server-protocol (~> 3.17.0.2)
|
94
94
|
lint_roller (~> 1.0)
|
95
|
-
rubocop (~> 1.
|
95
|
+
rubocop (~> 1.62.0)
|
96
96
|
standard-custom (~> 1.0.0)
|
97
97
|
standard-performance (~> 1.3)
|
98
98
|
standard-custom (1.0.2)
|
@@ -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: T::Types::Base).returns(T::Boolean) }
|
11
|
+
def used_for_type?(type)
|
12
|
+
type == T::Utils.coerce(T::Boolean)
|
13
|
+
end
|
14
|
+
|
15
|
+
sig { override.params(type: T::Types::Base, value: Value).returns(Result[Target, CoercionError]) }
|
16
|
+
def coerce(type:, value:)
|
17
|
+
return Failure.new(CoercionError.new("Type must be a T::Boolean.")) unless used_for_type?(type)
|
18
|
+
|
19
|
+
if type.recursively_valid?(value)
|
20
|
+
Success.new(value)
|
21
|
+
elsif value == "true"
|
22
|
+
Success.new(true)
|
23
|
+
elsif value == "false"
|
24
|
+
Success.new(false)
|
25
|
+
else
|
26
|
+
Failure.new(CoercionError.new)
|
27
|
+
end
|
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: T::
|
13
|
+
sig { abstract.params(type: T::Types::Base).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: T::Types::Base, value: Value).returns(Result[Target, CoercionError]) }
|
18
|
+
def coerce(type:, value:)
|
19
19
|
end
|
20
20
|
end
|
21
21
|
end
|
@@ -11,7 +11,19 @@ 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
|
+
DateCoercer,
|
21
|
+
EnumCoercer,
|
22
|
+
StructCoercer,
|
23
|
+
TypedArrayCoercer
|
24
|
+
],
|
25
|
+
Registry
|
26
|
+
)
|
15
27
|
|
16
28
|
sig { void }
|
17
29
|
def initialize
|
@@ -28,7 +40,7 @@ module Typed
|
|
28
40
|
@available = DEFAULT_COERCERS.clone
|
29
41
|
end
|
30
42
|
|
31
|
-
sig { params(type: T::
|
43
|
+
sig { params(type: T::Types::Base).returns(T.nilable(T.class_of(Coercer))) }
|
32
44
|
def select_coercer_by(type:)
|
33
45
|
@available.find { |coercer| coercer.new.used_for_type?(type) }
|
34
46
|
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
# typed: strict
|
2
|
+
|
3
|
+
require "date"
|
4
|
+
|
5
|
+
module Typed
|
6
|
+
module Coercion
|
7
|
+
class DateCoercer < Coercer
|
8
|
+
extend T::Generic
|
9
|
+
|
10
|
+
Target = type_member { {fixed: Date} }
|
11
|
+
|
12
|
+
sig { override.params(type: T::Types::Base).returns(T::Boolean) }
|
13
|
+
def used_for_type?(type)
|
14
|
+
T::Utils.coerce(type) == T::Utils.coerce(Date)
|
15
|
+
end
|
16
|
+
|
17
|
+
sig { override.params(type: T::Types::Base, value: Value).returns(Result[Target, CoercionError]) }
|
18
|
+
def coerce(type:, value:)
|
19
|
+
return Failure.new(CoercionError.new("Type must be a Date.")) unless used_for_type?(type)
|
20
|
+
|
21
|
+
return Success.new(value) if value.is_a?(Date)
|
22
|
+
|
23
|
+
Success.new(Date.parse(value))
|
24
|
+
rescue Date::Error, TypeError
|
25
|
+
Failure.new(CoercionError.new("'#{value}' cannot be coerced into Date."))
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,27 @@
|
|
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: T::Types::Base).returns(T::Boolean) }
|
11
|
+
def used_for_type?(type)
|
12
|
+
return false unless type.respond_to?(:raw_type)
|
13
|
+
|
14
|
+
!!(T.cast(type, T::Types::Simple).raw_type < T::Enum)
|
15
|
+
end
|
16
|
+
|
17
|
+
sig { override.params(type: T::Types::Base, value: Value).returns(Result[Target, CoercionError]) }
|
18
|
+
def coerce(type:, value:)
|
19
|
+
return Failure.new(CoercionError.new("Field type must inherit from T::Enum for Enum coercion.")) unless used_for_type?(type)
|
20
|
+
|
21
|
+
Success.new(T.cast(type, T::Types::Simple).raw_type.from_serialized(value))
|
22
|
+
rescue KeyError => e
|
23
|
+
Failure.new(CoercionError.new(e.message))
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -7,13 +7,15 @@ module Typed
|
|
7
7
|
|
8
8
|
Target = type_member { {fixed: Float} }
|
9
9
|
|
10
|
-
sig { override.params(type: T::
|
10
|
+
sig { override.params(type: T::Types::Base).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: T::Types::Base, value: Value).returns(Result[Target, CoercionError]) }
|
16
|
+
def coerce(type:, value:)
|
17
|
+
return Failure.new(CoercionError.new("Type must be a Float.")) unless used_for_type?(type)
|
18
|
+
|
17
19
|
Success.new(Float(value))
|
18
20
|
rescue ArgumentError, TypeError
|
19
21
|
Failure.new(CoercionError.new("'#{value}' cannot be coerced into Float."))
|
@@ -7,13 +7,15 @@ module Typed
|
|
7
7
|
|
8
8
|
Target = type_member { {fixed: Integer} }
|
9
9
|
|
10
|
-
sig { override.params(type: T::
|
10
|
+
sig { override.params(type: T::Types::Base).returns(T::Boolean) }
|
11
11
|
def used_for_type?(type)
|
12
|
-
type == Integer
|
12
|
+
type == T::Utils.coerce(Integer)
|
13
13
|
end
|
14
14
|
|
15
|
-
sig { override.params(
|
16
|
-
def coerce(
|
15
|
+
sig { override.params(type: T::Types::Base, value: Value).returns(Result[Target, CoercionError]) }
|
16
|
+
def coerce(type:, value:)
|
17
|
+
return Failure.new(CoercionError.new("Type must be a Integer.")) unless used_for_type?(type)
|
18
|
+
|
17
19
|
Success.new(Integer(value))
|
18
20
|
rescue ArgumentError, TypeError
|
19
21
|
Failure.new(CoercionError.new("'#{value}' cannot be coerced into Integer."))
|
@@ -7,13 +7,15 @@ module Typed
|
|
7
7
|
|
8
8
|
Target = type_member { {fixed: String} }
|
9
9
|
|
10
|
-
sig { override.params(type: T::
|
10
|
+
sig { override.params(type: T::Types::Base).returns(T::Boolean) }
|
11
11
|
def used_for_type?(type)
|
12
|
-
type == String
|
12
|
+
type == T::Utils.coerce(String)
|
13
13
|
end
|
14
14
|
|
15
|
-
sig { override.params(
|
16
|
-
def coerce(
|
15
|
+
sig { override.params(type: T::Types::Base, value: Value).returns(Result[Target, CoercionError]) }
|
16
|
+
def coerce(type:, value:)
|
17
|
+
return Failure.new(CoercionError.new("Type must be a String.")) unless used_for_type?(type)
|
18
|
+
|
17
19
|
Success.new(String(value))
|
18
20
|
end
|
19
21
|
end
|
@@ -7,21 +7,23 @@ module Typed
|
|
7
7
|
|
8
8
|
Target = type_member { {fixed: T::Struct} }
|
9
9
|
|
10
|
-
sig { override.params(type: T::
|
10
|
+
sig { override.params(type: T::Types::Base).returns(T::Boolean) }
|
11
11
|
def used_for_type?(type)
|
12
|
-
|
12
|
+
return false unless type.respond_to?(:raw_type)
|
13
|
+
|
14
|
+
!!(T.cast(type, T::Types::Simple).raw_type < T::Struct)
|
13
15
|
end
|
14
16
|
|
15
|
-
sig { override.params(
|
16
|
-
def coerce(
|
17
|
-
type
|
17
|
+
sig { override.params(type: T::Types::Base, value: Value).returns(Result[Target, CoercionError]) }
|
18
|
+
def coerce(type:, value:)
|
19
|
+
return Failure.new(CoercionError.new("Field type must inherit from T::Struct for Struct coercion.")) unless used_for_type?(type)
|
20
|
+
return Success.new(value) if type.recursively_valid?(value)
|
18
21
|
|
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)
|
22
|
+
return Failure.new(CoercionError.new("Value of type '#{value.class}' cannot be coerced to #{type} Struct.")) unless value.is_a?(Hash)
|
21
23
|
|
22
|
-
Success.new(type.from_hash!(HashTransformer.new.deep_stringify_keys(value)))
|
23
|
-
rescue ArgumentError
|
24
|
-
Failure.new(CoercionError.new(
|
24
|
+
Success.new(T.cast(type, T::Types::Simple).raw_type.from_hash!(HashTransformer.new.deep_stringify_keys(value)))
|
25
|
+
rescue ArgumentError, RuntimeError
|
26
|
+
Failure.new(CoercionError.new("Given hash could not be coerced to #{type}."))
|
25
27
|
end
|
26
28
|
end
|
27
29
|
end
|
@@ -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: T::Types::Base).returns(T::Boolean) }
|
11
|
+
def used_for_type?(type)
|
12
|
+
type.is_a?(T::Types::TypedArray)
|
13
|
+
end
|
14
|
+
|
15
|
+
sig { override.params(type: T::Types::Base, 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 used_for_type?(type)
|
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: T.cast(type, T::Types::TypedArray).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: T::Types::Base, 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
@@ -1,14 +1,44 @@
|
|
1
1
|
# typed: strict
|
2
2
|
|
3
3
|
module Typed
|
4
|
-
class Field
|
4
|
+
class Field
|
5
5
|
extend T::Sig
|
6
6
|
|
7
|
-
|
7
|
+
InlineSerializer = T.type_alias { T.proc.params(arg0: T.untyped).returns(T.untyped) }
|
8
8
|
|
9
|
-
|
10
|
-
|
11
|
-
|
9
|
+
sig { returns(Symbol) }
|
10
|
+
attr_reader :name
|
11
|
+
|
12
|
+
sig { returns(T::Types::Base) }
|
13
|
+
attr_reader :type
|
14
|
+
|
15
|
+
sig { returns(T::Boolean) }
|
16
|
+
attr_reader :required
|
17
|
+
|
18
|
+
sig { returns(T.nilable(InlineSerializer)) }
|
19
|
+
attr_reader :inline_serializer
|
20
|
+
|
21
|
+
sig do
|
22
|
+
params(
|
23
|
+
name: Symbol,
|
24
|
+
type: T.any(T::Class[T.anything], T::Types::Base),
|
25
|
+
required: T::Boolean,
|
26
|
+
inline_serializer: T.nilable(InlineSerializer)
|
27
|
+
).void
|
28
|
+
end
|
29
|
+
def initialize(name:, type:, required: true, inline_serializer: nil)
|
30
|
+
@name = name
|
31
|
+
@type = T.let(T::Utils.coerce(type), T::Types::Base)
|
32
|
+
@required = required
|
33
|
+
@inline_serializer = inline_serializer
|
34
|
+
end
|
35
|
+
|
36
|
+
sig { params(other: Field).returns(T.nilable(T::Boolean)) }
|
37
|
+
def ==(other)
|
38
|
+
name == other.name &&
|
39
|
+
type == other.type &&
|
40
|
+
required == other.required
|
41
|
+
end
|
12
42
|
|
13
43
|
sig { returns(T::Boolean) }
|
14
44
|
def required?
|
@@ -20,9 +50,23 @@ module Typed
|
|
20
50
|
!required
|
21
51
|
end
|
22
52
|
|
53
|
+
sig { params(value: Value).returns(Value) }
|
54
|
+
def serialize(value)
|
55
|
+
if inline_serializer && value
|
56
|
+
T.must(inline_serializer).call(value)
|
57
|
+
else
|
58
|
+
value
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
23
62
|
sig { params(value: Value).returns(Validations::ValidationResult) }
|
24
63
|
def validate(value)
|
25
64
|
Validations::FieldTypeValidator.new.validate(field: self, value: value)
|
26
65
|
end
|
66
|
+
|
67
|
+
sig { params(value: Value).returns(T::Boolean) }
|
68
|
+
def works_with?(value)
|
69
|
+
type.recursively_valid?(value)
|
70
|
+
end
|
27
71
|
end
|
28
72
|
end
|
@@ -3,19 +3,31 @@
|
|
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
|
+
Success.new(serialize_from_struct(struct: struct, should_serialize_values: should_serialize_values))
|
19
26
|
end
|
27
|
+
|
28
|
+
private
|
29
|
+
|
30
|
+
sig { returns(T::Boolean) }
|
31
|
+
attr_reader :should_serialize_values
|
20
32
|
end
|
21
33
|
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(serialize_from_struct(struct: struct, should_serialize_values: true)))
|
26
28
|
end
|
27
29
|
end
|
28
30
|
end
|
data/lib/typed/schema.rb
CHANGED
@@ -17,5 +17,15 @@ module Typed
|
|
17
17
|
end
|
18
18
|
)
|
19
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
|
20
30
|
end
|
21
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
|