sorbet-runtime 0.0.1.pre.prealpha → 0.4.4253

Sign up to get free protection for your applications and to get access to all the features.
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