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,296 @@
|
|
1
|
+
# typed: strict
|
2
|
+
|
3
|
+
module DataModel
|
4
|
+
# Provide Error building functionality as a mixin
|
5
|
+
module Errors
|
6
|
+
include Kernel
|
7
|
+
extend T::Sig
|
8
|
+
|
9
|
+
TTemporal = T.type_alias { T.any(::Date, ::Time, ::DateTime) }
|
10
|
+
|
11
|
+
## Constructors
|
12
|
+
|
13
|
+
# Type error applies when a value is not of the expected type
|
14
|
+
sig { params(cls: T.class_of(Object), value: Object).returns(TError) }
|
15
|
+
def type_error(cls, value)
|
16
|
+
[:type, [cls, value]]
|
17
|
+
end
|
18
|
+
|
19
|
+
# Coerce error applies when a value cannot be coerced to the expected type
|
20
|
+
sig { params(cls: T.class_of(Object), value: Object).returns(TError) }
|
21
|
+
def coerce_error(cls, value)
|
22
|
+
[:coerce, [cls, value]]
|
23
|
+
end
|
24
|
+
|
25
|
+
# Missing error applies when a value is missing
|
26
|
+
sig { params(cls: T.class_of(Object)).returns(TError) }
|
27
|
+
def missing_error(cls)
|
28
|
+
[:missing, cls]
|
29
|
+
end
|
30
|
+
|
31
|
+
# Inclusion error applies when a value is not in a set of allowed values
|
32
|
+
sig { params(set: T::Array[T.any(Symbol, String)]).returns(TError) }
|
33
|
+
def inclusion_error(set)
|
34
|
+
[:inclusion, set]
|
35
|
+
end
|
36
|
+
|
37
|
+
# Exclusive error applies when a value is in a set of disallowed values
|
38
|
+
sig { params(set: T::Array[T.any(Symbol, String)]).returns(TError) }
|
39
|
+
def exclusion_error(set)
|
40
|
+
[:exclusion, set]
|
41
|
+
end
|
42
|
+
|
43
|
+
# Blank error applies when a value is blank
|
44
|
+
sig { returns(TError) }
|
45
|
+
def blank_error
|
46
|
+
[:blank, nil]
|
47
|
+
end
|
48
|
+
|
49
|
+
# Extra keys error applies when a hash has extra keys
|
50
|
+
sig { params(keys: T::Array[Symbol]).returns(TError) }
|
51
|
+
def extra_keys_error(keys)
|
52
|
+
[:extra_keys, keys]
|
53
|
+
end
|
54
|
+
|
55
|
+
# Min applies when value is less then the minimum
|
56
|
+
sig { params(min: Numeric, val: Numeric).returns(TError) }
|
57
|
+
def min_error(min, val)
|
58
|
+
[:min, [min, val]]
|
59
|
+
end
|
60
|
+
|
61
|
+
# Max applies when value is less then the minimum
|
62
|
+
sig { params(min: Numeric, val: Numeric).returns(TError) }
|
63
|
+
def max_error(min, val)
|
64
|
+
[:max, [min, val]]
|
65
|
+
end
|
66
|
+
|
67
|
+
# Earliest applies when value is earlier then earliest
|
68
|
+
sig { params(earliest: TTemporal, val: TTemporal).returns(TError) }
|
69
|
+
def earliest_error(earliest, val)
|
70
|
+
[:earliest, [earliest, val]]
|
71
|
+
end
|
72
|
+
|
73
|
+
# Latest applies when value is earlier then earliest
|
74
|
+
sig { params(latest: TTemporal, val: TTemporal).returns(TError) }
|
75
|
+
def latest_error(latest, val)
|
76
|
+
[:latest, [latest, val]]
|
77
|
+
end
|
78
|
+
|
79
|
+
# Format applies when value does not match a format
|
80
|
+
sig { params(format: Object, val: String).returns(TError) }
|
81
|
+
def format_error(format, val)
|
82
|
+
[:format, [format, val]]
|
83
|
+
end
|
84
|
+
|
85
|
+
## Messages
|
86
|
+
|
87
|
+
# Generate a message for a type error
|
88
|
+
sig { params(cls: T.class_of(Object), value: Object).returns(String) }
|
89
|
+
def type_error_message(cls, value)
|
90
|
+
"#{value.inspect} is not a #{cls.name}, it is a #{value.class.name}"
|
91
|
+
end
|
92
|
+
|
93
|
+
# Generate a message for a coerce error
|
94
|
+
sig { params(cls: T.class_of(Object), value: Object).returns(String) }
|
95
|
+
def coerce_error_message(cls, value)
|
96
|
+
"cannot be coerced to #{cls.name}, it is a #{value.class.name}"
|
97
|
+
end
|
98
|
+
|
99
|
+
# Generate a message for a missing error
|
100
|
+
sig { params(cls: T.class_of(Object)).returns(String) }
|
101
|
+
def missing_error_message(cls)
|
102
|
+
"missing value, expected a #{cls.name}"
|
103
|
+
end
|
104
|
+
|
105
|
+
# Generate a message for an inclusion error
|
106
|
+
sig { params(set: T::Array[Symbol]).returns(String) }
|
107
|
+
def inclusion_error_message(set)
|
108
|
+
"must be one of #{set.join(', ')}"
|
109
|
+
end
|
110
|
+
|
111
|
+
# Generate a message for an exclusion error
|
112
|
+
sig { params(set: T::Array[Symbol]).returns(String) }
|
113
|
+
def exclusion_error_message(set)
|
114
|
+
"must not be one of #{set.join(', ')}"
|
115
|
+
end
|
116
|
+
|
117
|
+
# Generate a message for a blank error
|
118
|
+
sig { returns(String) }
|
119
|
+
def blank_error_message
|
120
|
+
"cannot be blank"
|
121
|
+
end
|
122
|
+
|
123
|
+
# Generate a message for an extra keys error
|
124
|
+
sig { params(keys: T::Array[Symbol]).returns(String) }
|
125
|
+
def extra_keys_error_message(keys)
|
126
|
+
"more elements found in closed hash then specified children: #{keys.join(', ')}"
|
127
|
+
end
|
128
|
+
|
129
|
+
# Generate a message for a min error
|
130
|
+
sig { params(min: Numeric, val: Numeric).returns(String) }
|
131
|
+
def min_error_message(min, val)
|
132
|
+
"value is less than the minimum of #{min}, it is #{val}"
|
133
|
+
end
|
134
|
+
|
135
|
+
# Generate a message for a min error
|
136
|
+
sig { params(max: Numeric, val: Numeric).returns(String) }
|
137
|
+
def max_error_message(max, val)
|
138
|
+
"value is more than the maximum of #{max}, it is #{val}"
|
139
|
+
end
|
140
|
+
|
141
|
+
# Generate a message for a value that occurs earlier then the specified earliest point
|
142
|
+
sig { params(earliest: TTemporal, val: TTemporal).returns(String) }
|
143
|
+
def early_error_message(earliest, val)
|
144
|
+
"value #{val} is before #{earliest}"
|
145
|
+
end
|
146
|
+
|
147
|
+
# Generate a message for a value that occurs later then the specified latest point
|
148
|
+
sig { params(latest: TTemporal, val: TTemporal).returns(String) }
|
149
|
+
def late_error_message(latest, val)
|
150
|
+
"value #{val} is after #{latest}"
|
151
|
+
end
|
152
|
+
|
153
|
+
# Generate a message for a value that does not match the format
|
154
|
+
sig { params(format: Object, val: String).returns(String) }
|
155
|
+
def format_error_message(format, val)
|
156
|
+
"value #{val} does not match format #{format}"
|
157
|
+
end
|
158
|
+
|
159
|
+
## API
|
160
|
+
# TODO: split this file
|
161
|
+
|
162
|
+
TErrorMessageBuilder = T.type_alias { T.proc.params(ctx: T.untyped).returns(String) }
|
163
|
+
|
164
|
+
# Register a custom error message for use with custom errors
|
165
|
+
sig { params(type: Symbol, block: TErrorMessageBuilder).void }
|
166
|
+
def register_error_message(type, &block)
|
167
|
+
error_message_builders[type] = block
|
168
|
+
end
|
169
|
+
|
170
|
+
TErrorMessages = T.type_alias { T::Hash[Symbol, TErrorMessageBuilder] }
|
171
|
+
TClassValueCtx = T.type_alias { [T.class_of(Object), Object] }
|
172
|
+
TClassCtx = T.type_alias { T.class_of(Object) }
|
173
|
+
TSetCtx = T.type_alias { T::Array[Symbol] }
|
174
|
+
TWithinCtx = T.type_alias { [Numeric, Numeric] }
|
175
|
+
TWithinTemporalCtx = T.type_alias { [TTemporal, TTemporal] }
|
176
|
+
TFormatCtx = T.type_alias { [Object, String] }
|
177
|
+
|
178
|
+
# Get the error message builders
|
179
|
+
sig { returns(TErrorMessages) }
|
180
|
+
def error_message_builders
|
181
|
+
if @error_messages.nil?
|
182
|
+
@error_messages ||= T.let({}, T.nilable(TErrorMessages))
|
183
|
+
|
184
|
+
# wire up defaults
|
185
|
+
|
186
|
+
register_error_message(:type) do |ctx|
|
187
|
+
cls, val = T.let(ctx, TClassValueCtx)
|
188
|
+
type_error_message(cls, val)
|
189
|
+
end
|
190
|
+
|
191
|
+
register_error_message(:coerce) do |ctx|
|
192
|
+
cls, val = T.let(ctx, TClassValueCtx)
|
193
|
+
coerce_error_message(cls, val)
|
194
|
+
end
|
195
|
+
|
196
|
+
register_error_message(:missing) do |ctx|
|
197
|
+
cls = T.let(ctx, TClassCtx)
|
198
|
+
missing_error_message(cls)
|
199
|
+
end
|
200
|
+
|
201
|
+
register_error_message(:inclusion) do |ctx|
|
202
|
+
set = T.let(ctx, TSetCtx)
|
203
|
+
inclusion_error_message(set)
|
204
|
+
end
|
205
|
+
|
206
|
+
register_error_message(:exclusion) do |ctx|
|
207
|
+
set = T.let(ctx, TSetCtx)
|
208
|
+
exclusion_error_message(set)
|
209
|
+
end
|
210
|
+
|
211
|
+
register_error_message(:extra_keys) do |ctx|
|
212
|
+
set = T.let(ctx, TSetCtx)
|
213
|
+
extra_keys_error_message(set)
|
214
|
+
end
|
215
|
+
|
216
|
+
register_error_message(:min) do |ctx|
|
217
|
+
min, val = T.let(ctx, TWithinCtx)
|
218
|
+
min_error_message(min, val)
|
219
|
+
end
|
220
|
+
|
221
|
+
register_error_message(:max) do |ctx|
|
222
|
+
max, val = T.let(ctx, TWithinCtx)
|
223
|
+
max_error_message(max, val)
|
224
|
+
end
|
225
|
+
|
226
|
+
register_error_message(:earliest) do |ctx|
|
227
|
+
earliest, val = T.let(ctx, TWithinTemporalCtx)
|
228
|
+
early_error_message(earliest, val)
|
229
|
+
end
|
230
|
+
|
231
|
+
register_error_message(:latest) do |ctx|
|
232
|
+
latest, val = T.let(ctx, TWithinTemporalCtx)
|
233
|
+
late_error_message(latest, val)
|
234
|
+
end
|
235
|
+
|
236
|
+
register_error_message(:blank) do
|
237
|
+
blank_error_message
|
238
|
+
end
|
239
|
+
|
240
|
+
register_error_message(:format) do |ctx|
|
241
|
+
format, val = T.let(ctx, TFormatCtx)
|
242
|
+
format_error_message(format, val)
|
243
|
+
end
|
244
|
+
end
|
245
|
+
|
246
|
+
@error_messages
|
247
|
+
end
|
248
|
+
|
249
|
+
# Build the error message for a given error
|
250
|
+
sig { params(error: TError).returns(String) }
|
251
|
+
def error_message(error)
|
252
|
+
type = T.let(error[0], Symbol)
|
253
|
+
ctx = T.let(error[1], T.untyped)
|
254
|
+
|
255
|
+
builder = error_message_builders[type]
|
256
|
+
|
257
|
+
if builder.nil?
|
258
|
+
raise "no error message builder for #{type}"
|
259
|
+
end
|
260
|
+
|
261
|
+
builder.call(ctx)
|
262
|
+
end
|
263
|
+
|
264
|
+
# TODO: separate builders from other use cases for this mixin
|
265
|
+
# Build error messages from error object
|
266
|
+
sig { params(error: Error).returns(T::Hash[Symbol, T::Array[String]]) }
|
267
|
+
def error_messages(error)
|
268
|
+
error.to_h.transform_values do |error_list|
|
269
|
+
error_list.map { |e| error_message(e) }
|
270
|
+
end
|
271
|
+
end
|
272
|
+
|
273
|
+
sig { params(error: Error, from: T.class_of(Object), to: T.class_of(Object)).void }
|
274
|
+
def set_error_class(error, from, to)
|
275
|
+
error.transform_context do |ctx, type|
|
276
|
+
case type
|
277
|
+
when :type, :coerce
|
278
|
+
cls, val = T.cast(ctx, TClassValueCtx)
|
279
|
+
if cls == from
|
280
|
+
[to, val]
|
281
|
+
else
|
282
|
+
[cls, val]
|
283
|
+
end
|
284
|
+
when :missing
|
285
|
+
if ctx == from
|
286
|
+
[to, val]
|
287
|
+
else
|
288
|
+
[cls, val]
|
289
|
+
end
|
290
|
+
else
|
291
|
+
[cls, val]
|
292
|
+
end
|
293
|
+
end
|
294
|
+
end
|
295
|
+
end
|
296
|
+
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
# typed: strict
|
2
|
+
|
3
|
+
module DataModel
|
4
|
+
module Fixtures::Array
|
5
|
+
extend self
|
6
|
+
extend T::Sig
|
7
|
+
include Fixtures
|
8
|
+
|
9
|
+
sig { returns(Example) }
|
10
|
+
def string_array
|
11
|
+
Example.new(
|
12
|
+
[:array, :string],
|
13
|
+
variants: {
|
14
|
+
strings: ["a", "b", "c"],
|
15
|
+
string: ["a", ["a"]],
|
16
|
+
number: [1, ["1"]],
|
17
|
+
missing: nil,
|
18
|
+
numbers: [[1, 2, 3], ["1", "2", "3"]],
|
19
|
+
other_type: Object.new
|
20
|
+
},
|
21
|
+
)
|
22
|
+
end
|
23
|
+
|
24
|
+
sig { returns(Example) }
|
25
|
+
def wrapping_string_array
|
26
|
+
Example.new(
|
27
|
+
[:array, { wrap_single_value: true }, :string],
|
28
|
+
variants: {
|
29
|
+
strings: ["a", "b", "c"],
|
30
|
+
string: ["a", ["a"]],
|
31
|
+
number: [1, ["1"]],
|
32
|
+
missing: nil,
|
33
|
+
numbers: [[1, 2, 3], ["1", "2", "3"]],
|
34
|
+
other_type: Object.new
|
35
|
+
},
|
36
|
+
)
|
37
|
+
end
|
38
|
+
|
39
|
+
sig { returns(Example) }
|
40
|
+
def optional_string_array
|
41
|
+
Example.new(
|
42
|
+
[:array, { optional: true }, :string],
|
43
|
+
variants: {
|
44
|
+
strings: ["a", "b", "c"],
|
45
|
+
missing: nil
|
46
|
+
},
|
47
|
+
)
|
48
|
+
end
|
49
|
+
|
50
|
+
sig { returns(Example) }
|
51
|
+
def array_optional_string
|
52
|
+
Example.new(
|
53
|
+
[:array, [:string, { optional: true }]],
|
54
|
+
variants: {
|
55
|
+
optional_strings: ["a", nil, "c"],
|
56
|
+
numbers: [1, nil, 3]
|
57
|
+
},
|
58
|
+
)
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
# typed: strict
|
2
|
+
|
3
|
+
require "bigdecimal/util"
|
4
|
+
|
5
|
+
module DataModel
|
6
|
+
module Fixtures::BigDecimal
|
7
|
+
include Fixtures
|
8
|
+
extend T::Sig
|
9
|
+
extend self
|
10
|
+
|
11
|
+
sig { returns(Example) }
|
12
|
+
def simple
|
13
|
+
Example.new(
|
14
|
+
[:decimal],
|
15
|
+
variants: {
|
16
|
+
valid: 5.to_d,
|
17
|
+
missing: nil,
|
18
|
+
string: ["5", 5.to_d]
|
19
|
+
},
|
20
|
+
)
|
21
|
+
end
|
22
|
+
|
23
|
+
sig { returns(Example) }
|
24
|
+
def optional
|
25
|
+
Example.new(
|
26
|
+
[:decimal, { optional: true }],
|
27
|
+
variants: {
|
28
|
+
missing: nil
|
29
|
+
},
|
30
|
+
)
|
31
|
+
end
|
32
|
+
|
33
|
+
sig { returns(Example) }
|
34
|
+
def min
|
35
|
+
Example.new(
|
36
|
+
[:decimal, { min: 5 }],
|
37
|
+
variants: {
|
38
|
+
bigger: 6.to_d,
|
39
|
+
smaller: 4.to_d
|
40
|
+
},
|
41
|
+
)
|
42
|
+
end
|
43
|
+
|
44
|
+
sig { returns(Example) }
|
45
|
+
def max
|
46
|
+
Example.new(
|
47
|
+
[:decimal, { max: 5 }],
|
48
|
+
variants: {
|
49
|
+
bigger: 6.to_d,
|
50
|
+
smaller: 4.to_d
|
51
|
+
},
|
52
|
+
)
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
# typed: strict
|
2
|
+
|
3
|
+
module DataModel
|
4
|
+
module Fixtures::Boolean
|
5
|
+
extend T::Sig
|
6
|
+
extend self
|
7
|
+
include Fixtures
|
8
|
+
|
9
|
+
sig { returns(Example) }
|
10
|
+
def simple
|
11
|
+
Example.new(
|
12
|
+
[:boolean],
|
13
|
+
variants: {
|
14
|
+
true: true,
|
15
|
+
false: false,
|
16
|
+
string: ["true", true],
|
17
|
+
missing: nil
|
18
|
+
},
|
19
|
+
)
|
20
|
+
end
|
21
|
+
|
22
|
+
sig { returns(Example) }
|
23
|
+
def optional
|
24
|
+
Example.new(
|
25
|
+
[:boolean, { optional: true }],
|
26
|
+
variants: {
|
27
|
+
true: true,
|
28
|
+
false: false,
|
29
|
+
string: "true",
|
30
|
+
missing: nil
|
31
|
+
},
|
32
|
+
)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
# typed: strict
|
2
|
+
|
3
|
+
module DataModel
|
4
|
+
module Fixtures::Date
|
5
|
+
extend T::Sig
|
6
|
+
extend self
|
7
|
+
include Fixtures
|
8
|
+
|
9
|
+
sig { returns(::Date) }
|
10
|
+
def earliest_date
|
11
|
+
return ::Date.today - 1
|
12
|
+
end
|
13
|
+
|
14
|
+
sig { returns(::Date) }
|
15
|
+
def latest_date
|
16
|
+
return ::Date.today + 1
|
17
|
+
end
|
18
|
+
|
19
|
+
sig { returns(T::Hash[Symbol, Object]) }
|
20
|
+
def variants
|
21
|
+
today = ::Date.today
|
22
|
+
|
23
|
+
{
|
24
|
+
date: today,
|
25
|
+
string: [today.to_s, today],
|
26
|
+
invalid: "invalid",
|
27
|
+
early: earliest_date - 1,
|
28
|
+
late: latest_date + 1,
|
29
|
+
missing: nil
|
30
|
+
}
|
31
|
+
end
|
32
|
+
|
33
|
+
sig { returns(Example) }
|
34
|
+
def simple
|
35
|
+
Example.new([:date], variants:)
|
36
|
+
end
|
37
|
+
|
38
|
+
sig { returns(Example) }
|
39
|
+
def optional
|
40
|
+
Example.new([:date, { optional: true }], variants:)
|
41
|
+
end
|
42
|
+
|
43
|
+
sig { returns(Example) }
|
44
|
+
def earliest
|
45
|
+
Example.new([:date, { earliest: earliest_date }], variants:)
|
46
|
+
end
|
47
|
+
|
48
|
+
sig { returns(Example) }
|
49
|
+
def latest
|
50
|
+
Example.new([:date, { latest: latest_date }], variants:)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
# typed: strict
|
2
|
+
|
3
|
+
module DataModel
|
4
|
+
class Fixtures::Example
|
5
|
+
extend T::Sig
|
6
|
+
|
7
|
+
sig { params(schema: TSchema, variants: T::Hash[::Symbol, Object]).void }
|
8
|
+
def initialize(schema, variants:)
|
9
|
+
@schema = schema
|
10
|
+
@variants = variants
|
11
|
+
end
|
12
|
+
|
13
|
+
sig { returns(Model) }
|
14
|
+
def model
|
15
|
+
DataModel.define(@schema)
|
16
|
+
end
|
17
|
+
|
18
|
+
sig { params(type: Symbol).returns([Model, Object]) }
|
19
|
+
def [](type)
|
20
|
+
if !@variants.key?(type)
|
21
|
+
raise "#{type} is not a defined variant: #{@variants}"
|
22
|
+
end
|
23
|
+
|
24
|
+
result = @variants.fetch(type)
|
25
|
+
|
26
|
+
return [model, result]
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
# typed: strict
|
2
|
+
|
3
|
+
module DataModel
|
4
|
+
module Fixtures::Float
|
5
|
+
include Fixtures
|
6
|
+
extend T::Sig
|
7
|
+
extend self
|
8
|
+
|
9
|
+
sig { returns(Example) }
|
10
|
+
def simple
|
11
|
+
Example.new(
|
12
|
+
[:float],
|
13
|
+
variants: {
|
14
|
+
valid: 5.0,
|
15
|
+
missing: nil,
|
16
|
+
string: ["5", 5.0]
|
17
|
+
},
|
18
|
+
)
|
19
|
+
end
|
20
|
+
|
21
|
+
sig { returns(Example) }
|
22
|
+
def optional
|
23
|
+
Example.new(
|
24
|
+
[:float, { optional: true }],
|
25
|
+
variants: {
|
26
|
+
missing: nil
|
27
|
+
},
|
28
|
+
)
|
29
|
+
end
|
30
|
+
|
31
|
+
sig { returns(Example) }
|
32
|
+
def min
|
33
|
+
Example.new(
|
34
|
+
[:float, { min: 5 }],
|
35
|
+
variants: {
|
36
|
+
bigger: 6.0,
|
37
|
+
smaller: 4.0
|
38
|
+
},
|
39
|
+
)
|
40
|
+
end
|
41
|
+
|
42
|
+
sig { returns(Example) }
|
43
|
+
def max
|
44
|
+
Example.new(
|
45
|
+
[:float, { max: 5.0 }],
|
46
|
+
variants: {
|
47
|
+
bigger: 6.0,
|
48
|
+
smaller: 4.0
|
49
|
+
},
|
50
|
+
)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
@@ -0,0 +1,66 @@
|
|
1
|
+
# typed: strict
|
2
|
+
|
3
|
+
module DataModel
|
4
|
+
module Fixtures::Hash
|
5
|
+
include Fixtures
|
6
|
+
extend T::Sig
|
7
|
+
extend self
|
8
|
+
|
9
|
+
TContact = T.type_alias { T::Hash[Symbol, T.untyped] }
|
10
|
+
|
11
|
+
sig { returns(TContact) }
|
12
|
+
def example_contact
|
13
|
+
{
|
14
|
+
first_name: "foo",
|
15
|
+
last_name: "bar",
|
16
|
+
email: "foo@bar.com"
|
17
|
+
}
|
18
|
+
end
|
19
|
+
|
20
|
+
sig { returns(Example) }
|
21
|
+
def contact
|
22
|
+
Example.new(
|
23
|
+
[:hash,
|
24
|
+
[:first_name, :string],
|
25
|
+
[:last_name, :string, { optional: true }],
|
26
|
+
[:email, :string]],
|
27
|
+
variants: {
|
28
|
+
valid: example_contact,
|
29
|
+
missing: nil,
|
30
|
+
coercible: example_contact.to_a,
|
31
|
+
missing_email: example_contact.tap { |h| T.cast(h, TContact).delete(:email) },
|
32
|
+
invalid_field: example_contact.merge(email: 123),
|
33
|
+
other_type: []
|
34
|
+
},
|
35
|
+
)
|
36
|
+
end
|
37
|
+
|
38
|
+
sig { returns(Example) }
|
39
|
+
def optional_contact
|
40
|
+
Example.new(
|
41
|
+
[:hash, { optional: true },
|
42
|
+
[:first_name, :string],
|
43
|
+
[:last_name, :string, { optional: true }],
|
44
|
+
[:email, :string]],
|
45
|
+
variants: {
|
46
|
+
valid: example_contact,
|
47
|
+
missing: nil
|
48
|
+
},
|
49
|
+
)
|
50
|
+
end
|
51
|
+
|
52
|
+
sig { returns(Example) }
|
53
|
+
def closed_contact
|
54
|
+
Example.new(
|
55
|
+
[:hash, { open: false },
|
56
|
+
[:first_name, :string],
|
57
|
+
[:last_name, :string, { optional: true }],
|
58
|
+
[:email, :string]],
|
59
|
+
variants: {
|
60
|
+
valid: example_contact,
|
61
|
+
extra_keys: example_contact.merge(extra: "keys")
|
62
|
+
},
|
63
|
+
)
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
# typed: strict
|
2
|
+
|
3
|
+
module DataModel
|
4
|
+
module Fixtures::Integer
|
5
|
+
include Fixtures
|
6
|
+
extend T::Sig
|
7
|
+
extend self
|
8
|
+
|
9
|
+
sig { returns(Example) }
|
10
|
+
def simple
|
11
|
+
Example.new(
|
12
|
+
[:integer],
|
13
|
+
variants: {
|
14
|
+
valid: 5,
|
15
|
+
missing: nil,
|
16
|
+
string: ["5", 5]
|
17
|
+
},
|
18
|
+
)
|
19
|
+
end
|
20
|
+
|
21
|
+
sig { returns(Example) }
|
22
|
+
def optional
|
23
|
+
Example.new(
|
24
|
+
[:integer, { optional: true }],
|
25
|
+
variants: {
|
26
|
+
missing: nil
|
27
|
+
},
|
28
|
+
)
|
29
|
+
end
|
30
|
+
|
31
|
+
sig { returns(Example) }
|
32
|
+
def min
|
33
|
+
Example.new(
|
34
|
+
[:integer, { min: 5 }],
|
35
|
+
variants: {
|
36
|
+
bigger: 6,
|
37
|
+
smaller: 4
|
38
|
+
},
|
39
|
+
)
|
40
|
+
end
|
41
|
+
|
42
|
+
sig { returns(Example) }
|
43
|
+
def max
|
44
|
+
Example.new(
|
45
|
+
[:integer, { max: 5 }],
|
46
|
+
variants: {
|
47
|
+
bigger: 6,
|
48
|
+
smaller: 4
|
49
|
+
},
|
50
|
+
)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|