data_model 0.0.1 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.editorconfig +6 -2
- data/.rubocop.yml +11 -2
- data/.ruby-version +2 -0
- data/Gemfile.lock +91 -54
- data/Guardfile +20 -0
- data/Rakefile +32 -0
- data/data_model.gemspec +52 -0
- data/lib/data_model/boolean.rb +7 -0
- data/lib/data_model/builtin/array.rb +73 -0
- data/lib/data_model/builtin/big_decimal.rb +64 -0
- data/lib/data_model/builtin/boolean.rb +37 -0
- data/lib/data_model/builtin/date.rb +60 -0
- data/lib/data_model/builtin/float.rb +64 -0
- data/lib/data_model/builtin/hash.rb +119 -0
- data/lib/data_model/builtin/integer.rb +64 -0
- data/lib/data_model/builtin/string.rb +88 -0
- data/lib/data_model/builtin/symbol.rb +64 -0
- data/lib/data_model/builtin/time.rb +60 -0
- data/lib/data_model/builtin.rb +23 -0
- data/lib/data_model/error.rb +107 -0
- data/lib/data_model/errors.rb +296 -0
- data/lib/data_model/fixtures/array.rb +61 -0
- data/lib/data_model/fixtures/big_decimal.rb +55 -0
- data/lib/data_model/fixtures/boolean.rb +35 -0
- data/lib/data_model/fixtures/date.rb +53 -0
- data/lib/data_model/fixtures/example.rb +29 -0
- data/lib/data_model/fixtures/float.rb +53 -0
- data/lib/data_model/fixtures/hash.rb +66 -0
- data/lib/data_model/fixtures/integer.rb +53 -0
- data/lib/data_model/fixtures/string.rb +110 -0
- data/lib/data_model/fixtures/symbol.rb +56 -0
- data/lib/data_model/fixtures/time.rb +53 -0
- data/lib/data_model/logging.rb +23 -0
- data/lib/data_model/model.rb +21 -44
- data/lib/data_model/scanner.rb +92 -56
- data/lib/data_model/testing/minitest.rb +79 -0
- data/lib/data_model/testing.rb +6 -0
- data/lib/data_model/type.rb +41 -39
- data/lib/data_model/type_registry.rb +68 -0
- data/lib/data_model/version.rb +3 -1
- data/lib/data_model.rb +32 -16
- data/sorbet/config +4 -0
- data/sorbet/rbi/annotations/rainbow.rbi +269 -0
- data/sorbet/rbi/gems/minitest@5.18.0.rbi +1491 -0
- data/sorbet/rbi/gems/zeitwerk.rbi +196 -0
- data/sorbet/rbi/gems/zeitwerk@2.6.7.rbi +966 -0
- data/sorbet/rbi/todo.rbi +5 -0
- data/sorbet/tapioca/config.yml +13 -0
- data/sorbet/tapioca/require.rb +4 -0
- metadata +139 -17
- data/config/sus.rb +0 -2
- data/fixtures/schema.rb +0 -14
- data/lib/data_model/registry.rb +0 -44
@@ -0,0 +1,110 @@
|
|
1
|
+
# typed: strict
|
2
|
+
|
3
|
+
module DataModel
|
4
|
+
module Fixtures::String
|
5
|
+
extend self
|
6
|
+
extend T::Sig
|
7
|
+
include Fixtures
|
8
|
+
|
9
|
+
sig { returns(Example) }
|
10
|
+
def simple
|
11
|
+
Example.new(
|
12
|
+
[:string],
|
13
|
+
variants: {
|
14
|
+
valid: "valid",
|
15
|
+
other_type: 22,
|
16
|
+
missing: nil
|
17
|
+
},
|
18
|
+
)
|
19
|
+
end
|
20
|
+
|
21
|
+
sig { returns(Example) }
|
22
|
+
def email
|
23
|
+
Example.new(
|
24
|
+
[:string, { format: "@" }],
|
25
|
+
variants: {
|
26
|
+
valid: "foo@bar.com",
|
27
|
+
invalid: "invalid"
|
28
|
+
},
|
29
|
+
)
|
30
|
+
end
|
31
|
+
|
32
|
+
sig { returns(Example) }
|
33
|
+
def email_regexp
|
34
|
+
Example.new(
|
35
|
+
[:string, { format: /@/ }],
|
36
|
+
variants: {
|
37
|
+
valid: "foo@bar.com",
|
38
|
+
invalid: "invalid"
|
39
|
+
},
|
40
|
+
)
|
41
|
+
end
|
42
|
+
|
43
|
+
sig { returns(Example) }
|
44
|
+
def email_proc
|
45
|
+
Example.new(
|
46
|
+
[:string, { format: ->(val) { val.match?(/@/) } }],
|
47
|
+
variants: {
|
48
|
+
valid: "foo@bar.com",
|
49
|
+
invalid: "invalid"
|
50
|
+
},
|
51
|
+
)
|
52
|
+
end
|
53
|
+
|
54
|
+
sig { returns(Example) }
|
55
|
+
def optional
|
56
|
+
Example.new(
|
57
|
+
[:string, { optional: true }],
|
58
|
+
variants: {
|
59
|
+
valid: "valid",
|
60
|
+
blank: "",
|
61
|
+
missing: nil
|
62
|
+
},
|
63
|
+
)
|
64
|
+
end
|
65
|
+
|
66
|
+
sig { returns(Example) }
|
67
|
+
def inclusion
|
68
|
+
Example.new(
|
69
|
+
[:string, { included: ["valid"] }],
|
70
|
+
variants: {
|
71
|
+
valid: "valid",
|
72
|
+
outside: "invalid"
|
73
|
+
},
|
74
|
+
)
|
75
|
+
end
|
76
|
+
|
77
|
+
sig { returns(Example) }
|
78
|
+
def exclusion
|
79
|
+
Example.new(
|
80
|
+
[:string, { excluded: ["invalid"] }],
|
81
|
+
variants: {
|
82
|
+
valid: "valid",
|
83
|
+
inside: "invalid"
|
84
|
+
},
|
85
|
+
)
|
86
|
+
end
|
87
|
+
|
88
|
+
sig { returns(Example) }
|
89
|
+
def allow_blank
|
90
|
+
Example.new(
|
91
|
+
[:string, { allow_blank: true }],
|
92
|
+
variants: {
|
93
|
+
blank: "",
|
94
|
+
not_blank: "content",
|
95
|
+
missing: nil
|
96
|
+
},
|
97
|
+
)
|
98
|
+
end
|
99
|
+
|
100
|
+
sig { returns(Example) }
|
101
|
+
def dont_allow_blank
|
102
|
+
Example.new(
|
103
|
+
[:string, { allow_blank: false }],
|
104
|
+
variants: {
|
105
|
+
blank: ""
|
106
|
+
},
|
107
|
+
)
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
# typed: strict
|
2
|
+
|
3
|
+
module DataModel
|
4
|
+
module Fixtures::Symbol
|
5
|
+
extend self
|
6
|
+
include Fixtures
|
7
|
+
extend T::Sig
|
8
|
+
|
9
|
+
sig { returns(Example) }
|
10
|
+
def simple
|
11
|
+
Example.new(
|
12
|
+
[:symbol],
|
13
|
+
variants: {
|
14
|
+
valid: :valid,
|
15
|
+
coerce: "valid",
|
16
|
+
missing: nil,
|
17
|
+
other_type: 22
|
18
|
+
},
|
19
|
+
)
|
20
|
+
end
|
21
|
+
|
22
|
+
sig { returns(Example) }
|
23
|
+
def optional
|
24
|
+
Example.new(
|
25
|
+
[:symbol, { optional: true }],
|
26
|
+
variants: {
|
27
|
+
missing: nil,
|
28
|
+
present: :valid,
|
29
|
+
number: 22
|
30
|
+
},
|
31
|
+
)
|
32
|
+
end
|
33
|
+
|
34
|
+
sig { returns(Example) }
|
35
|
+
def inclusion
|
36
|
+
Example.new(
|
37
|
+
[:symbol, { included: [:valid] }],
|
38
|
+
variants: {
|
39
|
+
valid: :valid,
|
40
|
+
outside: :outside
|
41
|
+
},
|
42
|
+
)
|
43
|
+
end
|
44
|
+
|
45
|
+
sig { returns(Example) }
|
46
|
+
def exclusion
|
47
|
+
Example.new(
|
48
|
+
[:symbol, { excluded: [:invalid] }],
|
49
|
+
variants: {
|
50
|
+
valid: :valid,
|
51
|
+
inside: :invalid
|
52
|
+
},
|
53
|
+
)
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
# typed: strict
|
2
|
+
|
3
|
+
module DataModel
|
4
|
+
module Fixtures::Time
|
5
|
+
extend T::Sig
|
6
|
+
extend self
|
7
|
+
include Fixtures
|
8
|
+
|
9
|
+
sig { returns(::Time) }
|
10
|
+
def earliest_time
|
11
|
+
return ::Time.now - 1
|
12
|
+
end
|
13
|
+
|
14
|
+
sig { returns(::Time) }
|
15
|
+
def latest_time
|
16
|
+
return ::Time.now + 1
|
17
|
+
end
|
18
|
+
|
19
|
+
sig { returns(T::Hash[Symbol, Object]) }
|
20
|
+
def variants
|
21
|
+
now = ::Time.now
|
22
|
+
|
23
|
+
{
|
24
|
+
time: now,
|
25
|
+
string: [now.strftime("%H:%M:%S.%6N"), now],
|
26
|
+
invalid: "invalid",
|
27
|
+
early: earliest_time - 1,
|
28
|
+
late: latest_time + 1,
|
29
|
+
missing: nil
|
30
|
+
}
|
31
|
+
end
|
32
|
+
|
33
|
+
sig { returns(Example) }
|
34
|
+
def simple
|
35
|
+
Example.new([:time], variants:)
|
36
|
+
end
|
37
|
+
|
38
|
+
sig { returns(Example) }
|
39
|
+
def optional
|
40
|
+
Example.new([:time, { optional: true }], variants:)
|
41
|
+
end
|
42
|
+
|
43
|
+
sig { returns(Example) }
|
44
|
+
def earliest
|
45
|
+
Example.new([:time, { earliest: earliest_time }], variants:)
|
46
|
+
end
|
47
|
+
|
48
|
+
sig { returns(Example) }
|
49
|
+
def latest
|
50
|
+
Example.new([:time, { latest: latest_time }], variants:)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
# typed: strict
|
2
|
+
|
3
|
+
require "logger"
|
4
|
+
|
5
|
+
module DataModel
|
6
|
+
module Logging
|
7
|
+
extend T::Sig
|
8
|
+
include Kernel
|
9
|
+
|
10
|
+
sig { returns(Logger) }
|
11
|
+
def log
|
12
|
+
target = T.let(respond_to?(:name) ? self : self.class, T.any(Class, Module))
|
13
|
+
|
14
|
+
logger = Logger.new(
|
15
|
+
STDERR,
|
16
|
+
level: Logger::FATAL,
|
17
|
+
progname: target.name,
|
18
|
+
)
|
19
|
+
|
20
|
+
return @log ||= T.let(logger, T.nilable(Logger))
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
data/lib/data_model/model.rb
CHANGED
@@ -1,54 +1,31 @@
|
|
1
|
-
|
2
|
-
module Model
|
3
|
-
extend self
|
4
|
-
|
5
|
-
def defaults
|
6
|
-
{
|
7
|
-
# name of validator if a child, and validating a named property
|
8
|
-
property: nil,
|
1
|
+
# typed: strict
|
9
2
|
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
children: [],
|
14
|
-
|
15
|
-
# default writer
|
16
|
-
write: ->(val) { val }
|
17
|
-
}
|
18
|
-
end
|
3
|
+
module DataModel
|
4
|
+
class Model
|
5
|
+
extend T::Sig
|
19
6
|
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
children: Array,
|
25
|
-
}
|
7
|
+
sig { params(schema: TSchema, type: Type).void }
|
8
|
+
def initialize(schema, type)
|
9
|
+
@schema = schema
|
10
|
+
@type = type
|
26
11
|
end
|
27
12
|
|
28
|
-
|
29
|
-
|
30
|
-
invoke(model, :read, data)
|
31
|
-
end
|
13
|
+
sig { returns(TSchema) }
|
14
|
+
attr_reader :schema
|
32
15
|
|
33
|
-
|
34
|
-
|
35
|
-
|
16
|
+
# Validate data against the model. This will return true if the data is valid,
|
17
|
+
# or false if it is not. If it is not valid, it will raise an exception.
|
18
|
+
sig { params(data: TData).returns(Error) }
|
19
|
+
def validate(data)
|
20
|
+
_, err = @type.read(data)
|
21
|
+
return err
|
36
22
|
end
|
37
23
|
|
38
|
-
|
39
|
-
|
40
|
-
def
|
41
|
-
|
42
|
-
|
43
|
-
case fn.arity
|
44
|
-
when 1
|
45
|
-
fn.call(data)
|
46
|
-
when 2
|
47
|
-
ctx = model.slice(:config, :types, :children)
|
48
|
-
fn.call(data, ctx)
|
49
|
-
else
|
50
|
-
raise "expected an arity of 1 or 2, got: #{fn.arity}"
|
51
|
-
end
|
24
|
+
# Read data with the model. This will return a tuple of [data, error]
|
25
|
+
sig { params(data: TData).returns([TData, Error]) }
|
26
|
+
def coerce(data)
|
27
|
+
result = @type.read(data, coerce: true)
|
28
|
+
return result
|
52
29
|
end
|
53
30
|
end
|
54
31
|
end
|
data/lib/data_model/scanner.rb
CHANGED
@@ -1,67 +1,103 @@
|
|
1
|
+
# typed: strict
|
2
|
+
|
3
|
+
# Scan a schema into a struct that can be inspected to construct a model validator
|
4
|
+
#
|
5
|
+
# schema eg:
|
6
|
+
# [:string, { min: 1, max: 10}]
|
7
|
+
# [:tuple, { title: "coordinates" }, :double, :double]
|
8
|
+
# [:hash, { open: false },
|
9
|
+
# [:first_name, :string]
|
10
|
+
# [:last_name, :string]]
|
11
|
+
#
|
12
|
+
# first param is type, which is a key lookup in the registry
|
13
|
+
# second param is args, this is optional, but is a way to configure a type
|
14
|
+
# rest are type params. these are used to configure a type at the point of instantiation. Think of them as generics.
|
15
|
+
#
|
16
|
+
# params are either
|
17
|
+
# symbol, for example tuple types
|
18
|
+
# array, for object types to configure child properties.
|
1
19
|
module DataModel
|
2
20
|
module Scanner
|
21
|
+
include Kernel
|
22
|
+
include Logging
|
23
|
+
|
24
|
+
extend T::Sig
|
3
25
|
extend self
|
4
26
|
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
end
|
41
|
-
|
42
|
-
context.merge(
|
43
|
-
validator: validator.merge(config: token),
|
44
|
-
state: :configured,
|
45
|
-
)
|
46
|
-
|
47
|
-
when Array
|
48
|
-
unless [:defined, :configured].include?(state)
|
49
|
-
raise "#{schema.inspect} at pos #{pos}: expected (String | Hash | Symbol | Array), got #{token.class.name}"
|
50
|
-
end
|
51
|
-
|
52
|
-
children = token.map { |s| scan(s, registry) }
|
53
|
-
|
54
|
-
context.merge(
|
55
|
-
validator: validator.merge(children:),
|
56
|
-
state: :complete,
|
57
|
-
)
|
58
|
-
else
|
59
|
-
raise "got token #{token.inspect} at position #{pos} which was unexpected given the scanner was in a state of #{state}"
|
27
|
+
class Node < T::Struct
|
28
|
+
prop :type, Symbol, default: :nothing
|
29
|
+
prop :args, T::Hash[Symbol, Object], default: {}
|
30
|
+
prop :params, T::Array[Object], default: []
|
31
|
+
end
|
32
|
+
|
33
|
+
# Scan a schema, which is defined as a data structure, into a struct that is easier to work with.
|
34
|
+
# "Syntax" validations will be enforced at this level.
|
35
|
+
sig { params(schema: TSchema, registry: DataModel::TypeRegistry).returns(Node) }
|
36
|
+
def scan(schema, registry = TypeRegistry.instance)
|
37
|
+
# state:
|
38
|
+
# nil (start) -> :type (we have a type) -> :args (we have arguments)
|
39
|
+
scanned = Node.new
|
40
|
+
state = T.let(nil, T.nilable(Symbol))
|
41
|
+
|
42
|
+
log.debug("scanning schema: #{schema.inspect}")
|
43
|
+
|
44
|
+
for pos in (0...schema.length)
|
45
|
+
token = schema[pos]
|
46
|
+
dbg = "pos: #{pos}, token: #{token.inspect}, state: #{state.inspect}"
|
47
|
+
log.debug(dbg)
|
48
|
+
|
49
|
+
# detect optional args missing
|
50
|
+
if !token.is_a?(Hash) && state == :type
|
51
|
+
log.debug("detected optional args missing at (#{dbg}), moving state to :args")
|
52
|
+
|
53
|
+
# move state forward
|
54
|
+
state = :args
|
55
|
+
end
|
56
|
+
|
57
|
+
# we are just collecting params at this point
|
58
|
+
if state == :args
|
59
|
+
|
60
|
+
if !token.is_a?(Array) && !token.is_a?(Symbol)
|
61
|
+
raise "expected type params at (#{dbg}), which should be either a symbol or an array"
|
60
62
|
end
|
63
|
+
|
64
|
+
scanned.params << token
|
65
|
+
log.debug("collecting params at (#{dbg})")
|
66
|
+
|
67
|
+
next
|
68
|
+
end
|
69
|
+
|
70
|
+
# we can determine meaning based on type and state
|
71
|
+
case token
|
72
|
+
when Symbol
|
73
|
+
if !state.nil?
|
74
|
+
raise "got a symbol at(#{dbg}), but validator already defined"
|
75
|
+
end
|
76
|
+
|
77
|
+
if !registry.type?(token)
|
78
|
+
# TODO: need a much better error here, this is what people see when registration is not there
|
79
|
+
raise "expected a type in (#{dbg}), but found #{token.inspect} which is not a registered type"
|
80
|
+
end
|
81
|
+
|
82
|
+
scanned.type = token
|
83
|
+
state = :type
|
84
|
+
log.debug("got a symbol, determined token is a type at (#{dbg}), moving state to :type")
|
85
|
+
|
86
|
+
when Hash
|
87
|
+
if state != :type
|
88
|
+
raise "got a hash at (#{dbg}), but state is not :type (#{state.inspect})"
|
89
|
+
end
|
90
|
+
|
91
|
+
scanned.args = token
|
92
|
+
state = :args
|
93
|
+
log.debug("got a hash, determined token is args at (#{dbg}), moving state to :args")
|
94
|
+
|
95
|
+
else
|
96
|
+
raise "got token #{token.inspect} at (#{dbg}) which was unexpected given the scanner was in a state of #{state}"
|
61
97
|
end
|
62
98
|
end
|
63
99
|
|
64
|
-
|
100
|
+
return scanned
|
65
101
|
end
|
66
102
|
end
|
67
103
|
end
|
@@ -0,0 +1,79 @@
|
|
1
|
+
# typed: strict
|
2
|
+
|
3
|
+
require "minitest/assertions"
|
4
|
+
|
5
|
+
module DataModel
|
6
|
+
module Testing::Minitest
|
7
|
+
extend T::Sig
|
8
|
+
include Minitest::Assertions
|
9
|
+
include Kernel
|
10
|
+
|
11
|
+
sig { params(err: Error, type: Symbol, key: T.nilable(Symbol)).void }
|
12
|
+
def assert_child_model_error(err, type, key = nil)
|
13
|
+
assert(err.children.any?, "validate was successful, but should not have been")
|
14
|
+
|
15
|
+
for k in key ? [key] : err.children.keys
|
16
|
+
found = err.children[k]&.any? { |(t, _ctx)| t == type }
|
17
|
+
assert(found, "validation was not successful, but #{type} error was not found #{err.inspect}")
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
sig { params(err: Error, type: Symbol).void }
|
22
|
+
def assert_model_error(err, type)
|
23
|
+
assert(err.base.any?, "validate was successful, but should not have been")
|
24
|
+
|
25
|
+
found = err.base.any? { |(t, _ctx)| t == type }
|
26
|
+
|
27
|
+
assert(found, "validation was not successful, but #{type} error was not found #{err.inspect}")
|
28
|
+
end
|
29
|
+
|
30
|
+
sig { params(err: Error, type: T.nilable(Symbol), key: T.nilable(Symbol)).void }
|
31
|
+
def refute_child_model_error(err, type = nil, key = nil)
|
32
|
+
if !err.any?
|
33
|
+
return
|
34
|
+
end
|
35
|
+
|
36
|
+
if type.nil?
|
37
|
+
refute(err.base.any?, "validation was not successful #{err.inspect}")
|
38
|
+
return
|
39
|
+
end
|
40
|
+
|
41
|
+
for k in key ? [key] : err.children.keys
|
42
|
+
found = err.children[k]&.any? { |(t, _ctx)| t == type }
|
43
|
+
refute(found, "validation was not successful, but #{type} error was not found #{err.inspect}")
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
sig { params(err: Error, type: T.nilable(Symbol)).void }
|
48
|
+
def refute_model_error(err, type = nil)
|
49
|
+
if !err.any?
|
50
|
+
return
|
51
|
+
end
|
52
|
+
|
53
|
+
if type.nil?
|
54
|
+
refute(err.base.any?, "validation was not successful #{err.inspect}")
|
55
|
+
return
|
56
|
+
end
|
57
|
+
|
58
|
+
found = err.base.any? { |(t, _ctx)| t == type }
|
59
|
+
|
60
|
+
refute(found, "#{type} error was found #{err.inspect}")
|
61
|
+
end
|
62
|
+
|
63
|
+
sig { params(err: Error, type: T.nilable(Symbol)).void }
|
64
|
+
def refute_all_errors(err, type = nil)
|
65
|
+
if !err.any?
|
66
|
+
return
|
67
|
+
end
|
68
|
+
|
69
|
+
if type.nil?
|
70
|
+
refute(err.all.any?, "validation was not successful #{err.inspect}")
|
71
|
+
return
|
72
|
+
end
|
73
|
+
|
74
|
+
found = err.all.any? { |(t, _ctx)| t == type }
|
75
|
+
|
76
|
+
refute(found, "#{type} error was found #{err.inspect}")
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
data/lib/data_model/type.rb
CHANGED
@@ -1,48 +1,50 @@
|
|
1
|
+
# typed: strict
|
2
|
+
|
3
|
+
# Mixin included on every type. Type::Generic and Type::Parent are higher level specializations.
|
1
4
|
module DataModel
|
2
|
-
|
3
|
-
extend
|
5
|
+
class Type
|
6
|
+
extend T::Sig
|
7
|
+
extend T::Helpers
|
4
8
|
|
5
|
-
|
6
|
-
{
|
7
|
-
read: lambda do |val|
|
8
|
-
err = {}
|
9
|
+
abstract!
|
9
10
|
|
10
|
-
|
11
|
-
|
12
|
-
|
11
|
+
TArguments = T.type_alias { T::Hash[Symbol, T.untyped] }
|
12
|
+
TTypeParams = T.type_alias { T::Array[Object] }
|
13
|
+
TTypeResult = T.type_alias { [Object, Error] }
|
13
14
|
|
14
|
-
|
15
|
-
|
16
|
-
|
15
|
+
sig { params(args: TArguments, registry: TypeRegistry).void }
|
16
|
+
def initialize(args, registry: TypeRegistry.instance)
|
17
|
+
@type_args = args
|
18
|
+
@type_registry = registry
|
17
19
|
end
|
18
20
|
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
children.each do |child|
|
36
|
-
child => {property:}
|
37
|
-
v, e = Model.read(child, val.fetch(property))
|
38
|
-
|
39
|
-
val = val.merge({ property => v })
|
40
|
-
err[property] = e
|
41
|
-
end
|
42
|
-
|
43
|
-
[val, err]
|
44
|
-
end
|
45
|
-
}
|
21
|
+
sig { returns(TArguments) }
|
22
|
+
attr_reader :type_args
|
23
|
+
|
24
|
+
# configure must be overridden to use params
|
25
|
+
sig { overridable.params(params: TTypeParams).void }
|
26
|
+
def configure(params); end
|
27
|
+
|
28
|
+
# invoke another type by name
|
29
|
+
sig { params(name: Symbol, val: Object, coerce: T::Boolean, args: Type::TArguments, params: T.nilable(TTypeParams)).returns(TTypeResult) }
|
30
|
+
def invoke(name, val, coerce: false, args: {}, params: nil)
|
31
|
+
t = instantiate(name, args:, params:)
|
32
|
+
|
33
|
+
result = t.read(val, coerce:)
|
34
|
+
|
35
|
+
return result
|
46
36
|
end
|
37
|
+
|
38
|
+
# instanciate another type
|
39
|
+
sig { params(name: Symbol, args: Type::TArguments, params: T.nilable(TTypeParams)).returns(Type) }
|
40
|
+
def instantiate(name, args: {}, params: nil)
|
41
|
+
t = @type_registry.type(name, args:, params:)
|
42
|
+
|
43
|
+
return t
|
44
|
+
end
|
45
|
+
|
46
|
+
# default reader
|
47
|
+
sig { abstract.params(data: Object, coerce: T::Boolean).returns(TTypeResult) }
|
48
|
+
def read(data, coerce: false); end
|
47
49
|
end
|
48
50
|
end
|