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.
- checksums.yaml +5 -5
- data/lib/sorbet-runtime.rb +100 -0
- data/lib/types/_types.rb +245 -0
- data/lib/types/abstract_utils.rb +50 -0
- data/lib/types/boolean.rb +8 -0
- data/lib/types/compatibility_patches.rb +37 -0
- data/lib/types/configuration.rb +368 -0
- data/lib/types/generic.rb +23 -0
- data/lib/types/helpers.rb +31 -0
- data/lib/types/interface_wrapper.rb +158 -0
- data/lib/types/private/abstract/data.rb +36 -0
- data/lib/types/private/abstract/declare.rb +39 -0
- data/lib/types/private/abstract/hooks.rb +43 -0
- data/lib/types/private/abstract/validate.rb +128 -0
- data/lib/types/private/casts.rb +22 -0
- data/lib/types/private/class_utils.rb +102 -0
- data/lib/types/private/decl_state.rb +18 -0
- data/lib/types/private/error_handler.rb +37 -0
- data/lib/types/private/methods/_methods.rb +344 -0
- data/lib/types/private/methods/call_validation.rb +1177 -0
- data/lib/types/private/methods/decl_builder.rb +275 -0
- data/lib/types/private/methods/modes.rb +18 -0
- data/lib/types/private/methods/signature.rb +196 -0
- data/lib/types/private/methods/signature_validation.rb +232 -0
- data/lib/types/private/mixins/mixins.rb +27 -0
- data/lib/types/private/runtime_levels.rb +41 -0
- data/lib/types/private/types/not_typed.rb +23 -0
- data/lib/types/private/types/string_holder.rb +26 -0
- data/lib/types/private/types/void.rb +33 -0
- data/lib/types/profile.rb +27 -0
- data/lib/types/props/_props.rb +165 -0
- data/lib/types/props/constructor.rb +20 -0
- data/lib/types/props/custom_type.rb +84 -0
- data/lib/types/props/decorator.rb +826 -0
- data/lib/types/props/errors.rb +8 -0
- data/lib/types/props/optional.rb +73 -0
- data/lib/types/props/plugin.rb +15 -0
- data/lib/types/props/pretty_printable.rb +106 -0
- data/lib/types/props/serializable.rb +376 -0
- data/lib/types/props/type_validation.rb +98 -0
- data/lib/types/props/utils.rb +49 -0
- data/lib/types/props/weak_constructor.rb +30 -0
- data/lib/types/runtime_profiled.rb +36 -0
- data/lib/types/sig.rb +28 -0
- data/lib/types/struct.rb +8 -0
- data/lib/types/types/base.rb +141 -0
- data/lib/types/types/class_of.rb +38 -0
- data/lib/types/types/enum.rb +42 -0
- data/lib/types/types/fixed_array.rb +60 -0
- data/lib/types/types/fixed_hash.rb +59 -0
- data/lib/types/types/intersection.rb +36 -0
- data/lib/types/types/noreturn.rb +25 -0
- data/lib/types/types/proc.rb +51 -0
- data/lib/types/types/self_type.rb +31 -0
- data/lib/types/types/simple.rb +33 -0
- data/lib/types/types/type_member.rb +7 -0
- data/lib/types/types/type_parameter.rb +23 -0
- data/lib/types/types/type_template.rb +7 -0
- data/lib/types/types/type_variable.rb +31 -0
- data/lib/types/types/typed_array.rb +20 -0
- data/lib/types/types/typed_enumerable.rb +141 -0
- data/lib/types/types/typed_enumerator.rb +22 -0
- data/lib/types/types/typed_hash.rb +29 -0
- data/lib/types/types/typed_range.rb +22 -0
- data/lib/types/types/typed_set.rb +22 -0
- data/lib/types/types/union.rb +59 -0
- data/lib/types/types/untyped.rb +25 -0
- data/lib/types/utils.rb +223 -0
- metadata +122 -15
@@ -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,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
|