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.
- 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
|