sorbet-runtime 0.0.1.pre.prealpha → 0.4.4253

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.
Files changed (69) hide show
  1. checksums.yaml +5 -5
  2. data/lib/sorbet-runtime.rb +100 -0
  3. data/lib/types/_types.rb +245 -0
  4. data/lib/types/abstract_utils.rb +50 -0
  5. data/lib/types/boolean.rb +8 -0
  6. data/lib/types/compatibility_patches.rb +37 -0
  7. data/lib/types/configuration.rb +368 -0
  8. data/lib/types/generic.rb +23 -0
  9. data/lib/types/helpers.rb +31 -0
  10. data/lib/types/interface_wrapper.rb +158 -0
  11. data/lib/types/private/abstract/data.rb +36 -0
  12. data/lib/types/private/abstract/declare.rb +39 -0
  13. data/lib/types/private/abstract/hooks.rb +43 -0
  14. data/lib/types/private/abstract/validate.rb +128 -0
  15. data/lib/types/private/casts.rb +22 -0
  16. data/lib/types/private/class_utils.rb +102 -0
  17. data/lib/types/private/decl_state.rb +18 -0
  18. data/lib/types/private/error_handler.rb +37 -0
  19. data/lib/types/private/methods/_methods.rb +344 -0
  20. data/lib/types/private/methods/call_validation.rb +1177 -0
  21. data/lib/types/private/methods/decl_builder.rb +275 -0
  22. data/lib/types/private/methods/modes.rb +18 -0
  23. data/lib/types/private/methods/signature.rb +196 -0
  24. data/lib/types/private/methods/signature_validation.rb +232 -0
  25. data/lib/types/private/mixins/mixins.rb +27 -0
  26. data/lib/types/private/runtime_levels.rb +41 -0
  27. data/lib/types/private/types/not_typed.rb +23 -0
  28. data/lib/types/private/types/string_holder.rb +26 -0
  29. data/lib/types/private/types/void.rb +33 -0
  30. data/lib/types/profile.rb +27 -0
  31. data/lib/types/props/_props.rb +165 -0
  32. data/lib/types/props/constructor.rb +20 -0
  33. data/lib/types/props/custom_type.rb +84 -0
  34. data/lib/types/props/decorator.rb +826 -0
  35. data/lib/types/props/errors.rb +8 -0
  36. data/lib/types/props/optional.rb +73 -0
  37. data/lib/types/props/plugin.rb +15 -0
  38. data/lib/types/props/pretty_printable.rb +106 -0
  39. data/lib/types/props/serializable.rb +376 -0
  40. data/lib/types/props/type_validation.rb +98 -0
  41. data/lib/types/props/utils.rb +49 -0
  42. data/lib/types/props/weak_constructor.rb +30 -0
  43. data/lib/types/runtime_profiled.rb +36 -0
  44. data/lib/types/sig.rb +28 -0
  45. data/lib/types/struct.rb +8 -0
  46. data/lib/types/types/base.rb +141 -0
  47. data/lib/types/types/class_of.rb +38 -0
  48. data/lib/types/types/enum.rb +42 -0
  49. data/lib/types/types/fixed_array.rb +60 -0
  50. data/lib/types/types/fixed_hash.rb +59 -0
  51. data/lib/types/types/intersection.rb +36 -0
  52. data/lib/types/types/noreturn.rb +25 -0
  53. data/lib/types/types/proc.rb +51 -0
  54. data/lib/types/types/self_type.rb +31 -0
  55. data/lib/types/types/simple.rb +33 -0
  56. data/lib/types/types/type_member.rb +7 -0
  57. data/lib/types/types/type_parameter.rb +23 -0
  58. data/lib/types/types/type_template.rb +7 -0
  59. data/lib/types/types/type_variable.rb +31 -0
  60. data/lib/types/types/typed_array.rb +20 -0
  61. data/lib/types/types/typed_enumerable.rb +141 -0
  62. data/lib/types/types/typed_enumerator.rb +22 -0
  63. data/lib/types/types/typed_hash.rb +29 -0
  64. data/lib/types/types/typed_range.rb +22 -0
  65. data/lib/types/types/typed_set.rb +22 -0
  66. data/lib/types/types/union.rb +59 -0
  67. data/lib/types/types/untyped.rb +25 -0
  68. data/lib/types/utils.rb +223 -0
  69. metadata +122 -15
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+ # typed: true
3
+
4
+ module T::Props
5
+ class Error < StandardError; end
6
+ class InvalidValueError < Error; end
7
+ class ImmutableProp < Error; end
8
+ end
@@ -0,0 +1,73 @@
1
+ # frozen_string_literal: true
2
+ # typed: false
3
+
4
+ module T::Props::Optional
5
+ include T::Props::Plugin
6
+ end
7
+
8
+
9
+ ##############################################
10
+
11
+
12
+ # NB: This must stay in the same file where T::Props::Optional is defined due to
13
+ # T::Props::Decorator#apply_plugin; see https://git.corp.stripe.com/stripe-internal/pay-server/blob/fc7f15593b49875f2d0499ffecfd19798bac05b3/chalk/odm/lib/chalk-odm/document_decorator.rb#L716-L717
14
+ module T::Props::Optional::DecoratorMethods
15
+ # TODO: clean this up. This set of options is confusing, and some of them are not universally
16
+ # applicable (e.g., :on_load only applies when using T::Serializable).
17
+ VALID_OPTIONAL_RULES = Set[
18
+ :existing, # deprecated
19
+ :on_load,
20
+ false,
21
+ true,
22
+ ].freeze
23
+
24
+ def valid_props
25
+ super + [
26
+ :default,
27
+ :factory,
28
+ :optional,
29
+ ]
30
+ end
31
+
32
+ def prop_optional?(prop); prop_rules(prop)[:fully_optional]; end
33
+
34
+ def add_prop_definition(prop, rules)
35
+ rules[:fully_optional] = !T::Props::Utils.need_nil_write_check?(rules)
36
+ super
37
+ end
38
+
39
+ def prop_validate_definition!(name, cls, rules, type)
40
+ result = super
41
+
42
+ if (rules_optional = rules[:optional])
43
+ if !VALID_OPTIONAL_RULES.include?(rules_optional)
44
+ raise ArgumentError.new(":optional must be one of #{VALID_OPTIONAL_RULES.inspect}")
45
+ end
46
+ end
47
+
48
+ if rules.key?(:default) && rules.key?(:factory)
49
+ raise ArgumentError.new("Setting both :default and :factory is invalid. See: go/chalk-docs")
50
+ end
51
+
52
+ result
53
+ end
54
+
55
+ def has_default?(rules)
56
+ rules.include?(:default) || rules.include?(:factory)
57
+ end
58
+
59
+ def get_default(rules, instance_class)
60
+ if rules.include?(:default)
61
+ default = rules[:default]
62
+ T::Props::Utils.deep_clone_object(default)
63
+ elsif rules.include?(:factory)
64
+ # Factory should never be nil if the key is specified, but
65
+ # we do this rather than 'elsif rules[:factory]' for
66
+ # consistency with :default.
67
+ factory = rules[:factory]
68
+ instance_class.class_exec(&factory)
69
+ else
70
+ nil
71
+ end
72
+ end
73
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+ # typed: true
3
+
4
+ module T::Props::Plugin
5
+ include T::Props
6
+ extend T::Helpers
7
+
8
+ module ClassMethods
9
+ def included(child)
10
+ super
11
+ child.plugin(self)
12
+ end
13
+ end
14
+ mixes_in_class_methods(ClassMethods)
15
+ end
@@ -0,0 +1,106 @@
1
+ # frozen_string_literal: true
2
+ # typed: true
3
+
4
+ module T::Props::PrettyPrintable
5
+ include T::Props::Plugin
6
+
7
+ # Return a string representation of this object and all of its props
8
+ def inspect
9
+ T.unsafe(T.cast(self, Object).class).decorator.inspect_instance(self)
10
+ end
11
+
12
+ # Override the PP gem with something that's similar, but gives us a hook
13
+ # to do redaction
14
+ def pretty_inspect
15
+ T.unsafe(T.cast(self, Object).class).decorator.inspect_instance(self, multiline: true)
16
+ end
17
+
18
+ module DecoratorMethods
19
+ extend T::Sig
20
+
21
+ sig {returns(T::Array[Symbol])}
22
+ def valid_props
23
+ super + [:inspect]
24
+ end
25
+
26
+ sig do
27
+ params(instance: T::Props::PrettyPrintable, multiline: T::Boolean, indent: String)
28
+ .returns(String)
29
+ end
30
+ def inspect_instance(instance, multiline: false, indent: ' ')
31
+ components =
32
+ inspect_instance_components(
33
+ instance,
34
+ multiline: multiline,
35
+ indent: indent
36
+ )
37
+ .reject(&:empty?)
38
+
39
+ # Not using #<> here as that makes pry highlight these objects
40
+ # as if they were all comments, whereas this makes them look
41
+ # like the structured thing they are.
42
+ if multiline
43
+ "#{components[0]}:\n" + T.must(components[1..-1]).join("\n")
44
+ else
45
+ "<#{components.join(' ')}>"
46
+ end
47
+ end
48
+
49
+ sig do
50
+ params(instance: T::Props::PrettyPrintable, multiline: T::Boolean, indent: String)
51
+ .returns(T::Array[String])
52
+ end
53
+ private def inspect_instance_components(instance, multiline:, indent:)
54
+ pretty_props = T.unsafe(self).all_props.map do |prop|
55
+ [prop, inspect_prop_value(instance, prop, multiline: multiline, indent: indent)]
56
+ end
57
+
58
+ joined_props = join_props_with_pretty_values(
59
+ pretty_props,
60
+ multiline: multiline,
61
+ indent: indent
62
+ )
63
+
64
+ [
65
+ T.unsafe(self).decorated_class.to_s,
66
+ joined_props,
67
+ ]
68
+ end
69
+
70
+ sig do
71
+ params(instance: T::Props::PrettyPrintable, prop: Symbol, multiline: T::Boolean, indent: String)
72
+ .returns(String)
73
+ end
74
+ private def inspect_prop_value(instance, prop, multiline:, indent:)
75
+ val = T.unsafe(self).get(instance, prop)
76
+ rules = T.unsafe(self).prop_rules(prop)
77
+ if (custom_inspect = rules[:inspect])
78
+ if T::Utils.arity(custom_inspect) == 1
79
+ custom_inspect.call(val)
80
+ else
81
+ custom_inspect.call(val, {multiline: multiline, indent: indent})
82
+ end
83
+ elsif rules[:sensitivity] && !rules[:sensitivity].empty? && !val.nil?
84
+ "<REDACTED #{rules[:sensitivity].join(', ')}>"
85
+ else
86
+ val.inspect
87
+ end
88
+ end
89
+
90
+ sig do
91
+ params(pretty_kvs: T::Array[[Symbol, String]], multiline: T::Boolean, indent: String)
92
+ .returns(String)
93
+ end
94
+ private def join_props_with_pretty_values(pretty_kvs, multiline:, indent: ' ')
95
+ pairs = pretty_kvs
96
+ .sort_by {|k, _v| k.to_s}
97
+ .map {|k, v| "#{k}=#{v}"}
98
+
99
+ if multiline
100
+ indent + pairs.join("\n#{indent}")
101
+ else
102
+ pairs.join(', ')
103
+ end
104
+ end
105
+ end
106
+ end
@@ -0,0 +1,376 @@
1
+ # frozen_string_literal: true
2
+ # typed: false
3
+
4
+ module T::Props::Serializable
5
+ include T::Props::Plugin
6
+ # Required because we have special handling for `optional: false`
7
+ include T::Props::Optional
8
+ # Required because we have special handling for extra_props
9
+ include T::Props::PrettyPrintable
10
+
11
+ # Serializes this object to a hash, suitable for conversion to
12
+ # JSON/BSON.
13
+ #
14
+ # @param strict [T::Boolean] (true) If false, do not raise an
15
+ # exception if this object has mandatory props with missing
16
+ # values.
17
+ # @return [Hash] A serialization of this object.
18
+ def serialize(strict=true)
19
+ decorator = self.class.decorator
20
+ h = {}
21
+
22
+ decorator.props.keys.each do |prop|
23
+ rules = decorator.prop_rules(prop)
24
+ hkey = rules[:serialized_form]
25
+
26
+ val = decorator.get(self, prop)
27
+
28
+ if strict && val.nil? && T::Props::Utils.need_nil_write_check?(rules)
29
+ # If the prop was already missing during deserialization, that means the application
30
+ # code already had to deal with a nil value, which means we wouldn't be accomplishing
31
+ # much by raising here (other than causing an unnecessary breakage).
32
+ if self.required_prop_missing_from_deserialize?(prop)
33
+ T::Configuration.log_info_handler(
34
+ "chalk-odm: missing required property in serialize",
35
+ prop: prop, class: self.class.name, id: decorator.get_id(self)
36
+ )
37
+ else
38
+ raise T::Props::InvalidValueError.new("#{self.class.name}.#{prop} not set for non-optional prop")
39
+ end
40
+ end
41
+
42
+ # Don't serialize values that are nil to save space (both the
43
+ # nil value itself and the field name in the serialized BSON
44
+ # document)
45
+ next if decorator.prop_dont_store?(prop) || val.nil?
46
+
47
+ if rules[:type_is_serializable]
48
+ val = val.serialize(strict)
49
+ elsif rules[:type_is_array_of_serializable]
50
+ if (subtype = rules[:serializable_subtype]).is_a?(T::Props::CustomType)
51
+ val = val.map {|el| el && subtype.serialize(el)}
52
+ else
53
+ val = val.map {|el| el && el.serialize(strict)}
54
+ end
55
+ elsif rules[:type_is_hash_of_serializable_values] && rules[:type_is_hash_of_custom_type_keys]
56
+ key_subtype = rules[:serializable_subtype][:keys]
57
+ value_subtype = rules[:serializable_subtype][:values]
58
+ if value_subtype.is_a?(T::Props::CustomType)
59
+ val = val.each_with_object({}) do |(key, value), result|
60
+ result[key_subtype.serialize(key)] = value && value_subtype.serialize(value)
61
+ end
62
+ else
63
+ val = val.each_with_object({}) do |(key, value), result|
64
+ result[key_subtype.serialize(key)] = value && value.serialize(strict)
65
+ end
66
+ end
67
+ elsif rules[:type_is_hash_of_serializable_values]
68
+ value_subtype = rules[:serializable_subtype]
69
+ if value_subtype.is_a?(T::Props::CustomType)
70
+ val = val.transform_values {|v| v && value_subtype.serialize(v)}
71
+ else
72
+ val = val.transform_values {|v| v && v.serialize(strict)}
73
+ end
74
+ elsif rules[:type_is_hash_of_custom_type_keys]
75
+ key_subtype = rules[:serializable_subtype]
76
+ val = val.each_with_object({}) do |(key, value), result|
77
+ result[key_subtype.serialize(key)] = value
78
+ end
79
+ elsif rules[:type_is_custom_type]
80
+ val = rules[:type].serialize(val)
81
+
82
+ unless T::Props::CustomType.valid_serialization?(val, rules[:type])
83
+ msg = "#{rules[:type]} did not serialize to a valid scalar type. It became a: #{val.class}"
84
+ if val.is_a?(Hash)
85
+ msg += "\nIf you want to store a structured Hash, consider using a T::Struct as your type."
86
+ end
87
+ raise T::Props::InvalidValueError.new(msg)
88
+ end
89
+ end
90
+
91
+ h[hkey] = T::Props::Utils.deep_clone_object(val)
92
+ end
93
+
94
+ extra_props = decorator.extra_props(self)
95
+ h.merge!(extra_props) if extra_props
96
+
97
+ h
98
+ end
99
+
100
+ # Populates the property values on this object with the values
101
+ # from a hash. In general, prefer to use {.from_hash} to construct
102
+ # a new instance, instead of loading into an existing instance.
103
+ #
104
+ # @param hash [Hash<String, Object>] The hash to take property
105
+ # values from.
106
+ # @param strict [T::Boolean] (false) If true, raise an exception if
107
+ # the hash contains keys that do not correspond to any known
108
+ # props on this instance.
109
+ # @return [void]
110
+ def deserialize(hash, strict=false)
111
+ decorator = self.class.decorator
112
+
113
+ matching_props = 0
114
+
115
+ decorator.props.each do |p, rules|
116
+ hkey = rules[:serialized_form]
117
+ val = hash[hkey]
118
+ if val.nil?
119
+ if T::Utils::Props.required_prop?(rules)
120
+ val = decorator.get_default(rules, self.class)
121
+ if val.nil?
122
+ msg = "Tried to deserialize a required prop from a nil value. It's "\
123
+ "possible that a nil value exists in the database, so you should "\
124
+ "provide a `default: or factory:` for this prop (see go/optional "\
125
+ "for more details). If this is already the case, you probably "\
126
+ "omitted a required prop from the `fields:` option when doing a "\
127
+ "partial load."
128
+ storytime = {prop: hkey, klass: self.class.name}
129
+
130
+ # Notify the model owner if it exists, and always notify the API owner.
131
+ begin
132
+ if defined?(Opus) && defined?(Opus::Ownership) && decorator.decorated_class < Opus::Ownership
133
+ T::Configuration.hard_assert_handler(
134
+ msg,
135
+ storytime: storytime,
136
+ project: decorator.decorated_class.get_owner
137
+ )
138
+ end
139
+ ensure
140
+ T::Configuration.hard_assert_handler(msg, storytime: storytime)
141
+ end
142
+ end
143
+ elsif T::Props::Utils.need_nil_read_check?(rules)
144
+ self.required_prop_missing_from_deserialize(p)
145
+ end
146
+
147
+ matching_props += 1 if hash.key?(hkey)
148
+ else
149
+ if (subtype = rules[:serializable_subtype])
150
+ val =
151
+ if rules[:type_is_array_of_serializable]
152
+ if subtype.is_a?(T::Props::CustomType)
153
+ val.map {|el| el && subtype.deserialize(el)}
154
+ else
155
+ val.map {|el| el && subtype.from_hash(el)}
156
+ end
157
+ elsif rules[:type_is_hash_of_serializable_values] && rules[:type_is_hash_of_custom_type_keys]
158
+ key_subtype = subtype[:keys]
159
+ values_subtype = subtype[:values]
160
+ if values_subtype.is_a?(T::Props::CustomType)
161
+ val.each_with_object({}) do |(key, value), result|
162
+ result[key_subtype.deserialize(key)] = value && values_subtype.deserialize(value)
163
+ end
164
+ else
165
+ val.each_with_object({}) do |(key, value), result|
166
+ result[key_subtype.deserialize(key)] = value && values_subtype.from_hash(value)
167
+ end
168
+ end
169
+ elsif rules[:type_is_hash_of_serializable_values]
170
+ if subtype.is_a?(T::Props::CustomType)
171
+ val.transform_values {|v| v && subtype.deserialize(v)}
172
+ else
173
+ val.transform_values {|v| v && subtype.from_hash(v)}
174
+ end
175
+ elsif rules[:type_is_hash_of_custom_type_keys]
176
+ val.map do |key, value|
177
+ [subtype.deserialize(key), value]
178
+ end.to_h
179
+ else
180
+ subtype.from_hash(val)
181
+ end
182
+ elsif (needs_clone = rules[:type_needs_clone])
183
+ val =
184
+ if needs_clone == :shallow
185
+ val.dup
186
+ else
187
+ T::Props::Utils.deep_clone_object(val)
188
+ end
189
+ elsif rules[:type_is_custom_type]
190
+ val = rules[:type].deserialize(val)
191
+ end
192
+
193
+ matching_props += 1
194
+ end
195
+
196
+ self.instance_variable_set(rules[:accessor_key], val) # rubocop:disable PrisonGuard/NoLurkyInstanceVariableAccess
197
+ end
198
+
199
+ # We compute extra_props this way specifically for performance
200
+ if matching_props < hash.size
201
+ pbsf = decorator.prop_by_serialized_forms
202
+ h = hash.reject {|k, _| pbsf.key?(k)}
203
+
204
+ if strict
205
+ raise "Unknown properties for #{self.class.name}: #{h.keys.inspect}"
206
+ else
207
+ @_extra_props = h
208
+ end
209
+ end
210
+ end
211
+
212
+ # with() will clone the old object to the new object and merge the specified props to the new object.
213
+ def with(changed_props)
214
+ with_existing_hash(changed_props, existing_hash: self.serialize)
215
+ end
216
+
217
+ private def recursive_stringify_keys(obj)
218
+ if obj.is_a?(Hash)
219
+ new_obj = obj.class.new
220
+ obj.each do |k, v|
221
+ new_obj[k.to_s] = recursive_stringify_keys(v)
222
+ end
223
+ elsif obj.is_a?(Array)
224
+ new_obj = obj.map {|v| recursive_stringify_keys(v)}
225
+ else
226
+ new_obj = obj
227
+ end
228
+ new_obj
229
+ end
230
+
231
+ private def with_existing_hash(changed_props, existing_hash:)
232
+ serialized = existing_hash
233
+ new_val = self.class.from_hash(serialized.merge(recursive_stringify_keys(changed_props)))
234
+ old_extra = self.instance_variable_get(:@_extra_props) # rubocop:disable PrisonGuard/NoLurkyInstanceVariableAccess
235
+ new_extra = new_val.instance_variable_get(:@_extra_props) # rubocop:disable PrisonGuard/NoLurkyInstanceVariableAccess
236
+ if old_extra != new_extra
237
+ difference =
238
+ if old_extra
239
+ new_extra.reject {|k, v| old_extra[k] == v}
240
+ else
241
+ new_extra
242
+ end
243
+ raise ArgumentError.new("Unexpected arguments: input(#{changed_props}), unexpected(#{difference})")
244
+ end
245
+ new_val
246
+ end
247
+
248
+ # @return [T::Boolean] Was this property missing during deserialize?
249
+ def required_prop_missing_from_deserialize?(prop)
250
+ return false if @_required_props_missing_from_deserialize.nil?
251
+ @_required_props_missing_from_deserialize.include?(prop)
252
+ end
253
+
254
+ # @return Marks this property as missing during deserialize
255
+ def required_prop_missing_from_deserialize(prop)
256
+ @_required_props_missing_from_deserialize ||= Set[]
257
+ @_required_props_missing_from_deserialize << prop
258
+ nil
259
+ end
260
+ end
261
+
262
+
263
+ ##############################################
264
+
265
+ # NB: This must stay in the same file where T::Props::Serializable is defined due to
266
+ # T::Props::Decorator#apply_plugin; see https://git.corp.stripe.com/stripe-internal/pay-server/blob/fc7f15593b49875f2d0499ffecfd19798bac05b3/chalk/odm/lib/chalk-odm/document_decorator.rb#L716-L717
267
+ module T::Props::Serializable::DecoratorMethods
268
+ def valid_props
269
+ super + [
270
+ :dont_store,
271
+ :name,
272
+ :raise_on_nil_write,
273
+ ]
274
+ end
275
+
276
+ def required_props
277
+ @class.props.select {|_, v| T::Utils::Props.required_prop?(v)}.keys
278
+ end
279
+
280
+ def prop_dont_store?(prop); prop_rules(prop)[:dont_store]; end
281
+ def prop_by_serialized_forms; @class.prop_by_serialized_forms; end
282
+
283
+ def from_hash(hash, strict=false)
284
+ raise ArgumentError.new("#{hash.inspect} provided to from_hash") if !(hash && hash.is_a?(Hash))
285
+
286
+ i = @class.allocate
287
+ i.deserialize(hash, strict)
288
+
289
+ i
290
+ end
291
+
292
+ def prop_serialized_form(prop)
293
+ prop_rules(prop)[:serialized_form]
294
+ end
295
+
296
+ def serialized_form_prop(serialized_form)
297
+ prop_by_serialized_forms[serialized_form.to_s] || raise("No such serialized form: #{serialized_form.inspect}")
298
+ end
299
+
300
+ def add_prop_definition(prop, rules)
301
+ rules[:serialized_form] = rules.fetch(:name, prop.to_s)
302
+ res = super
303
+ prop_by_serialized_forms[rules[:serialized_form]] = prop
304
+ res
305
+ end
306
+
307
+ def prop_validate_definition!(name, cls, rules, type)
308
+ result = super
309
+
310
+ if (rules_name = rules[:name])
311
+ unless rules_name.is_a?(String)
312
+ raise ArgumentError.new("Invalid name in prop #{@class.name}.#{name}: #{rules_name.inspect}")
313
+ end
314
+
315
+ validate_prop_name(rules_name)
316
+ end
317
+
318
+ if !rules[:raise_on_nil_write].nil? && rules[:raise_on_nil_write] != true
319
+ raise ArgumentError.new("The value of `raise_on_nil_write` if specified must be `true` (given: #{rules[:raise_on_nil_write]}).")
320
+ end
321
+
322
+ result
323
+ end
324
+
325
+ def get_id(instance)
326
+ prop = prop_by_serialized_forms['_id']
327
+ if prop
328
+ get(instance, prop)
329
+ else
330
+ nil
331
+ end
332
+ end
333
+
334
+ EMPTY_EXTRA_PROPS = {}.freeze
335
+ private_constant :EMPTY_EXTRA_PROPS
336
+
337
+ def extra_props(instance)
338
+ get(instance, '_extra_props') || EMPTY_EXTRA_PROPS
339
+ end
340
+
341
+ # @override T::Props::PrettyPrintable
342
+ private def inspect_instance_components(instance, multiline:, indent:)
343
+ if (extra_props = extra_props(instance)) && !extra_props.empty?
344
+ pretty_kvs = extra_props.map {|k, v| [k.to_sym, v.inspect]}
345
+ extra = join_props_with_pretty_values(pretty_kvs, multiline: false)
346
+ super + ["@_extra_props=<#{extra}>"]
347
+ else
348
+ super
349
+ end
350
+ end
351
+ end
352
+
353
+
354
+ ##############################################
355
+
356
+
357
+ # NB: This must stay in the same file where T::Props::Serializable is defined due to
358
+ # T::Props::Decorator#apply_plugin; see https://git.corp.stripe.com/stripe-internal/pay-server/blob/fc7f15593b49875f2d0499ffecfd19798bac05b3/chalk/odm/lib/chalk-odm/document_decorator.rb#L716-L717
359
+ module T::Props::Serializable::ClassMethods
360
+ def prop_by_serialized_forms; @prop_by_serialized_forms ||= {}; end
361
+
362
+ # @!method self.from_hash(hash, strict)
363
+ #
364
+ # Allocate a new instance and call {#deserialize} to load a new
365
+ # object from a hash.
366
+ # @return [Serializable]
367
+ def from_hash(hash, strict=false)
368
+ self.decorator.from_hash(hash, strict)
369
+ end
370
+
371
+ # Equivalent to {.from_hash} with `strict` set to true.
372
+ # @return [Serializable]
373
+ def from_hash!(hash)
374
+ self.decorator.from_hash(hash, true)
375
+ end
376
+ end